目前GOLANG有大行其道的趨勢,尤爲是在網絡編程方面。由於和c/c++比較起來,雖然GC佔用了一部分機器性能,可是出錯機率小了,開發效率大大提高,並且應用其原生支持的協程很容易就能開發出高併發的服務端程序。筆者接觸VR行業兩年有餘,接觸了一些商業unity網絡引擎,總覺的用的東西都落伍了,因而本身寫了一個簡單的引擎。目前實現了的基本功能:html
也實現了一個簡單的demo,同步效果見下圖,後面會有更詳細的介紹。linux
項目地址:https://github.com/netgo-framework
下面是一個簡單的項目覆盤。android
數據通訊格式的定義是整個項目的基石。咱們這裏的客戶端和服務端是跨平臺,跨語言通訊。所以要定義一種語言無關,平臺無關而且簡單易用,高效不費流量的數據格式。這裏咱們選用了Google的 Protobuf,詳細介紹參考這篇帖子。c++
Protobuf的C#代碼庫有兩種選擇,一種是protobuf-net,一種是protobuf-csharp-port,前者的接口書寫更加符合C# 語法規範,會讓人看起來更舒服一些。若是須要跨平臺的話,推薦使用後者,由於不一樣語言的接口書寫比較相似,開發起來會更容易一些。看看原做者的回覆。git
如何使用protobuf呢,首先要書寫proto文件,定義本身的結構化數據,在netgo中,下面是netgo中定義的消息體的一部分:github
enum CacheOptions{ AddToRoomCache = 0; RemoveFromRoomCache = 1; } message NGVector3{ float x = 1; float y = 2; float z = 3; } message NGQuaternion{ float x = 1; float y = 2; float z = 3; float w = 4; } message NGColor{ float r = 1; float g = 2; float b = 3; float a = 4; }
完整定義參考。golang
更新好命名空間後,執行下面的命令生成API文件:編程
golangjson
protoc --go_out=. *.protoc#
c#
protoc --csharp_out=. *.proto
一個Unity網絡同步引擎的實現包括服務端和客戶端兩部分。Nego 是Unity網絡同步引擎的服務端,使用golang實現,充分利用了它的原生協程來實現高併發。其網絡模型基於gotcp來實現。
參考上圖,netgo會爲每一個socket連接創建一個協程,一個socket協程內部創建三個協程:
參考代碼:
func (c *Conn) Do() { if !c.srv.callback.OnConnect(c) { return } asyncDo(c.handleLoop, c.srv.waitGroup) asyncDo(c.readLoop, c.srv.waitGroup) asyncDo(c.writeLoop, c.srv.waitGroup) }
寫API基本上是面向用戶編程,筆者覺得,清晰的代碼結構,好的命名方式能省掉大部分註釋,代碼寫的亂只能靠註釋來拯救,代碼結構看下圖:
按照命名空間,分爲 Library,網絡層和應用層(之後用戶接口層會分出來).
這裏的同步是指一個房間內的數據同步,一個房間內存在着來自網絡上的多個終端用戶,每一個Client都會將房間內其它人的數據在本地作一個Clone,而數據同步是指將你本身的數據同步到其餘Cient你本身的Clone上面,所以發送範圍是其它用戶都會接收。
數據同步分爲一下兩種:
View Sync是毫秒級別的數據同步。可用於虛擬角色動做同步。
每次同步由用戶手動觸發。可用於換裝等同步。
Custom Event不是向全部其它Client的Clone實體發送同步消息,而是向一個或者幾個指定的Client發送消息。
//加入或者建立房間 public static void JoinOrCreateRoom(string roomid,uint maxnumber) //建立房間 public static void CreateRoom(string roomid, uint maxnumber) //加入房間 public static void JoinRoom(string roomid) //離開房間 public static void LeaveRoom()
//建立房間成功 void OnGreatedRoom(); //建立房間失敗 void OnGreateRoomFailed(string errmsg); //加入房間成功 void OnJoinedRoom(); //加入房間失敗 void OnJoinRoomFailed(); //離開房間成功 void OnLeftRoom();
//實例化一個物體 public static void Instantiate(string prefabname, Vector3 position, Quaternion rotation, uint[] viewids) //有其它用戶進入房間 void OnOtherPlayerEnteredRoom(NGPlayer player); //有其它用戶離開房間 void OnOtherPlayerLeftRoom(NGPlayer player);
//發送事件 public static void SendCustomEvent(uint eventid, uint[] targetpeerids, NGAny[] customdata)
//接收事件 void OnCustomEvent(uint eventID, NGAny[] data);
視圖同步須要本身實現組件腳本,實現序列化反序列化接口,而且須要掛載到物體上:
public interface INGSerialize { void SerializeViewComponent(NGViewStream stream); void DeserializeViewComponent(NGViewStream stream); } public class CubeViewComponent : NGIncomingEvent, INGSerialize { public void SerializeViewComponent(NGViewStream stream) { stream.Send(this.transform.position); stream.Send(this.transform.rotation); } public void DeserializeViewComponent(NGViewStream stream) { mCorrentPosition = (NGVector3)stream.Receive(); mCorrentRotation = (NGQuaternion)stream.Receive(); } }
Clone實體接受數據反序列化後在Update中實時更新便可:
void Update() { if (!view.IsMine) { transform.position = mCorrentPosition;//Vector3.Lerp(transform.position, mCorrentPosition, Time.deltaTime * 5); transform.rotation = mCorrentRotation;//Quaternion.Lerp(transform.rotation, mCorrentRotation, Time.deltaTime * 5); } }
使用RPC須要在視圖腳本中寫一個RPC函數:
[NGRPCMethod] public void OnColor(NGAny[] c) { mMat.color = c[0].NgColor; }
調用下面的接口向其它Clone實體發送RPC調用:
public static void SendRPC(uint viewID, string methodname, RPCTarget target, params NGAny[] parameters)
有關RPC,View Sync和Custom Event 的詳細使用方法 參考源碼
git clone https://github.com/netgo-framework/netgo.git
go get -d ./...
打開main.go
tcpAddr, err := net.ResolveTCPAddr("tcp", "192.168.0.104:8686")
go run main.go
客戶端支持windows/MacOS/Andorid/IOS多平臺。下面在Android和MacOS上測試:
安裝APK後的初始化界面以下:
兩個Client進入同一個房間,每一個Client會實例化出來兩個Cube,一個爲本機實體(Mine Cube),一個爲對方的實體(Clone Cube)。
點擊按鈕Move後,會經過視圖同步的方式進行postion和rotation同步。也就是文章剛開始的動圖展現的樣子:
點擊Mine Cube以後,Cube的顏色會發生變化,同時同步到別的機器上,這裏的顏色同步是經過RPC來實現的。
點擊Clone Cube以後,會向對方實體發送消息,效果是對方的Mine Cube Scale會增長。
接下來考慮會加入或者須要優化的功能: