看板 GameDesign 關於我們 聯絡資訊
網頁版 https://yekdniwue.blogspot.com/2020/08/CustomMove2.html 簡介 在前一篇我已經介紹一個完整的範例, 說明如何製作一個支援網路的功能, 如何做測試, 跟如何驗證這個做好的功能, 在網路延遲的環境是否能正常運作。 前一個做法確定是不行的,玩家會感到畫面不流暢。 因此本篇要介紹如何正確地修改移動相關的功能, 讓Server端能夠信任Client發送的指令, 避免Server頻繁的矯正Client的位置。 這篇其實會比較偏向程式碼實作,敘述會少很多。 簡單來說就是看code比較快啦。 當初是從CharacterMovementComponent的Crouch挖出來的。 所以如果想要自己試試看的話,可以去挖出引擎有關Crouch的程式來看。 跟之前一樣,裡面的程式碼都是我經過精簡過了, 多餘的實作項目我盡可能的都沒列在裡面, 所以最好還是按照順序看完,以免出錯。 再繼續往下看之前,請確認以下的詞你都知道是什麼意思: 1. Replicated Property 2. ROLE Authority 3. ROLE Simulated Proxy 4. ROLE AutonomousProxy 礙於篇幅的關係,這邊不會多做介紹。如果有不熟悉的項目,請先前往 https://docs.unrealengine.com/en-US/Gameplay/Networking/Actors/Roles/index.html 惡補一下。 為角色新增移動模式 要製作這種由玩家的操作改變移動速度的作法, 其實要用到的是MovementMode的切換。 也就是製作一個新的MovementModeStrafe, 然後玩家按鍵的時候進到這個MovementMode, 放開的時候回到預設的MovementMode。 為了達到這個目的,我們總共需要新增 兩個C++ class,以及一個BP class, 所以就是5個檔案。 1. CustomCharacter 2. CustomMoveComponent 3. BP_CustomCharacter 而移動速度的改變,核心做法是override GetMaxSpeed(), 如果角色正在Strafe狀態,MaxSpeed就回傳 Super::GetMaxSpeed()*StrafeSpeedRatio CustomCharacter實作 大致上要實作的項目 CustomCharacter要能接受玩家的輸入,所以跟之前的作法一樣, 要開出Strafe以及UnStrafe兩個函式給外部使用。 為了得知Strafe/UnStrafe的狀態變化時機,所以我也開出了四個函式: 1. OnStartStrafe 2. OnEndStrafe 3. BP_OnStartStrafe 4. BP_OnEndStrafe 分別是C++以及BP的事件,提供給Gameplay需要的時候使用。 Server需要將Movement的狀態同步給SimulatedProxy, 所以要新增一個變數bIsStrafed, 用來讓SimulatedProxy獲得事件通知。 也因為要Replicate變數bIsStrafed, 就要實作GetLifetimeReplicatedProps函式。 因為我們會以CustomMoveComponent取代 原生的CharacterMovementComponent, 我會建立一個指標指向CustomMoveComponent。 然後在Constructor的時候做替換。 替換MoveComponent以及同步變數 在Constructer替換MoveComponent, 然後在GetLifetimeReplicatedProps同步變數bIsStrafed, 並且只同步給Simulated Proxy。 因為Autonumous Proxy在輸入的當下就切換狀態了。 Strafe與UnStrafe實作 在CustomCharacter內,Strafe跟UnStrafe只是負責 將指令帶給CustomMoveComponent, 後續就交給CustomMoveComponent在傳遞資訊的時候做處理。 變數同步與事件通知 收到變數bIsStrafed改變的通知時, 我們就是呼叫CharMovement對應的函式做處理。 而收到OnStartStrafe與OnEndStrafe時,就是呼叫BP版本的對應函式。 可能會有人有疑惑說為什麼一個事件要分C++跟BP兩個函式, 而不是直接使用BlueprintNativeEvent直接一個函式定義起來。 主要是因為如果使用NativeEvent,那BP端是可以不呼叫C++實作的。 這個做法可以確保C++的部分一定會被執行到,不會被跳過。 到這邊CustomCharacter的實作就結束了。 CustomMoveComponent實作 大致上要實作的項目 CustomCharacter的實作其實還是比較偏Gameplay層, 就是開出函式,建立事件通知。 CustomMoveComponent要實作的項目才是核心的部份, 裡面的程式碼比較少見(其他系統不會看到這些類別與函式)。 CustomMoveComponent的實作根據不同的ROLE,有不同的實作部份 1. Autonomuous Proxy handling 2. Authority handling Autonomuous Proxy要把bWantsToStrafe的資訊塞進FCustomSavedMove中, 這樣client給Server的每個移動資訊都會帶有這個移動是否是Strafe的資訊。 Authority從FCustomSavedMove內抽出bWantsToStrafe的資訊後, 就會以正確的速度計算移動,需要減速移動就會減速移動。 因為移動跟速度變化綁定在同一包, 所以Server不會認為Client的移動有異常,就不會做位置矯正。 一些基本雜項設定 設定PawnOwner: 有兩個地方要設定PawnOwner。SetUpdatedComponent以及PostLoad。 Strafe與UnStrafe: 如果是Server(Authority)的話,直接變更bIsStrafed, 這樣SimulatedProxy會在OnRep_IsStrafed收到通知並處理。 而到底移動速度的更改怎麼實作,就是透過檢查現在是否是Strafe, 然後override GetMaxSpeed函式,如果Strafe中就乘上減速比例。 移動資訊傳遞 剩下的部份就是處理移動資訊的傳遞, 總共還有以下幾個函式在CustomMoveComponent要實作: 1. UpdateCharacterStateBeforeMovement 2. UpdateFromCompressedFlags 3. GetPredictionData_Client 修改Client送給Server的移動結構 要把Strafe的狀態告訴Server,就是要將Strafe的狀態塞進移動資料。 在FSavedMove的CompressedFlags提供了四個custom的bit可供我們使用, 也就是說我們被允許最多建立出16種移動狀態。 在這邊我會借用FLAG_Custom_0的0代表UnStrafe,1代表Starfe。 為了修改FSavedMove,我們要實作自己的版本: class FCustomSavedMove : public FSavedMove_Character 而SavedMove是被裝在FNetworkPredictionData_Client_Character裡面。 所以我們要實作自己的版本: class FNetworkPredictionData_Client_Custom : public FNetworkPredictionData_Client_Character 然後實作函式AllocateNewMove,裡面回傳我們自定義的FCustomSavedMove。 最後就是在CustomMoveComponent裡面實作GetPredictionData_Client。 在GetPredictionData_Client裡面我們要回傳自定義的class FNetworkPredictionData_Client_Custom。 這樣就能以我們自定義的移動結構取代原來的移動結構了。 將Strafe狀態整合進移動資料 可分為輸入輸出兩種情況, 輸入:Client把Strafe狀態塞進移動資料傳給Server。 輸出:Server從移動資料獲得Client送來的Strafe狀態。 輸入實作: Client(Autonumous)傳送移動資料給Server的處理流程: CharacterMovementComponent::TickComponent() ReplicateMoveToServer SetMoveFor CanCombineWith PerformMovement UpdateCharacterStateBeforeMovement CallServerMove GetCompressedFlags SetMoveFor: 從CustomMoveComponent複製bWantsToStrafe到FCustomSavedMove。 CanCombineWith: 如果bWantsToStrafe有變動,那兩個SavedMove要避免合併, 以免Strafe狀態的資料因為合併而遺失。 UpdateCharacterStateBeforeMovement: 在套用移動前再次檢查狀態是否有變化。 GetCompressedFlags: 將bWantsToStrafe存入CompressedFlags的FLAG_Custom_0。 輸出實作: Server(Authority)收到玩家資料的處理流程: ServerMove_Implementation MoveAutonomous UpdateFromCompressedFlags PerformMovement UpdateCharacterStateBeforeMovement UpdateFromCompressedFlags: 從SavedMove的FLAG_Custom_0取得Strafe狀態,存入CustomMoveComponent。 UpdateCharacterStateBeforeMovement: 在套用移動前再次檢查狀態是否有變化。 Server端Strafe狀態的變化事件會在這裡觸發。 程式碼在哪? 說了這麼多,你一定想問"到底要不要附程式碼" 你可以參考我當初找到的範例 [連結] 或是參考UnrealEngine的原始碼有關bWantsToCrouch相關的部份。 或是參考我實作的版本,已做成Plugin,但是BP角色的串接就要自己實作了。 [連結] 最後一哩路 按照以上作法實作,BP的character改為繼承CustomCharacter之後, 只要在使用者輸入的時候呼叫Strafe/UnStrafe,就完成了。 其他的事情都已經在C++處理完畢。如圖所示。 [圖] 實作完畢後你可以用前一篇提的方法做測試, 會發現就算lag設為500ms也不會有異常。 可參考影片: [影片] 結論 本系列所提的方法是為了解決如果玩家操作可以改變移動速度, 如果沒有把狀態變化跟移動資料一起傳給Server, Server與Client就會計算出不同的位置, 然後Client就會收到Server傳來的位置矯正, 造成玩家感受不良的問題。 然而一般常見的玩家對玩家,例如緩速技能,凍結技能, 這種牽扯的對象不是只有玩家對Server, 就沒有辦法使用這種方式處理。 要解決這種問題的困難度也高很多。 目前沒有繼續往後研究下去,所以這個系列應該會到此結束。 哪一天真的有實作出來並且經過驗證再分享吧(應該不會有機會)。 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 59.120.146.90 (臺灣) ※ 文章網址: https://www.ptt.cc/bbs/GameDesign/M.1596596646.A.EE7.html
coolrobin: 未看先推 08/05 21:24
sampp1213205: 有沒有考慮做教學影片放YouTube 08/05 21:49
coolrobin: y大的內容比較偏引擎、技術研究 不太適合錄成影片吧 08/05 23:25
yekdniw: 有很多原因 主因是製作影片需要時間太長 08/06 10:01
yekdniw: 所以暫時沒有考慮 謝謝~ 08/06 10:01