關於 web service, unity, blogger 等軟體工程筆記

Unity5 with Photon

Edit icon 沒有留言
Unity5 with Photon

在前次寫完參加 Photon 的說明會的感想後,在幾個禮拜前,按照台灣代理商的官方部落格的教學文章,嘗試弄一次連線測試的專案,記錄那時候做的筆記。

Setup Environment

Photon App-ID

採用 Photon Real-time 免費方案,可以同時支援連線數 20 個,該方案不能加入伺服器客製化邏輯 Plugins,但對於測試專案來說,不能加入伺服器邏輯好似也沒有什麼差別。

Photon 官方網站申請帳號,建立 Photon Application,取得 App-ID。

從 Photon 官網取得 App-ID

Get Photon App-ID

Photon Unity Networking (PUN)

Unity Asset Store 中,下載 Photon Free,並且加入 (Import) 到 Unity 專案中。

從 Unity 商店下載 PUN

Download PUN

MenuItem > Windows > Photon Unity Networking > PUN Wizard 開啟 Wizard,初始化遊戲專案,把剛剛獲得的 App-ID 填入,並且設定 Photon Configs,主要是選擇日本伺服器 JP,完成環境設定。

Photon Wizard

Photon Wizard

Photon Settings

Photon Settings

Unity-chan

Demo 使用 Unity-chan 的 2D 資源,也到 Unity-chan 官方網站把所需的資源下載,並且加入到 Unity 專案中。

下載 Unity-chan

Download Unity-chan

使用 Unity 5.5.0b6 來建立測試專案,Unity-chan 的資源都必須升級,除了使用 Unity 自動升級的功能以外,有些程式碼得手動調整,幸好測試專案只是需要美術資源而已,對於那些編譯或是無法正常運作的程式碼,就直接砍掉了。

Framework

使用 PhotonView 透過 Photon Clouds 同步兩個用戶端 (Client) 的角色資料,使用 PUN 所提供的 PhotonTransformView 以及 PhotonAnimatorView 來處理角色位移 (Transition Only) 以及動畫狀態 (Animator States) 的資料同步,來完成這次專案測試。

Photon Cloud & Unity PUN

Unity with Photon

一開始先建立 Photon Manager,用來處理與伺服器連線的初始化,並且利用常常使用到的 Singleton Pattern,讓 Photon Manager 可以讓其他元件存取的到。

using UnityEngine;

public partial class PhotonManager : Photon.PunBehaviour
{
public static PhotonManager Singleton
{
get
{
return instance;
}
}

static PhotonManager instance;

void Awake()
{
if (instance != null)
{
DestroyImmediate(gameObject);
return;
}

DontDestroyOnLoad(gameObject);
instance = this;
}

void Start()
{
// 連線初始化
PhotonNetwork.ConnectUsingSettings("UnityChanPUN_v1.0");
}
}

然後根據流程以及 Photon 設計架構,處理與遊戲大廳連線 (ConnectedToMaster) 以及連線到房間的函數以及事件 (OnJoinedRoom) 處理。

在這測試專案中,Client 必須等到與遊戲大廳連線後,才可以使用方向鍵選擇角色,完成選擇後按下 X 開始才與房間連線。

在 PhotonManager 處理 ConnectedToMaster 的行為,把事件丟出:

public partial class PhotonManager : Photon.PunBehaviour
{
public event System.EventHandler MasterConnected;

public override void OnConnectedToMaster()
{
if (this.MasterConnected != null)
{
this.MasterConnected(this, System.EventArgs.Empty);
}
}
}

在初始頁面撰寫程式碼,控制流程,監聽 PhotonManager.MasterConnected 的事件,當完成大廳連線後,便可選擇角色,直到玩家按下 X 按鍵後加入房間:

using UnityEngine;

public class StartPage : MonoBehaviour
{
public CharacterSelector selector;
public GameObject info_connecting;
public GameObject info_start;

void Awake()
{
// 初始化控制項
this.selector.AllowSelect = false;
this.info_connecting.SetActive(true);
this.info_start.SetActive(false);
}

void OnMasterConnected(object sender, System.EventArgs e)
{
// 大廳完成連線
this.selector.AllowSelect = true;
this.info_connecting.SetActive(false);
this.info_start.SetActive(true);
}

void Start()
{
PhotonManager.Singleton.MasterConnected += this.OnMasterConnected;
}

void Update ()
{
if (this.selector.AllowSelect && Input.GetKeyDown(KeyCode.X))
{
// 加入房間
PhotonManager.Singleton.JoinGameRoom(selector.Current);
}
}
}

處理 JoinGameRoom 的函數實做,設定連線參數以及名稱連線,

public partial class PhotonManager : Photon.PunBehaviour
{
public void JoinGameRoom(string characterName)
{
this.CharacterName = characterName;

RoomOptions options = new RoomOptions();
options.MaxPlayers = 20;
PhotonNetwork.JoinOrCreateRoom("Room", options, null);
}
}

處理完成房間連線的事件,載入另外一個場景,這裡與 Photon Taiwan 的教學文章不同,Unity5 以後已經沒有在支援 OnLevelWasLoaded,所以改成 UnityEngine.SceneManagement.SceneManager.sceneLoaded 的事件處理:

public partial class PhotonManager : Photon.PunBehaviour
{
public override void OnJoinedRoom()
{
SceneManager.sceneLoaded += this.OnLevelFinishedLoading;
PhotonNetwork.LoadLevel("Stage");
}

void OnLevelFinishedLoading(Scene scene, LoadSceneMode mode)
{
SceneManager.sceneLoaded -= OnLevelFinishedLoading;

// 若不在 Photon 的房間內,則網路有問題
if (!PhotonNetwork.inRoom)
return;

// 建立玩家的控制實體
var charName = this.CharacterName;
var charPos = new Vector3(-8.0f, 4.5f, 0);
var charRot = Quaternion.identity;
PhotonNetwork.Instantiate(charName, charPos, charRot, 0);
}
}

與 Photon 溝通的主流程程式碼大概是這樣,剩下的是 PhotonNetwork.Instantiate 的 Prefab 物件的設定。

Photon Prefab

PhotonNetwork.Instantiate 所初始化的 Prefabs 必須放置在 Resources 資料夾中,並且設定 Photon View,決定哪些資料需要被同步。

在這次測試專案中,每個角色各自有自己的 Prefab,並且設定 Photon View,同步角色 Transform 以及 Animator states:

Photon View 設定

Photon View 設定,紅色區塊為 Transform View,綠色區塊為 Animator View

另外處理 Input 來控制角色移動的組件 PlayerMovement 也跟著調整,該組件接收輸入調整 Rigibody2D 的參數,使用 Unity 物理引擎來完成角色的移動。PlayerMovement 改繼承 Photon.PunBehaviour:

public partial class PlayerMovement : Photon.PunBehaviour
{
...
}

Start 時,檢查這一個 PhotonView 是否為本機所建立的,若是其他玩家建立的,則關閉 Rigibody2D 的運作,不讓物裡引擎控制 Rigibody2D 的位移旋轉等,其 Transform 的位移將會由 PhotonTransformView 來同步伺服器資料來控制處理:

public partial class PlayerMovement : Photon.PunBehaviour
{
void Start()
{
if (!this.photonView.isMine)
{
this.rig2d.isKinematic = true;
}
}
}

此外,Update 的行為也必須有所限制,玩家僅能控制自己所建立的 Photon instance:

public partial class PlayerMovement : Photon.PunBehaviour
{
void Update ()
{
if (!this.photonView.isMine)
{
return;
}

// ... Control Code
}
}

大致上這樣就完成了大半,雙開遊戲,可以看見兩個角色經由 Photon Server 同步角色資料。

完整專案請見 Github 或是開啟 WebGL 展示。

Github
WebGL Demo

簡單的感想

使用 Photon 來進行資料同步算是相當簡單快速,基礎的 Transform & Animator 資料同步功能都已經有實做。要同步其他資料,自訂自己的資料 View 也不是不可能,可以讓開發者專注在其他開發工作上。

接下來應該是得研究如何寫 Server code,開發處理伺服器邏輯資料的 Photon plugins 了。寫安全的網路應用最重要概念,永遠不要相信用戶端的資料,伺服器必須有自己的一套邏輯運算,必須檢查驗證所有用戶端傳入的資料,這是一個非常大的坑。

Reference

沒有留言: