(一定得模擬測試,方纔能get到!)
I. Replication
Replication指的是 ***從服務端向客戶端*** 傳遞數據和信息的行爲。注意是單向的,不會從客戶端傳遞信息和數據到服務端。
假設一個Actor被設置爲Replicates, ***當且僅當它被服務端生成*** ,那麼它會被所有客戶端生成,並被Replicated。言外之意,即使是Replicates的Actor,如果在某個客戶端生成,它也只會在本客戶端生成,是 不會在其他客戶端或者服務端生成 的,自然也談不上Replication。
還有一種情況容易讓人糊塗:假設在Pawn類中的Authority端生成一個Actor,並且把它存爲變量,那麼這個變量的Replicates屬性和這個Actor本身的Replicates屬性是什麼關係?實際上,這裏變量只是指向這個Actor的一個引用/指針,掌握住這點就好理解了。如果這個Actor沒有Replicates,而讓這個指針Replicates,那麼它在客戶端上就會指向一個NULL,這個並不會讓引擎崩潰,但是沒有任何意義。如果Actor Replicates,但是指針不是Replicated,那麼在客戶端這個Actor是存在的,但是指針沒辦法指向正確的對象。所以這種情況下需要Actor和變量都是Replicates(ed)才能達到正確的效果
另外,GameMode是不會Replicates的,它只存在於服務端,所以對它存儲的變量設置Replicated沒有任何意義。
那麼再考慮一下,如果這個Actor在編輯階段就被置於場景中呢,它在多人聯網的情形下是什麼狀況?
我做了以下幾個實驗來逐步分析:
(以下實驗如無特殊說明,均採用默認的第三人稱場景,Play選項Number of Players選擇2,New Editor Window(PIE))
實驗1:Actor不勾選Replicates,直接放置於場景中,初始一個隨機速度運動
結果:客戶端和服務端都各自以各自的速度運動,證明這兩個物體在不同的instance上是獨立的,互無關聯的。
實驗2:Actor勾選Replicates,但不勾選Replicate Movement,直接放置於場景中,初始一個隨機速度運動
結果:和實驗1一樣。那麼大家可能就會懷疑,這種情況下是不是和實驗1是完全一樣的?實際上和實驗1有很大的區別,這個Actor有了Replication,也就可以進行屬性的Replicates和RPC調用,而實驗1裏的Actor就不可能進行這些操作。
實驗3:Actor勾選Replicates,勾選Replicate Movement,直接放置於場景中,初始一個隨機速度運動
結果:雖然初始化了不同的速度(print出來了),但是服務端和客戶端運動基本上是同步的,只是客戶端的運動會有些許“抖動”。出現這種情況的原因是客戶端“想”以自己的速度去運動,但是由於更新了Replicate Movement,服務端會以固定的頻率去把自身的位置同步給客戶端,因此客戶端會在更新的瞬間跳躍到服務器指定的位置,從而產生了抖動。
放置於場景中的Actor的情況我們搞清楚了,再來考慮一下更麻煩的——動態生成:
實驗4:我們先在LevelBlueprint的BeginPlay中直接生成Actor,這個Actor是實驗2的,也就是Actor勾選了Replicate,但並不同步移動。
結果:服務端有一個Actor,客戶端有2個Actor,並且3個Actor均以不同的速度在運動。不難理解,因爲在LevelBP中生成,所以服務端生成了一個,但是這個Actor是Replicates的,所以被複制到客戶端,同時客戶端本身的LevelBP中也生成了一個,所以客戶端有兩個。
所以如果要在LevelBlueprint中生成Replicates的Actor,一定要在前面加上Switch has authority,並在Authority後面生成。
上述幾個實驗的工程源碼存放在這裏
II. Ownership
所謂“Own”(擁有)的主語其實是一個“連接”的實例或者PlayerController。
每個“連接”的實例肯定會Own(擁有)一個PlayerController
那麼決定一個Actor是否被擁有,就是向上查找他的Ownership結構樹,找到最上層的擁有者,如果是個PlayerController,它就被這個PlayerController以及它的“連接”所擁有。
最典型的的例子,一個Pawn被PlayerController Possess的時候它就被Owned。如果它被Unposses了,Ownership也就丟失了。
那麼一個普通的Actor能不能被“擁有”呢?答案是可以的,使用Set Owner方法就可以讓他被某個PlayerController擁有。
爲什麼要強調Ownership呢?
- 後面我們將描述這個問題:RPC需要決定在哪個客戶端執行 Run on Client的 RPC
- Actor網絡複製和連接的Rlevancy(相關性)
- 牽扯到Owner時候的property Replication條件
III. Actor Role 和 Remote Role
Actor Role在我的 這篇文章 裏已經很詳細的描述過了,這裏做一下補充:
Actor Role 和 Remote Role是一組相對的概念,Actor Role指的是在本地的角色,Remote Role指的是遠程端的角色。這裏的“遠程端”有點tricky,容易誤解,我也是糊塗了很久才明白。
設想有一個服務端加兩個或兩個以上的客戶端,那麼所謂的“遠程端”到底指的是在哪個“端”?實際上我們只需要分兩個“端”,一個服務端,一個客戶端,把所有的客戶端都看成一端,這個問題的答案就很顯而易見了。
另外 官方文檔專門說明了, “目前,僅服務端可以負責同步信息到客戶端,因此僅服務端可以看到Role=Authority,並且RemoteRole=Simulated_Proxy或者Autonomous_Proxy”,這個“目前”說的是目前的網絡構架。
總之,只有服務端纔可能有Authority,所有Replicates的Actor在服務端的Role都是Authority,要牢記這點。(非Replciates的有沒有Authority?有待驗證)
實際上,Simulated_Proxy和Autonomous_Proxy講的是兩種同步模式(Mode of Replication),因爲服務器不可能每一幀都去把信息同步給所有客戶端,而是以一定的頻率去下發信息,那麼兩次下發之間的空白怎麼填補呢?虛幻設計了兩種同步的模式:
Simulated_Proxy是標準模式,用最後的速度和位置去移動物體.(使用最後的速度只是一種算法,你也可以實施自己的算法)
Autonomous_Proxy基本上只會用於被PlayerController 所Possess的對象(那就是被Possess的Pawn啦),那麼在空白期間就直接用用戶的操作來填補。
看到這裏,我們需要明確兩點:
1、Autonomous_Proxy就是Pawn(當然說的是被Possess的)在本客戶端的Role——目前我能想到僅有這一種情況。它的RemoteRole——也就是在服務端的Role是Authority,它在其他客戶端的Role是Simulated_Proxy。
2、而任何非Pawn的Actor,在服務端的Role都是Authority,他們的RemoteRole都是SimulatedProxy。
在C++中可以使用Role==XXX來判斷一個Pawn的Role,但是在藍圖中我們可能需要分兩步來進行:首先判斷是否has authority,如果是,則是服務端,如果否,還要繼續判斷pawn是否isLocallyControlled,如果是,則是Autonomous_Proxy,如果否則是Simulated_Proxy。
IV. Actor Role和Ownership的關係
很重要的一點, Actor Role 和 Ownership沒有任何關係 ,是不同的概念。
只是很巧合,Autonomous_Proxy肯定會被Own,但不能說被Own了就一定是Autonomous_Proxy,例如我們可以對一個非Pawn的Actor 設置所有者(Set Owner),但是它的Actor Role沒有改變。
V. RPC
RPC本質是用來調用在 另外一個遊戲實例上的函數的 。
RPC無法獲取返回值 ,這就是爲爲什麼沒辦法把藍圖中的Function設置爲RPC的原因,因爲Function是可以帶返回值的。而CustomEvent是沒法設置返回值的,所以RPC只能標記在CustomEvent上。
RPC分三種,Run on Server,Run on owning Client,NetMulticast
RPC必須在Actor或者其子類上調用
Actor必須是Replicated的(否則不存在RPC一說)
Run on Server
表示在Actor的服務端實例上執行。 如果RPC是從客戶端調用,讓其在服務端執行,客戶端必須 Own(擁有) 這個Actor。
Run on owning Client
表示在Actor的Owner上執行。 如果RPC是從服務端調用,讓其在客戶端執行,只有 Own(擁有) 這個Actor的客戶端纔會執行。 注意Run on Server和 Run on owning Client的條件:客戶端必須Own這個Actor,也就是 這個Actor必須有Ownership 。
NetMulticast
表示在Actor的所有實例上執行。 如前所述,前兩種RPC模式都要求Actor必須有Ownership。 Multicast是個例外,它不需要OwnerShip 。 如果從服務端調用,服務端會本地執行,所有目前連接的客戶端也都會執行。 如果從客戶端端執行,則只會本地執行,服務端不會執行,其他客戶端也不會執行。