UDN
Search public documentation:

ReplicationPatternRepNotifyKR
English Translation
日本語訳
中国翻译

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 홈 > 네트워킹과 리플리케이션 > RepNotify 패턴

RepNotify 패턴


문서 변경내역: James Tan 작성. 홍성진 번역.

개요


RepNotify 는 리플리케이션으로 변수가 변경되는 것을 감지하려할 때 사용할 수 있는 프로퍼티 플랙입니다. 이 패턴은 클라이언트에만 사용되는데, 클라이언트는 데이터를 서버로 푸시할 때 클라이언트 투 서버 리모트 프로시저 콜을 사용해야 하기 때문입니다. 이 패턴을 언제 사용하면 좋은가에 대한 일반적인 예제라면, 리플리케이트된 변수에 따라 클라이언트 특수 효과를 업데이트하고 싶을 때입니다.

보안 관련 주


보통의 경우 어떠한 형태의 데이터 인증이나 검사 없이 클라이언트가 게임 관련 변수를 그냥 변경하도록 하는 것은 좋지 않습니다. 여기서는 패턴 예제 단순화를 위해 보안 관련 내용을 생략 했습니다.

ExampleReplicationInfo 클래스


이 리플리케이션 인포는 단지 서버와 클라이언트 둘 다 변경 가능한 데이터를 담기만 할 뿐입니다. 서버에서 변수가 업데이트되면, 자동으로 클라이언트에 리플리케이트됩니다. 변수가 리플리케이트되자마자, ReplicatedEvent 가 호출됩니다.

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
{
  // 더티가 되면 리플리케이트
  if (bNetDirty)
    IntExample, ByteExample, FloatExample, StringExample, VectorExample, RotatorExample, ColorExample;
}

/**
 * RN_ExampleReplicationInfo 생성 후 PostBeginPlay 가 실행됩니다.
 *
 * 네트워크: 모두
 */
simulated function PostBeginPlay()
{
  Super.PostBeginPlay();

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

/**
 * RepNotify 프로퍼티 플랙을 가진 변수가 업데이트되면 ReplicatedEvent 가 호출됩니다.
 *
 * 네트워크: 모두
 */
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 클래스


플레이어 콘트롤러 클래스는 클라이언트가 ExampleReplicationInfo 안에 포함된 변수를 변경할 수 있도록 합니다. 이 작업을 위해 플레이어 콘트롤러 는 그 서버 버전으로 리모트 프로시저 콜을 전송하며, 그 후 서버 측에서 ExampleReplicationInfo 를 변경합니다. 서버측 변수가 변경되면, 다시 클라이언트로 리플리케이트되어 돌아옵니다. 실제 월드 상황에서라면 그 왕복 대기 시간에는 별로 신경쓸 필요 없이, 가능한 한 ExampleReplicationInfo 클라이언트 버전을 업데이트하기만 하면 될 것입니다. 그러나 업데이트를 마치기 위해 서버측 인증과 로직이 약간 필요한 경우라면, 왕복 대기 시간을 신경쓰지 않을 수 없습니다. 이 패턴 예제 단순화를 위해 이 실제 월드 상황이 생략되었습니다. 이 예제에서 서버측 ExampleReplicationInfo 버전이 변경될 때, ReplicatedEvent 역시 동시에 호출됨에 주목합시다. 이는 단지 서버가 변수 변경시의 로직도 실행할 수 있도록 하기 위함입니다.

RN_PlayerController.uc
class RN_PlayerController extends PlayerController;

/**
 * Example Replication Info 인스턴스 반환
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
function RN_ExampleReplicationInfo GetExampleReplicationInfoInstance()
{
  local RN_ExampleReplicationInfo ExampleReplicationInfo;

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

/**
 * int 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * byte 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * float 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * string 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * vector 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * rotator 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * color 업데이트 RPC
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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');
  }
}

/**
 * 플레이어 콘트롤러가 int 를 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
exec function UpdateInt(int I)
{
  if (Role < Role_Authority)
  {
    ServerUpdateInt(I);
  }
}

/**
 * 플레이어 콘트롤러가 byte 를 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
exec function UpdateByte(byte B)
{
  if (Role < Role_Authority)
  {
    ServerUpdateByte(B);
  }
}

/**
 * 플레이어 콘트롤러가 float 를 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
exec function UpdateFloat(float F)
{
  if (Role < Role_Authority)
  {
    ServerUpdateFloat(F);
  }
}

/**
 * 플레이어 콘트롤러가 string 을 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
exec function UpdateString(string S)
{
  if (Role < Role_Authority)
  {
    ServerUpdateString(S);
  }
}

/**
 * 플레이어 콘트롤러가 vector 를 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
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);
  }
}

/**
 * 플레이어 콘트롤러가 rotator 를 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
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);
  }
}

/**
 * 플레이어 콘트롤러가 color 를 업데이트하도록 허용
 *
 * 네트워크: 로컬
 */
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 클래스


게임 인포 클래스는 랜덤 값으로 ExampleReplicationInfo 를 업데이트할 때 사용됩니다. 이는 RepNotify 메서드가 작동하는 것을 확인하기 위함입니다. 게임 인포 는 서버측 전용이기에, 별도의 리플리케이션 로직 없이 ExampleReplicationInfo 를 변경해도 됩니다.

RN_GameInfo.uc
class RN_GameInfo extends GameInfo;

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

/**
 * 서버에서 GameInfo 생성후 PostBeginPlay 가 실행됩니다.
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
function PostBeginPlay()
{
  Super.PostBeginPlay();

  // 예제 리플리케이션 인포 생성
  ExampleReplicationInfo = Spawn(class'RN_ExampleReplicationInfo');

  // 신선한 데이터가 클라이언트에 전송될 수 있도록 타이머 시작
  if (ExampleReplicationInfo != None)
  {
    SetTimer(10.f, true, 'UpdateExampleReplicationInfo');
  }
}

/**
 * 예제 리플리케이션 인포를 업데이트하고 새 데이터를 클라이언트에 전송
 *
 * 네트워크: 데디케이티드/리슨 서버
 */
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;
  }

  // 플레이어 연결 확인
  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"
}

테스팅


클라이언트측:
  • Red - ExampleReplicationInfo 가 리플리케이션을 통해 클라이언트측에서 스폰되었음을 나타냅니다.
  • Green - 서버상의 FloatExample 변수 값이 변경되어 리플리케이트되었음을 나타냅니다.
  • Blue - 클라이언트에 의해 서버상의 IntExample 변수 값이 변경되어 리플리케이트되었음을 나타냅니다.
RepNotifyClientConsoleWindow.png

서버측:

  • Red - ExampleReplicationInfo 가 서버에서 스폰되었음을 나타냅니다.
  • Green - FloatExample 변수가 서버에 의해 변경되었음을 나타냅니다.
  • Blue - IntExample 변수가 클라이언트에 의해 변경되었음을 나타냅니다. 서버가 값을 변경하면 ReplicatedEvent 가 호출되는데, 거기에 포함된 로직을 복제하는 데 있어서의 필요사항이 없도록 하기 위해서입니다.
RepNotifyServerConsoleWindow.png

내려받기