一、简介
Mirror 是一个用于 Unity 的开源多人游戏网络框架,它提供了一套简单高效的网络同步机制,特别适用于中小型多人游戏的开发。以下是 Mirror 插件的一些关键特点和组件介绍:
- 简单高效:Mirror 以其简洁的 API 和高效的网络代码而受到开发者的欢迎。
- 基于 UnityEngine 生命周期:Mirror 利用 Unity 的生命周期回调进行数值同步,简化了网络开发流程。
- RPC 调用:Mirror 提供了三种远程过程调用(RPC)的方式:[Command]、[ClientRpc] 和[TargetRpc],允许开发者在不同客户端或服务器上执行特定的函数。
- 网络组件:
- NetworkManager:用于管理网络会话,包括开始服务器、客户端和处理玩家连接。
- NetworkIdentity:为游戏对象添加网络身份,使其能够在网络上被识别和同步。
- NetworkStartPosition:用于设置玩家的初始生成位置。 网络发现:Mirror
-
支持局域网内的网络发现功能,方便玩家发现并加入游戏。
-
运输层兼容性:Mirror 兼容多种低级运输层,包括但不限于 TCP、UDP 和 KCP 协议。
-
开箱即用:Mirror 在 Unity Asset Store 中免费提供,并且具有丰富的文档和社区支持。
-
适用于小体量游戏:Mirror 更适合小型到中型的游戏项目,对于大型游戏项目可能需要更复杂的网络解决方案。
Mirror官网方文档:https://mirror-networking.gitbook.io/docs。
二、 使用示例
2.1 效果预览
实现简单多人联机小游戏
2.2 步骤1 添加网络管理
新建一个空对象,并为其添加Network Manager、Kcp Transport、Network Manager HUD组件。
其中:在Network Manager中添加在线场景和离线场景以及玩家预制体,玩家预制体可以从网上寻找模型或者使用内置模型代替,注意玩家预制体要添加 Network Identity组件。
最大玩家设为4对应4个玩家生成点,玩家生成方法可以设为随机或轮循。
在Kcp Transport中的配置可以不用动,也可以根据需要配置。
2.3 环境设置
- 地形可以简单用一个Plane来代替,如果对质量要求高可以后期替换为别的素材,添加天气系统,使用HDRP等可以看往期内容。
- 为玩家添加生成位置,这里放置四个位置生成玩家,前面已经设置了最大玩家数为4,则可以有四个不同玩家进入游戏,并在预设位置生成。可以先设置好一个,在复制加下来几个并设置好想要生成的位置。使用一个空对象添加Network Start Position即可。
2.3 代码部分
为玩家添加代码来操作玩家移动
playerScript参考代码
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Numerics;
using Mirror;
using UnityEngine;
using static System.Runtime.CompilerServices.RuntimeHelpers;
using Color = UnityEngine.Color;
using Random = UnityEngine.Random;
using Vector3 = UnityEngine.Vector3;
public class PlayerScrpt : NetworkBehaviour
{
public GameObject floatingInfo;
public TextMesh nameText;
//武器
public GameObject[] weaponArray;
private int currentWeapon;
private Weapon activeWeapon;
private float coolDownTime;
private Material playMaterialClone;
//private UIScript UI;
[SyncVar(hook = nameof(OnPlayerNameChanged))]
private string playerName;
[SyncVar(hook = nameof(OnPlayerColorChanged))]
private Color playerColor;
[SyncVar(hook = nameof(OnWeaponChanged))]
private int currentWeaponSynced;
//name改变触发
private void OnPlayerNameChanged(string oldName, string newName)
{
nameText.text = newName;
}
//颜色改变触发
private void OnPlayerColorChanged(Color oldColor, Color newColor)
{
//同步名称颜色
nameText.color = newColor;
//同步材质颜色
playMaterialClone = new Material(GetComponent<Renderer>().material);
playMaterialClone.SetColor("_Color", newColor);
}
//切换武器
private void OnWeaponChanged(int oldIndex, int newIndex)
{
//判断旧武器是否存在,若存在则隐藏
if (0 < oldIndex && oldIndex < weaponArray.Length && weaponArray[oldIndex] != null)
{
weaponArray[oldIndex].SetActive(false);
}
if (0 < newIndex && newIndex < weaponArray.Length && weaponArray[newIndex] != null)
{
weaponArray[newIndex].SetActive(true);
activeWeapon = weaponArray[newIndex].GetComponent<Weapon>();
//显示当前武器子弹数量
//UI.canvasBulletText.text = activeWeapon.bulletCount.ToString();
}
else
{
activeWeapon = null; //若武器不存在,则激活武器为空
//UI.canvasBulletText.text = "No Weapon";
}
}
[Command]
public void CmdSetupPlayer(string newName, Color colorValue)
{
playerName = newName;
playerColor = colorValue;
}
[Command]
public void CmdActiveWeapon(int index)
{
currentWeaponSynced = index;
}
[Command]
public void CmdFire()
{
/*if (activeWeapon == null)
return;*/
RpcFire();
}
[ClientRpc]
public void RpcFire()
{
var bullet = Instantiate(activeWeapon.bullet, activeWeapon.firePos.position, activeWeapon.firePos.rotation);
bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * activeWeapon.bulletSpeed;
Destroy(bullet, activeWeapon.bulletLifetime);
}
private void Awake()
{
foreach (var weapon in weaponArray)
{
if (weapon != null)
{
weapon.SetActive(false);
}
}
}
public override void OnStartLocalPlayer()
{
//摄像机与Player绑定
Camera.main.transform.SetParent(transform);
Camera.main.transform.localPosition = Vector3.zero;
floatingInfo.transform.localPosition = new Vector3(0f, -2.7f, 6f);
floatingInfo.transform.localScale = new Vector3(1f, 1f, 1f);
changePlayerNameAndColor();
}
// Update is called once per frame
void Update()
{
if (!isLocalPlayer)
{
floatingInfo.transform.LookAt(Camera.main.transform);
return;
}
var moveX = Input.GetAxis("Horizontal") * Time.deltaTime * 110f;
var moveZ = Input.GetAxis("Vertical") * Time.deltaTime * 4.0f;
transform.Rotate(0, moveX, 0);
transform.Translate(0, 0, moveZ);
if (Input.GetKeyDown(KeyCode.C)) //按下c改变颜色
{
changePlayerNameAndColor();
}
if (Input.GetButtonDown("Fire2"))
{
currentWeapon += 1;
if (currentWeapon > weaponArray.Length)
currentWeapon = 1;
CmdActiveWeapon(currentWeapon);
}
if (Input.GetButtonDown("Fire1"))
{
if (activeWeapon != null && Time.time > coolDownTime && activeWeapon.bulletCount > 0)
{
coolDownTime = Time.time + activeWeapon.coolDown;
//更新子弹数量
activeWeapon.bulletCount--;
//UI.canvasBulletText.text = activeWeapon.bulletCount.ToString();
CmdFire();
}
}
}
//改变玩家名称和颜色
private void changePlayerNameAndColor()
{
var tempName = $"Player {Random.Range(1, 999)}";
var tempColor = new Color(Random.Range(0, 1f), Random.Range(0, 1f), Random.Range(0, 1f));
CmdSetupPlayer(tempName, tempColor);
}
}
Weapon参考代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Weapon : MonoBehaviour
{
public Transform firePos; //子弹位置
public GameObject bullet; // 子弹对象
public float bulletSpeed = 5f; //子弹速度
public float bulletLifetime = 3f;//子弹生命周期
public int bulletCount = 15; //子弹数量
public float coolDown = 0.5f;
}
三、项目生成
接下来就可以点击文件->构建和运行,把游戏文件发给小伙伴一起玩耍。
将localhost改成自己当前ip,即可让小伙伴加入房间远程游玩了。如果链接失败,记得关闭防火墙!