Unity3D中實現幀同步 Part 1

2021-07-03 12:01:21 字數 3302 閱讀 3924

lockstep implementation in unity3d

在幀同步模型中,每個客戶端都會對整個遊戲世界進行模擬。這種方法的好處在於減少了需要傳送的資訊。幀同步只需要傳送使用者的輸入資訊,而對於反過來的中心伺服器模型來說,單位的資訊則傳送越頻繁越好。

比如說你在遊戲世界中移動角色。在中心伺服器模型中,物理模擬只會在伺服器執行。客戶端告訴伺服器,角色要往哪個方向移動。伺服器會執行尋路而且開始移動角色。伺服器緊接著就會盡可能頻繁地告知每個客戶端該角色的位置。對於遊戲世界中的每個角色都要執行這樣的過程。對於實時策略遊戲來說,同步成千上萬的單位在中心伺服器模型中幾乎是不可能的任務。

在幀同步模型中,在使用者決定移動角色之後,就會告訴所有客戶端。每個客戶端都會執行尋路以及更新角色位置。只有使用者輸入的時候才需要通知每個客戶端,然後每個客戶端都會自己更新物理以及位置。

這個模型帶來了一些問題。每個客戶端的模擬都必須執行得一模一樣。這意味著,物理模擬必須執行同樣的更新次數而且每個動作都需要同樣的順序執行。如果不這麼做,其中乙個客戶端就會跑在其他客戶端之前或者之後,然後在新的命令發出之後,跑得太快或者太慢的客戶端走出的路徑就會不同。這些不同會根據不同的遊戲玩法而不同。

另乙個問題就是跨不同的機器和平台的確定性問題。計算上很小的不同都會對遊戲造成蝴蝶效應。這個問題會在後續的文章中講到。

這裡的實現方案靈感來自於這篇文章:《1500個弓箭手》。每個玩家命令都會在後續的兩個回合中執行。在傳送動作與處理動作之間存在延遲有助於對抗網路延遲。這個實現還給我們留下了根據延遲以及機器效能動態調整每回合時長的空間。這部分在這裡先不討論,會在後續文章再說。

對於這個實現,我們有如下定義:

幀同步回合

遊戲回合

動作注意:我們將不使用unity3d的物理引擎。而是使用乙個確定性的自定義引擎。在後續文章中會有實現。

unity3d的迴圈是執行在單執行緒下的。可以通過在這兩個函式插入自定義**:

unity3d的主迴圈每次遍歷更新都會呼叫update()。主迴圈會以最快速度執行,除非設定了固定的幀率。fixedupdate()會根據設定每秒執行固定次數。在主迴圈遍歷中,它會被呼叫零次或多次,取決於上次遍歷所花費的時間。fixedupdate()有著我們想要的行為,就是每次幀同步回合都執行固定時長。但是,fixedupdate()的頻率只能在執行之前設定好。而我們希望可以根據效能調節我們的遊戲幀率。

這個實現有著與fixedupdate()在update()函式中執行所類似的邏輯。主要不同的地方在於,我們可以調整頻率。這是通過增加」累計時間」來完成的。每次呼叫update()函式,上次遍歷所花費的時間會新增到其中。這就是time.deltatime。如果累計時間大於我們的固定遊戲回合幀率(50ms),那麼我們就會呼叫gameframe()。我們每次呼叫gameframe()都會在累計時間上減去50ms,所以我們一直呼叫,知道累計時間小於50ms。

private

float

accumilatedtime=0f

;private

float

framelength

=0.05f

;//50 miliseconds

//called once per unity frame

public

void

update

()}

我們跟蹤當前幀同步回合中遊戲幀的數量。每當我們在幀同步回合中達到我們想要的遊戲回合次數,我們就會更新幀同步回合到下一輪。如果幀同步還不能到下一輪,我們就不能增加遊戲幀,而且我們會在下一次同樣執行幀同步檢查。

private

void

gameframeturn()}

else

}}

在遊戲回合中,物理模擬會更新而且我們的遊戲邏輯也會更新。遊戲邏輯是通過介面(ihasgameframe)來實現的,而且新增這個物件到集合中,然後我們就可以進行遍歷。

private

void

gameframeturn()}

else

}foreach

(ihasgameframe

objin

finished

)gameframe++;

if(gameframe

==gameframesperlocksetpturn)}

}

ihasgameframe介面有乙個方法叫做gameframeturn,它以當前每秒遊戲幀的個數為引數。乙個具體的帶遊戲邏輯的物件應該基於gameframespersecond來計算。比如說,如果乙個單位正在攻擊另乙個單位,而且他攻擊頻率為每秒鐘10點傷害,你可能會通過將它除以gameframespersecond來新增傷害。而gameframespersecond會根據效能進行調整。

ihasgameframe介面也有屬性標記著結束。這使得實現ihasgameframe的物件可以通知遊戲幀迴圈自己已經結束。乙個例子就是乙個物件跟著路徑行走,而在到達目的地之後,這個物件就不再需要了。

為了與其他客戶端保持同步,每次幀同步回合我們都要問以下問題:

我們有兩個物件,confirmedactions和pendingactions。這兩個都有各自可能收到訊息的集合。在我們進入下乙個回合之前,我們會檢查這兩個物件。

private

bool

nextturn

()return

false

;}

動作,也就是命令,都通過實現iaction介面來通訊。有著乙個無引數函式叫做processaction()。這個類必須為serializable。這意味著這個物件的所有欄位也是serializable的。當使用者與ui互動,動作的例項就會建立,然後傳送到我們的幀同步管理器的佇列中。佇列通常在遊戲太慢而使用者在乙個幀同步回合中傳送多於乙個命令的時候用到。雖然每次只能傳送乙個命令,但沒有乙個會忽略。

當傳送動作到其他玩家的時候,動作例項會序列化為位元組陣列,然後被其他玩家反序列化。乙個預設的」非動作」物件會在使用者沒有執行任何操作的時候傳送。而其他則會根據特定遊戲邏輯而定。這裡是乙個建立新單位的動作:

using

system

;using

unityengine;[

serializable

]public

class

createunit

:iaction

public

void

processaction

()}

例項**可以在下面找到:

bitbucket – sample lockstep

Unity3D中單鏈表實現

unity3d中單鏈表實現 using unityengine using system.collections 單鏈表結點類,採用泛型 public class node 構造器 引用域,頭結點 public node nodep 構造器 資料域,尾結點 public node t val 構造器...

Unity3D開發(九) Unity3d流光效果

遊戲開 壇 hello game 遊戲開發群 201276069 之前曾經注意過material 中紋理的屬性都有 tiling 和offset 但沒有深究過其用途,今天才知道竟然可以利用 offset做uv 動畫,從而完成各種有趣的動畫,比如流光效果!流過效果即通常一條高光光在物體上劃過,模擬高光...

Unity 3D模型動畫匯出為幀序列

1.要引用的命名空間 using system.io using unityengine using system.collections.generic using system.collections 2.擷取當前螢幕,儲存為texture2d物件,存入檔名 字典中。存入字典會占用較高的記憶體,...