Unity中INetworkSerializable序列化GameObject报错:需为非可空值类型
Unity Netcode序列化GameObject的问题解决
问题代码
自定义序列化结构体
public struct MyStruct : INetworkSerializable{ public static GameObject OBJ1 = GameObject.Find("Obj1"); public static GameObject OBJ2 = GameObject.Find("Obj2"); public static GameObject OBJ3 = GameObject.Find("Obj3"); public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { serializer.SerializeValue(ref OBJ1); serializer.SerializeValue(ref OBJ2); serializer.SerializeValue(ref OBJ3); } }
Start函数中的调用代码
string DoNotDelete = PlayerPrefs.GetString("ActiveObj"); MyStruct Struct = new MyStruct(); { if (Struct.OBJ1.name != DoNotDelete) { DestroyServerRpc(Struct.OBJ1); } }
报错信息
The type 'GameObject' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method
BufferSerializer<T>.SerializeValue<T>(ref T, FastBufferWriter.ForPrimitives)
核心原理讲解
首先明确两个关键概念:
值类型 vs 引用类型
- 值类型(比如int、bool、自定义struct)直接存储数据本身,默认不可为空;
- 引用类型(比如GameObject、string、class)存储的是指向内存对象的地址,本身可以为空。
你代码里的GameObject是引用类型,完全不符合SerializeValue要求的「非可空值类型」条件,这就是报错的根本原因。
Netcode序列化的限制
Netcode的SerializeValue只能处理基础值类型和自定义可序列化值类型结构体,不能直接序列化引用类型对象——因为客户端和服务器的内存地址完全独立,传递对象地址没有实际意义。
另外你代码还有两个额外问题:
OBJ1/OBJ2/OBJ3是static静态字段,不属于结构体实例,序列化静态字段完全无效;而且你在Start里访问Struct.OBJ1本质是访问全局静态变量,和创建的Struct实例无关;GameObject.Find在结构体初始化时调用风险极高,可能场景未加载完成导致返回null。
修复方案
Netcode中同步GameObject引用,正确做法是用NetworkObjectReference或同步NetworkObjectId:
方法1:使用NetworkObjectReference(推荐)
public struct MyStruct : INetworkSerializable { // 用Netcode专门的类型存储对象引用 public NetworkObjectReference obj1Ref; public NetworkObjectReference obj2Ref; public NetworkObjectReference obj3Ref; // 单独做初始化,避免结构体初始化时的查找风险 public void Initialize() { obj1Ref = GameObject.Find("Obj1").GetComponent<NetworkObject>(); obj2Ref = GameObject.Find("Obj2").GetComponent<NetworkObject>(); obj3Ref = GameObject.Find("Obj3").GetComponent<NetworkObject>(); } public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { // 序列化NetworkObjectReference,符合Netcode要求 serializer.SerializeValue(ref obj1Ref); serializer.SerializeValue(ref obj2Ref); serializer.SerializeValue(ref obj3Ref); } // 封装获取GameObject的方法 public GameObject GetObj1() { if (obj1Ref.TryGet(out NetworkObject netObj)) { return netObj.gameObject; } return null; } // 同理实现GetObj2、GetObj3方法 }
修改Start函数调用
string DoNotDelete = PlayerPrefs.GetString("ActiveObj"); MyStruct myStruct = new MyStruct(); myStruct.Initialize(); // 先初始化对象引用 GameObject obj1 = myStruct.GetObj1(); if (obj1 != null && obj1.name != DoNotDelete) { DestroyServerRpc(obj1); }
方法2:手动同步NetworkObjectId
如果不想用NetworkObjectReference,可以同步对象的NetworkObjectId(ulong值类型,符合序列化要求):
public struct MyStruct : INetworkSerializable { public ulong obj1Id; public ulong obj2Id; public ulong obj3Id; public void Initialize() { obj1Id = GameObject.Find("Obj1").GetComponent<NetworkObject>().NetworkObjectId; obj2Id = GameObject.Find("Obj2").GetComponent<NetworkObject>().NetworkObjectId; obj3Id = GameObject.Find("Obj3").GetComponent<NetworkObject>().NetworkObjectId; } public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { serializer.SerializeValue(ref obj1Id); serializer.SerializeValue(ref obj2Id); serializer.SerializeValue(ref obj3Id); } public GameObject GetObj1() { if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(obj1Id, out NetworkObject netObj)) { return netObj.gameObject; } return null; } }
内容的提问来源于stack exchange,提问作者Billibambessou
相关产品推荐
相关产品推荐

