Mirror官方案例操作
一、導入Mirror
在unity商城訂閱Mirror https://assetstore.unity.com/packages/tools/network/mirror-129321
使用unity創建工程? ? (推薦版本:目前建議使用 Unity 2020 或 2021 LTS 版本;超出這些版本的可能可以運行,但用戶需自行承擔風險,尤其是預覽版或測試版。)? 并導入Mirror。
二、初步設置
新建一個場景并放在Build Settings里
新建一個空物體,命名為NetworkManager 并添加以下三個組件:
-
NetworkManager? ? ? ? ? ? ?( 對整個網絡游戲對象進行管理。)
-
KCPTransport (TelepathyTransport is older, you do not need KCP and Telepathy)? ??
-
NetworkManagerHUD? ? ? ( 一個啟動的面板,方便客戶端或服務器端啟動停止。)
????????????????NetworkManager組件中把場景拖拽到Offline 和 Online中(拖拽的場景必須在Build Settings里)
三、設置場景
添加一個簡單的Plane 作為地面? ? ?設置 position(0,-1,0) 比例(2,2,2)
添加一個空物體 為他添加組件:NetworkStartPosition (作為出生點)復制多個放在Plane的各個角
四、創建玩家
創建一個膠囊? 并添加組件:NetworkTransform(Reliable)? 其中Sync Direction選項選為Client To Server? ? ? ? ? ? ? ? ? ? ? ? ? ? ???自動會再添加一個NetworkIdentity
????????????????????????在 NetworkTransform勾選 Client Authority (我沒找到)
? ? ? ? ? ? ? ? ? ? ? ? 重命名膠囊為:Player 新建PlayerScript代碼掛在Player上
? ? ? ? ? ? ? ? ? ? ? ? 把Player這個物體拽成預制體,并在場景里刪了他
? Network Manager中的Player Prefab選擇Player? ? ? ? ? ? ? ? ? ? ??
五、PlayerScript
添加以下代碼到playerscript里
using Mirror;
using UnityEngine;
namespace QuickStart
{
? ? public class PlayerScript : NetworkBehaviour
? ? {
? ? ? ? public override void OnStartLocalPlayer()
? ? ? ? {
? ? ? ? ? ? Camera.main.transform.SetParent(transform);
? ? ? ? ? ? Camera.main.transform.localPosition = new Vector3(0, 0, 0);
? ? ? ? }
? ? ? ? void Update()
? ? ? ? {
? ? ? ? ? ? if (!isLocalPlayer) { return; }? //如果不是本地玩家就return
? ? ? ? ? ? float moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;
? ? ? ? ? ? float moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4f;
? ? ? ? ? ? transform.Rotate(0, moveX, 0);
? ? ? ? ? ? transform.Translate(0, 0, moveZ);
? ? ? ? }
? ? }
}
六、第一次play
點擊Play 然后點擊左上角 Host (server + client)測試人物移動? ?成功后可以發布一版本聯機試試效果。
七、添加玩家名稱
在player預制體中,創建一個空的GameObject
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 命名FloatingInfo
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? y軸位置設置為1.5
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 縮放x軸 -1
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 添加3DText子物體
八、更新PlayerScripts代碼
using Mirror;
using UnityEngine;
namespace QuickStart
{
? ? public class PlayerScript : NetworkBehaviour
? ? {
? ? ? ? public TextMesh playerNameText;
? ? ? ? public GameObject floatingInfo;
? ? ? ? private Material playerMaterialClone;
? ? ? ? [SyncVar(hook = nameof(OnNameChanged))]//當playerName變了之后調用OnNameChange
? ? ? ? public string playerName;
? ? ? ? [SyncVar(hook = nameof(OnColorChanged))]
? ? ? ? public Color playerColor = Color.white;
? ? ? ? void OnNameChanged(string _Old, string _New)
? ? ? ? {
? ? ? ? ? ? playerNameText.text = playerName;
? ? ? ? }
? ? ? ? void OnColorChanged(Color _Old, Color _New)
? ? ? ? {
? ? ? ? ? ? playerNameText.color = _New;
? ? ? ? ? ? playerMaterialClone = new Material(GetComponent<Renderer>().material);
? ? ? ? ? ? playerMaterialClone.color = _New;
? ? ? ? ? ? GetComponent<Renderer>().material = playerMaterialClone;
? ? ? ? }
? ? ? ? public override void OnStartLocalPlayer()//當該對象成為本地玩家時調用的方法
? ? ? ? {
? ? ? ? ? ? Camera.main.transform.SetParent(transform);
? ? ? ? ? ? Camera.main.transform.localPosition = new Vector3(0, 0, 0);
? ? ? ? ? ??
? ? ? ? ? ? floatingInfo.transform.localPosition = new Vector3(0, -0.3f, 0.6f);
? ? ? ? ? ? floatingInfo.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
? ? ? ? ? ? string name = "Player" + Random.Range(100, 999);
? ? ? ? ? ? Color color = new Color(Random.Range(0f, 1f), Random.Range(0f, 1f), Random.Range(0f, 1f));
? ? ? ? ? ? CmdSetupPlayer(name, color);
? ? ? ? }
? ? ? ? [Command]? //以Cmd
開頭的方法或標記[Command]
的方法,只能由客戶端調用,在服務器上執行
? ? ? ? public void CmdSetupPlayer(string _name, Color _col)
? ? ? ? {
? ? ? ? ? ? // player info sent to server, then server updates sync vars which handles it on all clients
? ? ? ? ? ? playerName = _name;
? ? ? ? ? ? playerColor = _col;
? ? ? ? }
? ? ? ? void Update()
? ? ? ? {
? ? ? ? ? ? if (!isLocalPlayer)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? // make non-local players run this
? ? ? ? ? ? ? ? floatingInfo.transform.LookAt(Camera.main.transform);
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? ? ? float moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;
? ? ? ? ? ? float moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4f;
? ? ? ? ? ? transform.Rotate(0, moveX, 0);
? ? ? ? ? ? transform.Translate(0, 0, moveZ);
? ? ? ? }
? ? }
}
八、第二次Play
這時候測試人頭上會有隨機數字的名字
九、場景
新建一個空物體命名為SceneScript。并新建一個SceneScript.cs掛在這個物體上。也掛載NetworkIdentity代碼。
創建一個button 和 text
修改PlayerScript代碼
添加以下內容
private SceneScript sceneScript;
void Awake()
{
? ? //allow all players to run this
? ? sceneScript = GameObject.FindObjectOfType<SceneScript>();
}
[Command]
public void CmdSendPlayerMessage()
{
? ? if (sceneScript)?
? ? ? ? sceneScript.statusText = $"{playerName} says hello {Random.Range(10, 99)}";
}
[Command]
public void CmdSetupPlayer(string _name, Color _col)
{
? ? //player info sent to server, then server updates sync vars which handles it on all clients
? ? playerName = _name;
? ? playerColor = _col;
? ? sceneScript.statusText = $"{playerName} joined.";
}
public override void OnStartLocalPlayer()
{
? ? sceneScript.playerScript = this;
? ? //. . . . ^ new line to add here
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
SceneScript代碼如下:
using Mirror;
using UnityEngine;
using UnityEngine.UI;
namespace QuickStart
{
? ? public class SceneScript : NetworkBehaviour
? ? {
? ? ? ? public Text canvasStatusText;
? ? ? ? public PlayerScript playerScript;
? ? ? ? [SyncVar(hook = nameof(OnStatusTextChanged))]
? ? ? ? public string statusText;
? ? ? ? void OnStatusTextChanged(string _Old, string _New)
? ? ? ? {
? ? ? ? ? ? //called from sync var hook, to update info on screen for all players
? ? ? ? ? ? canvasStatusText.text = statusText;
? ? ? ? }
? ? ? ? public void ButtonSendMessage()
? ? ? ? {
? ? ? ? ? ? if (playerScript != null) ?
? ? ? ? ? ? ? ? playerScript.CmdSendPlayerMessage();
? ? ? ? }
? ? }
}
十、第三次play
這個時候play
點擊右上角button會有個says hello
十一、添加武器切換
添加以下代碼到PlayerScript.cs
private int selectedWeaponLocal = 1;
public GameObject[] weaponArray;
[SyncVar(hook = nameof(OnWeaponChanged))]
public int activeWeaponSynced = 1;
void OnWeaponChanged(int _Old, int _New)
{
? ? // disable old weapon
? ? // in range and not null
? ? if (0 < _Old && _Old < weaponArray.Length && weaponArray[_Old] != null)
? ? ? ? weaponArray[_Old].SetActive(false);
? ??
? ? // enable new weapon
? ? // in range and not null
? ? if (0 < _New && _New < weaponArray.Length && weaponArray[_New] != null)
? ? ? ? weaponArray[_New].SetActive(true);
}
[Command]
public void CmdChangeActiveWeapon(int newIndex)
{
? ? activeWeaponSynced = newIndex;
}
void Awake()?
{
? ? // disable all weapons
? ? foreach (var item in weaponArray)
? ? ? ? if (item != null)
? ? ? ? ? ? item.SetActive(false);?
}
添加武器切換代碼,要放在!isLocalPlayer檢查后面
void Update()
{if (!isLocalPlayer){// make non-local players run thisfloatingInfo.transform.LookAt(Camera.main.transform);return;}float moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110.0f;float moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4f;transform.Rotate(0, moveX, 0);transform.Translate(0, 0, moveZ);if (Input.GetButtonDown("Fire2")) //Fire2 is mouse 2nd click and left alt{selectedWeaponLocal += 1;if (selectedWeaponLocal > weaponArray.Length) selectedWeaponLocal = 1; CmdChangeActiveWeapon(selectedWeaponLocal);}
}
玩家按鼠標右鍵 →
本地修改 selectedWeaponLocal(臨時記錄) →
調用 CmdChangeActiveWeapon(將本地值發給服務器) →
服務器修改 activeWeaponSynced(權威同步變量) →
Mirror 自動將 activeWeaponSynced 同步到所有客戶端 →
所有客戶端觸發 OnWeaponChanged(根據同步值切換武器模型)
十二、武器制作
雙擊player進入預制體進行制作
添加空物體WeaponHolder,位置旋轉設置0,0,0
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 添加Cube子物體并移除Cube碰撞體
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?重命名Cube為Weapon1并調整參數
復制Weapon1并重命名為Weapon2
修改參數
點擊Player把武器拖給代碼位置
十三、第四次Play
運行后點擊鼠標切換武器。
十四、小調整(新增SceneReference)
因為使用 GameObject.Find() 可能無法保證找到SceneScript。NetworkIdentity場景對象被禁用,它們會被禁用,直到玩家處于“就緒”狀態(就緒狀態通常在玩家生成時設置)。
創建一個名為 SceneReference.cs 的新腳本
using UnityEngine;
namespace QuickStart
{
? ? public class SceneReference : MonoBehaviour
? ? {
? ? ? ? public SceneScript sceneScript;
? ? }
}
打開SceneScript.cs并添加以下變量。
public SceneReference sceneReference;
現在,在 Unity 場景中創建一個游戲對象,將其命名為 SceneReference,并添加新腳本。在兩個游戲對象上將引用設置為彼此。因此,SceneReference 可以與 SceneScript 通信,SceneScript 可以與 SceneReference 通信。
打開PlayerScript.cs并將 Awake 函數覆蓋為以下內容:
void Awake()
{//allows all players to run thissceneScript = GameObject.Find(“SceneReference”).GetComponent<SceneReference>().sceneScript;
}
Mirror的核心用法
public class Player : NetworkBehaviour{// 自動同步,只能在服務器上被修改[SyncVar] public int health = 100;// 列表SyncList<Item> inventory = new SyncList<Item>();// 只有服務器或客戶端執行[Server] void LevelUp() {}[Client] void Animate() {}void Update(){// 運行時檢查是在服務器還是在客戶端if (isServer) Heal();if (isClient) Move();}// 零開銷遠程調用[Command] void CmdUseItem(int slot) {} // 客戶端到服務器[ClientRpc] void RpcRespawn() {} // 服務器到所有的客戶端[TargetRpc] void Hello() {} // 服務器到單個客戶端
}
- RPC( Remote Procedure CallsRemote Procedure Calls):跨網絡執行操作,也叫遠程過程調用,Mirror的網絡系統中的RPC分為兩種:Command和ClientRpc。
- Command:在客戶端被調用,在服務端運行。
- ClientRpc:在服務端被調用,在所有客戶端運行。
- TargetRpc:在服務端被調用,在某個客戶端運行。
- SyncVars
- 一個特性,被修飾的字段會自動同步,且其只能在服務端被修改,同步方向為從服務端到客戶端。當一個游戲對象被生成后,或者有新的玩家加入時,他們就會得到最新的狀態信息。該特性帶有一個hook(SyncVar Hooks)參數,在服務器上的值被修改時在所有的客戶端調用這個鉤子函數。
- 同樣的還有SyncLists和SyncDictionary等,不過用法略微不同,詳情請看?SyncLists?以及?SyncDictionary
- NetworkManager
網絡管理器,管理網絡游戲對象的組件,其一些主要的功能包括:- 游戲狀態管理
- 游戲對象的生成管理
- 場景管理
- 調試信息
- 自定義等
以下是該組件的生命周期:
- NetworkBehaviour
- 網絡行為組件,可以在這里實現聯網對象的網絡行為,做法是新建一個類繼承這個組件。配合NetworkIdentity組件一起使用,一般在這里使用Command、ClientRpc、SyncVars等高級API(high-level API),以下是這個組件的生命周期:
負責處理游戲對象在網絡中的數據傳輸,確保游戲狀態的一致性和實時性。
-
- 參考鏈接:(1 封私信) Unity 多人聯機庫Mirror教程 - 知乎
鏡像快速入門項目 |鏡子
- 參考鏈接:(1 封私信) Unity 多人聯機庫Mirror教程 - 知乎