Morgemil Part 17 – Engine Rework Chapter 2

Here’s where I left off. Everything is made up of behaviors.

interface ICastSpell
{
  void Behave(Spell spell);
}
class DungeonMonster
{
  public IMove MoveBehavior {get; set; }
  public IAttack AttackBehavior {get; set; }
  public ICastSpell SpellBehavior {get; set }
  public void Move(Direction direction)
  {
     MoveBehavior.Behave(direction);
  }
  public void Attack(Direction direction)
  {
     AttackBehavior.Behave(direction);
  }
  public void CastSpell(Spell spell)
  {
     SpellBehavior.Behave(spell);
  }
}
class LightMold: DungeonMonster
{  
  public LightMold()
  {   
    MoveBehavior = new Immobile();
    AttackBehavior = new Defenseless();
    SpellBehavior = new EvilSpellCast(); //LightMolds cast evil spells apparently.
  }
}
class Goblin: DungeonMonster
{ 
  public Goblin()
  {
    MoveBehavior = new Mobile();
    AttackBehavior = new Defensive();
    SpellBehavior = new NoSpells(); //Goblins have no magical aptitude
  }
}

Remember, the idea is composition. Composition should provide flexibility. I plan to do it through the use of entities. So here’s my implementation of an entity:

type EntityId = EntityId of int
 
type EntityType = 
  | Person
  | Door
  | Stairs
  | Object
 
type Entity = 
  { Id : EntityId
    Type : EntityType }

It is an integer (technically a single case discriminated union) with a bit of immutable meta-data.

An entity has nothing. All info about an entity is contained in components.

type PositionComponent = 
  { EntityId : EntityId
    Position : Vector2i
    Mobile : bool }
 
type PlayerComponent = 
  { EntityId : EntityId
    IsHumanControlled : bool }
 
type ResourceComponent = 
  { EntityId : EntityId
    ResourceAmount : double }

Entities and components are linked together only by the entity’s ID. Certainly a far departure from Object-Oriented convention. But does this remind you of anything?

Relational databases are fond of using an integer as a primary key to make nice foreign key. So the entity and components above could look like this
17_1
This has a couple benefits:

  • Composition of behavior through whether an entity has a component(s) or not.
  • No longer have to maintain giant object hierarchies
  • Systems can operate on isolated information. Example: Field-of-view, collision detection, and spatial operations in general should only need to operate on a collection of PositionComponents. They don’t care what other components an entity may have.
  • Serialization is much easier. This is important when loading or saving game state, sending game state over network, and logging.

There are other benefits as well, but those will become apparent over time and examples. Of course there are drawbacks too, but those will become apparent with time as well.

Morgemil Part 16 – Engine Rework Chapter 1

I’ve come up with some new design decisions in my rework of the engine:

  • Everything inside a level is an entity
  • Logic is reactionary
  • Mutability is not a complete evil so long as it is contained

Entities are this concept that things are better through composition. Now this is not a new idea, object oriented programmers recognize the idea of preferring composition over inheritance.

So in the beginning was inheritance

class DungeonMonster
{
  public void Move(Direction direction)...
  public void Attack(Direction direction)...
}
class Goblin: DungeonMonster
{
   ...
}

This seems pretty straightforward, monsters can move and attack. But there’s only one monster, the goblin.

Now let’s add some unique monsters, like some evil sentient plant. Not too different from those trees in the Wizard of Oz that throw apples at Dorothy.

class EvilAppleThrowingTree: DungeonMonster
{
  ...
}

Now we’re in trouble: not only are there evil trees, but they don’t work well with the base class DungeonMonster because trees can’t move. So, next step is to rework those classes:

class DungeonMonster
{
  public virtual void Move(Direction direction)...  //Default is now able to move.
  public void Attack(Direction direction)...
}
class Goblin: DungeonMonster
{
  //Don't override Move.
}
class EvilAppleThrowingTree: DungeonMonster
{
  public override void Move(Direction direction)...  //Does not allow move
}

Boom! That wasn’t so bad. We can just override whatever behavior whenever we want.

Let’s add a few more monsters to make this game really unique:

class PoisonIvy: DungeonMonster
{  
  public override void Move(Direction direction)...  //Does not allow move
}
class Mouse: DungeonMonster
{
  //Shouldn't be able to attack. Just moves around and stuff. 
}

Now this is getting to be a problem. PoisonIvy also can’t move. So I have the option of either copy-pasting code from EvilAppleThrowingTree’s Move function into PoisonIvy’s Move function which is stupid, or I could add another layer to the heirarchy.

class ImmobileDungeonMonster: DungeonMonster
{
  public override void Move(Direction direction)...  //unable to move.
}
class PoisonIvy: ImmobileDungeonMonster
{  
}
class EvilAppleThrowingTree: ImmobileDungeonMonster
{
}

But wait, didn’t I add a mouse too?

class Mouse: DungeonMonster
{
  //Shouldn't be able to attack. Just moves around and stuff. 
}

I guess the Attack function needs the same reworking that the Move function did. This is all the code so far

class DungeonMonster
{
  public virtual void Move(Direction direction)...  //Default is now able to move.
  public virtual void Attack(Direction direction)...
}
class ImmobileDungeonMonster: DungeonMonster
{
  public override void Move(Direction direction)...  //unable to move.
}
class Goblin: DungeonMonster
{
  //Don't override Move.
  //Don't override Attack.
}
class Mouse: DungeonMonster
{
  public override void Attack(Direction direction) ... //Shouldn't be able to attack.
}
class PoisonIvy: ImmobileDungeonMonster
{  
}
class EvilAppleThrowingTree: ImmobileDungeonMonster
{
}

I could keep going on and adding more monsters, but I’m trying to make the point that a dungeon monster can have many attributes. And a multitude of monsters means these attributes will be combined in many different ways. For example: I might just add a LightMold. A LightMold is a sentient fungus that cannot move, but it can cast spells.

class LightMold: ImmobileDungeonMonster
{  
  public override void Attack(Direction direction) ... //Shouldn't be able to attack. 
}

We also need to add the spell-casting attribute to DungeonMonster.

class DungeonMonster
{
  public virtual void Move(Direction direction)...  //Default is now able to move.
  public virtual void Attack(Direction direction)...
  public virtual void CastSpell(Spell spell)...  //Most monsters can't cast spells. So virtual
}

Guess what? An egregious sin has happened for now I have to go back and override all the other monster’s CastSpell function to do nothing. I could have the base class DungeonMonster do nothing with spells, but then the problem of copy pasting spell casting logic to all classes that can cast spells appears (we haven’t even added evil wizards yet). Not to mention the fact that I just copy pasted the Mouse’s Attack function override.

In short, I’ve made a mess of everything.

Let’s rework this using composition. A dungeon monster is made up of behaviors. A behavior is how something acts.

interface IMove
{
  void Behave(Direction direction)
}
interface IAttack
{
  void Behave(Direction direction)
}
 
class DungeonMonster
{
  public IMove MoveBehavior {get; set; }
  public IAttack AttackBehavior {get; set; }
  public void Move(Direction direction)
  {
     MoveBehavior.Behave(direction);
  }
  public void Attack(Direction direction)
  {
     AttackBehavior.Behave(direction);
  }
}

I don’t care what those behaviors do so long as they behave. They’re just an interface.

class Mobile: IMove
{
  public void Behave(Direction direction) ... //Move
}
class Immobile: IMove
{
  public void Behave(Direction direction) ... //Don't Move
}
 
class Defensive: IAttack 
{
  public void Behave(Direction direction) ... //Attack
}
class Defenseless: IAttack 
{
  public void Behave(Direction direction) ... //Don't Attack
}

I have any kind of monster I could ever wish for now by combining these behaviors

class Goblin: DungeonMonster
{ 
  public Goblin()
  {
    MoveBehavior = new Mobile();
    AttackBehavior = new Defensive();
  }
}

Adding an attribute to DungeonMonster got a lot easier too. No more complex inheritance or copy-pasting logic. All logic is sitting in those behaviors. Combine the behaviors to make the logic how you wish.

interface ICastSpell
{
  void Behave(Spell spell);
}
class DungeonMonster
{
  public IMove MoveBehavior {get; set; }
  public IAttack AttackBehavior {get; set; }
  public ICastSpell SpellBehavior {get; set }
  public void Move(Direction direction)
  {
     MoveBehavior.Behave(direction);
  }
  public void Attack(Direction direction)
  {
     AttackBehavior.Behave(direction);
  }
  public void CastSpell(Spell spell)
  {
     SpellBehavior.Behave(spell);
  }
}
class LightMold: DungeonMonster
{  
  public LightMold()
  {   
    MoveBehavior = new Immobile();
    AttackBehavior = new Defenseless();
    SpellBehavior = new EvilSpellCast(); //LightMolds cast evil spells apparently.
  }
}
class Goblin: DungeonMonster
{ 
  public Goblin()
  {
    MoveBehavior = new Mobile();
    AttackBehavior = new Defensive();
    SpellBehavior = new NoSpells(); //Goblins have no magical aptitude
  }
}

This is not what I’m actually doing, but it is a great step forward. I’ve laid out the code above because that is actually how I arrived at my current solution. So join me next time when I explain my understanding of entities.