1
\$\begingroup\$

I am trying to implement the Entity Component System pattern to use in a roguelike game. Right now, I have 3 systems. Input, AI, and Action. The input system basically is just a system used by the player to get input from the keyboard. The AI system is for the NPCs. The action system processes anything with an Action component, which is used to describe either a move, an attack, an item usage, basically anything that advances the game time.

However, I am having trouble with implementing the correct behaviors. It is clear that the player should have a turn, getting his input. If the input is invalid, he repeats his turn, again and again until he makes a turn that passes time. Then the enemies should get their move from the AI system, and initiate it. So what the behavior should be like is this:

while(!input.passesTime())
    input.getInput()
    draw()
action.do(Player)
for all Entities with AI
    AI.think(Entity)
    action.do(Entity)
draw()

Notice how everything is intertwined and doesn't really make sense as a single (or multiple) system. How can I design this better? Note especially that I need to update the screen everytime, even with invalid inputs since they may write something out (e.g. "Nothing interesting happens").

After some thinking it seems clear that the renderer should also be a system, but this doesn't really clear up the requirements.

\$\endgroup\$
4
\$\begingroup\$

Note: I'm playing fast and loose with the pseudocode here, so let me know if anything is unclear.

Ideally, the player shouldn't be special - just another set of components. The main function of entities is to group components. You might think of it this way: components get updates, not entities.

From the good old Evolve Your Hierarchy article:

enter image description here

Updates flow down from the Component Manager, and entity association flows across.

So, you'd start from something like this:

while(running)
    turn = timer.Now     // or something, time management is another problem altogether
    foreach ComponentType in ComponentManager ordered by ComponentType.UpdatePriority
        foreach Component in ComponentType
            Component.Update(time)
    foreach Component in ComponentManager.AllComponents ordered by Component.DrawPriority
        if Component is Drawable
            Component.Draw(time)

Note the complete lack of references to entities in this loop! The update and draw methods are really the only two special cases you need, and we only tolerate draw being separate as an optimization in the face of system limitations (it's generally better to try and group draw calls, and draw doesn't need to be called every pass) - it's not architecturally ideal. If each component has a reference to its parent entity, it can talk to the other components on its parent entity and there's no need to pass the entity as an argument to its update method. There's no need to have special methods for each component either, since that could just be wrapped into a uniform set of method declarations:

Input.Update(time)
    getInput()

AI.Update(time)
    think()

Rule of thumb: if you can wrap it, you can combine it into a generic method shared by similar classes.

So, your class hierarchy should look something like

class Component { Update(), parentEntity }
class Drawable : Component { Draw() }
class Entity { Components }
class AI : Component { Update() { /* think stuff */ } }
class Action : Component { Update() { /* do stuff */ } }
class Input : Component { Update() { /* get input and pass it on to other components */ } }
// etc.

Since this is a turn based game, you'll probably want a game keeper component like

class GameKeeper : Component
{
    Update(time)
    {
        if isOdd(time) currentTurn = Player
        else currentTurn = Cpu
    }
    // or use events
    TurnFinished(entity)
    {
        if entity == PlayerEntity
            currentTurn = Cpu
        else currentTurn = Player
    }
}

Then

class AI
{
    Update(time)
    {
        gamekeeper = FindComponent(GameKeeper)
        if gamekeeper.currentTurn != Cpu
            return
        // otherwise think stuff
        action = parentEntity.GetComponent(Action)
        action.Do("Some action")
    }
}
class Action
{
    Do(message)
    {
        // Get ready to do whatever is in the message
    }
    Update(time)
    {
        gamekeeper = FindComponent(GameKeeper)
        if gamekeeper.currentTurn != Cpu
            return
        // actually do all the things
    }
}
\$\endgroup\$
  • \$\begingroup\$ I'm still having trouble understanding how this would help my case. How do I make the Input system stall until I get a proper input? \$\endgroup\$ – user975989 Jul 22 '15 at 1:10
  • \$\begingroup\$ You don't really want the input system to stall, because you might want other things to update. Rather, it should just think "nope, no input yet" or "this input doesn't need to be handled." When there is input that is significant, the input component messages other components to tell them that something has happened, and what needs to happen in response is going to be different for each component. \$\endgroup\$ – jzx Jul 22 '15 at 1:19
  • \$\begingroup\$ I am using a playerTag component to find the player for various purposes, so Input still functions like a generic system. But the issue is that AI and Action have to be run one after another for each entity. Notice that this is a turn based game, so AI has to think about what just happened after each event. What I mean is, once anyone does an action, the next Entity to be processes should think using the AI, and so on. \$\endgroup\$ – user975989 Jul 22 '15 at 1:21
  • \$\begingroup\$ You can break this down further for your purposes, but "time" could be the turn index, and you'd simply prioritize the AI component list ahead of the Action component list. When it isn't that entity's turn, Action.Update should just return. You'll probably want a separate entity to keep track of whose turn it is. \$\endgroup\$ – jzx Jul 22 '15 at 1:30
  • 1
    \$\begingroup\$ Another way of looking at it is this: you're making a turn based game in a real time system, both because the system doesn't stop when it's someone's turn and because you'll probably want to animate things and handle things like system menus out of turn. So a lot of things are just going to say "not my turn, do nothing," and others don't care whose turn it is. Instead of waiting on anyone, we just ask everyone if they have something to do on that turn. \$\endgroup\$ – jzx Jul 22 '15 at 1:54

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.