UDN
Search public documentation:

ReplicationPatternRepNotify
日本語訳
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 Home > Networking & Replication > RepNotify pattern

RepNotify pattern


Overview


RepNotify is a variable property flag that you can use when you want to detect when a variable has been changed by replication. This pattern is only used for clients, since clients must use client to server remote procedure call to push data to the server. A common example of when to use this pattern is when you wish to update client special effects based on a replicated variable.

Notes about security


Normally you would not want the client to simply modify game related variables without some form of data verification and checking. Security has been omitted to simplify this pattern example.

ExampleReplicationInfo class


This replication info simply holds some data that both the server and the client may modify. When variables are updated on the server, they will automatically be replicated to the client. As soon as the variables are replicated, ReplicatedEvent is then called.

RN_ExampleReplicationInfo.uc
class RN_ExampleReplicationInfo extends ReplicationInfo;

var RepNotify int IntExample;
var RepNotify byte ByteExample;
var RepNotify float FloatExample;
var RepNotify String StringExample;
var RepNotify Vector VectorExample;
var RepNotify Rotator RotatorExample;
var RepNotify Color ColorExample;

replication
{
  // Replicate when dirty
  if (bNetDirty)
    IntExample, ByteExample, FloatExample, StringExample, VectorExample, RotatorExample, ColorExample;
}

/**
 * PostBeginPlay is executed after the RN_ExampleReplicationInfo is created
 *
 * Network: All
 */
simulated function PostBeginPlay()
{
  Super.PostBeginPlay();

  if (Role == Role_Authority)
  {
    `Log(Self$":: PostBeginPlay():: Executed on the server.");
  }
  else
  {
    `Log(Self$":: PostBeginPlay():: Executed on the client.");
  }
}

/**
 * ReplicatedEvent is called when a variable with the RepNotify property flag is updated
 *
 * Network: All
 */
simulated event ReplicatedEvent(name VarName)
{
  local String Text;

  if (Role == Role_Authority)
  {
    Text = "Server";
  }
  else
  {
    Text = "Client";
  }

  switch (VarName)
  {
  case 'IntExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: IntExample is now "$IntExample$".");
    break;

  case 'ByteExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: ByteExample is now "$ByteExample$".");
    break;

  case 'FloatExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: FloatExample is now "$FloatExample$".");
    break;

  case 'StringExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: StringExample is now '"$StringExample$"'.");
    break;

  case 'VectorExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: VectorExample is now (X="$VectorExample.X$", Y="$VectorExample.Y$", Z="$VectorExample.Z$").");
    break;

  case 'RotatorExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: RotatorExample is now (Pitch="$RotatorExample.Pitch$", Yaw="$RotatorExample.Yaw$", Roll="$RotatorExample.Roll$").");
    break;

  case 'ColorExample':
    `Log(Self$":: ReplicatedEvent():: "$Text$":: ColorExample is now (R="$ColorExample.R$", G="$ColorExample.G$", B="$ColorExample.B$", A="$ColorExample.A$").");
    break;
  }
}

defaultproperties
{
}

Player Controller class


The player controller class allows the client to modify the variables contained within ExampleReplicationInfo. In order to do this, the player controller sends a remote procedure call to its server version, which then modifies ExampleReplicationInfo on the server side. As the server side variables are changed, they will then be replicated back to the client. In real world situation, you would not bother waiting for the round about trip, you would simply update the client's version of ExampleReplicationInfo if you are allowed to do so. However, if some server side verification and logic is required to complete the update; then a round about trip is unavoidable. This real world situation has been omitted to simplify this pattern example. In this example, notice that when the server side version of ExampleReplicationInfo was changed, ReplicatedEvent is also called at the same time. This just allows the server to execute the logic of when any of the variables change as well.

RN_PlayerController.uc
class RN_PlayerController extends PlayerController;

/**
 * Returns an instance of Example Replication Info
 *
 * Network: Dedicated/Listen Server
 */
function RN_ExampleReplicationInfo GetExampleReplicationInfoInstance()
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ForEach DynamicActors(class'RN_ExampleReplicationInfo', ExampleReplicationInfo)
  {
    return ExampleReplicationInfo;
  }
}

/**
 * RPC to update the int
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateInt(int I)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateInt:: Updating int with "$I$".");
    ExampleReplicationInfo.IntExample = I;
    ExampleReplicationInfo.ReplicatedEvent('IntExample');
  }
}

/**
 * RPC to update the byte
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateByte(byte B)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateByte:: Updating byte with "$B$".");
    ExampleReplicationInfo.ByteExample = B;
    ExampleReplicationInfo.ReplicatedEvent('ByteExample');
  }
}

/**
 * RPC to update the float
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateFloat(float F)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateFloat:: Updating float with "$F$".");
    ExampleReplicationInfo.FloatExample = F;
    ExampleReplicationInfo.ReplicatedEvent('FloatExample');
  }
}

/**
 * RPC to update the string
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateString(string S)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateString:: Updating string with "$S$".");
    ExampleReplicationInfo.StringExample = S;
    ExampleReplicationInfo.ReplicatedEvent('StringExample');
  }
}

/**
 * RPC to update the vector
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateVector(Vector V)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateVector:: Updating vector with (X="$V.X$", Y="$V.Y$", Z="$V.Z$").");
    ExampleReplicationInfo.VectorExample = V;
    ExampleReplicationInfo.ReplicatedEvent('VectorExample');
  }
}

/**
 * RPC to update the rotator
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateRotator(Rotator R)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateRotator:: Updating rotator with (Pitch="$R.Pitch$", Yaw="$R.Yaw$", Roll="$R.Roll$").");
    ExampleReplicationInfo.RotatorExample = R;
    ExampleReplicationInfo.ReplicatedEvent('RotatorExample');
  }
}

/**
 * RPC to update the color
 *
 * Network: Dedicated/Listen Server
 */
reliable server function ServerUpdateColor(Color C)
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

  ExampleReplicationInfo = GetExampleReplicationInfoInstance();

  if (ExampleReplicationInfo != None)
  {
    `Log(Self$":: ServerUpdateColor:: Updating color with (R="$C.R$", G="$C.G$", B="$C.B$", A="$C.A$").");
    ExampleReplicationInfo.ColorExample = C;
    ExampleReplicationInfo.ReplicatedEvent('ColorExample');
  }
}

/**
 * Allows the player controller update the int
 *
 * Network: Local
 */
exec function UpdateInt(int I)
{
  if (Role < Role_Authority)
  {
    ServerUpdateInt(I);
  }
}

/**
 * Allows the player controller update the byte
 *
 * Network: Local
 */
exec function UpdateByte(byte B)
{
  if (Role < Role_Authority)
  {
    ServerUpdateByte(B);
  }
}

/**
 * Allows the player controller update the float
 *
 * Network: Local
 */
exec function UpdateFloat(float F)
{
  if (Role < Role_Authority)
  {
    ServerUpdateFloat(F);
  }
}

/**
 * Allows the player controller update the string
 *
 * Network: Local
 */
exec function UpdateString(string S)
{
  if (Role < Role_Authority)
  {
    ServerUpdateString(S);
  }
}

/**
 * Allows the player controller update the vector
 *
 * Network: Local
 */
exec function UpdateVector(float X, float Y, float Z)
{
  local Vector V;

  if (Role < Role_Authority)
  {
    V.X = X;
    V.Y = Y;
    V.Z = Z;
    ServerUpdateVector(V);
  }
}

/**
 * Allows the player controller update the rotator
 *
 * Network: Local
 */
exec function UpdateRotator(int Pitch, int Yaw, int Roll)
{
  local Rotator R;

  if (Role < Role_Authority)
  {
    R.Pitch = Pitch;
    R.Yaw = Yaw;
    R.Roll = Roll;
    ServerUpdateRotator(R);
  }
}

/**
 * Allows the player controller update the color
 *
 * Network: Local
 */
exec function UpdateColor(int R, int G, int B, int A)
{
  local Color C;

  if (Role < Role_Authority)
  {
    C.R = R;
    C.G = G;
    C.B = B;
    C.A = A;
    ServerUpdateColor(C);
  }
}

defaultproperties
{
}

GameInfo class


The game info class is used to update the ExampleReplicationInfo with random values. This to see the RepNotify method in action. As the game info is server side only, ExampleReplicationInfo can be modified directly without having to do further replication logic.

RN_GameInfo.uc
class RN_GameInfo extends GameInfo;

var RN_ExampleReplicationInfo ExampleReplicationInfo;
var const String Alphabet[26];

/**
 * PostBeginPlay is executed after the GameInfo is created on the server
 *
 * Network: Dedicated/Listen Server
 */
function PostBeginPlay()
{
  Super.PostBeginPlay();

  // Create the example replication info
  ExampleReplicationInfo = Spawn(class'RN_ExampleReplicationInfo');

  // Start the timer so that fresh data is sent to clients
  if (ExampleReplicationInfo != None)
  {
    SetTimer(10.f, true, 'UpdateExampleReplicationInfo');
  }
}

/**
 * Updates the example replication info and sends the new data to the client
 *
 * Network: Dedicated/Listen Server
 */
function UpdateExampleReplicationInfo()
{
  local int Index, i;
  local String Text;
  local Vector V;
  local Rotator R;
  local Color C;
  local Controller Controller;

  if (ExampleReplicationInfo == None || WorldInfo == None)
  {
    return;
  }

  // Ensure that we have players connected
  ForEach WorldInfo.AllControllers(class'Controller', Controller)
  {
    Index = int(RandRange(0.f, 7.f));

    switch (Index)
    {
    case 0: // int
      ExampleReplicationInfo.IntExample = int(RandRange(-32768, 32768));
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating int with "$ExampleReplicationInfo.IntExample$".");
      break;

    case 1: // byte
      ExampleReplicationInfo.ByteExample = byte(RandRange(0, 255));
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating byte with "$ExampleReplicationInfo.ByteExample$".");
      break;

    case 2: // float
      ExampleReplicationInfo.FloatExample = RandRange(-1234.5678, 1234.5678);
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating float with "$ExampleReplicationInfo.FloatExample$".");
      break;

    case 3: // string
      for (i = 0; i < 32; ++i)
      {
        Text $= Alphabet[Rand(26)];
      }
      ExampleReplicationInfo.StringExample = Text;
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating string with "$ExampleReplicationInfo.StringExample$".");
      break;

    case 4: // vector
      V.X = RandRange(-1234.5678, 1234.5678);
      V.Y = RandRange(-1234.5678, 1234.5678);
      V.Z = RandRange(-1234.5678, 1234.5678);
      ExampleReplicationInfo.VectorExample = V;
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating vector with (X="$V.X$", Y="$V.Y$", Z="$V.Z$")");
      break;

    case 5: // rotator
      R.Pitch = Rand(65535);
      R.Yaw = Rand(65535);
      R.Roll = Rand(65535);
      ExampleReplicationInfo.RotatorExample = R;
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating rotator with (Pitch="$R.Pitch$", Yaw="$R.Yaw$", Roll="$R.Roll$")");
      break;

    case 6: // color
      C.R = Rand(255);
      C.G = Rand(255);
      C.B = Rand(255);
      C.A = Rand(255);
      ExampleReplicationInfo.ColorExample = C;
      `Log(Self$":: UpdateExampleReplicationInfo():: Updating color with (R="$C.R$", G="$C.G$", B="$C.B$", A="$C.A$")");
      break;
    }

    break;
  }
}

defaultproperties
{
  PlayerControllerClass=class'RN_PlayerController'
  Alphabet(0)="A"
  Alphabet(1)="B"
  Alphabet(2)="C"
  Alphabet(3)="D"
  Alphabet(4)="E"
  Alphabet(5)="F"
  Alphabet(6)="G"
  Alphabet(7)="H"
  Alphabet(8)="I"
  Alphabet(9)="J"
  Alphabet(10)="K"
  Alphabet(11)="L"
  Alphabet(12)="M"
  Alphabet(13)="N"
  Alphabet(14)="O"
  Alphabet(15)="P"
  Alphabet(16)="Q"
  Alphabet(17)="R"
  Alphabet(18)="S"
  Alphabet(19)="T"
  Alphabet(20)="U"
  Alphabet(21)="V"
  Alphabet(22)="W"
  Alphabet(23)="X"
  Alphabet(24)="Y"
  Alphabet(25)="Z"
}

Testing


On the client:
  • Red - This show that ExampleReplicationInfo was spawned on the client via replication.
  • Green - This shows that the FloatExample variable was replicated as the value was changed on the server.
  • Blue - This shows that the IntExample variable was replicated as the value was changed on the server by the client.
RepNotifyClientConsoleWindow.png

On the server:

  • Red - This shows that ExampleReplicationInfo was spawned on the server.
  • Green - This shows that the FloatExample variable was changed by the server.
  • Blue - This shows that the IntExample variable was changed by the client. ReplicatedEvent is called when the server changes the value, so that there is no requirement to duplicate the logic that it contains.
RepNotifyServerConsoleWindow.png

Downloads


  • Download the source code used in this pattern. (RepNotifyExample.zip)