UDN
Search public documentation:

VariableReplicationKR
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

변수의 복제

문서 변경 내역: 변수가 한 게임 인스턴스로부터 다른 인스턴스로 복제되는 방법을 설명합니다.

문서 변경 내역: 최종 업데이트 Michiel Hendriks - UDN 문서로 고침. 원저자 Mike Lambert (UdnStaff?).

개요

변수는 복제 문을 통해 전달될 수 있는 두 가지 데이터 타입 중의 하나입니다 (다른 하나는 함수). 이제 데이터가 어떻게 한 Unreal 인스턴스로부터 다른 인스턴스로 복제되는지에 대해 논의할 것입니다. 이 과정을 처음 예상했던 것보다 다소 복잡하게 만드는 사소한 요인들이 많습니다.

변수가 복제되는 것은 로컬 컴퓨터에서의 변경을 다른 컴퓨터가 계속 업데이트하도록 하기 위해서입니다. 대부분의 경우, 이는 Unreal 서버가 변경된 정보를 알려주기 위해 클라이언트에게 변수를 복사하는 것을 의미합니다. 만일 플레이어의 속도가 변경되었다면 이를 클라이언트에게 복사하여, 클라이언트가 두 네트워크의 업데이트 시간 사이에 올바르게 그 플레이어의 행동을 시뮬레이트할 수 있도록 할 필요가 있습니다.

이 문서는 NetworkingTomeKR 의 일부입니다.

변수 데이터의 신뢰도

간단한 답변은, 변수들은 언제나 reliable 이라는 것입니다. 한 액터의 변수가 복제되었을 때, 해당 객체에 대한 변수와 결합된 퇴각 변수는 발신 packetid 의 사본을 받게 됩니다. 그 패킷에 대한 승인으로서 마이너스의 값이 돌아오면 해당 packetid 에 결합된 변수들은 채널의 Dirty 배열에 넣어집니다. 다음 번에 그 액터가 다시 복제될 때, 이 변수들이 복제될 예정이 아니라면 액터가 이들을 모두 추가합니다. 따라서 변수 데이터가 다른 쪽에 도달하는 것이 보장되지만, 업데이트2를 진행하기 전에 업데이트1을 기다려야 한다는 면에서 보면 reliable 이 아닙니다.

새로 관련되는 액터 (스폰하기)

한 액터가 클라이언트에 관련이 있게 되면, 접속 상에 새 UActorChannel 이 만들어져서 클라이언트에 그 액터의 defaultproperties 를 가진 적절한 클래스의 액터가 스폰되도록 합니다. 만일 서버가 그 액터가 스폰된 직후에 변수들을 설정하면, (관련성 점검 이전에), 이 변수들이 복제문의 조건에 맞는다고 가정하고, 이 변수들이 그 액터의 데이터에 대한 추가 부록으로서 클라이언트에게 보내집니다. 복제는 현재의 틱이 끝나기 까지는 행해지지 않으므로,액터 자신과의 초기 복제를 위해 이 변수들을 설정할 시간이 충분합니다. 액터에 설정된 이 변수들을 다른 쪽에서 수신하게 되며, 그 다른 쪽에서는 PostNetBeginPlay (PostBeginPlay 는 액터의 변수들이 네트워크로부터 메모리 내로 읽혀지기 전에 호출되었으므로)를 호출하게 됩니다.

처음에 어떤 변수들을 복제할 것인지 결정할 때, 서버는 defaultproperties 와 차이가 있는 변수들을 모두 보냅니다. 서버가 액터와 클라이언트의 defaultproperties 가 다른 것을 발견한 경우에는, 서버의 버전과 다른 변수만을 복제합니다 (같은 이유에서, 클라이언트측 코드에서 변수가 변경되면 서버는 이에 대해 알지 못하므로 변경된 데이터를 복제하지 않습니다). 액터의 defaultproperties 를 클라이언트와 서버 모두에서 변경하면 이 문제가 해결될 것이라고 생각할 수도 있습니다만, 만일 액터가 관련성이 없게 되면 그 액터에서 simulated 함수가 호출될 수 없게 되고, 액터가 다시 관련 있는 것으로 되면 서버와 클라이언트의 defaultproperties 가 서로 달라지게 됩니다. 일반적으로, 그 변수가 네트워킹 속성을 가지고 있지 않은 경우를 제외하고는 변수의 defaultproperties 를 변경하지 마십시오.

좋지 않은 결과를 가져올 수 있는 한 예로, Unreal Tournament 의 한 Mod 에는 MeshDefault.Mesh 가 모두 플레이어가 선택한 playerclass 의 메쉬로 설정되어 있습니다. 그러나 플레이어들은 네트워크 대역폭을 절약하기 위해 유효범위 내를 들락날락 하도록 되어 있습니다. 이들은 대개 여러분이 볼 수 있을 때만 관련성이 있으며, 한 번에 3 초 가량 시야에서 사라집니다. 플레이어들이 이 시점에서, 관련성이 없어진 상태에서, 그들의 playerclass 를 변경한다고 합시다. 또는 그들이 게임이 처음 시작될 때, 아직 라운드가 시작되지 않아 그들이 보이지 않아 관련성이 없을 때 클래스를 변경한다고 해봅시다. 그들이 다시 클라이언트에게 관련이 있게 되었을 때는, (말하자면 그들이 길목을 돌아 나오거나, 라운드가 막 시작되고 그들이 스폰되어 보이게 되었을 때) Mesh == Default.Mesh 가 되고, 따라서 Mesh 는 결코 복제되지 않습니다. 그러나 스킨은 여전히 정상적으로 복제됩니다. 게임에서의 클라이언트는 플레이어를 다른 모델을 위해 만들어진, 어울리지 않는 스킨을 가진 원래의 메쉬로서 봅니다. 네트워크 변수에 대한 기본 변수에 대해 조심함으로써 쉽게 피할 수 있는, 이상한 행태이지요. 이 경우, Default.Mesh 는 모든 것에 필요한 것이 아니었으며, 그 할당을 제거함으로써 모든 것이 제대로 작용하게 되었습니다.

변수 복제의 시기

복제되는 변수는 오직 각 틱의 끝부분에서만 복제됩니다. 이것은 변수를 루프하는 동안 또는 다른 코드 블록에서 반복해서변경하면, 오직 마지막의 값만이 복제된다는 것을 의미합니다. 틱 동안에 변수를 여러 차례 변경하고 틱이 완료되었을 때 다시 원래의 값으로 되돌리면, 변수의 변경이 전혀 보이지 않으며 대역폭도 전혀 소비되지 않습니다.

물론 여기에는 예외가 있습니다. 서버에서 액터를 새로 스폰하면,틱이 끝날때까지의 변수들이 복제될 시간이 있습니다. 그러나 복제된 함수들을 호출할 경우, 이 복제된 함수들은 즉시 전송되며, 다른 쪽에서 이들을 수신하도록 하기 위해서는 그 위에서 작업할 액터가 있어야 합니다. 따라서 액터가 아직 복제되지 않았다면, Unreal 은 복제된 함수가 처음 호출되었을 때 그 액터 및 그 변수들의 복제를 강제하게 됩니다. 그리고 틱의 끝부분에서 다시 어떤 변수들을 송신할지 점검합니다.

클라이언트에서 서버로의 복제

또 하나 조심해야 할 점은 클라이언트-서버 복제는 오직 여러분 자신의 PlayerController 에 대해서만 존재한다는 것입니다. 만일 변수가 클라이언트에서 서버로 복제되기 원한다면 여러분의 현 PlayerController (또는 부모 클래스 정의 중의 한곳에서)에서 이 일이 행해져야 한다는 뜻입니다. 클라이언트->서버 함수들은 PlayerController 및 이것에 의해 소유되는 모든 것들에 대해 집행되므로, 이 한계를 극복하는데 복제된 함수들이 사용될 수 있습니다. location 및 velocity 들이 복제됨에 따라 클라이언트에 많은 폰들이 있게 되지만, 여러분이 소유하는 것은 오직 현재의 폰뿐이며, 오직 이 폰만이 변수를 서버에 복제할 수 있게 됩니다. 만일 다른 액터에 관한 정보를 서버에 복제할 수 있게 된다면, 서버가 액터 Z 에 대해 클라이언트 A 또는 클라이언트 B 의 값을 취하려하는 문제가 생기게 됩니다. 이것들을 식별할 것이 아무것도 없기 때문에, 서버는 폰 변수를 서버에 복제하는 것만을 허용합니다.

Native 복제

빈번하게 복제되는 데이터를 가지고 있는 일부 베이스 클래스들은 스크립트 복제 대신 native (C++) 복제를 사용합니다. 이 클래스들은 클래스 정의에 nativereplication 키워드를 가지고 있습니다. 이 클래스들은 스크립트의 조건들을 무시하고, 그 대신 C++ GetOptimizedRepList() 함수를 바탕으로 속성을 복제합니다. 그러나 UnrealScript? 복제 블록은 여전히 필요합니다. 복제될 수 있는 모든 속성들을 나열하여 컴파일러가 이들을 복제 가능으로 표시함으로써 속성들이 정확하게 전송되도록 하기 위해서입니다.

필요할 때만 복제

변수 복제를 좀더 최적화할 수도 있습니다. 변수는 클라이언트가 서버의 최신 버전과 비교해볼 때 정확하지 않은 버전을 가지고 있다고 서버가 판단할 경우에만 클라이언트에게 복제됩니다. 서버가 데이터를 복제하려 할 때는 현재의 값을 지난번에 보내져 Recent 배열에 저장되어 있는 값과 비교합니다. 이것이 처음으로 복제되는 것이라면, 코드에서 액터의 기본 속성에 있는 값에 대해 현재의 값을 확인합니다. 따라서 서버는 클라이언트에 있는 것이 무엇인지에 상관없이 변경된 변수를 보내게 됩니다.만일 클라이언트와 서버 모두에서 실행되는 simulated 함수에서 값이 변경된다면, 서버는 클라이언트의 값에 대해 알지 못한 채 여전히 그 자신의 값을 복제하게 됩니다. 이 변수가 모든 경우에 클라이언트와 서버 모두에서 변경되는 것이 보장된다면 이 변수에서 복제 조건을 제거할 수 있습니다. 액터의 관련성이 있다가 없다가 할 경우에는, 앞에서 언급한 것처럼 이 액터가 관련성이 있게 되었을 때 클라이언트에서 defaultproperties 에 초기화 됩니다. 이것이 부정확하고, 여러분이 복제 값을 가지고 있지 않다면 이는 부정확한 상태로 남아있게 됩니다. 액터가 처음으로 관련성이 있게 되었을 때 bNetInitial 변수를 사용하여 어떤 것을 복제하고, 그 다음부터는 클라이언트와 서버 모두에서 실행하는 simulated 함수에 의존하여 값을 변경할 수 있습니다. 변수 bNetInitial 및 그 친구들을 다음 차례로 설명합니다.

특별 복제 변수들

복제 조건문을 작성할 때 함수/변수가 복제되어야 할 것인지 평가하는데 사용할 수 있는 유용한 변수들이 몇 가지 있습니다. 이 변수들은 액터 내에서 정의되며, 다음과 같은 것들이 있습니다:

bNetInitial
해당 액터가 네트워크에 걸쳐 처음으로 복제되는 경우 true 입니다. defaultproperties 와 다르지만, 액터의 수명 동안에는 변경되지 않는 변수들에 대해 유용합니다.
bNetOwner
복제하고 있는 플레이어가 해당 액터를 직접 소유하는 경우 true 입니다.
bNetRelevant
액터가 현재 관련되어 있습니다. 서버 측에서만 유효합니다.
bDemoRecording bClientDemoRecording bRepClientDemo bClientDemoNetFunc bDemoOwner
시험 레코딩 용도로 사용됩니다.

변수 복제의 예

이제 몇 가지 중요한 복제의 예와 이것이 어떻게 변수와 함께 사용되었는지 살펴보겠습니다. 이 예들은 여러분에게 복제가 어떻게 작용하는지에 대한 더 나은 시각과, 여러분 자신의 색다른 코드 작성의 보기를 제공하기 위한 것입니다.

몇 가지 예를 취해 이것들이 하는 일에 대해 설명하겠습니다. 쉬운 것부터 시작하여 차츰 복잡도를 높여나갈 것입니다. 다음의 예들은, 달리 명기되지 않은 한,모두 Actor 에서 취해 온 것입니다.

주: 이 예들은 사실상 Unreal Tournament 에서 가져온 것입니다. 실제 코드는 더 이상 적절하지 않습니다. 그러나 아래의 예들은 여전히 변수의 복제를 이해하는데 도움이 됩니다.

PlayerPawn 에서

reliable if ( Role < ROLE_Authority ) Password, bReadyToPlay;

이 코드는 어디서 Password 가 서버에 보내지는지를 나타냅니다. 암호가 요구되는 서버들에 합류하려고 할 때인지, 아니면 그 서버에 관리자로서 로그인할 때인지. 서버는 확인을 위해 암호가 필요하고, 이 코드는 서버에 그 암호를 보냅니다. 이 점검을 서버와 클라이언트 양 쪽의 입장에서 평가해 보겠습니다.

서버
서버에서는 Role == ROLE_Authority 입니다. 조건문을 보면 이것이 false 라는 것을 쉽게 알 수 있습니다. 따라서 Password 가 서버측에서 복제되지 않습니다. 이것은 서버가 접속의 다른 쪽에 Password 를 보내지 않는다는 뜻입니다.
클라이언트
클라이언트에서는 RoleROLE_Authority 가 아니므로 (이것은 서버 자체에서만 Authority 라는 것을 기억하십시오), 클라이언트에서 이 복제 점검의 평가는 true 입니다. 그리고 변수의 복제는 오직 귀하의 현재 PlayerPawn 에서만 작용하므로, 이는 오로지 귀하 혼자만을 위해 서버에 보내집니다 (다른 사람의 암호를 보내지 않는 것이 이치에 맞는 일이지요. 그렇지 않습니까?).

여기서는 또한 게임을 시작하기 위해서는 누구나 클릭해야 하는 Tournament 스타일의 게임에서 사용되는 bReadyToPlay 가 있습니다. 이것은 귀하 자신에게만 보내진다는 점을 기억하십시오. 클라이언트는 변수가 그 자신의 playerpawn 액터의 일부일 때만 서버에 변수를 복제할 수 있기 때문입니다.

unreliable if( Role == ROLE_Authority ) Owner, Role, RemoteRole;

이것은 객체의 소유주가 언제나 클라이언트에 복제되는 것을 보장합니다. 소유주의 객체 자체는 복제되지 않지만(재귀 복제가 없기 때문) 액터 자체는 복제된다는 점을 기억하십시오. 이것은 단순 비교 if (PlayerPawn? = Owner) 에 문제가 없다는 것을 의미합니다. =Owner 에의 참조가 클라이언트에 복제되기 때문입니다. UT 에서는 unreliable 과 reliable 이 동등하게 취급되기 때문에, 이 둘이 차이를 나타내지 않는다는 점을 기억하십시오. Unreal 1 시절의 어떤 시기에는 영향이 있었을지 모르지만, 이것들은 이제 더 이상 개발에서의 인수 역할을 하지 않습니다. 이 키워드들 때문에 혼동되지 않도록 하십시오 .

RoleRemoteRole 을 복제하는 것이 처음에는 이상하게 보일 것입니다. 클라이언트에서는 이것들이 반대로 되기 때문입니다. 그러나, 이 교체가 이루어지도록 하는 native 코드가 있습니다. 이 코드는 클라이언트에서 두 변수들이 반대로 되는 것을 보증하여 RemoteRoleROLE_Authority 가 되도록 합니다. 그러면 애당초 왜 이것들이 복제되는지 의아하실 것입니다. 클라이언트가 결코 코드에서 이 변수들을 직접 사용하지 않기 때문에, 대단히 중요한 것을 간과하기 쉬운 한 곳이 있습니다 : 바로 복제문입니다. 클라이언트가 어떤 함수의 호출 또는 playerpawn 의 변수를 서버에 복제해야 할지의 여부를 평가할 때, 이 클라이언트에게는 어떤 것이 Role 이고 어떤 것이 RemoteRole 인지를 정확하게 알 수 있는 사본이 필요합니다. 이것이 없으면 서버에게 어느 것을 보낼지에 대한 중요한 결정을 내리는 것이 불가능하게 됩니다. 또 서버가 Role/RemoteRole 로 서버에게 전달된 복제 데이터의 적법성을 점검하기 때문에, 클라이언트가 그 데이터를 성공적으로 복제하기 위해서는 최신의 사본이 필요합니다.

unreliable if( bNetOwner && Role == ROLE_Authority ) bNetOwner, Inventory;

Inventory 는 (대개 Pawn 과 함께만 활용되는) Actor 에 대한 Inventory 전체를 나열하는 연결 리스트의 머리 부분입니다. 여기서는 inventory 가 클라이언트에게 복제되어, 그가 그의 HUD 및 그밖의 것들에 표시할 Inventory 로 어떤 것을 가지고 있는지 알도록 하려고 합니다 (그럴듯 하지요?). 그렇지만 단지 연결 리스트(각 객체가 리스트의 다음 객체를 가리키는)의 머리 부분을 복제하는 것으로는 충분하지 않습니다. 각 액체는 반드시 그 자체로서 관련성이 있어야 하며 (반드시 현 playerpawn 에 의해 소유되어야 한다는 조건을 충족시킴으로써), 반드시 각 링크 자체가 복제되어야 합니다. 그리고 Inventory 는 이 사슬의 어디에선가 Actor 를 하위클래스화 하기 때문에, Inventory 가 또한 복제된 Inventory 변수도 가지게 됩니다. 이렇게 해서 Inventory 를 클라이언트 쪽에서 보는 것이 가능해지는 것입니다. 여기서는 다른 모든 플레이어들이 게임에서 가지는 Inventory 의 목록 전체를 알 필요가 없으므로 (현재의 무기 및 방패 허리띠 효과는 다른 방법으로 가져온 것입니다), 해당 클라이언트가 소유주인 Inventory 만 복제됩니다.

위 복제문에는 bNetOwner 라는 또 하나의 변수가 있습니다. 이 변수는 본래 클라이언트와 서버 양쪽 모두에서 설정되기 때문에 복제될 필요가 없습니다. 이것이 아주 적은 량의 대역폭을 추가하게 된다는 논란이 있으나 그 효과는 무시해도 좋을 것입니다. 아마 향후 패치에서는 이것이 제거될 것입니다.

unreliable if( DrawType == DT_Mesh && Role == ROLE_Authority )
    Mesh, PrePivot, bMeshEnviroMap, Skin, MultiSkins, Fatness, AmbientGlow,
    ScaleGlow, bUnlit;

여기서는 메쉬 특정의 변수들이 오직 현재 해당 액터가 메쉬로서 표시되고 있을 때만 복제된다는 것을 알 수 있습니다.그것이 스프라이트나 브러쉬, 또는 전혀 drawtype 을 가지지 않은 것이라면 이 변수들을 보낼 이유가 없습니다.

이제 보다 더 복잡한 변수 복제문의 몇 가지 예를 살펴 보겠습니다...

unreliable if( RemoteRole == ROLE_SimulatedProxy ) Base;

이것은 Role == ROLE_Authority 가 관련되지 않은, 약간 더 복잡한 복제문입니다. 현재의 Base (SetBase 를 통해 설정)는 오직 액터가 simulated 프록시로 설정된 경우에만 복제된다는 것을 알 수 있습니다. 액터가 DumbProxy 가 되도록 설정하면 클라이언트 쪽에서는 Base 가 변경되지 않고, 그대신 서버가 보내주는 변덕스러운 Location 이 업데이트되는 것을 확인할 수 있습니다 (이에 대해서는 나중에 좀 더 설명하겠습니다). 그러므로 SetBase 를 사용할 때는, 반드시 이것이 simulated 프록시가 되도록 하십시오. Simulated 프록시가 아닐 경우에는, SetBase 가 아닌 다른 것을 사용하십시오. 한편 SetBase 와 simulated 프록시가 모두 필요한 경우에는, Base 가 복제되지 않게 됩니다. 이를 위해서는 뭔가 다른 메커니즘을 생각해봐야 할 것입니다. 아마 베이스를 설정하는 simulated 함수를 이용하여,이것이 클라이언트에서 실행되도록 하는 것도 한 방법일 것입니다 (이에 대해서도 나중에 좀 더 설명하겠습니다). 그래도 액터가 관련성이 없는 동안 그 액터에 simulated 함수가 호출되면 여전히 문제점이 남게 될 것입니다. simulated 함수의 실행 결과가 결코 클라이언트 측에서 호출되지 않으며, 그러면 마침내 액터가 관련성이 있어질 때는 그 함수가 전혀 호출되지 않은 것과 마찬가지가 됩니다 (또 base 가 설정되지 않습니다). 이런 것들은 우리가 애교로 ‘버그’라고 부르는 결과를 가져옵니다. 이 액터가 bAlwaysRelevant 에 첨부되도록 (그리고 첨부되는 대상이 되도록) 만들어 simulated 함수가 언제나 호출될 수 있도록 할 수 있지만 , 이것은 네트워크 대역폭에 좀 지나치게 부담이 될 것입니다.

unreliable if( RemoteRole == ROLE_SimulatedProxy && Physics == PHYS_Rotating
    && bNetInitial )
    bFixedRotationDir, bRotateToDesired, RotationRate, DesiredRotation;

이것은 비교적 쉬운 예이지만, 앞으로는 좀 더 복잡해집니다. 여기서도 위의 예에서와 비슷한 논리로, 클라이언트가 simulated 프록시일 때만 서버가 이 변수들을 클라이언트에게 보냅니다. 그러나 이 변수들은 PHYS_Rotating 에만 적용되므로, 클라이언트가 PHYS_Rotating 을 사용하지 않는다면 이 변수들을 클라이언트에 복제할 필요가 없습니다. 끝으로, 맨 끝에 있는 bNetInitial 은 매우 중요한 조항입니다. 이것은 이 변수들이 처음으로 복제되는 것일 경우에만 복제된다고 선언하는 것입니다.

unreliable if( bSimFall || (RemoteRole == ROLE_SimulatedProxy && bNetInitial
    && !bSimulatedPawn) )
    Physics, Acceleration, bBounce;

여기서는 몇 가지 흥미로운 변수들이 복제됩니다. bSimFall 이 true 로 설정되면, Physics 를 클라이언트에 복제하는 것을 볼 수 있습니다. 이것은 인벤토리에서 무기를 내던질 때 사용됩니다. 무기가 던져질 때는 공중을 날 것이므로, 아직 인벤토리에 있는 동안에 PhysicsPHYS_None 에서 PHYS_Falling 으로 변경되어야 합니다. 무기가 땅에 떨어지면 다시 PHYS_None 설정으로 되돌아 갑니다. 이 모든 Physics 변경은 TournamentWeapon 의 생애 중 주요 지점들에서 bSimFall 을 설정함으로써 이루어집니다. 무기가 인벤토리로부터 던져질 때 그 변경을 포착하기 위해 true 로 설정되고, Physics 를 다시 변경하는 시점인 무기가 땅에 떨어지는 순간까지 그대로 있습니다. 마침내 무기가 땅에 떨어지고 물리가 재설정되면 bSimFall=이 false 로 설정되어 더 이상의 =Physics 변경이 일어나지 않도록 합니다. 위 반복문의 후반부를 보면 simulated 프록시일 경우, 이 액터가 처음으로 인터넷에서 복제되는 경우, 그리고 simulated 폰이 아닐 경우에만 Physics 가 복제됩니다. ROLE_SimulatedProxyRemoteRole 을 가진 Pawn 일 경우에는 bSimulatedPawn 변수가 true 로 설정됩니다 (누가 생각이나 했겠습니까? :). 이것은 simulated 프록시인, 폰이 아닌 액터가 복제되기 전의 Physics 에 행해진 변경이 모두 클라이언트에 복제된다는 것을 의미합니다. 폰에 대해서는 절대 Physics 가 복제되지 않습니다. 그보다는 코드에 그들의 물리에 대한 부분이 이들이 지면으로 밀어 떨어지도록 하드코드 됩니다. 기본적으로, 폰이 점프할 경우 서버는 그가 위를 향해 이동하도록 속도 (Velocity) 를 설정합니다. 그러면 이 Velocity 가 클라이언트에 복제됩니다 (아래에서 설명). 클라이언트는 이 폰이 플레이어(예: 봇 또는 playerpawn)인지, 현재 날 수 없는지 (Pawn 의 bCanFly 변수로 설정), 그리고 물이 있는 지역에 있지 않은지 (여기에는 서로 다른 중력과 물리가 관계되므로) 확인합니다. 그러므로 폰에 대한 Physics 는 사실상 절대 클라이언트에 복제되지 않습니다. 다만 복제되는 것으로 보일 뿐입니다. playerpawn 에 대해 대체 물리를 만들고자 할 경우에는 반드시 bCanFly 를 true 로 설정하여 native 코드가 플레이어가 벽 위나 공중에 있을 때 그에 대한 낙하 Physics 를 집행하지 않도록 해야 합니다. 그 다음 대체 수송 수단을 직접 구현해야 합니다.

unreliable if( !bCarriedItem && (bNetInitial || bSimulatedPawn
    || RemoteRole < ROLE_SimulatedProxy) && Role == ROLE_Authority )
    Location;

이 예는 또 하나의 중요한 변수인 Location (위치) 과 이의 복제에 대한 것입니다. 휴대되는 아이템들에 대해서는 이 변수가 복제되지 않는 것을 볼 수 있습니다. 이것은 Inventory 의 경우에 유용합니다. 플레이어가 이를 휴대할 때는 그 위치가 아무 곳에도 쓰이지 않으므로 복제되어야 할 이유가 없습니다. 플레이어의 인벤토리를 전부 복제하는 것은 네트워크 대역폭에 큰 부담이 될 것입니다. 이 복제문의 다음 섹션을 보기로 하지요. 이 액터가 처음으로 복제되는 것인 경우에는 Location 이 복제됩니다 (조건문의 다른 부분들이 true 라고 가정함). 이것은 매우 유용합니다. 여러분이 게임을 시작할 때는 모든 폰들이 각각 다른 위치에서 스폰될 것이며, 또 로켓들은 로켓 발사대에서 스폰되어 나올 때 그 시작 위치가 설정되어야 하기 때문입니다. 또한 bSimulatedPawn=의 경우에도 위치가 보내집니다. 이것은 폰을 복제할 때 생길 수 있는 오류를 정정하는데 도움이 됩니다. 폰이 그 방향을 바꿀 수 있으며 클라이언트는 항상 이에 대해 알지 못할 수 있기 때문에(지체 등으로 인해), 이 위치 재설정은 위치 관련 업데이트가 ‘정정될 수 있는’ 유일한 방법입니다. 이것은 지체를 겪을 경우 여러분 자신의 위치를 정정하는데는 사용되지 않는다는 점을 알아두십시오. 이 경우에는 여러분이 =AutonomousProxy 이며, PlayerPawn 특정의 함수가 이를 (즉 ClientAdjustPosition) 처리합니다. 예를 들면, PHYS_Walking 은 위치를 아주 정확히 추정할 수 있는 PHYS_Projectile 과는 다릅니다. PHYS_Walking 은 기본적으로 “그들이 계속 지면에 부착되어 있도록 한다”는 뜻일 뿐입니다. 클라이언트의 유일한 업데이트 (location 없이)는 그들의 속도 뿐이어서, 상당한 시간에 걸쳐 플레이어의 움직임에서 오류로 이어지기 쉽습니다. 이 위치는 클라이언트의 시야가 플레이어가 있을 곳으로 기대되는 곳에서 크게 빗나가지 않도록 하는 동시에 velocity 의 복제를 유지함으로써, 서버의 업데이트 사이에 클라이언트를 가늠할 수 있게 하는 정정 요인으로서 사용됩니다. Location 의 복제를 허용하는 다른 옵션은 RemoteRoleROLE_SimulatedProxy 보다 적은 경우,즉 ROLE_DumbProxy 입니다. ROLE_None 은 애초부터 관련성을 방해하기 때문입니다. DumbProxies 는 서버로부터 몇 번의 틱에 한번씩 주기적으로 업데이트 되며, 넷플레이에서 튀어오르는 효과를 만들어내게 됩니다. 이것은 얼마전에 제가 시도한 하키 모드에서 퍽이 덜커덕거리는 원인이었습니다. 그 당시 저는 이것을 어떻게 수정해야 할지 전혀 감을 잡을 수 없었습니다. LocationVelocity 및 현 Physics 의 사용을 통해 예측되기 때문에 SimulatedProxy 에는 이의 업데이트가 보내지지 않습니다. DumbProxies 는 클라이언트 쪽에서 평형을 이루기 위해 시뮬레이트 되지 않으므로, 오직 이것들만이 Location 의 업데이트를 받게 됩니다. 여러가지 복제된 변수의 예를 설명한 후, 이것을 모두 명확히 하겠습니다.

unreliable if( !bCarriedItem && (DrawType == DT_Mesh || DrawType == DT_Brush)
    && (bNetInitial || bSimulatedPawn || RemoteRole < ROLE_SimulatedProxy)
    && Role == ROLE_Authority )
    Rotation;

이 예에는 또 하나의 중요한 변수인 Rotation (회전) 이 등장합니다. 이 예에도 위에서 설명한 것과 같은 이유에서 !bCarriedItem 절이 있습니다. Rotation 도 액터가 메쉬 또는 브러쉬일 때만 복제됩니다. 스프라이트는 언제나 플레이어를 향하고 있으므로, 그 rotation 을 복제하는 것은 소용이 없습니다. bNetInitial 또한 처음의 경우에만 rotation 이 복제되도록 합니다 (여기서도 조건문의 다른 부분들이 true 라고 가정함). 로켓이 공중을 날 때는 바른 방향을 향해야 하기 때문입니다. 로켓의 속도가 이동 방향을 결정하지만, 그것이 향하고 있는 방향도 똑같이 중요하며 이는 그 회전에 의해 결정됩니다. Rotation 은 폰들이 simulated 폰일 경우 복제됩니다(여러분 자신을 제외한, 레벨에 있는 모든 playerpawn 및 봇). 이것은 다른 사람들이 어느 방향을 향하고 있는지 볼 수 있도록 하기 위해서입니다. (클라이언트가 어디를 보고있는지 결정하는) ViewRotationServerMove 를 통해 서버에 보내져 rotation 으로 변환됩니다. 클라이언트에 복제되는 것은 이 rotation 입니다. 끝으로, DumbProxy (유효한 것 중 유일하게 SimulatedProxy 보다 적은 것)도 rotation 의 업데이트를 받게 됩니다. Rotation 의 업데이트는 location 에서와 달리 업데이트 사이의 보간을 전혀 필요로 하지 않습니다. 플레이어가 빠르게 움직일 때는 Location 의 지체가 훨씬 더 눈에 띄지만, 결코 Rotation 의 지체는 알아차리지 못할 것입니다. 그밖에, 실제로 rotation 을 예측하거나 예보할 방법은 없습니다. 이는 전적으로 다른 사용자의 마우스에 달려 있습니다.

unreliable if( bSimFall || ((RemoteRole == ROLE_SimulatedProxy
    && (bNetInitial || bSimulatedPawn)) || bIsMover) )
    Velocity;

복합 복제문에서 사용되는 중요한 변수들 중에서 마지막 몇 개중 하나입니다. Velocity (속도)는 bSimFall=이 설정된 경우 복제됩니다 (인벤토리에서 무기를 던질 때 사용됨). =PHYS_Falling 만으로는 충분치 않습니다. 폰으로부터 발사될 때의 초기 속도를 정확하게 알아야 합니다. 그리고 여기서는 bNetInitial 이 작용하지 않습니다. 처음으로 복제되는 것이 아니기 때문입니다. 이것은 한동안 무기로서 존재하고 있습니다. 우리가 업데이트 하고자 하는 것은 단지 이것이 플레이어로부터 던져졌을 때의 초기 속도일 뿐입니다. 그 다음, 새로 스폰된 로켓, 또는 수류탄, 또는 충격탄 등에서 복제 채널이 처음인 경우 SimulatedProxiesVelocities 가 복제되는 것을 알 수 있습니다. SimulatedProxies 또한 simulated 폰일 경우 그 속도가 복제됩니다. 여러가지 다양한 폰들의 속도가 복제되어 로컬에서 예측할 수 있어야 하기 때문입니다. 끝으로, 무버들의 속도가 복제되어 클라이언트가 로컬에서 무버의 움직임을 정확히 예측할 수 있게 됩니다. 초기 Unreal 1 시절에는 무버가 DumbProxy 였기 때문에 그 속도가 복제되지 않아, 클라이언트는 주기적으로 위치의 업데이트를 받았습니다. 이것은 넷플레이에서 무버들이 심하게 튀어오르는 결과를 가져왔으며, Unreal 224 +에서 수정되었습니다.

unreliable if( DrawType == DT_Mesh && ((RemoteRole <= ROLE_SimulatedProxy
    && (!bNetOwner || !bClientAnim)) || bDemoRecording) )
    AnimSequence, SimAnim, AnimMinRate, bAnimNotify;

이 예에는 몇 가지 변수가 더 있는데, 모두 애니메이션에 관련된 것들입니다. 이 변수들은 현재 Mesh 로서 그려질 때만 복제됩니다. 데모를 녹화하고 있다면, 애니메이션이 항상 보내집니다. 그렇지 않다면, 이 코드는 다소 혼동스러운 조건문을 점검합니다. 액터가 DumbProxy 이고, 플레이어가 이 객체의 소유주가 아니며, bClientAnim 이 설정되지 않았다면,애니메이션 변수들을 복제합니다. 무기의 경우, 이 애니메이션들을 클라이언트 측에서 보게 되며, 따라서 애니메이션 변수들이 서버로부터 복제될 필요가 없습니다. 모든 TournamentWeapons 에서 bClientAnim 이 true 로 설정되었기 때문인데, 이것은 그들의 애니메이션이 클라이언트 측에서 처리된다는 것을 가리킵니다. 애니메이션들이 클라이언트 측에서 처리되지 않고 액터가 DumbProxy? 라면, 서버로부터 애니메이션들을 보내게 됩니다. 폰과 로켓 및 기타 발사체를 포함하는 simulated 프록시는 서버로부터 애니메이션을 받지 않습니다. 그 대신 클라이언트가 클라이언트측의 예측을 사용하여 이것들을 애니메이트 합니다. 폰의 경우에는 이것이 엔진에서 내부적으로 처리되며(또 하나의 튜토리얼;), 여러분은 이에 대해 염려하지 않아도 됩니다.