UE4聯網游戲中讓不同的客戶端生成不同的Pawn類型


效果描述

一個服務器,兩個客戶端,讓他們連接后分別生成不同的Pawn,並且在不同的位置生成。

意義

這是個項目需求,但是我發現如果能夠徹底理解並制作出這個功能,會對虛幻4內置的網絡功能以及一些重要的Gameplay 的類有更深入的了解。

目前已有解決方案

在Google上也搜了好久,但是相關信息並不多,比較靠譜的最終都指向同一個wiki頁面:Spawn Different Pawns For Players in Multiplayer,但是他的解決方案有個缺陷:開發過程中不好測試——原因是他的方案需要一個外部存儲的數據或者SaveGame,而開發時你不可能把一個工程復制兩份。除非發布以后,把發布好的文件復制兩份去測試,但這樣也很麻煩。

我的解決方案概述

我采用了藍圖實現。區分不同的客戶端主要是依靠OnPostLogin的連接順序。

在GameMode中設置一個int變量作為用戶索引,每次OnPostLogin后遞增1。因為服務器(Listen Server)的PostLogin肯定是第一個(沒哪個客戶端的連接速度會快過本機),所以服務端連接時索引為0。而其他兩個客戶端在項目中並不需要立即區分其角色,只需要先分別給一個不同的角色,后面如果需要修改,在服務端提供界面進行手動修改即可。

預備知識

  • 首先要熟悉UE4中的網絡/復制/RPC相關概念以及引擎GameMode,PlayerController等類在網絡環境中的表現,這些信息需要仔細閱讀和理解官方的文檔:

NetWorking and Multiplayer 其中的ActorReplication章節非常重要需要仔細閱讀和理解,MultiPlayer in Blueprints章節是個引導概況性質的章節,也需要仔細讀

How to replicate Actors  

Replicating Functions

Replicating Variables

  • 其次要閱讀GameModeBase源碼,了解從PostLogin到真正生成Pawn之間都發生了什么,這里我已經總結成了一幅圖:

 

 

具體實施步驟

准備工作

先使用ThirdPerson模板創建工程,然后創建ThirdPersonCharacter的三個子類,給不同的顏色作為標記,分別是紅,綠,藍,其中紅色准備作為服務器的Pawn,其他兩個作為客戶端的。

以GameModeBase為基類創建一個GameMode藍圖,這里命名為MyGameMode

以PlayerController為基類窗機一個Controller藍圖,這里明明為MyPlayerController

創建一個Enum,命名為ClientType,包含三個值:Server, ClientA, ClientB

在ThirdPersionExampleMap中,刪掉場景中的Character,然后把PlayerStart復制2個出來,擺放好位置,分別給三個PlayerStart的Player Start Tag屬性設置為Server,ClientA,ClientB

關鍵步驟

MyPlayerController

在MyPlayerController中創建一個變量:

MyClientType:ClientType枚舉類型,設置為Replicated,用於標記該PlayerController的類型

MyGameMode

在MyGameMode中,創建6個變量:

ClientIndex : int型,默認設置,用於標記不同的客戶端連接,每次OnPostLogin后會遞增1

PlayerStarts:PlayerStart引用類型,數組,其他默認,用於存放所有的PlayerStart 

ServerPawnClass: Pawn類類型,默認設置,表示服務器端Pawn的類

ClientAPawnClass: Pawn類類型,默認設置,表示客戶端APawn的類

ClientBPawnClass: Pawn類類型,默認設置,表示客戶端BPawn的類

CurrentPlayer: MyPlayerController引用類型,默認設置,臨時存放傳入的PlayerController

 

創建兩個關於獲取PlayerStart的函數:

GetAllPlayerStarts

GetPlayerStartByTag:

這兩個函數的含義如其名稱,功能也比較簡單。不過需要注意調用時機。有人可能會想,直接在BeginPlay里調用GetAllPlayerStarts就可以了,實際上這樣不行,因為OnPostLogin事件會在BeginPlay之前發生。

 

右鍵搜索OnPostLogin, 創建Event OnPostLogin事件,連接如下圖:

 

步驟釋義:

  1. 當有玩家連接進來后(包括服務器自身連接自身),把PlayerController存入一個臨時變量Current Player。
  2. 根據Client Index設置Current Player的MyClientType,依次設置為Server,ClientA,ClientB。
  3. 然后把Client Index自增1。
  4. 判斷Controller是否已經擁有了Pawn,如果有則銷毀。
  5. 調用Restart Player重新生成該Controller的Pawn(注意看上文中的流程圖,Restart Player之后進行了什么操作)

到這步之后,Restart之后並沒有改變要使用的Pawn的類。

根據上文中的流程圖,Pawn的類是在GetDefaultPawnClassForController函數中獲取的,在三處都使用了該函數來返回Pawn的類型,因此我們需要覆蓋這個函數,點"Functions"中的Override按鈕,覆蓋該函數。

函數截圖如下:

步驟釋義:

獲取PlayerController,轉換為MyPlayerController,根據剛才存入的MyClientType來返回不同的Pawn類型。使用MyGameMode里的三個Pawn Class 變量。

到這里,Pawn類別已經可以正常區分了,但是起始點還不行,都是在同一個位置生成。下面要解決的就是區分PlayerStart。

 

看上文中的流程圖,可以看到,在Restart Player函數中是通過調用Find Player Start函數來決定使用哪個PlayerStart。因此要覆蓋FindPlayerStart函數。

在MyGameMode里的Functions里點"Override按鈕,覆蓋FindPlayerStart函數,覆蓋后的截圖如下:

 

因為之前已經給不同的PlayerController分配了不同的角色,所以這步比較簡單,也是區別My Client Type,返回不同的PlayerStart即可。

到此為止就完成了

效果截圖


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM