Whether or not to use polymorphism via a static class hierarchy depends on:
- how “static” your character types are, i.e. how frequently do you need to introduce new character types?
- how much the character types have in common in behaviour and properties
- whether you want to add new character types without rebuilding your application
- whether you want to configure the properties and behaviour of your character types without rebuilding your application
And that all totally depends on the kind of game you are making. Polymorphism via subclassing generally is useful for decoupling the client of a type from the different subclasses/incarnations of a type, so that once a new subclass/incarnation is added, the client hopefully does not have to be modified.
However, static polymorphism says nothing about the consistency in behaviour of each subclass.
This is what the “Liskov Substitution Principle” is all about: Even if you had used subclassing it can well be that the client only depending on the superclass/interface still needs to know the specific subclasses because one or more subclasses do not adhere to the behavioural contract defined in the superclass/interface.
This is when one uses “x instanceof Y” checks in client code.
A very common example of that is with the common misconception that a square “is a” rectangle by modeling that via subclassing. Or by assuming that a circle “is an” ellipsis by modeling that using subclassing.
So, before modeling your character types using subclassing, try to first define the behavioural interface of a “Player.” What can he do (i.e. what behaviour does it exhibit as methods?) and do all possible subclasses adhere to that contract?