UDN
Search public documentation:

GameStateReplicationKR
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 홈 > 네트워킹과 리플리케이션 > 게임 스테이트 리플리케이션

게임 스테이트 리플리케이션


문서 변경내역: Mike Lambert 원저. Michiel Hendriks, James Tan 업데이트. 홍성진 번역.

개요


특정 클라이언트에 연관성이 있는 각각의 액터에 대해, 서버는 해당 클라이언트에 대한 해당 액터에서 일련의 리플리케이션 검사를 실행합니다. 이 검사를 통해 어느 변수를 클라이언트로 리플리케이트시킬지 결정하고, 주어진 함수를 로컬 머신이 아닌 클라이언트로 전송하여 실행시킬 것인지를 결정합니다. 마찬가지로 클라이언트는 (변수를 제외한) 함수를 서버로 되돌려 리플리케이트시킬 수 있습니다. 이러한 액터 관련 데이터의 전송을 리플리케이션이라 합니다. 액터에 대한 데이터를 전부 보냈다간 회선이 넘쳐버릴 테니까요. 변경된 데이터 전송도 마찬가지고요. 그 대신 언리얼이 취하는 접근법은, 해당 클라이언트에만 필요한 데이터를 리플리케이트하는 것입니다.

리플리케이션 검사


서버에 있는 클라이언트 수, 각 클라이언트에 대해 관련이 있는 액터의 수, 각 액터의 변수에 대한 리플리케이션 검사 횟수 등을 생각해 보면, 네트워크로 전송할 데이터를 결정하기 위해 해야 하는 검사가 많이 있습니다. 그래서 전송할 것을 계산하는 데 있어 CPU 를 너무 많이 낭비하지 않기 위해서는 리플리케이션 문(statement)을 구현할 때 신경을 써야 합니다. 리플리케이션 문 안에서는 함수를 호출하지 마십시오. 함수 호출은 리플리케이션 문에는 너무 느리기 때문입니다. 필요한 경우 틱에서 함수 결과를 계산하고, 그 변수의 값을 리플리케이션 문에서 사용하십시오. 동일 선상에서, 검사는 꼭 필요할 때만 조건을 판단하여 가급적 단순하게 유지해야 할 것입니다. UnrealScript 로 Actor, Inventory 나 자주 쓰이는 클래스 안의 변수에 대한 리플리케이션 조건을 평가하는 데는 시간이 걸리기 때문에, 이런 검사는 네이티브 코드로 옮겼습니다. 그때문에 몇몇 Engine 클래스에서 nativereplication 을 볼 수 있는 것입니다. 리플리케이션 용으로 평가되는 변수는 네이티브 리플리케이션 코드가 처리할 것입니다. UnrealScript 리플리케이션 문은 여전히 중요하기는 하며, 치트 방지나 소스가 없는 모드 제작자들을 위한 참고용으로 사용됩니다. nativereplication 클래스에 정의되어 있지 않(고 non-nativereplication 서브클래스나 수퍼클래스에 정의되어 있)는 변수는 언리얼스크립트 정의를 통해 리플리케이트됩니다.

신뢰성 vs 비신뢰성


네트워킹에서 사용되는 또 한가지 최적화 방법은, 신뢰성(Reliable) 과 비신뢰성(Unreliable) 데이터 간의 차이입니다. 변수의 경우, 모든 데이터는 신뢰성입니다. 변수가 클라이언트에 도달하지 못한 경우 어떻게든 클라이언트로 재전송하여 결국엔 도달하게 만듭니다. 문서 무관 패킷 전달을 가능하게 하기 위해, 가장 높은 번호 패킷에서의 변수 데이터를 사용하여 신선도를 유지합니다. 그러나 함수는 신뢰성과 비신뢰성 두 가지 형태 모두 존재합니다. 신뢰성 함수는 반드시 다른 신뢰성 함수와의 클라이언트 전송 순서를 그대로 유지하여 전송합니다. 그러나 비신뢰성 함수는 클라이언트에 전송은 하되, 제대로 받든 말든 신경쓰지 않습니다 (클라이언트에 도달하지 못해도 서버는 함수를 재전송하지 않습니다). 일반적으로 신뢰성 함수는 게임플레이에 중요한 함수에 사용하여, 클라이언트의 시뮬레이션 상태가 서버와 비교적 근접한 상태를 유지하도록 합니다. 비신뢰성 함수는 있으면 보기 좋지만 없어도 그만인 특수 효과같은 것에 딱입니다.

리플리케이션 데이터 추정


언리얼의 벡터, 로테이터, 배열 인터넷 전송 방법을 통한 최적화도 있습니다. 이 규칙은 함수의 인수와 변수 둘 다에 적용됩니다.

  • Vectors - 각 컴포넌트는 가장 가까운 정수로 반올립니다.
  • Planes - 각 컴포넌트는 가장 가까운 정수로 반올립니다.
  • Rotators - 각 컴포넌트에 대해 255 개의 구분된 값을 가질 수 있습니다. 그런 다음 연결 반대편에서 값 스케일을 0-255 에서 0-65536 으로 올립니다.
  • Structs - 전부 가거나 아무것도 안가거나 입니다. 한 요소가 바뀌면 전체 구조체가 리플리케이트됩니다.
  • Static Arrays - 배열 내 변경된 요소만 전송됩니다. 동적 배열은 리플리케이트할 수 없습니다.
  • Strings, Structs - 448 바이트 미민이어야 하며, 그 이상이면 해당 액터의 변수 나머지는 굶어 죽습니다.
  • CompressedPosition - 벡터와 로테이터( 중 Rotator.Roll 은 항상 0 이라 제외) 에 대한 위의 트랜스폼이 적용됩니다. 그리고 속도 컴포넌트는 벡터와 같은 트랜스폼을 갖습니다.

자세한 것은 Replication Variable Replication Notes KR 페이지를 참고하세요. 이런 것들은 구조체 고유의 최적화이니 정교한 벡터, 면, 로테이터가 필요하다면 구조체를 새로 만드는 것이 좋습니다. 보내는 데이터는 가급적 최소화시키는 것이 항상 최선이기에, 클라이언트에서 할 수 있는 만큼 시뮬레이션하는 것을 강력 추천합니다.

리플리케이션 문 규칙


자체 리플레케이션 문(statement)을 작성할 때 알아두면 좋은 것이 몇 가지 있습니다. 먼저, 변수가 정의되어 있는 클래스 안에서만 리플리케이션 문을 정의할 수 있습니다. 즉 리플리케이션 문은 절대로 덮어쓸 수 없다는 뜻이며, 데이터를 정의하는 바로 그 클래스에 리플리케이션 문을 정의해야 한다는 뜻입니다. 한 변수의 리플리케이션 조건을 새로 써야겠다 싶은 경우라면 액터에 대한 규칙을 잘못 사용하고 있을 수가 있기 때문인데, 이 데이터 리플리케이션을 허용함에 있어 어떤 종류의 롤을 갖도록 해야 할지 다시 생각해 봐야 합니다. 이런 식으로 Actor 클래스가 변수 DrawType 을 포함한다면 리플리케이션 조건을 어디서 찾아봐야 할 지 알 수 있습니다: Actor 클래스에밖에 있을 수 없는 것입니다. 여전히 데이터를 리플리케이트해 줘야 한다면, 보통은 메시지 캐리어 역할을 하는 변수를 새로 만든 다음 다른 편에 있는 실제 변수로 복사해 주는 편이 나을 것입니다.

리플리케이트된 함수 정의는 UE3 에서 함수 지정자 server, client, reliable, unreliable 로만 가능합니다. 이 지정자의 의미는 말 그대로입니다. server 선언된 함수는 서버에서 실행되며, 주로 클라이언트에 의해 호출됩니다. client 선언된 함수는 액터가 소유하는 클라이언트에서 실행됩니다. clientserver 는 상호 배제적이며, reliableunreliable 도 마찬가지입니다. 즉 함수에 serverclient 가 동시에 붙을 수는 없다는 뜻입니다. reliable 선언된 함수는 항상 순서대로 실행되며 반드시 전달됩니다. unreliable 선언된 함수는 다른 측에 전달되지 않을 수도 있습니다. 즉 네트워크가 포화상태라면 이 함수 호출은 버려질 수도 있다는 뜻입니다.

리플리케이션 문 가이드라인


리플리케이션문은 단순하게 유지하십시오. 이 클래스에 연관성이 있는 인스턴스가 많으면, 리플리케이션 조건이 매우 자주 실행될 확률이 높고, 할 수 있는 최적화도 멀리 돌아가게 됩니다. 이미 오버레이를 복잡하게 하지 말아야 하는 것과, 룰을 준수해야 하는 것에 대한 중요성은 이미 논의했습니다. 둘째, 가능하면 bNet... 변수를 사용하십시오. bNetInitial, bNetOwner, bNetDirty 와 같은 변수는 꽤나 유용하며, 비용도 들지 않습니다. 이 변수를 사용하기 위해 추가로 필요한 CPU 요구사항이 없는데, 자동으로 설정되기 때문입니다. Role/RemoteRole 가 Role_SimulatedProxy 인지 Role_AutonomousProxy 인지에 따라 여러가지 것들을 할 수 있도록 검사할 수도 있습니다. 이러한 것들 중 좀 더 복잡한 검사 예제를 찾아보기 좋은 곳은 Engine 클래스 내, 특히 Actor , Pawn , PlayerController 입니다. 리플리케이션 문에서는 아무것도 바꾸지 말아야 한다든 것도 빼놓지 말아야 하겠습니다. 리플리케이션 문에서 i++ 를 해버리면 리플리케이트되면서 i 가 예상치 못하게 바뀔 수가 있는데, 그다지 좋지 않은 상황일 것입니다. 그리고 마지막으로 한 말씀 드리자면, 서버는 들어오는 모든 데이터의 적법성을 검사합니다. 클라이언트가 뭔가를 (변수나 함수를) 서버에 리플리케이트하는 경우, 서버는 RoleRemoteRole 변수를 임시로 교환한 다음, 다른 편에 그 데이터를 전송할 만한 충분한 이유가 있는지 알아보기 위해 리플리케이션 조건을 평가합니다. 검사가 실패하면 데이터(함수 호출이나 변수)는 버려집니다. 즉 리플리케이션 문을 작성할 때는, 서버 역시도 리플리케이션 검사를 하는 데 필요한 데이터가 전부 있는지 확인해야 한다는 뜻입니다. 그렇지 않으면 서버가 처리하거나 받아들이지 못할 것입니다.

리플리케이션 문 작성하기


언리얼스크립트 안의 모든 클래스는 리플리케이션 문을 여럿 담을 수 있는 리플리케이션 블록을 하나 가질 수 있습니다. 각 리플리케이션 문은 (참이냐 거짓이냐를 평가하는) 리플리케이션 조건과, 조건이 적용되는 변수 하나 또는 그 이상으로 된 목록으로 구성됩니다.

클래스가 리플리케이션 문을 포함하지 않는대도 전혀 문제없으며, 그저 클래스에 정의된 변수를 반대편으로 리플리케이트하지 않는다는 것을 뜻입니다. 사실 대부분의 클래스는 리플리케이션 문이 필요 없는데, 디스플레이에 영향을 끼치는 대부분의 "흥미로운" 변수들은 Actor 클래스에 정의되고 서브클래스에서 코드로만 변경되기 때문입니다.

클래스에 새 변수를 정의하면서 리플리케이션 정의에는 나열하지 않는다면, 그 변수는 절대로 리플리케이트되지 않는다는 뜻입니다. 이게 기준입니다. 대부분의 변수는 리플리케이트할 필요가 없습니다.

리플리케이션 문에 대한 UnrealScript 구문 예제는 다음과 같습니다. Pawn 클래스에서 따온 것입니다:

Pawn.uc
replication
{
   // 서버가 모든 클라이언트로 보내는 변수
  if (bNetDirty && Role == ROLE_Authority)
  FlashLocation, bSimulateGravity, bIsWalking, PlayerReplicationInfo, HitDamageType, TakeHitLocation, DrivenVehicle, Health;

   // 소유중인 클라이언트로 전송되는 변수
   if ( bNetDirty && bNetOwner && Role==ROLE_Authority )
      InvManager, Controller, GroundSpeed, WaterSpeed, AirSpeed, AccelRate, JumpZ, AirControl;

   // 소유중이지 않은 클라이언트로 전송되는 변수
   if ( bNetDirty && !bNetOwner && Role==Role_Authority )
      bIsCrouched, FlashCount, FiringMode;

   // 폰이 떨어졌을(bTearOff) 때 모든 클라이언트에 전송되는 변수
  if (bTearOff && bNetDirty && Role == ROLE_Authority)
      TearOffMomentum;

   // 소유중인 것을 제외한 모든 클라이언트에 전송되는 변수
   if ( !bNetOwner && Role==ROLE_Authority )
      RemoteViewPitch;
}

리플리케이트되는 함수 예제 조금으로, Controller 클래스에서 따온 것입니다:

Controller.uc
reliable server function ServerRestartPlayer()
{
  if (WorldInfo.NetMode != NM_Client && Pawn != None)
   {
      ServerGivePawn();
   }
}

reliable client function ClientSetWeapon( class<Weapon> WeaponClass )
{
   local Inventory Inv;

   if ( Pawn == None )
  {
      return;
  }

   Inv = Pawn.FindInventoryType( WeaponClass );
   if ( Weapon(Inv) != None )
  {
      Pawn.SetActiveWeapon( Weapon(Inv) );
}
}