Unet基础学习
轉(zhuǎn)載自:http://www.cnblogs.com/xianunity/p/5643469.html
包含
[SyncVar]
[Command]
[ClientCallback]
[SeverCallback]等
?
Unet針對不同用戶的使用有高級和低級API兩種--用Unity制作多玩家游戲的用戶,這類用戶應(yīng)該從NetworkManager或者高級API開始。
搭建網(wǎng)絡(luò)架構(gòu)或制作高級的多玩家游戲的用戶,這類用戶應(yīng)該從網(wǎng)絡(luò)傳輸層低級API開始。
下面從高級API(HLAPI)開始介紹,使用高級API可以實現(xiàn):
- 通過Network Manager控制游戲的網(wǎng)絡(luò)狀態(tài)
- 操作客戶端主持的游戲,主持游戲的客戶端同時也是一個玩家
- 使用一個通用的序列化器序列化數(shù)據(jù)
- 發(fā)送和接收網(wǎng)絡(luò)消息
- 從客戶端向服務(wù)器發(fā)送網(wǎng)絡(luò)命令
- 在服務(wù)器上遠(yuǎn)程調(diào)用客戶端提供的過程(RPC)
- 從服務(wù)器向客戶端發(fā)送網(wǎng)絡(luò)事件
關(guān)于Unet的介紹網(wǎng)上有很多,大家可以自行查找。
?
首先,Unet提供了網(wǎng)絡(luò)連接以及管理的組件:
網(wǎng)絡(luò)管理器(NetworkManager)可以作為控制多人游戲的核心組件。在場景中創(chuàng)建一個空的游戲?qū)ο蠡蛱暨x一個方便管理器對象。從Network/NetworkManager菜單項里選擇添加NetworkManager組件。新添加的NetworkManager應(yīng)如圖所示:
在編輯器中的NetworkManager的的inspector 面板上,有允許您配置和控制與網(wǎng)絡(luò)相關(guān)的很多東西。
NetworkManagerHUD 是與NetworkManager相關(guān)的的另一個組件。游戲運行控制網(wǎng)絡(luò)狀態(tài)時,它給你一個簡單的用戶界面。用來調(diào)試或者實驗都可以,但不能用作游戲最終的 ui 設(shè)計。NetworkManagerHUD 看起來像:
真正的游戲用戶需要自己設(shè)置正確的UI ,用于控制游戲狀態(tài)和允許玩家選擇什么樣的游戲 。用戶設(shè)置自己的UI時,腳本需要繼承NetworkManager,使用里面的StartHost(),StartClient()來開啟伺服器和客戶端。并且可以重寫方法來檢測連接或者實現(xiàn)相應(yīng)功能,eg:OnClientConnect,OnStartHost等。
之后便可以使用一系列組件實現(xiàn)自己想要的功能了:
unity自帶的組件有上述這些,根據(jù)名字也可以大致推測出可以實現(xiàn)什么功能,下面簡要介紹一下:
--Animator:動畫同步
--Identity:網(wǎng)絡(luò)標(biāo)識組件(帶有Identity的物體只有網(wǎng)絡(luò)開啟后才會顯示出來)
--LobbyManager:游戲大廳
--LobbyPlayer:玩家控制
--Manager:網(wǎng)絡(luò)管理器
--ManagerHUD:默認(rèn)UI
--StartPosition:游戲開始玩家所處的位置
--Tranform:物體位移等的同步控制(掛載在需要同步物體的最小父物體上即可)
?
?
?
同步中的操作
?
同步變量[SyncVar]--
同步變量是NetworkBehaviour腳本中的成員變量,他們會從服務(wù)器同步到客戶端上。當(dāng)一個物體被派生出來之后,或者一個新的玩家中途加入游戲后,他會接收到他的視野內(nèi)所有物體的同步變量。成員變量通過[SyncVar]標(biāo)簽被配置成同步變量:
class Player :NetworkBehaviour
{
[SyncVar]
int health;
public void TakeDamage(int amount)
{
if (!isServer)
return;
health -= amount;
}
}
同步變量的狀態(tài)在OnStartClient()之前就被應(yīng)用到物體上了,所以在OnStartClient函數(shù)中,物體的狀態(tài)已經(jīng)是最新的數(shù)據(jù)。
同步變量可以是基礎(chǔ)類型,如整數(shù),字符串和浮點數(shù)。也可以是Unity內(nèi)置數(shù)據(jù)類型,如Vector3和用戶自定義的結(jié)構(gòu)體,但是對結(jié)構(gòu)體類型的同步變量,如果只有幾個字段的數(shù)值有變化,整個結(jié)構(gòu)體都會被發(fā)送。每個NetworkBehaviour腳本可以有最多32個同步變量,包括同步列表(見下面的解釋)。
當(dāng)同步變量有變化時,服務(wù)器會自動發(fā)送他們的最新數(shù)據(jù)。不需要手工為同步變量設(shè)置任何的臟數(shù)據(jù)標(biāo)志位。
注意在屬性設(shè)置函數(shù)中設(shè)置一個同步變量的值不會使他的臟數(shù)據(jù)標(biāo)志被設(shè)置。如果這樣做的話,會得到一個編譯期的警告。因為同步變量使用他們自己內(nèi)部的標(biāo)識記錄臟數(shù)據(jù)狀態(tài),在屬性設(shè)置函數(shù)中設(shè)置臟位會引起遞歸調(diào)用問題。
同步變量還可以指定函數(shù),使用hook:
當(dāng)服務(wù)器改變了playerName的值,客戶端會調(diào)用OnMyName這個函數(shù)
[SyncVar(hook = "OnMyName")]
public string playerName = "";
public void OnMyName(string newName)
{
??????????? playerName = newName;
??????????? nameInput.text = playerName;
}
同步列表(SyncLists)--
同步列表類似于同步變量,但是他們是一些值的列表而不是單個值。同步列表和同步變量都包含在初始的狀態(tài)更新里。同步列表不需要[SyncVar]屬性標(biāo)識,他們是特殊的類。內(nèi)建的基礎(chǔ)類型屬性列表有:
SyncListString
SyncListFloat
SyncListInt
SyncListUInt
SyncListBool
還有個SyncListStruct可以給用戶自定義的結(jié)構(gòu)體用。從SyncListStruct派生出的結(jié)構(gòu)體類可以包含基礎(chǔ)類型,數(shù)組和通用Unity類型的成員變量,但是不能包含復(fù)雜的類和通用容器。
同步列表有一個叫做SyncListChanged的回調(diào)函數(shù),可以使客戶端能接收到列表中的數(shù)據(jù)改動的通知。這個回調(diào)函數(shù)被調(diào)用時,會被通知到操作類型,和修改的變量索引。
public class MyScript :NetworkBehaviour
{
public struct Buf
{
public int id;
public string name;
public float timer;
};
public class TestBufs : SyncListStruct<Buf> {}
TestBufs m_bufs = new TestBufs();
void BufChanged(Operation op, int itemIndex)
{
Debug.Log("buf changed:" + op);
}
void Start()
{
m_bufs.Callback = BufChanged;
}
}
定制序列化函數(shù)--
通常在腳本中使用同步變量就夠了,但是有時候也需要更復(fù)雜的序列化代碼。NetworkBehaviour中的虛函數(shù)允許開發(fā)者定制自己的序列化函數(shù),這些函數(shù)有:
public virtual boolOnSerialize(NetworkWriter writer, bool initialState);
public virtual voidOnDeSerialize(NetworkReader reader, bool initialState);
initalState可以用來標(biāo)識是第一次序列化數(shù)據(jù)還是只發(fā)送增量的數(shù)據(jù)。如果是第一次發(fā)送給客戶端,必須要包含所有狀態(tài)的數(shù)據(jù),后續(xù)的更新只需要包含增量的修改,以節(jié)省帶寬。同步變量的鉤子函數(shù)在initialState為True的時候不會被調(diào)用,而只會在增量更新函數(shù)中被調(diào)用。
如果一個類里面聲明了同步變量,這些函數(shù)的實現(xiàn)會自動被加到類里面,因此一個有同步變量的類不能擁有自己的序列化函數(shù)。
OnSerialize函數(shù)應(yīng)該返回True來指示有更新需要發(fā)送,如果它返回了true,這個類的所有臟標(biāo)志位都會被清除,如果它返回False,則臟標(biāo)志位不會被修改。這可以允許將多次改動合并在一起發(fā)送,而不需要每一幀都發(fā)送。?
序列化流程--
具有NetworkIdentity組件的游戲物體可以帶有多個從NetworkBehaviour派生出來的腳本,這些物體的序列化流程為:
在服務(wù)器上:
- 每個NetworkBehaviour上都有一個臟數(shù)據(jù)掩碼,這個掩碼可以在OnSerialize函數(shù)中通過syncVarDirtyBits訪問到
- NetworkBehavious中的每個同步變量被指定了臟數(shù)據(jù)掩碼中的一位
- 對同步變量的修改會使對應(yīng)的臟數(shù)據(jù)位被設(shè)置
- 或者可以通過調(diào)用SetDirtyBit函數(shù)直接修改臟數(shù)據(jù)標(biāo)志位
-?服務(wù)器的每個Update調(diào)用都會檢查他的NetworkIdentity組件
- 如果有標(biāo)記為臟的NetworkBehaviour,就會為那個物體創(chuàng)建一個更新數(shù)據(jù)包
- 每個NetworkBehaviour組件的OnSerialize函數(shù)都被調(diào)用,來構(gòu)建這個更新數(shù)據(jù)包
- 沒有臟數(shù)據(jù)位設(shè)置的NetworkBehaviour在數(shù)據(jù)包中添加0標(biāo)志
- 有臟數(shù)據(jù)位設(shè)置的NetworkBehavious寫入他們的臟數(shù)據(jù)和有改動的同步變量的值
- 如果一個NetworkBehavious的OnSerialize函數(shù)返回了True,那么他的臟標(biāo)志位被重置,因此直到下一次數(shù)據(jù)修改之前不會被再次發(fā)送
- 更新數(shù)據(jù)包被發(fā)送到能看見這個物體的所有客戶端
在客戶端:
- 接收到一個物體的更新數(shù)據(jù)包
- 每個NetworkBehavious腳本的OnDeserialize函數(shù)被調(diào)用
- 這個物體上的每個NetworkBehavious腳本讀取臟數(shù)據(jù)標(biāo)識
- 如果關(guān)聯(lián)到這個NetworkBehaviour腳本的臟數(shù)據(jù)位是0,OnDeserialize函數(shù)直接返回;
- 如果臟數(shù)據(jù)標(biāo)志不是0,OnDeserialize函數(shù)繼續(xù)讀取后續(xù)的同步變量
- 如果有同步變量的鉤子函數(shù),調(diào)用鉤子函數(shù)
對下面的代碼:
public class data :NetworkBehaviour
{
[SyncVar]
public int int1 = 66;
[SyncVar]
public int int2 = 23487;
[SyncVar]
public string MyString = "esfdsagsdfgsdgdsfg";
}
產(chǎn)生的序列化函數(shù)OnSerialize將如下所示:
public override boolOnSerialize(NetworkWriter writer, bool forceAll)
{
if (forceAll)
{
// 第一次發(fā)送物體信息給客戶端,發(fā)送全部數(shù)據(jù)
writer.WritePackedUInt32((uint)this.int1);
writer.WritePackedUInt32((uint)this.int2);
writer.Write(this.MyString);
return true;
}
bool wroteSyncVar = false;
if ((base.get_syncVarDirtyBits() & 1u) != 0u)
{
if (!wroteSyncVar)
{
// write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.WritePackedUInt32((uint)this.int1);
}
if ((base.get_syncVarDirtyBits() & 2u) != 0u)
{
if (!wroteSyncVar)
{
// write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.WritePackedUInt32((uint)this.int2);
}
if ((base.get_syncVarDirtyBits() & 4u) != 0u)
{
if (!wroteSyncVar)
{
// write dirty bits if this is the first SyncVar written
writer.WritePackedUInt32(base.get_syncVarDirtyBits());
wroteSyncVar = true;
}
writer.Write(this.MyString);
}
if (!wroteSyncVar)
{
// write zero dirty bits if no SyncVars were written
writer.WritePackedUInt32(0);
}
return wroteSyncVar;
}
反序列化函數(shù)將如下:
public override voidOnDeserialize(NetworkReader reader, bool initialState)
{
if (initialState)
{
this.int1 = (int)reader.ReadPackedUInt32();
this.int2 = (int)reader.ReadPackedUInt32();
this.MyString = reader.ReadString();
return;
}
int num = (int)reader.ReadPackedUInt32();
if ((num & 1) != 0)
{
this.int1 = (int)reader.ReadPackedUInt32();
}
if ((num & 2) != 0)
{
this.int2 = (int)reader.ReadPackedUInt32();
}
if ((num & 4) != 0)
{
this.MyString = reader.ReadString();
}
}
如果這個NetworkBehaviour的基類也有一個序列化函數(shù),基類的序列化函數(shù)也將被調(diào)用。
注意更新數(shù)據(jù)包可能會在緩沖區(qū)中合并,所以一個傳輸層數(shù)據(jù)包可能包含多個物體的更新數(shù)據(jù)包。
遠(yuǎn)程動作--
網(wǎng)絡(luò)系統(tǒng)允許在網(wǎng)絡(luò)上執(zhí)行遠(yuǎn)程的動作。這類動作有時也叫做遠(yuǎn)程過程調(diào)用(RPC)。有兩種類型的遠(yuǎn)程過程調(diào)用,命令(Commands) – 由客戶端發(fā)起,運行在服務(wù)器上;和客戶端遠(yuǎn)程過程調(diào)用(ClientRpc) - 服務(wù)器發(fā)起,運行在客戶端上。
命令(Commands)--
命令從客戶端上的物體發(fā)給服務(wù)器上的物體。出于安全考慮,命令只能從玩家控制的物體上發(fā)出,因此玩家不能控制其他玩家的物體。要把一個函數(shù)變成命令,需要給這個函數(shù)添加[Command]屬性,并且為函數(shù)名添加"Cmd"前綴,這樣這個函數(shù)會在客戶端上被調(diào)用時在服務(wù)器上運行。所有的參數(shù)會自動和命令一起發(fā)送給服務(wù)器。
命令函數(shù)的名字必須要有"Cmd"前綴。在閱讀代碼的時候,這也是個提示 – 這個函數(shù)比較特殊,他不像普通函數(shù)一樣在本地被執(zhí)行。
class Player :NetworkBehaviour
{
public GameObject bulletPrefab;
[Command]
void CmdDoFire(float lifeTime)
{
GameObject bullet =(GameObject)Instantiate(
bulletPrefab,
transform.position +transform.right,
Quaternion.identity);
var bullet2D =bullet.GetComponent<Rigidbody2D>();
bullet2D.velocity = transform.right *bulletSpeed;
Destroy(bullet, lifeTime);
NetworkServer.Spawn(bullet);
}
void Update()
{
if (!isLocalPlayer)
return;
if (Input.GetKeyDown(KeyCode.Space))
{
CmdDoFire(3.0f);
}
}
}
注意如果每一幀都發(fā)送命令消息,會產(chǎn)生很多的網(wǎng)絡(luò)流量。
默認(rèn)情況下,命令是通過0號通道(默認(rèn)的可靠傳輸通道)進(jìn)行傳輸?shù)摹K阅J(rèn)情況下,所有的命令都會被可靠地發(fā)送到服務(wù)器。可以使用命令的"Channel"參數(shù)修改這個配置。參數(shù)是一個整數(shù),表示通道號。
1號通道是默認(rèn)的不可靠傳輸通道,如果要用這個通道,把這個參數(shù)設(shè)置為1,示例如下:
[Command(channel=1)]
從Unity5.2開始,可以從擁有客戶端授權(quán)的非玩家物體發(fā)出命令。這些物體必須是使用函數(shù)NetworkServer.SpawnWithClientAuthority()派生出來的,或者是使用NetworkIdentity.AssignClientAuthority()授權(quán)過的。從物體發(fā)送出來的命令會在服務(wù)器上運行,而不是在相關(guān)玩家物體所在的客戶端上。
客戶端遠(yuǎn)程過程調(diào)用(ClientRPC Calls)
客戶端遠(yuǎn)程過程調(diào)用從服務(wù)器的物體發(fā)送到客戶端的物體上去。他們可以從任何帶有NetworkIdentity并被派生出來的物體上發(fā)出。因為服務(wù)器擁有授權(quán),所以這個過程不存在安全問題。要把一個函數(shù)變成客戶端遠(yuǎn)程過程調(diào)用,需要給函數(shù)添加[ClientRpc]屬性,并且為函數(shù)名添加"Rpc"前綴。這個函數(shù)將在服務(wù)端上被調(diào)用時,在客戶端上執(zhí)行。所有的參數(shù)都將自動傳給客戶端。
客戶端遠(yuǎn)程調(diào)用必須帶有"Rpc"前綴。在閱讀代碼的時候,這將是個提示 – 這個函數(shù)比較特殊,不像一般函數(shù)那樣在本地執(zhí)行。
class Player :NetworkBehaviour
{
[SyncVar]
int health;
[ClientRpc]
void RpcDamage(int amount)
{
Debug.Log("Took damage:" +amount);
}
public void TakeDamage(int amount)
{
if (!isServer)
return;
health -= amount;
RpcDamage(amount);
}
}
當(dāng)使用伺服器模式運行游戲的時候,客戶端遠(yuǎn)程調(diào)用將在本地客戶端執(zhí)行 – 即使他其實和服務(wù)器運行在同一個進(jìn)程。因此本地客戶端和遠(yuǎn)程客戶端對客戶端遠(yuǎn)程過程調(diào)用的處理是一樣的。
如果想將[ClientRpc]用在點擊事件的同步操作上,不能直接綁定點擊事件函數(shù),而是應(yīng)該起一個新的Rpc函數(shù),點擊事件去綁定這個Rpc函數(shù),Rpc函數(shù)里才是對點擊事件的操作:
?//點擊事件
??? public void ClickDXView()
??? {
??????????? RpcDXView();
??? }
??? [ClientRpc]
??? public void RpcDXView()
??? {
????????readyPN.gameObject.SetActive(false);
??????? startGm();
??????? Camera.main.GetComponent<DOTweenPath>().DOPlay();
??? }
回調(diào)函數(shù)--
[ServerCallback]:只執(zhí)行在服務(wù)器端,并使一些特殊函數(shù)(eg:Update)不報錯(若在此函數(shù)中改變了帶有syncvar的變量,客戶端不同步)
(使用ServerCallback時,將Update中的重要語句摘出來寫入Rpc函數(shù)中并調(diào)用)
[ClientCallback]:只執(zhí)行在客戶端
另:[Server]:只執(zhí)行在服務(wù)器端但是不能標(biāo)識一些特殊函數(shù)(可以在這里調(diào)用Rpc類函數(shù))
? ?
遠(yuǎn)程過程的參數(shù)
傳遞給客戶端遠(yuǎn)程過程調(diào)用的參數(shù)會被序列化并在網(wǎng)絡(luò)上傳送,這些參數(shù)可以是:
- 基本數(shù)據(jù)類型(字節(jié),整數(shù),浮點樹,字符串,64位無符號整數(shù)等)
- 基本數(shù)據(jù)類型的數(shù)組
- 包含允許的數(shù)據(jù)類型的結(jié)構(gòu)體
- Unity內(nèi)建的數(shù)學(xué)類型(Vector3,Quaternion等)
- NetworkIdentity
- NetworkInstanceId
- NetworkHash128
- 帶有NetworkIdentity組件的物體
遠(yuǎn)程過程的參數(shù)不可以是游戲物體的子組件,像腳本對象或Transform,他們也不能是其他不能在網(wǎng)絡(luò)上被序列化的數(shù)據(jù)類型。
?
在使用過程中發(fā)現(xiàn)一個問題:帶有NetworkIdentity的組件在運行之前不能是隱藏的,否則同步會受影響,在代碼Start函數(shù)中置為SetActive = false,或者因為網(wǎng)絡(luò)問題一開始隱藏的物體在后續(xù)同步中都沒有問題。
總結(jié)
- 上一篇: EventTrigger接管所有事件导致
- 下一篇: Orcale本人工具使用