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.
Answer
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:
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
}
}
No comments:
Post a Comment