#7 LibGDX Jam

Category: 

Update: added artificial intelligence to the citizens. "Intelligence" is maybe not exactly what I implemented, but they are now walking around and they can deliver resources from one room to another.

What's new?

Let's start with a little gif animation:

So the graphics are really ugly, but they are just placeholders.
The little guy you see there, was idling around at the Rock Crusher. Then the Rock Crusher broadcasts a message, because it has run out of Rocks. The little guy receives that message and is happy to help! So he responds and goes to the Meteor Collector, where he collects some rocks (or meteors). Then he goes back and drops them at the Rock Crusher.
Not a complex task, but let's dig a bit into code.

gdx ai

The AI of my citizens is based on statemachines of the gdx ai library. Each citizen now owns a StackStateMachine. To be exact: My own implementation of it. The gdx-ai StackStateMachine is nice, but I missed some things there:

  • A simple changeState method. The gdx-ai version will always put the new state onto the stack, but in my case, I often want to replace the current state with the new one. Instead of calling revertToPreviousState and then changeState, I added a simple popAndChangeState method.
  • The stack is private. In most cases that's really good, but I want to serialize the StackMachine. Sure, LibGDX can serialize it, but I don't want to serialize all States. There are some states I don't care about. The GotoState is one of them. It contains a GraphPath and the start and end room. But instead of serializing it, I discard it, because the parent state is able to reproduce it anyway.

Most states are implemented as a simple Java enumeration:

public enum AI implements State<Citizen> {
  Idle() {
    @Override
    public boolean onMessage(Citizen entity, Telegram telegram) {
          // handle messages and maybe switch the stateMachine
          // ...
      return false;
    }
  },

  GotoSource() {
    @Override
    public void update(Citizen entity) {
          // goto to specific room and grab resources
          // ...
          // this will add a GotoState if we are not at the source:
          // if( not near source ) gotoRoom(entity, job.from);
        }
  },

  DeliverGoods() {
    @Override
    public void update(Citizen entity) {
          // goto to target room (add GotoState if necessary)
          // and drop items...
    }
  };

  public void gotoRoom(Citizen entity, Room target) {
    GameWorld world = entity.getWorld();
    BasicGraphPath path = world.findPath(world.getRoom(entity.position), target);
    if(path != null) entity.stateMachine.changeState(new GotoState(path));
  }

  @Override
  public void enter(Citizen entity) { }

  @Override
  public void update(Citizen entity) { /* ... */ }

  @Override
  public void exit(Citizen entity) { }

  @Override
  public boolean onMessage(Citizen entity, Telegram telegram) { return false; }
}

Of course, because the behaviour is described by an enumeration, I have to store any additional information inside the citizen. In this cases it is ok, because the only thing I need is a MoveJob instance. For other states, like the GotoState, I have to store a bit more data, like a GraphPath, intermediate target, speed, etc. So instead of putting that into the citizen, I added an extra State, which is not an enumeration, so it can store its own data.