Search code examples
c#unity-game-enginenetworkingmultiplayerabstract-data-type

Unity Netcode for GameObjects Command Pattern with RPC


I'm trying to implement a command pattern to use by sending a command via rpc but can't figure out how to send it correctly.

The command is set up as such

abstract class Command
{
   public virtual void Execute() { }
}

class ConcreteCommand:Command
{
//some fields (all of which need to be serializable)
//some constructor
//override Execute()
}

I want to be able to send any command through the rpc but havn't been able to figure out how to send it.

I have tried making Command inherit INetworkSerializable and having each subclass override NetworkSerialize but got an error saying "MissingMethodException: Default constructor not found for type Command"

I have tried using reflection

typename = ConcreteCommand.GetType().FullName;
parameters = ?????
//send via rpc
Activator.CreateInstance(Type.GetType(typeName), parameters);

but can't figure out how to send an object[] for the parameters (assuming all parameters are serializable)

Edit to clear up any confusion:

  • I have a class that generates Concrete Commands.
  • I have a function that accepts Abstract Commands.
  • This function needs to send the Abstract command to the server via rpc and the server needs to recreate the same Abstract Command (same subclass with the same parameters) so it can call Execute().

How can I do this? The methods I've tried have not worked.


Solution

  • After a lot of trial and error (and mostly error) this is what I came up with.

    public abstract class Command: INetworkSerializable
    {
        public virtual void Execute() { }
        public abstract void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter;
        public abstract void Send(NetworkObjectReference netObj);
        protected void Recieve(Command command, NetworkObjectReference netObj) { /*Do stuff here*/ }
    }
    
    public class ConcreteCommand: Command
    {
        public string field = "Default value";
        public ConcreteCommand(string field) { this.field = field; }
        public override void Execute() { /*Do stuff here*/ }
        public override void NetworkSerialize<T>(BufferSerializer<T> serializer)
        {
            serializer.SerializeValue(ref field);
        }
        public override void Send(NetworkObjectReference netObj) { CommandServerRpc(this, netObj); }
        [ServerRpc] public void CommandServerRpc(ConcreteCommand command, NetworkObjectReference netObj) { Recieve(command, netObj); }
    }
    

    The client code only needs to call Command.Send and the server can do it's thing with the Command regardless of the subtype and with all the correct parameters.