I think really you’re putting too much of your object definition in the hands of your class hierarchy. Instead of all this Poodle extends Dog extends Canine extends Animal extends Entity type ideas you have, you can probably snip off almost all the extra things, like Poodle and Dog for certain, and probably Canine, too.
The functionality that each subclass defines should be pretty major. In my example case, a Poodle is only different from a Dog in how it looks, and maybe how strong it is. That can be translated to fields that are defined differently when you create the object. Just change Poodle’s Sprite to reflect his different look and change his size to reflect his new strength. The differences between Dog and Canine will be similarly small - perhaps a Wolf also extends canine, and the difference between a Wolf and a Dog is that Wolves are bigger and meaner. Again, that comes down to a different Sprite, then perhaps the meanness can go into an “aggression” variable.
Then the specific behavior that a Canine exhibits can also be assigned to a bunch of enumerated separate actions, like walkType = TYPE_FOUR_LEGGED, attackType = TYPE_BITE, etc.
Typically the differences in each subclass are very major, like Entity and HostileEntity and FriendlyEntity. In an RPG, the difference between the two is very large (hostile entities need to have stats for combat, what “friends” they can bring with them, what XP they can give you, etc., whereas friendly entities need to know conversations, quests, item rewards, etc.). You should only have subclasses for information that cannot be logically represented by the superclass.
In your Zombie extends Undead extends Humanoid extends Thing example, Zombie is not at all different from Undead, Undead is barely different from Humanoid, and Humanoid probably isn’t much different from “Thing.” Instead these differences should be defined by different fields, and perhaps you can write a bunch of different behavior scripts they can utilize if you really need to. But typically you’ll stick with even just booleans… isUndead, isHumanoid, etc.
Real-world example:
In the game I’m working on right now, we’ve got:
Entity (comes with position, size, the capability of acting, drawing, doing hit tests, etc.)
Entity > MobileEntity (comes with variables for moving around, like pathfinding, velocity, etc.)
Entity > BackgroundEntity (adds nothing new to the table, but isn’t abstract, overrides act to do nothing except animate)
Entity > MobileEntity > Car (has a bunch of logic for dropping off Customers, lining up in the parking lot, etc.)
Entity > MobileEntity > Customer (has a bunch of logic for paying the Player, waiting in a venue to be ready to get their Car, etc.)
Entity > MobileEntity > Particle (has a bunch of logic for falloff, color changing, animation, flipping around, etc.)
Entity > ParticleEmitter (has a bunch of logic for timing of emitting particles)
Entity > MobileEntity > Valet (has a bunch of logic for driving cars, running back to their zone, etc.)
And those are all the entities that exist in the entire game, which is many thousands of lines long. And if I were redesigning it now I would do away with MobileEntity and BackgroundEntity, just because they have become redundant through so many code refactorings.