• 【Unity插件】实现多人在线游戏——Mirror插件的使用介绍


    前言

    终于来了,之前很多人私信我,想看关于如何实现多人游戏的流程,这不就来了。

    关于Mirror插件其实我已经关注很久了,最近才有时间把它整理出来。

    Mirror是一个简单高效的开源的unity多人游戏网络框架,Mirror在Unity商店中是免费的

    官方API地址:https://mirror-networking.gitbook.io/docs

    导入Mirror插件

    https://assetstore.unity.com/packages/tools/network/mirror-129321
    在这里插入图片描述

    简单介绍

    一、RPC调用

    有三个关键字如果被用于修饰函数,则该函数不会在本地调用,而是在远程调用。[Command]、[ClientRpc]、[TargetRpc]。

    • Command在客户端调用,在服务端执行,并且方法名称以"Cmd"开头。
    • ClientRpc在服务端调用,在所有与服务端连接的客户端执行,并且方法名称以"Rpc"开头。
    • TargetRpc在服务端调用,在指定的与服务端连接的客户端执行,该方法至少有一个,NetworkConnection的形参,用来确定是在哪一个客户端执行,并且方法名称以"Target"开头。
    • ServerCallback:只能由服务器调用,在服务器上执行。并且方法名称以"Server"开头。用法和TargetRpc类似

    使用

    using Mirror;
    using UnityEngine;
    
    public class MyNetworkBehaviour : NetworkBehaviour
    {
        [Command]
        void CmdFire()
        {
            // 在服务器上调用
        }
    
        [ClientRpc]
        void RpcGetHit()
        {
            // 在客户端上调用
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    比如某个客户端角色得分加分

    # TargetRpc
    NetworkIdentity netIdentity = GetComponent<NetworkIdentity();
    TargetShowMessage(netIdentity.connectionToClient,1);
    [TargetRpc]
    private void TargetShowMessage(NetworkConnection target, int count)
    {
        sumCount += count;//加分
    }
    
    # ServerCallback
    [ServerCallback]
    private void ServerPlayerReady(NetworkConnection connection)
    {
        // 将指定客户端标记为已准备状态的逻辑
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    二、错误注意

    • 1.场景内所有挂在了你的代码的物体都会默认添加network identity,但network manager组件与network identity组件放在一个物体上会报错

    • 2.如果物体内有network manager组件但是没有Kcp transport组件,会报错。

    • 3.如果场景内有多个network manager组件,会报错。

    • 4.如果角色预制体托不进player prefab栏,可能是没有挂在network identity组件。

    • 5.角色代码必须有if (!isLocalPlayer) return;否则。。。后果自己知道。

    基本使用

    一、创建场景的网络管理器

    网络管理器是多人游戏的核心控制组件。网络管理器是多人游戏的核心控制组件。

    在起始场景中创建一个空游戏对象,然后添加新创建的网络管理器组件(networkManager,Kcp Transport,networkManagerHUD组件)。
    在这里插入图片描述
    kcp transport组件挂载在networkManager的transport上
    在这里插入图片描述
    并配置Scene场景,offline和online是离线界面和游戏界面,比如说我们新建一个offline场景,在里面放一个network manager的network managerHUD,然后再新建一个online场景,把他们都注册到build setting(生成设置)里的build里的场景栏中(拖进去),offline在上。然后我们进入offline场景,运行,点击host,便会进入online(在线场景)。

    二、创建一个玩家

    创建一个玩家物体Player,为玩家添加networkIdentity作为在网络同步的唯一标识。一般游戏内除了包含network manager组件的物体都要挂在此组件,包括即将孵化的。这东西只有俩选项,一个勾选框Server Only,意思是只有服务端能操作,大家根据自己的需要勾选。第二个是visible,里面有三个选项:默认(Default)、强制隐藏(Force Hidden)、强制显示(ForceShown),个人感觉没啥用,大家默认就行。
    在这里插入图片描述
    只有挂载了networkIdentity,网络中枢才能识别到这个物件,并对之进行同步。接下来将Player作为一个预制体保存,并在场景中删除,后拖拽预制体到网络中枢(networkManager)的Player Prefab插槽中,以后它的产生就完全依靠网络中枢在连接到主机后自动生成。

    自动创建播放器(Auto Create Player):默认勾选,勾选的话当连接服务器时会自动生成上面的“玩家预制件”。
    在这里插入图片描述

    注:如果角色预制体托不进player prefab栏,可能是没有挂在network identity组件

    为玩家添加Network Transform,同步网络中联网游戏对象的位置、旋转和缩放,并勾选networkTransport的Client Authority属性。

    Mirror 目前提供2种Network Transform:
    在这里插入图片描述

    Reliable:低带宽,与Rpcs/Cmds/等相同的延迟。
    
    Unreliable:高带宽,极低延迟
    
    使用Reliable,除非需要超低延迟。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    注:后面我们会利用玩家body的Scale进行翻转,这里给body也加上Network Transform代码,记勾选Sync Scale.
    在这里插入图片描述

    三、添加玩家初始生成位置

    创建几个空物体作为玩家的初始生成位置,添加Network Start Position脚本,并将该物体拖动到合适的位置。
    在这里插入图片描述
    并在NetworkManager中选择随机(Random)或者轮询(Round Robin)的出生点选择方式。

    1.Random:生成为随机(可能相同的生成位置将被两个或更多玩家使用)
    
    2.Round Robin:循环(使用每个可用位置,直到客户端数超过生成点数)。
    
    • 1
    • 2
    • 3

    在这里插入图片描述
    效果
    在这里插入图片描述

    四、玩家控制

    网络同步需要注意的一些事情:

    1.需要用到联网功能的脚本中都要添加using Mirror来使用相应API,并且继承NetworkBehaviour而不是MonoBehaviour。

    2.涉及到玩家输入时,首先先要进行isLocalPlayer的判断,通过islocalplayer来判断是否具有当前对象的权限

    为控制游戏对象,添加一个简单的人物控制脚本为PlayerControl.cs,继承NetworkBehaviour。

    using UnityEngine;
    using Mirror;
    public class PlayerControl : NetworkBehaviour //MonoBehaviour --> NetworkBehaviour
    {
        private Rigidbody2D rb; // 刚体组件
        void Start()
        {
            rb = GetComponent<Rigidbody2D>(); // 获取刚体组件
        }
    
        //速度:每秒移动5个单位长度
        public float moveSpeed = 5;
    
        void Update()
        {
            if (!isLocalPlayer) return; //不应操作非本地玩家
            Move();
        }
    
        void Move()
        {
            //通过键盘获取水平轴的值,范围在-1到1
            float horizontal = Input.GetAxisRaw("Horizontal");
            rb.velocity = new Vector2(horizontal * moveSpeed, rb.velocity.y); // 设置刚体速度
            if (horizontal != 0)
            {
                transform.GetChild(0).localScale = new Vector3(-horizontal, 1, 1); // 翻转角色
            }
        } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    效果
    在这里插入图片描述

    五、同步摄像机

    对于在每个客户端独立生成的对象(这里以每位玩家的camera为例),需要将start方法修改为OnStartLocalPlayer(),这样可以避免多个客户端的摄像机被修改为同一台。

    OnStartLocalPlayer:仅在client执行,当脚本所在物体为玩家角色时调用,用来设置跟踪相机,角色初始化等

    public override void OnStartLocalPlayer()
    {
    	rb = GetComponent<Rigidbody2D>(); // 获取刚体组件
    	
        //摄像机与角色绑定
        Camera.main.transform.SetParent(transform);
        Camera.main.transform.localPosition = new Vector3(0, 0, Camera.main.transform.position.z);
     }   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    效果
    在这里插入图片描述

    六、同步不同角色的名字和颜色修改

    同步变量需要添加同步变量的标记[SyncVar(hook=nameof(FunctionExecOnClient))],当同步变量发生变化时就会调用后面的FunctionExecOnClient方法

    当服务器的场景中的一个SyncVar的值发生变化时,就同步给其它所有客户端。

    对于同步变量的修改,使用[Command]标记(针对方法的标记,方法名以Cmd开头)

    using TMPro;
    
    public TMP_Text nameText;
    
    //需要把name和颜色同步给其他玩家,添加同步变量的标记[SyncVar(hook=nameof(FunctionExecOnClient))]
    [SyncVar(hook = nameof(OnPlayerNameChanged))]
    public string playerName;
    [SyncVar(hook = nameof(OnPlayerColorChanged))]
    private Color playerColor;
    
    //申明OnPlayerNameChanged和OnPlayerColorChanged这两个方法
    //第一个变量(oldstr)是同步变量修改前的值,第二个(newstr)是同步变量修改后的值
    private void OnPlayerNameChanged(string oldstr, string newstr)
    {
        nameText.text = newstr;
    }
    private void OnPlayerColorChanged(Color oldCor, Color newCor)
    {
        nameText.color = newCor;
    }
    
    void Update()
    {
       if (!isLocalPlayer) return; //不应操作非本地玩家
       
       Move();
    
       if (Input.GetKeyDown(KeyCode.Space))
       {
           //随机生成颜色和名字
           ChangedColorAndName();
       }
    }
    public override void OnStartLocalPlayer()
    {
        //。。。
    
        //开始就随机生成颜色和名字
        ChangedColorAndName();
    }
    
    //player 的随机名称和颜色
    private void ChangedColorAndName()
    {
        //随机名称和颜色
        var tempName = $"Player{Random.Range(1, 999)}";
        var tempColor = new Color(Random.Range(0, 1f), Random.Range(0, 1f), Random.Range(0, 1f), 1);
    
        //同步变量进行修改
        CmdSetupPlayer(tempName, tempColor);
    }
    
    //对于同步变量的修改,使用[Command]标记(针对方法的标记,方法名以Cmd开头)
    //通过这个方法同时对name和颜色进行修改
    [Command]
    private void CmdSetupPlayer(string name, Color color)
    {
        playerName = name;
        playerColor = color;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    效果
    在这里插入图片描述

    七、同步动画

    挂载Network Animator组件
    在这里插入图片描述

    private Animator anim; // 动画组件
    anim = gameObject.GetComponentInChildren<Animator>(); // 获取动画组件
    
    public override void OnStartLocalPlayer()
    {
       //。。。
       
       anim = gameObject.GetComponentInChildren<Animator>(); // 获取动画组件
    }
    
    void Update()
    {
        if (!isLocalPlayer) return; //不应操作非本地玩家
    
        //。。。
        
        //攻击动画控制
        if (Input.GetMouseButtonDown(0))
        {
            anim.SetTrigger("isAttack");
            anim.SetBool("isIdle", false);
        }else{
            anim.SetBool("isIdle", true);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    八、同步子弹

    bomb就是普通的炸弹预制体
    在这里插入图片描述

    方法一

    [ClientRpc]关键字,服务端可以向所有的连接的客户端发送同步指令,方法名也需要Rpc开头

    public GameObject bomb;//炸弹预制体
    
    void Update()
    {
        if (!isLocalPlayer) return; //不应操作非本地玩家
    
        //。。。
        
        //生成炸弹
        if (Input.GetMouseButtonDown(1))
        {
            Cmdshoot();
        }
    }
    
    [Command]
    private void Cmdshoot()
    {
        RpcWeaponFire();
    }
    
    [ClientRpc]
    private void RpcWeaponFire()
    {
        GameObject b = Instantiate(bomb, transform.position, Quaternion.identity);
        b.transform.Translate(1, 0, 0);//防止子弹撞到角色
        b.GetComponent<Rigidbody2D>().AddForce(Vector2.up * 500f);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    效果
    在这里插入图片描述

    方法二

    NetworkManager最下面有个列表(Registered Spawnable Prefab),他是用来放游戏中需要孵化的物体的,比如说enemy(敌人),bullet(子弹)啊,都给它拖进去
    在这里插入图片描述
    ps:记得给炸弹添加Network Identity组件,不然拖不进去

    public GameObject bomb;//炸弹预制体
    
    void Update()
    {
        if (!isLocalPlayer) return; //不应操作非本地玩家
    
        //。。。
        
        //生成炸弹
        if (Input.GetMouseButtonDown(1))
        {
            Cmdshoot();
        }
    }
    
    [Command]
    private void Cmdshoot()
    {
        GameObject b = Instantiate(bomb, transform.position, Quaternion.identity);
        b.transform.Translate(1, 0, 0);//防止子弹撞到角色
        b.GetComponent<Rigidbody2D>().AddForce(Vector2.up * 500f);
        Destroy(b, 2.0f);//两秒后删除
        NetworkServer.Spawn(b);//服务器孵化,同步客户端
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    效果
    在这里插入图片描述
    问题

    你会发现客户端给炸弹施加的AddForce力并没有效果,原因是我们没有添加同步刚体的组件,给炸弹添加Network Rigidbody 2D组件
    在这里插入图片描述

    效果
    在这里插入图片描述

    九、聊天功能

    新增ChatController脚本

    using UnityEngine;
    using Mirror;
    using TMPro;
    using UnityEngine.UI;
    
    public class ChatController : NetworkBehaviour
    {
        public TMP_InputField chatText;//输入框
        public Button chatBtn;//发送按钮
        public GameObject chatInfo;//聊天框内容预制体
        public GameObject chatFrame;//聊天框
        public PlayerController playerController ;
    
        [SyncVar(hook = nameof(OnChatTextStringChanged))]
        public string chatTextString;
    
        private void OnChatTextStringChanged(string oldstr, string newstr)
        {
        	//添加聊天内容
            GameObject ci = Instantiate(chatInfo);
            ci.GetComponent<TMP_Text>().text = newstr;
            ci.transform.SetParent(chatFrame.transform);
        }
    
        void Awake()
        {
            chatBtn.onClick.AddListener(SendBtn);
        }
    
        public void SendBtn()
        {
            if (player != null)
            {
                playerController.CmdSendPLayerMessage(chatText.text);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    修改PlayerController,调用传送人物名字

    private ChatController chatController;
    
    void Awake()
    {
        chatController = FindObjectOfType<ChatController>();
    }
    
    public override void OnStartLocalPlayer()
    {
    	//。。。
    	
        chatController.playerController = this;
    }
    [Command]
    public void CmdSendPLayerMessage(string message)
    {
        if (chatController != null)
        {
            chatController.chatTextString = playerName + "说:" + message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    绘制UI页面,记得添加Network Identity组件
    在这里插入图片描述
    记得给聊天的UI canvas挂载Network Identity脚本
    在这里插入图片描述

    效果
    在这里插入图片描述

    十、场景同步切换

    新建三个场景NetworkManager对象上
    在这里插入图片描述
    新增ScenceController 代码,控制NetworkManagerHUD的显隐

    using UnityEngine;
    using UnityEngine.UI;
    using Mirror;
    using UnityEngine.SceneManagement;
    
    public class ScenceController : MonoBehaviour
    {
        private void Update()
        {
                Scene scene = SceneManager.GetActiveScene();
                //控制NetworkManagerHUD的显隐
                if(scene.name == "Main"){
                    GetComponent<NetworkManagerHUD>().enabled = false;
                }else{
                    GetComponent<NetworkManagerHUD>().enabled = true;
                }
        }
        
        //开始游戏,场景切换
        public void ButtonLoadScene()
        {
            SceneManager.LoadScene("SampleScene1");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    Main场景为游戏开始页面,默认就放一个按钮,按钮调用ButtonLoadScene方法,Network Manager只需要在初始场景挂载即可(及Main场景),前面代码已经控制了NetworkManagerHUD的显隐,报错HUD视图你不在主场景显示
    在这里插入图片描述
    挂载对应的场景
    在这里插入图片描述

    SampleScene1和SampleScene2场景基本没啥区别,更前面的游戏页面一样,删除原本的NetworkManager对象,防止与主界面Main场景的冲突
    在这里插入图片描述
    在这里插入图片描述
    新增ButtonChangeScene方法,控制游戏内的场景切换,方法挂载在SampleScene1和SampleScene2场景的场景切换按钮上

    //同步切换场景
    public void ButtonChangeScene()
    {
        if (isServer)
        {
            var scene = SceneManager.GetActiveScene();
            NetworkManager.singleton.ServerChangeScene
            (
                scene.name == "SampleScene1" ? "SampleScene2" : "SampleScene1"
            );
        }
        else
        {
            Debug.Log("你不是host");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    效果
    在这里插入图片描述

    十一、重新绘制一个HUD界面

    NetworkManagerHUD(需要配合Network Manager组件),他会自动绘制一个GUI:
    在这里插入图片描述
    Host(主机):相当于又是服务器又是客户端。

    Client:连接服务端,后面是服务端IP地址,localhost为本地端口,相当于自己连接自己。

    Server Only:只当服务端。

    但是,这个UI界面不太好看,所以我们一般不用这个组件,都会自己制作GUI。

    在场景中新增三个按钮
    在这里插入图片描述

    新增MyNetworkManagerHUD 代码,挂载在游戏页面,实例代码

    using UnityEngine;
    using UnityEngine.UI;
    using Mirror;
    public class MyNetworkManagerHUD : MonoBehaviour
    {
        private NetworkManager networkManager; // 创建 NetworkManager 对象
        public GameObject btn;
        public GUISkin mySkin;
        private GameObject startHost;//启动网络主机按钮
        private GameObject startClient;//启动网络客户端按钮
        private GameObject stopHost;//停止网络主机或客户端按钮
        void Awake()
        {
            networkManager = FindObjectOfType<NetworkManager>();
            startHost = GameObject.Find("StartHost");
            startClient = GameObject.Find("StartClient");
            stopHost = GameObject.Find("StopHost");
            startHost.GetComponent<Button>().onClick.AddListener(OnStartHost);
            startClient.GetComponent<Button>().onClick.AddListener(StartClient);
            stopHost.GetComponent<Button>().onClick.AddListener(StopHost);
        }
    
        private void Update()
        {
            // GetComponent().enabled = true;
    
            if (!NetworkClient.isConnected && !NetworkServer.active) // 检查客户端和服务器的连接状态
            {
                startHost.SetActive(true);
                startClient.SetActive(true);
                stopHost.SetActive(false);
            }
            else
            {
                startHost.SetActive(false);
                startClient.SetActive(false);
                stopHost.SetActive(true);
            }
    
        }
         private void OnStartHost()
        {
            networkManager.StartHost(); // 启动网络主机
        }
        private void StartClient()
        {
            networkManager.StartClient(); // 启动网络客户端
        }
        private void StopHost()
        {
            networkManager.StopHost(); // 停止网络主机或客户端
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    当然,原来的NetworkManagerHUD组件就没用了,可以删除了,记得同步删除前面控制NetworkManagerHUD显隐的代码

    运行效果
    在这里插入图片描述

    十二、查找服务器

    使用network discoveryHUD+network discovery组件替换原来的NetworkManagerHUD

    这个network discovery组件也需要配合network manager使用,他可以列出局域网内所有的服务器,其中有个transport栏,我们需要把与network manager组件在一起的Kcp transport组件拖进去,不然无法运行。
    在这里插入图片描述

    network discoveryHUD,与networkmanagerHUD差不多,唯独少了一个Client,多了一个find server

    在这里插入图片描述
    作用是点击find server就会 把局域网内的所有比赛(服务器)列出来,但仅限局域网。

    运行效果
    在这里插入图片描述

    十四、角色死亡复活

    // 角色是否死亡的标志
    [SyncVar(hook = nameof(OnIsDeadChanged))]
     public bool isDead = false;
    
     // 当角色死亡状态改变时的回调方法
     void OnIsDeadChanged(bool oldValue, bool newValue)
     {
         if (newValue)
         {
             // 执行死亡逻辑,例如播放死亡动画、禁用角色控制等
             Debug.Log("Player has died.");
             Destroy(gameObject, 2f); // 延迟2秒后销毁角色对象
         }
     }
    void Update()
    {
    	if (Input.GetKeyDown(KeyCode.T))
    	{
    	     CmdDestroyPlayerServer();
    	     
    	     // 创建一个新的Camera对象
    	     GameObject cameraObject = new GameObject("Main Camera");
    	     // 添加Camera组件到对象上
    	     Camera cameraComponent = cameraObject.AddComponent<Camera>();
    	     // 设置摄像机的位置和旋转
    	     cameraComponent.transform.position = new Vector3(0, 0, -10f);
    	     cameraComponent.transform.rotation = Quaternion.identity;
    	 }
    }
    
    [Command]
    private void CmdDestroyPlayerServer()
    {
        isDead = true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    其他地方书写复活方法(重新生成角色)

    public class GameManager : MonoBehaviour
    {
        void Update()
        {
            if (Input.GetKeyDown(KeyCode.R))
            {
               //让当前客户端复活角色
    		    NetworkClient.AddPlayer();
            } 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    效果
    在这里插入图片描述

    十三、自己编一个network manager

    这里简单实现一个创建角色的功能

    using Mirror;
    using UnityEngine;
    using UnityEngine.UI;
     
    public class MyNetworkManager : NetworkManager//继承network manager类
    {
        public InputField myname; // 输入玩家名称的InputField
    
        // 在服务器启动时调用
        public override void OnStartServer()
        {
            Debug.Log("启动服务器");
            // 启动服务器
            base.OnStartServer();
    
            // 注册CreateMMOCharacterMessage消息的处理方法
            NetworkServer.RegisterHandler<CreateMMOCharacterMessage>(OnCreateCharacter);
        }
        public override void OnStopServer()
        {
            Debug.Log("关闭服务器");
            // 关闭服务器
            base.OnStopServer();
        }
        public override void OnServerConnect(NetworkConnectionToClient conn)
        {
            Debug.Log("处理连接事件");
            // 处理连接事件
            base.OnServerConnect(conn);
        }
    
        public override void OnServerDisconnect(NetworkConnectionToClient conn)
        {
            Debug.Log("处理断开事件");
            // 处理断开事件
            base.OnServerDisconnect(conn);
        }
    
        // CreateMMOCharacterMessage消息结构体
        public struct CreateMMOCharacterMessage : NetworkMessage
        {
            public string playername; // 玩家名称
        }
    
        // 在客户端连接到服务器时调用
        public override void OnClientConnect()
        {
            base.OnClientConnect();
    
            // 在这里发送消息,或者您想要的任何其他地方
            CreateMMOCharacterMessage characterMessage = new CreateMMOCharacterMessage
            {
                // playername = myname.text // 设置玩家名称为InputField中的文本
                playername = "测试" // 设置玩家名称为InputField中的文本
            };
    
            NetworkClient.Send(characterMessage); // 发送消息给服务器
        }
    
        // 创建角色的方法,在收到CreateMMOCharacterMessage消息时调用
        // 参数conn:与服务器的连接;参数message:接收到的消息
        void OnCreateCharacter(NetworkConnectionToClient conn, CreateMMOCharacterMessage message)
        {
            //PlayerPrefab是在Network Manager的Inspector中指定的,
            //但您可以在每个比赛中使用不同的预制体,例如:
            GameObject gameobject = Instantiate(playerPrefab); // 实例化玩家预制体
    
            //根据游戏的需要,应用消息中的数据
            Player player = gameobject.GetComponent<Player>();
            player.playerName = message.playername; // 将玩家名称赋值给Playercode组件中的playername变量
    
            //将此游戏对象添加为连接上的玩家的控制对象
            NetworkServer.AddPlayerForConnection(conn, gameobject);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    去除原来的NetworkManager,挂载自己写的代码,去除自动的创建角色的勾选,不然开始会创建两个主角
    在这里插入图片描述
    运行效果,正常
    在这里插入图片描述

    AOI迷雾效果实现

    将Spatial Hashing Interest Management组件与您的网络管理器相同的对象中
    在这里插入图片描述

    参数:
    可见距离
    多久显示出来
    3d 2d切换
    调试滑块

    走近才显示物品
    在这里插入图片描述

    开房间的功能

    官方其实已经有个demo,在Mirror/Examples/MultipleMatches/Scenes/Main,我们可以先看看效果,
    在这里插入图片描述

    连接线上服务器(待续)

    linux服务器

    修改线上服务器ip和端口号
    在这里插入图片描述
    对应端口记得去服务器加安全组白名单
    在这里插入图片描述

    去除HUD脚本组件
    在这里插入图片描述
    书写代码,控制是服务器还是客户端
    在这里插入图片描述
    先打包一个服务器,所以先勾选是服务器
    在这里插入图片描述

    然后打包一个Linux服务器端程序
    在这里插入图片描述
    ps:没有的记得先去安装unity模块
    在这里插入图片描述
    把服务器端程序全部上传到服务器上
    先把可执行文件添加权限
    在这里插入图片描述
    运行可执行程序
    在这里插入图片描述

    然后就可正常打包客户端,连接服务器游玩了
    打包记得去除AppIsServer的勾选和修改Target Platfom为windows

    连接成功
    在这里插入图片描述

    源码

    https://gitcode.net/unity1/unity-mirrortest
    在这里插入图片描述

    完结

    赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。点赞越多,更新越快哦!当然,如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

    好了,我是向宇https://xiangyu.blog.csdn.net

    一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
    在这里插入图片描述

  • 相关阅读:
    如何找回删除的ps文件?误删ps文件恢复方法
    Linux GCC简明教程(使用GCC编译C语言程序)
    低代码发展趋势解读|低代码成为企业数字化转型“加速器”
    链表——移除链表元素
    伺服阀放大器控制器放大板
    linux 后台运行注意
    c语言结构体与共用体
    Flink通讯模型—Akka与Actor模型
    Bert相关面试题整理
    认识微服务
  • 原文地址:https://blog.csdn.net/qq_36303853/article/details/132789888