2013年12月25日 星期三

Unity網路好簡單:)


Unity, 一款強大易入手的遊戲開發工具, 除了在跨平台圖像呈現上表現已相當優異, Unity本身也提供了網路多人連線的功能.

Unity網路功能在使用上相當有彈性, 但說簡單也不簡單, 開發上若未規劃好如何使用, 很容易產生不必要的Component及多餘的傳輸量, 在除錯上也會變的相當不容易.

之前看到一篇文章, 教大家以"簡單"的方式來使用Unity的網路功能. 將文章消化吸收後, 在此分享給大家:)

原文出處(連結已不存在): http://lakehomegames.com/2012/12/05/unity-fps-networking-sample/
其它出處: http://www.gamasutra.com/blogs/DarrelCusey/20130221/187128/Unity_Networking_Sample_Using_One_NetworkView.php

-本文正式開始囉-

為了避免以上提及的問題, 原著提出需避免下列3個元素的使用:
  1. NetworkViews: 在需要同步的物件上加上NetworkView Component, Unity便會將其物件的資料同步, 但若未適當規劃管理, 便會造成多餘的網路傳輸量.
  2. Network.Instantiate: 在新的Player/Client加入時, 同樣會產生大量的網路傳輸量.
  3. RemoveRPCs: 使用Network.Instantiate才有作用的功能, 所以跟著一併不使用.

原著採用a Single World NetworkView的概念來設計網路資料傳輸的架構, 示意圖如下:
fps_sample_overview
圖片來源: http://www.lakehomegames.com/images/fps_sample_overview.png

因Unity內建網路溝通的功能必須透過NetworkView, 所以在整個設計架構下會使用到一個(只要一個哦)NetworkView來進行RPC的傳送及接收. 只要將NetworkView Component附加在任一空的GameObject, 並設定State Synchronization=OFF, Observed=NONE就能達成.

在網路架構下, 每個在Server產生的Player(Client連線)都必須要有唯一的識別ID, 以便Server辨識及管理. 通常要完成這項功能(產生唯一ID), 可能會去引用額外的套件, 此法CP值甚低, 因此原著提出一個簡單的技巧就能完成: 使用Network.AllocateViewID()來產生唯一ID.

了解以上技巧後, 接下來就是要來看在single NetworkView架構下, 需要負責管理哪些事啦:
  1. 需設計好要傳送的訊息, 要了解哪些訊息是傳給自己, 傳給Server, 或者是傳給其它Clients.
  2. 當有Player/Client連線成功加入Server或中斷連線時, 需處理對應的Player Object生成或消滅.
  3. NetworkPlayer和其對應的GameObject管理, 可利用像是dictionary的集合類型將其儲存起來以維護管理.

範例示意如下:
simple_fps
圖片來源: http://www.lakehomegames.com/images/simple_fps.png

請下載原著提供的原檔, 在networkController script裡就可看出整個流程. 針對比較重要的處理流程說明如下(請對照原始碼來看):

Server端的OnPlayerConnected()在收到玩家連線時, 透過Server端的NetworkView來告訴所有已連線的玩家以及Server本身(RPCMode.All), 有新玩家加入了.
networkView.RPC("JoinPlayer", RPCMode.All, newViewID, Vector3.zero, p);

當收到JoinPlayer() RPC時, 需生成對應的Player Object, 並存入HashTable做管理.
players.Add(p,newPlayer);

同理, 當有玩家斷線, 收到DisconnectPlayer() RPC時, 需消滅對應的Player Object, 並從HashTable中移除.
players.Remove(player);

在收到由Server傳送的Player加入訊息, 即JoinPlayer() RPC裡, 有一段關鍵的判斷式, 以Local IP判斷是不是Player/Client自己本身, 若是的話, 就可以在此處理Local Player Object的生成或操控.
if(Network.isClient)
{
    if(p.ipAddress==LocalAddress)

除了自己本身的連線加入訊息, 也需要知道已連線玩家的資訊. 在Unity內建的OnConnectedToServer()裡, 表示和Server連線完成, 可在此時向Server要求我要知道其它連線玩家的資訊.
void OnConnectedToServer()
{ 
   networkView.RPC("SendAllPlayers", RPCMode.Server);
}

Server收到SendAllPlayers() RPC, 可從Server管理的Player List裡將NetworkPlayer及NetworkViewID的資料透過呼叫JoinPlayer() RPC傳送給要求的Client. 當然要注意不要把Client自己的資料再送回去囉.

到目前為止的流程, 已充份利用single NetworkView架構來傳送及接收訊息, 以最精簡的網路傳輸來達到網路連線溝通. 以此繼續延伸設計, 不管是client-server或peer-to-peer架構, 在實際程式設計上都能簡單不複雜的完成啦:)

-本文結束-

註: 本文非直接翻譯原文, 若有任何問題或弄錯的地方, 歡迎提出討論:)

2013年12月22日 星期日

井底之蛙也有自己的一片天空

在小小的世界裡, 也想做一些些小小的分享~