Search code examples
c#unity-game-engineunity3d-unet

Cast different subclasses in a method


I'd like to send, receive and cast a subclass of a SkillEvent class and be able to grab custom parameters inside, depending of it's type.

There could be a lot of different subclasses with different parameters.

Is there a way to avoid writing a different method for each kind of skill ?

class SkillEvent { float timestamp, EventType type }
class GrenadeSkillEvent : SkillEvent { Vector3 target_point}

[ClientRpc]
RpcSendSkillEvent(SkillEvent event){
    eventManager.ExecuteEvent(event);
}

ExecuteEvent(SkillEvent event){
    switch(event.type){
        case GrenadeSkillEvent : 
            GrenadeSkillEvent grenadeEvent = (GrenadeSkillEvent) event;
            float grenadeParam = event.grenadeParam;
            break;

        case OtherSkillEvent : 
            OtherSkillEvent otherEvent = (OtherSkillEvent ) event;
            string stringParam = event.stringParam;
            break;
    }
}

I thought it could be done like that, but apparently not.

Is there a way to avoid writing a different method for each kind of skill ?

Edit :

GrenadeSkillEvent and OtherSkillEvent are child of SkillEvent.

They all have a timestamp and a type that I thought I could need to help casting the event variable into the right SkillEvent subclass.

My problem is that they have the same method, let say execute, but every kind of subclass needs a different type of parameter.

For example, a GrenadeEvent could need a target point, the size of the blast and the damages.

A BonusEvent could add some HP to a player.

A TerrainEvent could activate some custom animation on an important object close to the player, etc.

All the logic behind these skill is the same.

They all have 2 methods :

Simulate(SkillEvent Event) and Render(SkillEvent Event)

But in GrenadeSkill for example I need it to be

Simulate(GrenadeSkillEvent Event) and Render(GrenadeSkillEvent Event)

Edit 2 :

networkedPlayer.RpcOnSkillEvent(
    new SkillEvent[] {
        new GrenadeSkillEvent() {
            timestamp = state.timestamp,
            id = 0,
            point = target
        } 
    }
);

Edit 3:

var dictionary = new Dictionary<Type, Action<SkillEvent>> {
    {
        typeof(GrenadeSkillEvent), (SkillEvent e) => {
            Debug.Log("GrenadeSkill OnConfirm point"+ ((GrenadeSkillEvent)e).point);
        }
    }
};

public override void OnConfirm(SkillEvent skillEvent) {

    if (skillEvent == null) return;

    Debug.Log(skillEvent.GetType());
    dictionary[skillEvent.GetType()](skillEvent);
}

Solution

  • The short answer is that you could do what you are asking, by making your own Type, but you really don't want to do that. This sort of code is exactly what polymorphism was designed to prevent. To understand why, imagine that you've used this pattern in 5 or 10 different places in your code -- each of them has a switch statement based on Type, and different code that is run in each case. Now let's say you have a new type of SkillEvent to introduce: You have to hunt down every one of the places in your code where you switched on Type and add some more code. All that code that has already been QA'ed you've suddenly opened up, and it all has to be QA'ed again. Plus, what if you forget one of the spots? What a pain, to have to go edit all these disparate places just to add one new concrete instance of your class.

    Now consider the right way to do it: for each of these 10 places, you create an abstract method in the base class, and then override it in each of the concrete classes. The abstract method creates a contract, which every concrete class can fulfill in its own way.

    In your specific example, you say that you want to extract different parameters from the different concrete classes. What do you plan to do with those parameters? Is it to create a string which describes the object? Is it to pass those parameters to a service call you're about to make? Think in terms of the goal you want to accomplish. I'll assume that it is the latter case, because that is something where you actually need individual parameters. So, make your abstract method defined in the base class be

    abstract Map<String, Object> getParameters();
    

    or perhaps

    abstract void addParameters(Map<String, Object>);
    

    Now the concrete classes create a map and fill it in with their own parameters, but your calling method doesn't have to know anything about the implementation of this method. That's where you want the knowledge of the GrenadeSkillEvent to be, anyway, inside that class. You don't want some other class to know anything about the details of GrenadeSkillEvent. After all, you might decide to change its implementation in the future, such that it has a new parameter. At that point, you don't want to have to remember to go hunt down the callXYZServiceCall code which has this switch in it. (Worse still, it isn't you adding the new Parameter, but another engineer on the project, and he doesn't even know to worry about this switch statement.)