Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design pattern to customize possible statuses of a Character in a game

I decided to an solid rpg-like game structure with Java to practice design patterns.

Basically there are different types of characters in my game, which are all considered to be "game objects", having some common features:

public abstract class Character extends GameObject {
  Status status;
  //fields, methods, etc.
}

public abstract class Monster extends Character{
  //fields, methods, etc
}

public class Hero extends Character {
  //fields, methods, etc
}

Status here is an enumeration:

public enum Status {
  NORMAL,
  BURNT,
  POISONED,
  HEALED,
  FROZEN
}

I would like to make my code flexible, easy-to-modify, and I would like to follow the SOLID principles using the necessary design patterns effectively.

Let's suppose I would like to customize my characters, allowing to create custom Character extensions allowing to have only certain status changes. For example I would create a Monster called

public class FireGolem extends Monster{...}

, which is unable to get damaged by heat (hence is unable to get burnt).

I have 2 ideas to do this:

1) create a Set for the class Character, in which I would specify what kind of status changes can a Character have

2) create different interfaces (Burnable, Freezable, ...) and implement them when necessary.

What do you think? Which is better and why? Is there any better and cleaner option at all?

Thank you in advance.

like image 722
lyancsie Avatar asked Aug 26 '19 14:08

lyancsie


People also ask

Why design your own video game characters?

Designing characters in video games is one of the best ways to take a stock character and make it our own, creatively whittling down the more generic or default options, bringing a flavor of our own to the game world.

What is the purpose of this pattern in a game?

This pattern helps you avoid the problem of event notification in your game. Since games are user-interaction driven, objects can change state at almost any time. When an object changes state oftentimes that object needs to be animated or have other objects change their state with respect to the new state.

What makes a good character design?

Good character design will make the audience think back to the moments they’ve shared with that character, and long for more. Initially focused on web dev, Dustin was introduced to game design by a friend after college and was immediately attracted to the combination of technical skill and creativity required to make an awesome game.

What is the strategy design pattern?

This decoupling behavior and flexibility are possible thanks to a design pattern known as Strategy Design Pattern. This design pattern provides flexibility to your game by allowing it to change behavior dynamically without the need of modifying the game's logic. Learn how to implement this pattern here.


Video Answer


2 Answers

FireGolem might simply override the method setStatus and throw, let's say, an IllegalArgumentException when the given status can't be applied to its instances.

class FireGolem extends Monster {

    @Override
    public void setStatus(Status status) {
         if (Status.BURNT.equals(status)) {
             throw new IllegalArgumentException("FireGolem can't be burnt!");
         }

         super.setStatus(status);
    }

}

As @Vince Emigh pointed out, it's not a pure SOLID example: preconditions shouldn't be strengthened in subclasses.

like image 156
Andrew Tobilko Avatar answered Sep 30 '22 08:09

Andrew Tobilko


I think you might want to consider going the other way around.

Generally speaking, most characters will be burnable, freezable, etc.

So instead of creating a set for all kinds of status a character can have, create one for the characters Immunities.

This will allow you to handle immunities in the parent class (Character), so that when creating a new monster, all you have to do is add an immunity to it in its constructor and all will be well without having to override any methods.

Let's see how that would work in your example.

Oh but before that, short warning: I will call your status BURNING and not BURNT, just because I assume the character with that status is in fact still burning ;)

public abstract class Character extends GameObject {
  Status status;
  ArrayList<Status> immunities = new ArrayList<>();
  //fields, methods, etc.

  public void addImmunity(Status immunity) {
    immunities.add(immunity);
  }

  // return false if the status couldn't be set in case you want to do something
  // like show an "Immune!" message or something like that
  public boolean setStatus(Status status) {
    if (immunities.contains(status)) {
      return false;
    }
    this.status = status;
    return true;
  }
}

class FireGolem extends Monster {
  public FireGolem() {
    addImmunity(Status.BURNING);
  }
}

The great thing about this approach is that you will save quite a bit of memory in the long run. And you don't have to overengineer anything. Now... whether or not you're using an ArrayList or something else is of course up for debate, this is just a simple example.

Also, the setStatus method is returning a boolean as a result here. The reason I'm not throwing an exception is because I simply don't consider it one. Why wouldn't a player try to set the Fire Golem on fire? Sure, it shouldn't work, but it's still one of the expected cases. Then again, different people use different approaches and there's certainly nothing completely wrong with throwing an exception here, it's just that for me personally, it doesn't feel right. If you want more information than a simple true or false for visualization purposes, you could return more complicated objects instead, but I wanted to leave the example as simple as possible.

One more thing to add: Maybe you should also consider giving the character a status list instead of a single status because while freeze and burn might cancel each other out, I do think it's possible to be burning and poisoned at once, but that's just a matter of opinion. There are a lot of games out there that only allow a single status at once.

like image 24
Mark Avatar answered Sep 30 '22 07:09

Mark