Netcode for Entities裡如何對Ghost進行可見性篩選(1.2.3版本)

horeaper發表於2024-07-13

一行程式碼省流:SystemAPI.GetSingleton<GhostRelevancy>()

當你需要按照區域、距離或者場景對Ghost進行篩選的時候,Netcode for Entities裡並沒有類似FishNet那樣方便的過濾方式,需要獲取一個過濾專用的元件:GhostRelevancy
這個結構的內容不多,但功能很強大,但用起來很累,但概念其實挺簡單的:在伺服器端設定Ghost的GhostId和客戶端NetworkId的關聯性。有關聯就傳送,或者有關聯就不傳送。

在1.2.3版本,它裡面只有三個field:

  • GhostRelevancyMode:設定關聯性的模式,預設是Disabled,既無視關聯性,永遠向每一個客戶端傳送每一個Ghost
    當設定成SetIsRelevant的時候,在後面的GhostRelevancySet裡面設定的GhostId會被認為是“和客戶端有關聯的”,則這些Ghost會向客戶端傳送,未設定的則不會傳送
    當設定成SetIsIrrelevant的時候,規則就會反過來。
  • GhostRelevancySet:是一個HashMap,其中Value值的那個int項是沒有使用到的。也就是說是當個HashSet在用的。我不知道為啥這玩意兒沒有做成NativeParallelHashSet,可能是寫程式碼的時候Unity.Collection還沒有吧。
  • DefaultRelevancyQuery:一個EntityQuery,根據前面GhostRelevancyMode設定的關聯性模式,透過Query的結果判斷是否有關聯。不過根據Changelog,這個東西應該是1.3.0才有,然而在1.2.3就已經出現在程式碼裡了。目前可以先不管他,鬼知道里面有沒有什麼BUG。

啟用篩選的方式也很簡單,下面是我用的方案。

首先做一個一次性執行的ISystem,設定好GhostRelevancyMode。比如設定成“有關聯”模式:
SystemAPI.GetSingletonRW<GhostRelevancy>().ValueRW.GhostRelevancyMode = GhostRelevancyMode.SetIsRelevant;

接下來主要就是操作GhostRelevancySet了。方法是往裡面填充大量的RelevantGhostForConnection結構。這東西就是一個NetworkId和一個GhostId。把你想讓某個客戶端看到的每一個Ghost都做一個RelevantGhostForConnection,一股腦全扔到GhostRelevancySet裡,剩下的交給系統。

至於為什麼NetCode沒有選擇用MultiHashMap?猛一看,每一個NetworkId對應多個GhostId,好像MultiMap是更適合的資料結構。其實是因為NetCode內部實現上是:在遍歷每一個Ghost所在的Chunk的時候,用GhostRelevancySet.ContainsKey來判斷這個Ghost要不要傳送的。這種方式的話不用MultiHashMap反而是更高效的方法。具體的程式碼在GhostChunkSerializer.cs檔案裡,UpdateGhostRelevancy方法內。

因為我想做的是按區域過濾,比如玩家位於區域X的時候,那麼就只將區域X和這個區域附近的一圈區域內的Ghost傳送給他。而玩家會在不同的區域裡晃來晃去,因此我操作GhostRelevancySet方法是做了一個Singleton Entity,稱為SectorOperationCommands,上面有四個IBufferElementData,每一個都代表一個指令:

  • Ghost在區域X內生成
  • Ghost在區域X內刪除
  • 玩家進入區域X
  • 玩家離開區域X

使用的時候就只需要往上面新增命令,然後寫了一個ProcessSectorRelevancyCommandsSystem來統一處理。這個System只能放在ServerSimulation上。

我又做了一個Singleton Entity,稱為SectorInfoCollection,裡面儲存了所有區域的資料,比如某個區域裡有哪些GhostId,有哪些NetworkId什麼的。
這樣當玩家新進入一個特定區域的時候,要把哪些GhostId和他關聯起來就很好處理了。同樣的當這個區域裡生成了一個Ghost,要把它和哪些NetworkId關聯起來也一目瞭然。當然你也可以選擇直接把這堆資料就放在ISystem裡。因為我還要在別處用這些資料,所以做成了一個Entity。

對了,獲取GhostId的方法是對著Ghost Entity來一發GetComponent<GhostInstance>()
要注意的是,Instantiate之後GhostId並不會立刻生效,可以透過檢視GhostInstance.spawnTick.IsValid來確定相應的GhostId是否已經被設定。

程式碼就不貼了,我不喜歡在文章裡貼大段大段的程式碼。重要的是傳遞概念,而不是寫一堆不好CV還得人肉編譯的英文符號。

一句話吐槽:這個系統好用嗎?不好用。能用嗎?能用。╮( ̄▽ ̄")╭

相關文章