Replication要點
1.simulated function
在網絡環境中只有exec,client , simulated 函數才在客戶端進行調用。如果一個函數沒有任何前綴,它只會在Server中進行調用。
另外,對於一個simulated function,他要么是被另外一個simulated function 調用,要么就是在native函數中被調用才能在客戶端執行。
應用舉例
simulated function PostBeginPlay()
{
`log("PostBeginPlay");
NumberOne();
}
}
simulated function NumberOne()
{
`log("Number One");
NumberTwo();
}
}
function NumberTwo()
{
`log("Number Two");
NumberThree();
}
}
simulatedf function NumberThree()
{
`log("Number Three");
}
Server端的運行結果:
PostBeginPlay
NumberOne
NumberTwo
NumberThree
Client端的運行結果:
PostBeginPlay
NumberOne
結論:
由於NumberTwo()沒有simulated前綴,我們在前邊總結到,任何沒有前綴修飾的函數只會在Server端進行調用。
由於節省帶寬的需要,我們盡可能的讓客戶端和服務器端保持一樣的運行代碼,所以可以使用simulated來進行這樣的工作。
但是我們又不能把全部的函數都寫成simulated,因為在客戶端不能訪問到所有的Actor。(例如一些生成器Spawner Actor,即使在其Simulated PostBeginPlay中打Log都不會有任何的輸出)
2.哪些在服務器端運行,哪些在客戶端運行
我們知道非simulated function 是一定不再客戶端上運行的,但是當我們在simulated function中我們也想有一些權限,設置一些我們不願意在客戶端中運行的代碼段。
還有的是,我們怎么知道我們當前的游戲是完全運行在網絡環境的。
Role和RemoteRole
在Actor中有一個枚舉類型ENetRole ,定義了服務器和客戶端怎么來對待這一個Actor
Role_None
Role_SimulatedProxy
Role_AutonomousProxy
Role_Authority
var ENetRole Role,RemoteRole;
默認狀況下,在服務器端Role=Role_Authority。這是因為,在服務器端,服務器需要維護整個世界中的所有Actor,因此他有所有Actor的控制權。
RemoteRole設置我們在客戶端如何對待這個Actor。例如在以下RemoteRole的幾個屬性設置中:
Role_None: 這個Actor永遠不會被復制到客戶端。例如GameInfo永遠是在Server中運行的,其RemoteRole=Role_None,游戲中的ActorFactor或是Spawner都可以這樣設置,因為你不需要客戶端有這些內容。
Role_SimulatedProxy:基本上所有需要復制的Actor都是這個屬性,例如Projectile,Vehicle,所有客戶端需要預測其物理和其他行為的Actor。
Role_AutonomousProxy:只用在兩個地方,一個是client自己的Pawn,也就是玩家Pawn,另一個是DemoRecSpectators extends UTPlayerController。該屬性和Role_SimulatedProxy比較接近,除過他們不限於simulated
Role_Authority:RemoteRole不能使用,該屬性表示這個actor要么在服務器端,要么是單人游戲。
運行下面的代碼
simulated function PostBeginPlay()
{
`log("PostBeginPlay");
`log("Role="@Role);
`log("RemoteRole"@RemoteRole);
}
}
在服務器端的結果:
PostBeginPlay
Role=Role_Authority;
RemoteRole=Role_SimulatedProxy;
在客戶端的結果:
PostBeginPlay
Role=Role_SimulatedProxy
RemoteRole=Role_Authority
完全是相反的結果,這是為什么呢?
服務器端如我們預料的,但是對於客戶端完全相反,我們重新做一個實驗。
simulated function PostBeignPlay()
{
`log("PostBeginPlay");
if(Role==Role_Authority)
`log("I'm on Server");
else
`log("I'm on Client");
}
}
在Server端運行的話
I'm on Server
在客戶端的話
I'm on Server
因為在服務器端運行的話,Actor的Role為Role_Authority
在客戶端Actor的Role為Role_SimulatedProxy
根據這一個特性,我們可以划定有一些功能在特定的端口環境運行,例如一個濺血例子不需要在服務器端運行,所以只需要判斷當前的Role<Role_Authoriy,如果不是的話就說明其在客戶端中運行。
if(Role<Role_Authority)
Spawn(Particle); //濺血
有時有一些游戲性的代碼又只需要在服務器端,我們可以反着來即可。
3.是否運行在網絡環境NetMode
在WorldInfo中有一個枚舉ENetMode
NM_Standalone, //單機運行: 沒有服務器
NM_DedicatedServer, //偵測服務器: 運行在一個服務器上,其他客戶端連接過去。MMO
NM_ListenServer, //監聽服務器:一個本地客戶端主持作為服務器,Host Game,其他玩家連接過來,代替了遠程機器。
NM_Client //客戶端: 作為一個客戶端連接到服務器。
由於是在WorldInfo中,所以我們可以划定和查詢該關卡的NetMode
simulated function PostBeginPlay()
{
`log("NetMode:"@WorldInfo.NetMode);
}
對於服務器端
NetMode: NM_DedicatedServer
對於客戶端
NetMode:NM_Client
既然有了Role為什么還需要NetMode這種枚舉區分?
因為NetMode在服務器端有兩種:NM_DedicatedServer或NM_ListenServer,而對於Role在服務器端永遠是Role_Authority
那么什么時候使用NetMode呢?
前面說過不讓血粒子在服務器端進行播放,但是對於NM_ListenServer服務器,像CS中的玩家主機(其實就是客戶端在做HostGame主機),當然也要播放血粒子,因此可以用NM_ListenServer做出區別。
4.Replication變量復制
變量復制任何狀態下都是Reliable。
數組變量復制除外。static數組可以復制,動態數組不能。除非你將其每一個單獨值傳遞給中間變量,但最好不要。
做一個實例:
var int TestNum;
replication
{
if(bNetDirty)
TestNum;
}
}
simulated function PostBeginPlay()
{
if(Role=Authority) //在服務器端執行
SetTimer(1,true,'ServerTest');
else //在客戶端執行
SetTimer(1,true,'ClientTest');
}
}
function ServerTest()
{
TestNum++;
`log("Server:"@TestNum);
}
}
function ClientTest()
{
`log("Client:"@TestNum);
}
}
結果分析:
在Server端游
Server: 1
Server: 2
Server:3
...
在Client端有
Client: 1
Client: 2
Client: 3
...
因為Server端不斷地變化,bNetDirty==true,將會一直將值傳遞給Client
變量復制的一些關鍵詞:
NetPriority: 優先級,注意提高這個變量的優先級,復制可以比別的變量優先,但是不能使其復制變得更快,因為還存在別的Actor變量優先級。
bNetDirty:變量值發生了變化。
bNetInitial: true,如果所有變量初始化復制完成。
bNetOwner: 如果本地PlayerController擁有這個變量則為true
bAlwaysRelevant: 為true的話,這個變量將會針對所有client進行更新。注意一些情況,例如別的玩家根本看不見這個變量相關引起的內容就應該不要使其為true,以節省帶寬。
bReplicateInstigator: 如果這個變量有instigator,將其復制給Clients,例如給這個actor造成傷害的pawn。
bReplicateMovement:復制位置和運動變量,例如velocity。
bSkipActorPropertyReplication:不要復制這個Actor的任何屬性
NetUpdateFrequency:更新復制的頻率,數值越低優先級就越低
if ( (!bSkipActorPropertyReplication || bNetInitial) &&
(Role==ROLE_Authority)
&& bNetDirty && bReplicateInstigator )
Instigator;
Reading it, this tells us that if we're not skipping property replication or we're still initializing,
and we're the server, and a replicated property has changed, and we want to replicate the
instigator, then replicate the Instigator variable. These replication statements can seem
confusing at first, but examining what each variable does and taking a look at other examples
in the source code will give you an understanding of when they should be used.
5.ReplicateEvent
在一些變量前加修飾符 Repnotify,當這個變量被Replicate的時候會引起RelicateEvent被調用。
實例:

結果是在服務器端:
ServerTest:1
在客戶端:
This Replicated Event is Working
6.對Actor的一些設置
一般網絡Actor的出現
如果Actor只在Server顯示而沒有在Client上存在,設置其Actor屬性
RemoteRole=Role_SimulatedProxy //告知客戶端如何對待Actor,大部分的Projectile和Vehicle,可以被擊碎的箱子都可以這樣設置
bAlwaysRelevant=true //這個也可以隨着距離設置以便節省帶寬
RemoteRole=Role_SimulatedProxy- 告知游戲的客戶端怎么處理這個Actor,意思是:這個Client有一個復制於Server的本地copy表示這個Actor。
bAlwaysRelevant=true 針對於玩家這個是相關的
獲取本地PlayerController
GetALocalPlayerController()和Foreach LocalPlayerControllers是永遠失效的,因此必須使用別的遍歷方式例如DynamicActors等
7.GameReplicationInfo
GameInfo不存在於Client端,因此我們創建了GameReplicationInfo來給Client端傳遞GameInfo中的信息
在GameInfo的DefaultProperties中有
defaultproperties
{
GameReplicationInfoClass=class'ArtGameReplicationInfo'
}
}
下面舉一個示例:
如果GameInfo中有一些變量需要玩家知道,例如當前場景中還有多少敵人
var int EnemyNumLeft;
在ArtGame中有
ArtGameReplicationInfo(ArtGameReplicationInfo).EnemyNumLeft=EnemyNumLeft;
然后在class ArtGameReplication中
var int EnemyNumLeft;
replication
{
if(bNetDirty)
EnemyNumLeft;
}
}
同樣的道理,大部分在PlayerController中和Pawn中的數據我們可以搬到ArtPlayerReplicationInfo中去處理HUD顯示,例如玩家當前的經驗和等級
8.關於function的修飾符
Reliabale VS Unreliable
Reliable總是可靠地傳輸,基於TCP傳輸模式即使在網絡環境糟糕的情況下也會最終以重新發送的方式到達目的地。
Unreliable基於UDP方式,雖然效率高,但是有可能會傳輸丟失,一些不重要的執行可以用這種方式,例如濺血等。
client function:
當服務器想在客戶端上調用一個函數,這個函數永遠不會在服務器端而在只在客戶端執行。例如在PlayerController中有一個GivePawn函數,該函數負責給客戶端設置一個Pawn,而服務器端不關心,僅僅是對客戶端說:“嘿,這是你的責任,趕快去處理吧!”。
reliable client function GivePawn(Pawn NewPawn)
做一個示例:
unreliable client function PlayTeleportEffect()
{
//在客戶端播放一個傳送效果的粒子,
}
}
當然client function只在被這個客戶端擁有的Actor上執行,什么Actor通常被這個Client擁有呢?
一般情況下,像PlayerController,Pawn,Weapon,Inventory,PlayerReplicationInfo等被這個client擁有
大部分在場景中放置的Actor都不被Client擁有,Spawner往往也不被client擁有。
做一個實例:
在場景中放置一個Actor,然后PostBeginPlay中調用
//只在客戶端執行的函數
reliable client function ClientCall()
{
`log("Client Function Called by the Server");
}
發現在服務器端和客戶端都不會出現該Log.
再在ArtPawn和ArtPlayerController的客戶端中會出現.
Server function:
Server funcionn是從客戶端發送給扶我端的調用,例如客戶端會表達:“我要執行一個動作,你也得保證你在服務器端對這個事件做出對應的響應。”
例如一個exec函數只在本地客戶端執行,而服務器端不會發生相應的動作,這個時候就得通知。
進行一個實例:
exec function use()
{
`log("I'm using the trigger!") ;
}
}
在客戶端運行將會執行log,但是服務器端不會進行動作。這個時候只需要
exec function use()
{
`log("I'm using the trigger!") ;
ServerUse();
}
}
reliable server function ServerUse()
{
`log("Server using the trigger");
}
}
這時候服務器端將會執行對應的動作而客戶端不會
服務器端輸出:
Server using the trigger
當然我們也可以在客戶端傳遞參數給服務器端:
reliable server function ServerUse(int Num)
{
`log("Server using the trigger:"@num);
}
}
在use()中
exec function use()
{
`log("I'm using the trigger!") ;
ServerUse(3);
}
最后服務器端會輸出
Server using the trigger 3