很多做Unity联机游戏的开发者,应该都用过Mirror这个开源网络库——毕竟它比原生Unity Network更省心,不用自己折腾底层的网络通讯。但很多人用的时候,遇到网络问题只会懵,比如玩家连不上服务器、玩到一半突然掉线,不知道从哪查起。其实Mirror自带了错误处理和日志记录的机制,只要用对方法,就能轻松搞定这些问题,不用再瞎猜故障原因。

一、Mirror里的错误怎么抓

很多新手用Mirror时,遇到网络问题只会盯着Console里的红报错,但这些报错要么太模糊,要么只在开发环境显示,玩家玩的时候根本看不到,甚至客户端直接崩溃。其实Mirror帮我们预留了好几个专门抓错误的“口子”,不用自己从零写代码处理。

1.1 那些常碰到的网络错误场景

举几个最常见的情况:一是玩家点“加入游戏”,但服务器还没启动,客户端连不上;二是主机玩家中途关游戏,所有在线客户端得知道“主机退出了”,不然会一直卡在加载页;三是玩家放技能、发道具数据时,网络突然卡断,数据没传到服务器,这时候得给玩家弹提示说“操作失败,请重试”。这些场景只要对应Mirror的事件回调,就能精准捕捉错误,不会再让玩家一脸懵。

1.2 基础错误处理的代码示例

我们可以写一个自定义的网络管理器,继承Mirror的NetworkManager,重写它的错误回调,这样任何网络相关的错误都会触发我们写的处理逻辑。这里统一用Unity Mirror的C#技术栈,代码如下:

using Mirror;
using UnityEngine;

// 自定义网络管理器,负责处理Mirror的所有网络事件和错误
public class MyNetworkManager : NetworkManager
{
    // 通用网络错误回调:Mirror检测到任何网络错误都会触发这里
    public override void OnError(int connectionId, System.Exception exception)
    {
        // 先把错误信息打在Console里,方便开发时快速排查
        Debug.LogError($"Mirror网络出错 | 连接ID:{connectionId} | 错误类型:{exception.GetType().Name} | 错误详情:{exception.Message}");

        // 如果是客户端的话,给玩家弹提示(比如用UI文本组件显示)
        if (!NetworkClient.isConnected)
        {
            // 这里假设我们有一个全局的UI提示文本,实际开发中替换成自己的UI逻辑
            Debug.Log("给客户端弹提示:连接失败,请检查网络或服务器是否在线");
        }
    }

    // 客户端掉线回调:当玩家和服务器断开连接时触发
    public override void OnClientDisconnect()
    {
        base.OnClientDisconnect();
        // 给玩家弹提示,告诉他们已经掉线,不需要找Console看红报错
        Debug.Log("给客户端弹提示:你已断开游戏连接,请重新加入");
    }

    // 服务器超时回调:如果客户端连服务器超过30秒没响应,触发这里
    public override void OnClientTimeout()
    {
        base.OnClientTimeout();
        Debug.Log("给客户端弹提示:连接超时,请稍后再试");
    }
}

这段代码里,我们重写了三个最常用的错误回调,覆盖了连不上、掉线、超时三种新手最头疼的网络问题,不用自己抓底层的Socket错误,Mirror已经帮我们封装好了。

二、日志记录怎么用,才不会白打很多没用的内容

很多开发者打日志的时候,只会随便写个Debug.Log,等游戏上线后玩家出问题,拿到的日志就是一堆乱码一样的文字,根本找不到是哪个玩家、什么时间出的错。Mirror的日志可以自定义,我们只要加几个小细节,就能让日志变得有用,方便后续排查。

2.1 原生Mirror日志的坑

Mirror原生的日志只会显示“Connection lost”或者“Send failed”,没有上下文信息,比如不知道是哪个玩家丢的,也不知道是在打团的时候还是在加载场景的时候出的错,排查起来要花十几分钟甚至更久。所以我们要自己写个自定义日志工具,把这些关键信息加进去。

2.2 自定义日志的实现示例

我们可以写一个日志管理类,专门处理Mirror的网络日志,把日志存在手机或电脑的本地文件里,玩家出问题后可以直接把这个文件发给开发者,不用再描述半天。代码如下:

using Mirror;
using System;
using System.IO;
using UnityEngine;

// 自定义网络日志类,专门记录Mirror的错误,存在本地文件里
public class NetworkLogger : MonoBehaviour
{
    // 日志文件的路径,Unity提供的PersistentDataPath是跨平台的,安卓、苹果、PC都能用
    private string logFilePath;

    void Awake()
    {
        // 初始化日志路径,比如安卓就是/storage/Android/data/[包名]/files/network_log.txt
        logFilePath = Application.persistentDataPath + "/network_log.txt";
        // 每次启动游戏,先清空旧日志(如果要追加的话改成File.AppendAllText就好,按需调整)
        File.WriteAllText(logFilePath, $"--- 网络日志启动时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss} ---\n");
    }

    // 专门用来记录Mirror的网络错误,把关键信息都加上
    public void LogMirrorError(string errorType, string errorMsg, int playerId)
    {
        // 生成带时间、错误类型、玩家ID的日志内容,方便排查
        string logLine = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} | 错误类型:{errorType} | 玩家ID:{playerId} | 错误详情:{errorMsg}\n";
        // 把日志写到本地文件
        File.AppendAllText(logFilePath, logLine);
        // 同时在Console里打一遍,开发时不用跑游戏就能看到错误
        Debug.Log(logLine);
    }
}

然后我们把这个日志类的实例挂到某个空对象上,再修改之前的MyNetworkManager,在错误回调里调用日志方法,比如在OnError里加一行:

// 假设我们在Inspector里把NetworkLogger挂到了叫Logger的对象上
Logger.LogMirrorError("网络连接错误", exception.Message, connectionId);

这样,每次出错,日志都会自动存在本地文件里,玩家出问题后,开发者只要拿到这个文件,一眼就能看到错误的时间、哪个玩家出的错、具体错在哪,不用再反复问玩家细节。

三、实际用的时候要注意的坑

光有代码还不够,很多时候用错了,还是达不到效果,甚至会拖慢游戏性能,这里说几个最容易踩的坑:

  1. 别在Update里重复打日志:如果网络卡了,Update每秒跑60次,每次都打错误日志,游戏会变卡,日志文件还会变得特别大。应该只在错误发生的时候打一次,比如用Mirror的事件触发,而不是每帧检查。
  2. 日志一定要加上下文:比如刚才代码里的玩家ID、错误时间,要是没有这些,日志就是一堆没用的文字,所以一定要把和错误相关的信息都加上,哪怕只是玩家的角色名也比没有强。
  3. 别存敏感信息在日志里:比如玩家的用户名密码、手机型号这些隐私信息,日志如果没加密,可能会泄露,所以只存和网络错误相关的信息,比如玩家ID、错误类型就行。
  4. Mirror日志等级要合理设置:Mirror有自己的日志等级,比如Info、Warning、Error,开发的时候可以开Info,能看到网络同步的细节,上线的时候只开Error,这样既能看到开发时的细节,又不会浪费性能,避免日志文件太大占内存。

四、Mirror错误处理和日志的实际应用场景

这些机制在各种联机游戏里都有用,举几个最常见的场景:

  1. 休闲小游戏:比如四人斗地主,玩家中途掉线,日志里记录掉线时间、玩家ID,开发者就能知道是网络波动还是服务器挂了,后续可以优化服务器的稳定性,减少玩家掉线的情况。
  2. 竞技类游戏:比如MOBA游戏里,玩家放大招的时候,技能数据发送失败,这时候给玩家弹提示“技能发送失败,请重试”,同时日志里记录当时玩家的位置、放的技能ID,方便排查技能的网络同步问题,比如是不是技能数据太大导致丢包。
  3. 沙盒游戏:比如多人联机的建造类游戏,玩家建房子的时候突然掉线,日志里记录玩家的位置、正在建造的物品,这样如果玩家的建造没保存,开发者就能知道是网络问题还是同步逻辑的问题,后续优化保存的时机,减少玩家的损失。
  4. 塔防类游戏:当玩家的防御塔被攻击时,攻击数据需要从客户端传到服务器,再同步给其他玩家,如果出现延迟,防御塔的血量显示就会不一致,错误日志就能记录数据发送的时间戳,帮助开发者找到丢包的节点,优化网络同步的频率,减少延迟。

五、优缺点总结

用Mirror的错误处理和日志记录的优点很明显:首先,Mirror自带的错误事件不用自己写底层代码,省时间,中小团队几天就能接入;然后,自定义日志灵活,可以根据自己的游戏需求调整,比如要存本地文件还是云端,要加什么信息都能改;另外,上手快,新手只要跟着示例改改就能用,不用懂复杂的网络知识。当然也有缺点:Mirror的原生日志没有结构化,要自己写工具,大团队可能需要更专业的日志系统,比如用ELK之类的,这时候Mirror的原生日志可能不够用;另外,Mirror的错误处理是在主线程里的,如果处理不当,比如弹UI的逻辑太复杂,可能会影响游戏的帧率,所以尽量把弹UI的逻辑放在协程里,避免卡主线程。