[Unity plug-in] Implementing multiplayer online games - introduction to the use of Mirror plug-in

Preface

It’s finally here. Many people have sent me private messages before, wanting to know the process of how to implement multiplayer games. Here it is.

I have actually been paying attention to the Mirror plug-in for a long time, and I only recently had the time to sort it out.

Mirror is a simple and efficient open source Unity multiplayer game network framework. Mirror is free in the Unity store.

Official API address: https://mirror-networking.gitbook.io/docs

Import Mirror plugin

https://assetstore.unity.com/packages/tools/network/mirror-129321
Insert image description here

a brief introdction

1. RPC call

If there are three keywords used to modify a function, the function will not be called locally, but remotely. [Command], [ClientRpc], [TargetRpc].

  • Command is called on the client and executed on the server, and the method name starts with "Cmd".
  • ClientRpc is called on the server and executed on all clients connected to the server, and the method name starts with "Rpc".
  • TargetRpc is called on the server and executed on the specified client connected to the server. This method has at least one formal parameter of NetworkConnection, which is used to determine which client it is executed on, and the method name starts with "Target".
  • ServerCallback: can only be called by the server and executed on the server. And the method name starts with "Server". Usage is similar to TargetRpc

use

using Mirror;
using UnityEngine;

public class MyNetworkBehaviour : NetworkBehaviour
{
    
    
    [Command]
    void CmdFire()
    {
    
    
        // 在服务器上调用
    }

    [ClientRpc]
    void RpcGetHit()
    {
    
    
        // 在客户端上调用
    }
}

For example, a certain client character scores extra points

# 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)
{
    
    
    // 将指定客户端标记为已准备状态的逻辑
}

2. Note on errors

  • 1. All objects in the scene that are linked to your code will be added with network identity by default, but if the network manager component and the network identity component are placed on the same object, an error will be reported.

  • 2. If there is a network manager component but no Kcp transport component in the object, an error will be reported.

  • 3. If there are multiple network manager components in the scene, an error will be reported.

  • 4. If the character prefab cannot be loaded into the player prefab column, it may not be hung in the network identity component.

  • 5. The role code must have if (!isLocalPlayer) return; otherwise. . . You know the consequences.

Basic use

1. Create a scene network manager

The network manager is the core control component of multiplayer games. The network manager is the core control component of multiplayer games.

Create an empty game object in the starting scene and add the newly created network manager components (networkManager, Kcp Transport, networkManagerHUD components).
Insert image description here
The kcp transport component is mounted on the transport of networkManager
Insert image description here
and configures the Scene scene. Offline and online are offline interfaces and game interfaces. For example, we create a new offline scene, put a network manager's network managerHUD in it, and then create a new online scene. Register them all into the scene bar in the build in the build setting (drag in), with offline on top. Then we enter the offline scene, run it, click host, and we will enter the online scene.

2. Create a player

Create a player object Player and add networkIdentity to the player as a unique identifier for network synchronization. Generally, 除了all objects in the game that contain the network manager component must be hung in this component, including those that are about to be hatched. There are only two options for this thing. One check box is Server Only, which means that only the server can operate it. You can check it according to your own needs. The second one is visible, which has three options: Default, Force Hidden, and ForceShown. I personally feel that it is useless, so everyone can just default.
Insert image description here
Only when networkIdentity is mounted, the network hub can recognize this object and synchronize it. Next, 预制体save the Player as a player, delete it in the scene, and then drag the prefab into the Player Prefab slot of the network hub (networkManager). Its future generation will completely rely on the network hub to automatically generate it after connecting to the host.

Auto Create Player: Checked by default. If checked, the above "player prefab" will be automatically generated when connecting to the server.
Insert image description here

Note: If the character prefab cannot be loaded into the player prefab column, it may not be hung in the network identity component.

Add a Network Transform for the player, synchronize the position, rotation and scaling of the networked game objects in the network, and check the Client Authority attribute of the networkTransport.

Mirror currently provides 2 types of Network Transform:
Insert image description here

Reliable:低带宽,与Rpcs/Cmds/等相同的延迟。

Unreliable:高带宽,极低延迟

使用Reliable,除非需要超低延迟。

Insert image description here
Note: We will use the Scale of the player body to flip it later. Here we also add the Network Transform code to the body. Remember to check Sync Scale.
Insert image description here

3. Add the initial spawn position of the player

Create several empty objects as the player's initial spawn position, add the Network Start Position script, and drag the object to the appropriate location.
Insert image description here
And choose random (Random) or round robin (Round Robin) spawn point selection method in NetworkManager.

1.Random:生成为随机(可能相同的生成位置将被两个或更多玩家使用)

2.Round Robin:循环(使用每个可用位置,直到客户端数超过生成点数)。

Insert image description here
Effect
Insert image description here

4. Player Control

Some things to note about network synchronization:

1. Scripts that need to use networking functions must add using Mirror to use the corresponding API, and inherit NetworkBehaviour instead of MonoBehaviour.

2. When it comes to player input, you must first judge isLocalPlayer, and use islocalplayer to judge whether you have the permissions of the current object.

To control the game object, add a simple character control script to PlayerControl.cs, inheriting 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); // 翻转角色
        }
    } 
}

Effect
Insert image description here

5. Synchronize the camera

For objects generated independently on each client (here, each player's camera is taken as an example), the start method needs to be modified to OnStartLocalPlayer(), which can prevent multiple clients' cameras from being modified to the same one.

OnStartLocalPlayer: only executed in the client, called when the object where the script is located is a player character, used to set tracking cameras, character initialization, etc.

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);
 }   

Effect
Insert image description here

6. Synchronize name and color modifications of different characters

The synchronization variable needs to be marked with a synchronization variable [SyncVar(hook=nameof(FunctionExecOnClient))]. When the synchronization variable changes, the subsequent FunctionExecOnClient method will be called.

When the value of a SyncVar in the server scene changes, it is synchronized to all other clients.

For modification of synchronized variables, use [Command]tags (marks for methods, the method name starts with 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;
}

Effect
Insert image description here

7. Synchronized animation

Mount the Network Animator component
Insert image description here

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);
    }
}

Insert image description here

8. Synchronized bullets

Bomb is an ordinary bomb prefabricated body
Insert image description here

method one

[ClientRpc] keyword, the server can send synchronization instructions to all connected clients, and the method name also needs to start with 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);
}

Effect
Insert image description here

Method Two

NetworkManagerThere is a list at the bottom (Registered Spawnable Prefab), which is used to place objects that need to be hatched in the game, such as enemies and bullets. Drag them in. PS: Remember to add Network Identity to the bomb
Insert image description here
. component, otherwise it won’t be able to be dragged in.

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);//服务器孵化,同步客户端
}

effect
Insert image description here
problem

You will find that the AddForce force applied by the client to the bomb has no effect. The reason is that we did not add a synchronized rigid body component and added the Network Rigidbody 2D component to the bomb.
Insert image description here

Effect
Insert image description here

9. Chat function

Added ChatController script

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);
        }
    }
}

Modify PlayerController and call the transfer character name

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;
    }
}

When drawing the UI page, remember to add the Network Identity component.
Insert image description here
Remember to mount the Network Identity script to the chat UI canvas.
Insert image description here

Effect
Insert image description here

10. Synchronous scene switching

Create three new scene NetworkManager objects
Insert image description here
and add SceneController code to control the visibility of 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");
    }
}

The Main scene is the game start page. By default, a button is placed. The button calls the ButtonLoadScene method. The Network Manager only needs to be mounted in the initial scene (and the Main scene). The previous code has controlled the visibility of the NetworkManagerHUD. If you are not in the HUD view when the error is reported, The main scene displays
Insert image description here
the scene corresponding to the mount.
Insert image description here

There is basically no difference between the SampleScene1 and SampleScene2 scenes. It is the same as the previous game page. Delete the original NetworkManager object to prevent conflicts with the Main scene of the main interface. Add the ButtonChangeScene method to control the scene switching
Insert image description here
Insert image description here
in the game. The method is mounted in the SampleScene1 and SampleScene2 scenes. on the scene switch button

//同步切换场景
public void ButtonChangeScene()
{
    
    
    if (isServer)
    {
    
    
        var scene = SceneManager.GetActiveScene();
        NetworkManager.singleton.ServerChangeScene
        (
            scene.name == "SampleScene1" ? "SampleScene2" : "SampleScene1"
        );
    }
    else
    {
    
    
        Debug.Log("你不是host");
    }
}

Effect
Insert image description here

11. Redraw a HUD interface

NetworkManagerHUD (needs to cooperate with the Network Manager component), it will automatically draw a GUI:
Insert image description here
Host (host): equivalent to a server and a client.

Client: Connect to the server, followed by the server IP address, localhost is the local port, which is equivalent to connecting yourself.

Server Only: Only acts as a server.

However, this UI interface is not very good-looking, so we generally do not use this component and make our own GUI.

Add three new buttons to the scene
Insert image description here

Added MyNetworkManagerHUD code, mounted on the game page, example code

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<NetworkManagerHUD>().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(); // 停止网络主机或客户端
    }
}

Of course, the original NetworkManagerHUD component is useless and can be deleted. Remember to delete the previous code that controls the display and hiding of NetworkManagerHUD simultaneously.

running result
Insert image description here

12. Find the server

Use network discoveryHUD+network discovery component to replace the original NetworkManagerHUD

This network discovery component also needs to be used with the network manager. It can list all servers in the LAN. There is a transport column in it. We need to drag the Kcp transport component that is together with the network manager component into it, otherwise it will not run.
Insert image description here

network discoveryHUD is similar to networkmanagerHUD, except that there is one less Client and one more find server.

Insert image description here
The function is that clicking find server will list all competitions (servers) in the LAN, but only in the LAN.

running result
Insert image description here

14. Character death and resurrection

// 角色是否死亡的标志
[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;
}

Write the resurrection method (regenerate the character) elsewhere

public class GameManager : MonoBehaviour
{
    
    
    void Update()
    {
    
    
        if (Input.GetKeyDown(KeyCode.R))
        {
    
    
           //让当前客户端复活角色
		    NetworkClient.AddPlayer();
        } 
    }
}

Effect
Insert image description here

13. Make up a network manager yourself

Here is a simple function to create a role

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);
    }
}

Remove the original NetworkManager, mount the code you wrote, and remove the check box for automatically creating roles. Otherwise, two protagonists will be created at first
Insert image description here
. The running effect is normal.
Insert image description here

AOI fog effect implementation

Place the Spatial Hashing Interest Management component in the same object as your network manager
Insert image description here

Parameters:
visible distance,
how long it takes to display,
3d 2d switching,
debugging slider

Show items only when you get closer
Insert image description here

Room opening function

There is actually an official demo in Mirror/Examples/MultipleMatches/Scenes/Main. We can take a look at the effect first.
Insert image description here

Connect to online server (to be continued)

linux server

Modify the online server IP and port number.
Insert image description here
Remember to go to the server and add the security group whitelist for the corresponding port.
Insert image description here

Remove the HUD script component
Insert image description here
. Write code to control whether it is a server or a client.
Insert image description here
Package a server first, so check the server first.
Insert image description here

Then package a Linux server-side program
Insert image description here
ps: If you don’t have it, remember to install the unity module first.
Insert image description here
Upload all the server-side programs to the server.
First add permissions to the executable file
Insert image description here
and run the executable program.
Insert image description here

Then you can package the client normally and connect to the server to play. When
packaging, remember to remove the check box of AppIsServer and modify the Target Platfom to windows.

connection succeeded
Insert image description here

Source code

I'll put it up after I've sorted it out.

end

Gifts of roses, hand a fragrance! If the content of the article is helpful to you, please don't be stingy with your 点赞评论和关注feedback so that I can receive feedback as soon as possible. Your every feedback 支持is the biggest motivation for me to continue creating. The more likes, the faster the updates will be! Of course, if you find 存在错误something in the article 更好的解决方法, please feel free to comment and send me a private message!

Okay, I am 向宇, https://xiangyu.blog.csdn.net

A developer who has been working quietly in a small company recently started studying Unity by himself out of interest. If you encounter any problems, you are also welcome to comment and send me a private message. Although I may not necessarily know some of the problems, I will check the information from all parties and try to give the best suggestions. I hope to help more people who want to learn programming. People, encourage each other~
Insert image description here

Guess you like

Origin blog.csdn.net/qq_36303853/article/details/132789888
Recommended