Unity Netcode for GameObjects:RPC命令模式实现求助
Unity Netcode for GameObjects 命令模式RPC传输解决方案
方案一:基于INetworkSerializable的类型标识传输
问题根源
抽象类Command无法被实例化,Netcode反序列化时会尝试创建抽象类实例,从而抛出MissingMethodException。解决思路是让每个命令子类单独实现序列化逻辑,同时传输类型标识区分不同命令。
实现步骤
- 修改抽象
Command类,定义类型标识接口,无需继承INetworkSerializable; - 每个命令子类实现
INetworkSerializable,并提供公共无参构造函数(Netcode反序列化必需); - 发送RPC时先传输命令类型标识,再传输序列化后的命令数据;服务端根据标识创建对应子类实例并反序列化数据。
代码示例
抽象Command类
public abstract class Command { // 每个命令子类对应唯一类型ID public abstract string CommandTypeId { get; } public abstract void Execute(); }
具体命令类
public class ConcreteCommand : Command, INetworkSerializable { // Netcode支持的可序列化字段 public int TargetId; public string ActionName; // 必须提供公共无参构造函数 public ConcreteCommand() { } // 业务构造函数,用于客户端创建命令实例 public ConcreteCommand(int targetId, string actionName) { TargetId = targetId; ActionName = actionName; } public override string CommandTypeId => "ConcreteCommand"; public override void Execute() { Debug.Log($"执行ConcreteCommand:目标ID {TargetId},动作 {ActionName}"); } // 实现Netcode序列化逻辑 public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter { serializer.SerializeValue(ref TargetId); serializer.SerializeValue(ref ActionName); } }
RPC处理逻辑(NetworkBehaviour子类中)
public class CommandHandler : NetworkBehaviour { // 注册命令工厂,服务端和客户端需同步注册 private readonly Dictionary<string, Func<Command>> _commandFactories = new(); private void Awake() { _commandFactories.Add("ConcreteCommand", () => new ConcreteCommand()); // 可添加更多命令类型... } // 客户端调用此方法发送命令到服务端 public void SendCommandToServer(Command command) { if (!IsClient) return; SendCommandRpc(command.CommandTypeId, SerializeCommand(command)); } // 将命令序列化为字节数组 private byte[] SerializeCommand(Command command) { if (command is not INetworkSerializable serializableCmd) throw new ArgumentException("命令必须实现INetworkSerializable接口"); using var writer = new NetworkWriter(); serializableCmd.NetworkSerialize(writer); return writer.ToArray(); } // 将字节数组反序列化为命令实例 private Command DeserializeCommand(string commandTypeId, byte[] data) { if (!_commandFactories.TryGetValue(commandTypeId, out var factory)) throw new KeyNotFoundException($"未知命令类型:{commandTypeId}"); var command = factory(); if (command is not INetworkSerializable serializableCmd) throw new InvalidOperationException("命令未实现INetworkSerializable接口"); using var reader = new NetworkReader(data); serializableCmd.NetworkSerialize(reader); return command; } // 客户端调用、服务端接收的RPC方法 [Rpc(SendTo.Server)] private void SendCommandRpc(string commandTypeId, byte[] commandData, RpcParams rpcParams = default) { if (!IsServer) return; var command = DeserializeCommand(commandTypeId, commandData); command.Execute(); } }
方案二:反射+参数序列化实现
适用场景
不想为每个命令编写序列化代码的场景,核心是传输命令完整类型名和序列化后的参数数据,服务端通过反射重建实例并执行。
实现步骤
- 确保所有命令类提供公共无参构造函数;
- 客户端发送RPC时,传输命令的完整类型名和序列化后的字段数据(如JSON格式);
- 服务端根据类型名反射创建实例,反序列化数据并调用
Execute方法。
代码示例
命令类结构
public abstract class Command { public abstract void Execute(); } public class ConcreteCommand : Command { public int TargetId; public string ActionName; // 必需的无参构造函数 public ConcreteCommand() { } // 业务构造函数 public ConcreteCommand(int targetId, string actionName) { TargetId = targetId; ActionName = actionName; } public override void Execute() { Debug.Log($"执行ConcreteCommand:目标ID {TargetId},动作 {ActionName}"); } }
RPC处理逻辑
public class CommandHandler : NetworkBehaviour { // 客户端调用发送命令 public void SendCommandToServer(Command command) { if (!IsClient) return; // 获取命令的完整类型名(包含命名空间) string typeName = command.GetType().AssemblyQualifiedName; // 用JsonUtility序列化命令字段 string jsonData = JsonUtility.ToJson(command); SendCommandRpc(typeName, jsonData); } [Rpc(SendTo.Server)] private void SendCommandRpc(string typeName, string jsonData, RpcParams rpcParams = default) { if (!IsServer) return; // 根据类型名获取Type Type commandType = Type.GetType(typeName); if (commandType == null) { Debug.LogError($"未知命令类型:{typeName}"); return; } // 反射创建命令实例 Command command = Activator.CreateInstance(commandType) as Command; if (command == null) { Debug.LogError($"无法创建命令实例:{typeName}"); return; } // 反序列化JSON数据到实例 JsonUtility.FromJsonOverwrite(jsonData, command); // 执行命令 command.Execute(); } }
注意事项
- 方案一中,类型ID可改用枚举替代字符串,减少拼写错误并提升效率;
- 方案二中,
JsonUtility仅支持public字段或带[SerializeField]的私有字段,复杂类型(如Dictionary)需改用Newtonsoft.Json(需导入Unity官方包)或自定义Netcode序列化; - 服务端与客户端的命令类结构必须完全一致(命名空间、字段名、类型),否则反序列化会失败;
- 所有命令类必须提供无参构造函数,Netcode反序列化和反射创建实例均依赖此构造函数。
内容的提问来源于stack exchange,提问作者Page
相关产品推荐
相关产品推荐

