如果你在測試的時候發現Client和Server中的子彈沒有同步,or你扔一枚炸彈卻聽到重疊的爆炸聲…
撞了空氣?
剛剛接觸網絡同步的時候,很有可能會出現這樣的現象,開了一個Listen Server,然後在Client中跑來跑去,撞撞箱子,打打箱子…
然後發現人物撞着撞着撞不到箱子了,像是被什麼看不見的東西擋住了,然後試着開槍打,發現箱子被你打飛了,但是在Server中箱子竟然還在原地。
這是因爲你的拋射物子彈沒有同步到Server,Server並不知道你用projectile將箱子打飛了,這時,你需要使用遠程過程調用(RPC)進行Client和Server之間的通信。
RPC的聲明有三種:
- 將某個函數聲明爲在服務器上調用,但在客戶端執行
- 將某個函數聲明爲在客戶端上調用,但在服務器執行
- 從服務器調用,在服務器和當前所有連接的n個客戶端上執行(共n+1)
分別對應在函數聲明前添加
UFUNCTION(Client)
UFUNCTION(Server)
UFUNCTION(NetMulticast)
RPC默認爲不可靠,如果要在遠端保證調用,則添加關鍵字Reliable
UFUNCTION(Server, Reliable)
如果需要添加驗證,添加關鍵字WithValidation:
UFUNCTION(Server, Reliable, WithValidation)
讓拋射物在Server同樣顯示出來
對於剛纔提出的問題:Client的projectile沒有同步到Server具體該如何解決呢?
在角色頭文件MultiplayerCharacter.h中,添加函數的Server RPC聲明。
void OnFire(); //原本的OnFire()聲明
UFUNCTION(Server, Reliable, WithValidation)
void Server_OnFire(FVector Location, FRotator Rotation);
// bool Server_OnFire_Validate(FVector Location, FRotator Rotation);
void Server_OnFire_Implementation(FVector Location, FRotator Rotation);
在Server中生成projectile的任務交給Server_OnFire_Implementation完成,在OnFire()函數中調用Server_OnFire(),這樣當Client執行OnFire()時,也會通過RPC使Server同樣完成OnFire()。
void MultiplayerCharacter::OnFire()
{
if(ProjectileClass != NULL)
{
if(GetWorld())
{
// 聲明位置,旋轉,ActorSpawnParams...
// Spawn一個projectile
GetWorld()->SpawnActor<AMultiplayerProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams)
// 在OnFire()函數中調用Server_OnFire()
Server_OnFire(SpawnLocation, SpawnRotation);
}
}
}
void MultiplayerCharacter::Server_OnFire_Implementation(FVector Location, FRotator Rotation)
{
// 設置ActorSpawnParams...
// Implementation中同樣Spawn一個projectile,在服務端顯示
GetWorld()->SpawnActor<AMultiplayerProjectile>(ProjectileClass, Location, Rotation, ActorSpawnParams)
}
但是這時編譯運行會發現,在Client中開了一槍,Server上確實是顯示出來了,但是Client中卻產生了兩個子彈,這是因爲Client調用OnFire()時,不僅OnFire()本身會Spawn一個projectile,其中調用的Server_OnFire()會在Server中也同樣Spawn一個projectile,這個projectile會通過我們原本勾選的replicates複製一份回Client。
所以要記得把OnFire()原本Spawn projectile的邏輯刪掉,此任務交給Server_OnFire_Implementation()。
防止Server端調用Server_OnFire()
然後進行測試會發現,不僅在Client開一槍會調用Server_OnFire_Implementation(),在Server開一槍,也會調用Server_OnFire_Implementation()…
解決這個問題的方法就是在執行Server_OnFire()之前進行判斷,判斷是在客戶端還是在服務端,如果確定是在客戶端,才繼續調用Server_OnFire()。
圖片來源:頁遊http://www.hp91.cn/頁遊
判斷方式有三種:
- 進行權威(Authority)判斷,在UE4中,對Actor的擁有權限分爲三種:權威、主控、模擬;比如現有客戶端A,客戶端B,和一個服務器,服務器擁有最高權限Authority,那麼對於服務器來說,其權限爲“權威A”,“權威B”,對於A和B來說,它們對自己的權限爲“主控”,對另一方的權限爲“模擬”,據此可進行這樣的判斷,保證只有Client會調用到Server_OnFire():
if(!HasAuthority())
{
Server_OnFire(SpawnLocation, SpawnRotation);
}
- 利用GetWorld()->IsServer():
if(!GetWorld()->IsServer())
{
Server_OnFire(SpawnLocation, SpawnRotation);
}
- 利用Role和RemoteRole的特點,因爲只有服務器能夠向已連接的客戶端同步Actor,而客戶端不能夠向服務器同步,所以只有服務器才能看到
Role ==
ROLE_Authority,並且在UE4中GetLocalRole()返回的枚舉類型中ROLE_Authority爲最高值,利用此特點可進行判斷:
if(GetLocalRole() < ROLE_Authority)
{
Server_OnFire(SpawnLocation, SpawnRotation);
}
此三種方式均能區分當前執行位置爲Client還是Server。