5 - Property Changes
概述
當定義網路屬性時,Fusion將用自定義代碼替換提供的get
和set
存根,以訪問網路狀態。這意味著應用程式不能使用這些方法來處理屬性值的變化,而創建單獨的setter方法也只能在本地工作。
為了解決這個問題,Fusion為[Networked]
屬性提供了一個參數,可以用來指定一個靜態呼叫返回方法,在該屬性發生變化時被呼叫。
這對於回應狀態變化而產生本地視覺效果或執行其他不直接影響遊戲邏輯的任務是非常有用的。這是一個重要的注意事項,因為屬性的變化可能會因為重新模擬而發生多次(或者,準確的說是兩次,一次是預測,另一次是預測不正確),或者如果屬性在兩個值之間來回變化的速度比網路狀態的發送速度快(或者數據包被丟棄),它可能被完全跳過。
OnChanged
呼叫返回是作為Unity的Update()
周期的一部分被呼叫的(嚴格來說,它是在Update
之前被呼叫的),但它不是模擬的一部分,只是對從一個Unity幀到下一個的變化做出反應。這也是為什麼短暫的數值切換可能沒有被發現的原因之一。如果在一幀中處理了多個快照,而其中最後一個快照將網路化的屬性返回到它的原始值,那麼就不會檢測到任何變化。
使用變化呼叫返回而不是像RPC這樣的普通消息的主要好處是,呼叫返回會在值被改變的tick之後立即執行,而RPC可能在遊戲處於完全不同的狀態時才到達。
添加一個變化監聽器
屬性改變的呼叫返回是通過[Networked]
屬性本身的OnChanged
參數來定義的,它採用一個靜態方法的名稱,接受一個Changed<T>
類型的單一參數,其中T
是包圍行為的類型。
所提供的呼叫返回必須是一個靜態方法,因為這允許Fusion為該類型擁有一個委托,而不是為該對象的每個事件分配委托。
在這個例子中,我們的目標是當一個球被發射時將立方體染成白色,然後將其漸漸變成藍色。
為了觸發這個效果,主機將切換一個網路變量中的一個位。由於在一個tick中(由一個特定的玩家)不能產生超過一個球,每個新產生的球將改變該位的值,使其與前一個tick不同,從而觸發OnChanged
呼叫返回。
在添加代碼之前,請注意這個設計可能會失敗-正如已經提到的,變化可能不會被察覺,特別是如果它們是翻轉/浮動的那種。為了使它更有彈性,我們可以用一個byte
或int
來代替NetworkBool
,並在每次呼叫時對其進行碰撞。最後,這是一個視覺效果的重要性與消耗多少頻寬的問題。
結束後打開Player
類別,添加一個新的屬性,以及一個簡單的呼叫返回置入:
C#
[Networked(OnChanged = nameof(OnBallSpawned))]
public NetworkBool spawned { get; set; }
public static void OnBallSpawned(Changed<Player> changed)
{
changed.Behaviour.material.color = Color.white;
}
這顯然是假設Player
有一個材料屬性,可以用來改變立方體網格的顏色,所以繼續添加一個新屬性:
C#
private Material _material;
Material material
{
get
{
if(_material==null)
_material = GetComponentInChildren<MeshRenderer>().material;
return _material;
}
}
顏色應該在Render()
中被更新為從當前顏色到藍色的線性內插。這是在Render()
中完成的,而不是Update()
,因為它保証在FixedUpdateNetwork()
之後執行,它使用Time.deltaTime
而不是Runner.DeltaTime
,因為它是在Unity的渲染循環中執行,而不是作為Fusion模擬的一部分。
C#
public override void Render()
{
material.color = Color.Lerp(material.color, Color.blue, Time.deltaTime );
}
剩下的就是在呼叫Runner.Spawn()
後,通過切換spawned
屬性觸發呼叫返回:
C#
...
Runner.Spawn(_prefabBall, transform.position+_forward, Quaternion.LookRotation(_forward));
spawned = !spawned;
...
請記住,有兩個地方呼叫了Spawn()
,都應該切換spawned
。
但是為什麼?
問:但是為什麼不在呼叫spawn時立即設置顏色呢?
雖然這對主機和有輸入權限的客戶端都有效,因為兩者都是根據玩家的輸入來預測的,但這對代理不適用。
Q: 但是如果顏色是一個網路屬性,在Render()
中被簡單地應用於所有客戶端,那肯定就不需要OnChanged處理程序了?
這確實可行,但它需要在主機上做動畫,而且會產生很多不必要的流量。一般來說,視覺效果應該由國家機構觸發,然後讓它在每個客戶端上自主執行。就像每個人都喜歡火花一樣,沒有人關心某個特定的火花是朝一個方向還是另一個方向飛。
訪問以前的狀態
這個簡單的例子並不關心改變後的屬性的實際值,但應該注意的是,提供給呼叫返回的Changed<T>
參數有一個對改變發生時的源行為的引用,允許應用程式在該tick期間訪問該行為的所有聯網的屬性。
不太明顯的是,還有一個方法可以加載行為的先前狀態,有效地為應用程式提供前一個tick的狀態,用於所有的網路屬性。
``csharp
var newValue = changed.Behaviour.someNetworkedProperty;
changed.LoadOld();
var oldValue = changed.Behaviour.someNetworkedProperty;
同樣地,應用程式可以用`LoadNew()`再次加載新的狀態,但它不需要擔心在退出前重置狀態,因為狀態只在這個特定的呼叫返回上下文中相關。
Back to top