【UE4】網遊開發中的RPC和OnRep(一)

在這裏插入圖片描述

​如果你在測試的時候發現Client和Server中的子彈沒有同步,or你扔一枚炸彈卻聽到重疊的爆炸聲…

撞了空氣?

剛剛接觸網絡同步的時候,很有可能會出現這樣的現象,開了一個Listen Server,然後在Client中跑來跑去,撞撞箱子,打打箱子…

然後發現人物撞着撞着撞不到箱子了,像是被什麼看不見的東西擋住了,然後試着開槍打,發現箱子被你打飛了,但是在Server中箱子竟然還在原地。

這是因爲你的拋射物子彈沒有同步到Server,Server並不知道你用projectile將箱子打飛了,這時,你需要使用遠程過程調用(RPC)進行Client和Server之間的通信。

RPC的聲明有三種:

  1. 將某個函數聲明爲在服務器上調用,但在客戶端執行
  2. 將某個函數聲明爲在客戶端上調用,但在服務器執行
  3. 從服務器調用,在服務器和當前所有連接的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章