2

Introduction to my problem

I'm trying to design a very simple video game where there are several Animals players (they all inherit from Animal):

  • Cat
  • Dog
  • Rabbit

There are also some performable actions (only one per Animal) they can perform such as:

  • Bite: done only by Dogs (if an animal gets bitten, it will lose an amount of life)
  • Kick: done only by Rabbits (if an animal gets kicked they won't be able to move for X seconds)
  • Scratch: done only by Cats (if an animal gets scratched they will reduce their speed movement by -50% for Y seconds)

I was thinking about a design that could be extensible in the future: having new animals (or even new players such as aliens/plants/humans), actions and even being able to randomly assign an action to a player.

My solution so far

Interfaces

First of all, every player that would like to play the game, must implement the Playable interface (In this case, Animal would implement Playable):

The Playable interface

interface Playable {
  move(distance: number): void,
  getPosition(): number,
  attack(): void,
  beingBitten(): void,
  beingKicked(): void,
  beingScratched(): void
}

The Performer interface

Also, I thought about having an interface called Performer for every action a player could perform over another player:

interface Performer {
  perform(enemy: Playable): void
}

Implementations

This way, our Rabbit's .attack() implementation would be something like the following:

class Rabbit extends Animal {

  private action: Performer;

  // ... etc ... 

  public attack(enemy: Playable): void {
    action.perform(enemy);
  }

  // ... etc ... 

}

And Kick performable action would look something like this:

class Kick implements Performer {

  public perform(enemy: Playable): void {
    enemy.beingKicked();
  }

}

My concerns

In terms of organization and being clearer, I was thinking that I could break the Playable interface in three:

The Attacker interface

Actions related to attacking would go here.

interface Attacker { attack(enemy: Playable): void }

The Attackable interface

Actions related to being attackable would go here.

interface Attackable {
  beingBitten(): void,
  beingKicked(): void,
  beingScratched(): void
}

The Playable interface

Actions related to being able to move around would go here.

interface Playable extends Attacker, Attackable {
  move(distance: number): void,
  getPosition(): number
}

I don't have that much experience at all when it comes to OOP, so every time I end up overthinking things and designing some different alternatives but I struggle in deciding which one I should go for.

How do you see all of this? Does it make sense at all splitting Playable into three in order to make things clearer?

  • Perhaps you should think about how you'd like this to look from the outside. cat.attack(dog)? dog.beAttackedBy(cat)? Does it matter what specific attack is being dealt, to either party? – jonrsharpe Mar 5 '17 at 16:55
  • @jonrsharpe your point sounds interesting... beingAttackedBy(enemy: Playable) can be nice to avoid having to extend the Attackable interface and implementations each time a new action would be added. What I don't see here is the way to achieve that when someAnimal.beAttackedBy(otherAnimal) happens, the otherAnimal action is performed over someAnimal. Any ideas? – charliebrownie Mar 5 '17 at 17:10
1

I strongly recommend abstaining from all OOP techniques, as these do not do a good job of modeling the type of data you typically work with in a game.

The simplest example is any point in your game where you will be dealing with more than a small handful of "objects." In the OOP solution, you've conveyed the idea of managing each one as a discrete and separate ideas that inherit and/or override properties from one another. The reality of the data is that these properties can be expressed as structures of arrays, and more efficiently processed with your CPU's SIMD extensions.

See the presentation from Mike Acton, Senior Architect at Insomniac games for a thorough explanation of battle-tested game design.

Casey Muratori's blog at mollyrocket "Working on the Witness, part 11" and onward has expert analysis from a game development veteran about the fallacy of trying to apply OOP principles in a high-performance gaming system, and offers an alternative way of thinking about games programming. Also see Casey's streaming tutorials, Handmade Hero for a live, real-time coding of a fully featured game from scratch.

  • This strikes me as good advice if and only if the asker is trying to make a video game that performs well while handling a lot of objects at once. If there are only a small handful of objects, aren't the usual OOP techniques totally fine? – Tanner Swett Mar 6 '17 at 6:05
  • That isn't the only impediment OOP causes. For instance, you could to edit the game code, recompile, and reload it live while the game is running by loading the game at a fixed base address, but vtable lookups would break this. A better design strategy would be to only use OO features that you could definitively prove were 1) not harmful, and 2) provided some benefit to the game when included. – k2t0f12d Mar 6 '17 at 6:47
0

Not sure how much it helps you, but your question reminds me of this video, which stuck into my mind:

Composition over Inheritance

https://www.youtube.com/watch?v=wfMtDGfHWpA

The example is in JavaScript which is very malleable, many languages do not have this flexibility.

I just realized you asked this almost a year ago. I am curious which path did you choose.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.