一、频繁生成销毁的同步痛点

做联网游戏的时候,Mirror作为Unity常用的网络同步库,每帧都要同步角色、技能这些对象,要是每次都新建再销毁,就像每次寄快递都买新盒子,用完直接扔——不仅要花时间造盒子,扔的盒子还会占空间,系统得花精力清理(就是GC),多了就会卡帧,玩家一卡就骂。尤其是技能弹道、临时同步对象这种高频创建销毁的东西,同步开销会翻倍。

二、对象池和Mirror的结合实现

技术栈:C#、Unity 2021、Mirror v100.1.0 对象池其实就是把不用的对象收起来,下次直接拿新的,不用再从头造,Mirror的网络对象只要做好同步的状态管理就行。

using UnityEngine;
using Mirror;
using System.Collections.Generic;

// 网络对象池,专门管理Mirror同步对象的复用
public class NetworkSyncObjectPool : MonoBehaviour
{
    // 要复用的预制体,必须是Mirror的NetworkIdentity子类
    public NetworkIdentity reusablePrefab;
    // 初始池大小,提前备好的对象数量
    public int initialPoolCount = 30;
    // 存储空闲对象的队列,先进先出方便管理
    private Queue<NetworkIdentity> idleObjectQueue = new Queue<NetworkIdentity>();

    void Awake()
    {
        // 初始化池:提前生成对象,不激活,避免每帧动态创建的开销
        for (int i = 0; i < initialPoolCount; i++)
        {
            NetworkIdentity newObj = Instantiate(reusablePrefab, transform);
            newObj.gameObject.SetActive(false);
            Mirror.NetworkServer.UnSpawn(newObj.gameObject); // 暂时从Mirror同步列表移除,避免自动同步
            idleObjectQueue.Enqueue(newObj);
        }
    }

    // 从池取对象,没空闲就新建
    public NetworkIdentity GetObject(Vector3 spawnPos, Quaternion spawnRot)
    {
        NetworkIdentity targetObj;
        // 有空闲对象直接取,跳过新建步骤
        if (idleObjectQueue.Count > 0)
        {
            targetObj = idleObjectQueue.Dequeue();
        }
        // 池空了就临时新建,但要控制频率,避免频繁扩张
        else
        {
            targetObj = Instantiate(reusablePrefab, transform);
            Mirror.NetworkServer.UnSpawn(targetObj.gameObject);
        }
        // 重置对象状态!这步是核心,不然旧数据会残留
        ResetObjectState(targetObj);
        // 设置位置旋转,激活对象
        targetObj.transform.SetPositionAndRotation(spawnPos, spawnRot);
        targetObj.gameObject.SetActive(true);
        // 让Mirror把这个对象同步给所有客户端
        Mirror.NetworkServer.Spawn(targetObj.gameObject);
        return targetObj;
    }

    // 把不用的对象归还到池,而不是直接销毁
    public void ReturnObject(NetworkIdentity usedObj)
    {
        // 先从Mirror同步列表移除,停止同步
        Mirror.NetworkServer.UnSpawn(usedObj.gameObject);
        // 再次重置状态,确保归还后是干净的
        ResetObjectState(usedObj);
        // 隐藏对象,放回池
        usedObj.gameObject.SetActive(false);
        idleObjectQueue.Enqueue(usedObj);
    }

    // 重置对象的所有动态数据,避免复用残留
    private void ResetObjectState(NetworkIdentity obj)
    {
        // 示例:弹道对象的状态重置
        if (obj.TryGetComponent<SkillBullet>(out var bullet))
        {
            bullet.ResetBulletData();
        }
        // 可以加其他自定义组件的重置逻辑,比如移动组件、buff组件等
    }
}

// 示例:技能弹道的自定义脚本,和对象池配合
public class SkillBullet : NetworkBehaviour
{
    public float damage = 25f;
    public float maxLifeTime = 3f;
    private float currentLife;

    // 重置方法,被对象池调用,必须写得彻底
    public void ResetBulletData()
    {
        damage = 25f; // 重置伤害
        currentLife = 0f; // 重置生命周期
        // 重置移动速度、碰撞标记、特效状态等
        GetComponent<Rigidbody>().velocity = Vector3.zero;
    }

    void Update()
    {
        // 只有服务器控制弹道生命周期,Mirror的同步逻辑在SyncVar里
        if (isServer)
        {
            currentLife += Time.deltaTime;
            // 生命周期到了,归还对象到池,不是销毁
            if (currentLife >= maxLifeTime)
            {
                Object.FindObjectOfType<NetworkSyncObjectPool>().ReturnObject(GetComponent<NetworkIdentity>());
            }
        }
    }

    // 碰撞触发,命中后归还对象
    void OnCollisionEnter(Collision collision)
    {
        if (isServer)
        {
            // 命中逻辑:给目标造成伤害(省略具体数值判断)
            // 归还对象到池
            Object.FindObjectOfType<NetworkSyncObjectPool>().ReturnObject(GetComponent<NetworkIdentity>());
        }
    }
}

三、核心应用场景

3.1 技能弹道同步

联网游戏里的技能子弹、激光这些,每放一次就新建一次,用完就销毁,换成对象池后,服务器和客户端直接复用,不用每次走Mirror的新建同步流程,减少延迟。

3.2 角色状态缓冲同步

有时候需要临时同步角色的临时状态(比如护盾、减速),这些临时对象用完就丢,用对象池回收后,下次再用不用新建,降低GC压力。

3.3 临时网络消息中转

Mirror里的临时消息对象,每帧处理完就销毁,换成对象池后,中转速度提升,避免消息排队延迟。

四、技术的优缺点

4.1 优点

首先是减少GC,对象复用避免了频繁的内存分配和回收,游戏帧率更稳定;其次是降低Mirror的同步开销,不用每次新建网络对象,同步消息的体积更小;还有就是扩容灵活,根据场景调整池大小,不会出现新建对象的卡顿。

4.2 缺点

实现稍微复杂,得写对象重置逻辑,漏写就会出现旧数据残留的bug;池大小不好控制,太小不够用,还是会新建,太大占内存,比如同时放几百个闲置对象,会占不必要的内存。

五、注意事项

5.1 必须彻底重置对象状态

这是最容易踩的坑,比如子弹的伤害是上次的50,这次变成新的,就会出现技能伤害异常,所以每个组件都要写重置逻辑,确保归还后是全新的。

5.2 同步权限控制

Mirror的网络对象有Authority,池里的对象被借走后,不能随便改权限,不然会出现同步错误,比如客户端控制了服务器的对象,导致状态混乱。

5.3 池大小动态调整

固定大小的池适合小型游戏,但大型游戏要动态调整,比如副本里弹道多,就临时扩容,空闲的时候缩容,避免内存浪费。

六、总结

对象池和Mirror结合,是解决联网游戏同步性能问题的实用方案,适合高频创建销毁同步对象的场景,只要注意状态重置、权限控制和池大小调整,就能有效减少同步开销,提升游戏的流畅度。