Mirror学习笔记

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); } } } ## ![](https://i-blog.csdnimg.cn/direct/3ead0141d5fd498d9a4021753bda7092.png) ## 八、第二次Play 这时候测试人头上会有随机数字的名字 ## 九、场景 新建一个空物体命名为SceneScript。并新建一个SceneScript.cs挂在这个物体上。也挂载NetworkIdentity代码。 创建一个button 和 text ![](https://i-blog.csdnimg.cn/direct/4f9e953540194c8cac4ed99aa198e467.png) 修改PlayerScript代码 添加以下内容 private SceneScript sceneScript; void Awake() { //allow all players to run this sceneScript = GameObject.FindObjectOfType\(); } \[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 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);

    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 this
	sceneScript = 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分为两种:CommandClientRpc。
    • Command:在客户端被调用,在服务端运行。
    • ClientRpc:在服务端被调用,在所有客户端运行。
      • TargetRpc:在服务端被调用,在某个客户端运行。
  • SyncVars
    • 一个特性,被修饰的字段会自动同步,且其只能在服务端被修改,同步方向为从服务端到客户端。当一个游戏对象被生成后,或者有新的玩家加入时,他们就会得到最新的状态信息。该特性带有一个hook(SyncVar Hooks)参数,在服务器上的值被修改时在所有的客户端调用这个钩子函数。
    • 同样的还有SyncLists和SyncDictionary等,不过用法略微不同,详情请看 SyncLists 以及 SyncDictionary
  • NetworkManager
    网络管理器,管理网络游戏对象的组件,其一些主要的功能包括:
    • 游戏状态管理
    • 游戏对象的生成管理
    • 场景管理
    • 调试信息
    • 自定义等

以下是该组件的生命周期:

相关推荐
Pocker_Spades_A2 分钟前
从 0 到 1 开发图书管理系统:飞算 JavaAI 让技术落地更简单
java·开发语言·java开发·飞算javaai炫技赛
郝学胜-神的一滴9 分钟前
对于类似std::shared_ptr但有可能空悬的指针使用std::weak_ptr: Effective Modern C++ 条款20
开发语言·c++·程序人生·系统架构
想睡觉的树11 分钟前
windows双系统下ubuntu20.04安装教程
学习
半部论语13 分钟前
Spring **${}** vs **#{}** 语法全景图
java·数据库·spring boot·后端·spring
sql2008help16 分钟前
数据分页异步后台导出excel
java·excel
知行合一。。。17 分钟前
Spring--04--2--AOP自定义注解,数据过滤处理
java·后端·spring
wuxuanok21 分钟前
八股——Kafka相关
java·笔记·后端·学习·kafka
不可描述的两脚兽25 分钟前
学习笔记《区块链技术与应用》第六天 问答 匿名技术 零知识证明
笔记·学习·区块链
天天摸鱼的java工程师27 分钟前
MyBatis SQL 耗时记录的拦截器实战
java·后端·面试
linux修理工30 分钟前
使用 SecureCRT 连接华为 eNSP 模拟器的方法
服务器·开发语言·php