4

So I am making a Super Mario Bros NES clone in pygame and for all enemies that are drawn to the window, I have a class hierarchy that is 5 classes deep. Going in the order of:

  1. Object (A base class for all things drawn on the screen)
  2. Character (A class for any type of character, playable or not)
  3. Enemy (A class for any enemy in the game)
  4. BasicEnemy (A class for any enemy that only moves horizontally, as they both act the same, just different visuals. This includes Goombas and Koopas).

Then the deepest classes are Goomba and Koopa, with the class structure stated above. I am doing it this way because all objects that are drawn to the screen all have common properties, as well as all characters, enemies and basic enemies. For example, all enemies move horizontally through the exact same method (adding their x velocity to their x position). I have read in many places that too much inheritance is bad. I'm not sure if what I am doing is too much. And if it is too much, how should I be doing it?

Robbie
  • 222
  • 2
  • 8

3 Answers3

8

I would have your objects implement interfaces like ICharacter, IEnemy, IHorizontalMover. The relevant design guideline is known as Favor Composition over Inheritance, and it should allow your design to be more flexible. One difference is that interfaces tend to specify behavior and any given object can implement as much or as little as it needs.

If you feel that this causes code duplication that you did not have when a base class just handled mostly non-overridden methods, then you should be using forwarding methods, where your implementing classes forward method calls to a private class (or stateless static class) that provides a function common to several implementations. This keeps the codebase DRY (meaning Don't Repeat Yourself). It can feel like you are writing more boilerplate lines to achieve what inheritance takes care of for you behind the scenes, but it is generally considered worth the effort, leaving the codebase more flexible and open to change than a pure inheritance design.

Mike
  • 649
7

At first glance it sounds reasonable to use inheritance to separate different types of entities: this is the "real-world" approach to OOAD that most colleges teach in CS101. In other words, classes are named based on real-world objects that we can see: desk, dresser, door, etc.

However, that makes little sense in the context of a video game. There are really two categories of concern with the objects you are modeling:

  • The objects appear on screen and need to be drawn, so it makes sense to model them using classes. After all they have both properties (a sprite or 3D model) and behavior (e.g. movement) which make them candidates for classes.

  • The objects interact. A player object might interact with a brick block (this is a Mario clone, after all) by standing on it or smashing it. The object might interact with an enemy by stomping on it.

When I analyze these potential classes, I see a lot of the same concerns. Some object move, some do not. Some have "intelligence" behind them (AI or human), some do not. I do not see much value in the hierarchy you presented: the concerns are spread out too much.

I recommend moving the concerns about object control outside of the objects themselves. Here is how I would design it, based loosely on MVC.

Actor would replace Object in your hierarchy. The idea is an actor has some role on the game field. That role might be "do nothing" in the case of a brick that is unsmashable and performs no actions. It might be able to remove itself from the game field (brick gets smashed). It might move: this could be a platform that patrols on a loop, an enemy with an AI, or a player that receives input from a hardware device.

ActorView is passed in to an Actor and controls how it is displayed. This could be as simple as "always display this sprite" or "render this 3D model based on certain criteria."

ActorController is the "brain" of the Actor. One could have a "do nothing" implementation that never changes state (e.g. an unbreakable brick), a simple implementation that can remove the actor (e.g. a breakable brick), a more complex implementation (e.g. a platform that patrols back and forth), an AI implementation (e.g. an enemy), or a player (use hardware input).

Now you can use composition: Actor should really just be one class that takes a view and a controller. The ActorView might have a small number of subclasses that can be reused. Do you really need a different view for each static brick? No, you can simply reuse one class and pass a different bitmap in each time. ActorController could have a small number of subclasses for each broad type of Actor: static, scripted (moves, breaks), AI, player.

0

Instead of trying to figure out the exact class hierarchy you need, how about letting it grow organically?

The big advantage of inheritance is avoiding repetition. When you put functionality in a base class, you can share that functionality among many different classes without having to put the same code in each one.

So just inherit from Object, write your code, and then start creating intermediary classes where it makes sense to avoid repetition. When you find two objects doing the same thing, create an abstract class between them and Object and pull the common behavior into that abstract class.

If your hierarchy begins to get too complex, with common behavior among objects that aren't that closely related, that's when you can start to pull behavior out into separate objects and use composition to achieve that behavior...again, in order to avoid repetition.

Kyralessa
  • 3,724