Tuesday, March 2, 2010

Tutorial: Replicating a flashlight's state

I've been trying to understand replication so based on a question on the UDK forums I decided to implement a flash light whose on/off state can be replicated.

First, create a subclass of a SpotLightMovable which will act as the flash light.

class MyFlashLight extends SpotLightMovable
notplaceable;

defaultproperties
{
Begin Object name=SpotLightComponent0
LightColor=(R=255,G=0,B=0)
End Object
bNoDelete=FALSE
}



Next, we will modify our custom pawn by adding the flashlight as a variable.

var MyFlashLight FlashLight;

We also need a variable that we will replicate the flashlight's on/off state.

var repnotify bool bIsFlashlightOn; /// whether the flashlight is on or not

Then in the pawn's PostBeginPlay, we will attach the flash light to the pawn.

simulated function PostBeginPlay()
{
FlashLight = Spawn(class'MyFlashLight', self);
FlashLight.SetBase(self);
FlashLight.LightComponent.SetEnabled(self.default.bIsFlashlightOn);
super.PostBeginPlay();
}


And set the variable's default value in the pawn's default properties.

defaultproperties
{
...
bIsFlashlightOn=true
}


Now we need a way to switch the flashlight on and off. We will set this up via an exec function that will be called when the "R" key is pressed. I added my key bind to the DefaultInput.ini (You might need to make the file writable as its read-only by default).

Add this line at the end of the section marked as Primary default bindings

.Bindings=(Name="R",Command="ToggleFlashlight")

This says that when the R key is pressed the exec function ToggleFlashlight will be called. We will define the function in our cutsom PlayerController subclass.

exec function ToggleFlashlight()
{
MyPawn(Pawn).ToggleFlashlight();
}


This calls the PlayerController's Pawn's ToggleFlashlight function (a mouth-full I know). Now this is where the magic happens. Since we essentially have all the information we need to determine whether the flash light can be turned on or not (without consulting the server) we can do what is nessecary to switch on the flashlight. To do this, we need to mark the funtion with the simulated keyword.

simulated function ToggleFlashlight()
{
bIsFlashlightOn = !bIsFlashlightOn;
FlashLightToggled();
// if we are a remote client, make sure the Server toggles the flashlight
if( Role < Role_Authority )
{
ServerToggleFlashlight();
}
}

We start by changing the value of our variable that keeps track of the flashlight's state.

Next we check whether we are a client - in which case we need to tell the server what we have done by calling a server function. The server function essentially does the same thing as the client version, but on the server.

reliable server function ServerToggleFlashlight()
{
bIsFlashlightOn = !bIsFlashlightOn;
`log("ServerToggleFlashlight: " $ bIsFlashlightOn);
FlashLightToggled();
}


You'll notice that both functions call the FlashLightToggled function. This does the actual state switch.

simulated function FlashLightToggled()
{
if(bIsFlashlightOn)
{
FlashLight.LightComponent.SetEnabled(true);
}
else
{
FlashLight.LightComponent.SetEnabled(false);
}
}


This has taken care of the client that switched the flashlight on/off and the server, but what about the other clients. If you look at the definition of the bIsFlashlightOn variable, its defined as repnotify. What this does is whenever the variable changes, a special function ReplicatedEvent is called.

simulated event ReplicatedEvent(name VarName)
{
if (VarName == 'bIsFlashlightOn')
{
FlashLightToggled();
}
else
{
Super.ReplicatedEvent(VarName);
}
}


This function will be called on all the clients including the client that initiated the whole thing.

We also need a way to tell the server when to send the variable to its clients, this is done through a replication condition in our pawn class like this.

replication
{
// replicated properties
if ( bNetDirty )
bIsFlashlightOn;
}


That about covers it. Here are the full sources for each of the 3 classes we have touched on.

MyFlashlight.uc

class MFlashLight extends SpotLightMovable
notplaceable;


defaultproperties
{
Begin Object name=SpotLightComponent0
LightColor=(R=255,G=0,B=0) /// red so we can see the change
End Object
bNoDelete=FALSE
}

MyPawn.uc

class MyPawn extends UTPawn
config(Game)
notplaceable;

var MyFlashLight FlashLight;
var repnotify bool bIsFlashlightOn; /// whether the flashlight is on or not

replication
{
// replicated properties
if ( bNetDirty )
bIsFlashlightOn;
}

simulated function PostBeginPlay()
{
FlashLight = Spawn(class'KDFlashLight', self);
FlashLight.SetBase(self);
FlashLight.LightComponent.SetEnabled(self.default.bIsFlashlightOn);
super.PostBeginPlay();
}

/**
* Check on various replicated data and act accordingly.
*/
simulated event ReplicatedEvent(name VarName)
{
`log(VarName @ "replicated");
if (VarName == 'bIsFlashlightOn')
{
FlashLightToggled();
`log("bIsFlashlightOn replicated");
}
else
{
Super.ReplicatedEvent(VarName);
}
}

simulated function ToggleFlashlight()
{
bIsFlashlightOn = !bIsFlashlightOn;
`log("ToggleFlashlight: " $ bIsFlashlightOn);
FlashLightToggled();
// if we are a remote client, make sure the Server Set's toggles the flashlight
`log("Role:" @ Role);
if( Role < Role_Authority )
{
ServerToggleFlashlight();
}
}

reliable server function ServerToggleFlashlight()
{
bIsFlashlightOn = !bIsFlashlightOn;
`log("ServerToggleFlashlight: " $ bIsFlashlightOn);
FlashLightToggled(!bIsFlashlightOn);
}

simulated function FlashLightToggled()
{
if(bIsFlashlightOn)
{
FlashLight.LightComponent.SetEnabled(true);
}
else
{
FlashLight.LightComponent.SetEnabled(false);
}
}

defaultproperties
{
bIsFlashlightOn=true
}

MyPlayerController.uc

class MyPlayerController extends UTPlayerController;

exec function ToggleFlashlight()
{
MyPawn(Pawn).ToggleFlashlight();
}

state Dead
{
function EndState(name NextStateName)
{
SetBehindView(default.bBehindView);
}
}

defaultproperties
{
bBehindView = true
}

No comments: