I have a bunch of classes that represent several game events. Each game event extends a main class GameEvent:
abstract class GameEvent : INetworkSerializable {
public abstract void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter;
class ChooseInput : GameEvent {
public int Index { get; set; }
public override void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref Index);
}
}
class RandomList : GameEvent {
public int[] Permutation { get; set; }
public override void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref Permutation);
}
}
...
}
I did it this way because in addition to multiplayer, I also managed the events for another purpose, e.g. for undo function.
Now I want to send this events over Rpc method of a NetworkObject. But I get the error “Default constructor not found for type GameEvent”.
class MyNetworkObject : NetworkObject {
[Rpc(SendTo.NotMe)]
public void GameEventRpc(GameEvent gameEvent) {
controller.HandleGameEvent(gameEvent);
}
/* this does work, but not practical with many event classes
[Rpc(SendTo.NotMe)]
public void GameEventChooseInputRpc(GameEvent.ChooseInput gameEvent) {
controller.HandleGameEvent(gameEvent);
}
[Rpc(SendTo.NotMe)]
public void GameEventRandomListRpc(GameEvent.RandomList gameEvent) {
controller.HandleGameEvent(gameEvent);
}
*/
}
class OtherClass {
public void DoSomething() {
networkObject.GameEventRpc(new GameEvent.ChooseInput { Index = 3 });
}
}
Apparently Unity isn’t trying to instantiate the concrete classes but rather the abstract class GameEvent. So I have to write a method for each game event? Why is that? Is there a better workaround for this?
You need to write a default ctor. Neither the implementation nor the abstract base class provide one. I believe this just naturally looks upward the inheritance chain and that’s why it reports your base class in the error since that’s the last place that could have a default ctor.
Try with this:
class ChooseInput : GameEvent {
public int Index { get; set; }
public override void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref Index);
}
public ChooseInput() {}
}
Question is: what do you plan to do with the abstract base class?
You can also specify and implement the INetworkSerializable in each subclass separately. And if you don’t provide common methods you don’t need a base class either, instead it could be replaced by an interface itself as it were.
Like so:
public interface IGameEvent : INetworkSerializable { }
class ChooseInput : IGameEvent {
public int Index { get; set; }
public override void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter {
serializer.SerializeValue(ref Index);
}
public ChooseInput() {}
}
How exactly? RPCs implemented in an abstract base and overriden I would understand. But if one of the event classes above implements an RPC by itself this should work … right?
Well, the above code is not a good example because RPCs only work on NetworkBehaviour derived classes.
Which brings me to:
There is no need to override the NetworkObject! I would advise against doing so.
Specifically not for implementing RPCs. Create a custom component that derives from NetworkBehaviour.
Hmmm I’m under the impression that an RPC generally does not allow reference types as a parameters. At best it could be an implicit conversion like NetworkObjectReference which gets sent as a ulong.
Classic factory, that definitely works. We had done this 20+ years ago in Spellforce/Battleforge.
It may be worthwhile to investigate NGO custom messages for this sort of thing as it’s likely more lightweight than RPCs.
No, its still not possible, because apparently the serializer on the receiver side doesn’t know the concrete class.
The whole idea is to have ONE RPC method for all events. Of course I can create methods for each event type with the concrete class as parameter. But that’s what I wanted to avoid.
Thank you. I’ve already thought about that too. The information about the event/type is then represented twice, in the class inheritance and in the DerivedType identifier.
It’s a really sad that RPC and OOP don’t mix well here.