State machines seem to cause harmful dependencies in component-based architectures.
How, specifically, is communication handled between a state machine and the components that carry out state-related behavior?
Where I'm at:
- I'm new to component-based architectures.
- I'm making a fighting game, although I don't think that should matter. I envision my state machine being used to toggle states like "crouching", "dashing", "blocking", etc.
- I've found this state-management technique to be the most natural system for a component-based architecture, but it conflicts with techniques I've read about: Dynamic Game Object Component System for Mutable Behavior Characters It suggests that all components activate/deactivate themselves by continually checking a condition for activation.
- I think that actions like "running" or "walking" make sense as states, which is in disagreement with the accepted response here: https://gamedev.stackexchange.com/a/7934
I've found this useful, but ambiguous: How to implement behavior in a component-based game architecture? It suggests having a separate component that contains nothing but a state machine. But, this necessitates some kind of coupling between the state machine component and nearly all the other components. I don't understand how this coupling should be handled. These are some guesses:
A. Components depend on state machine:
Components receive reference to state machine component'sgetState()
, which returns an enumeration constant. Components update themselves regularly and check this as needed.B. State machine depends on components:
The state machine component receives references to all the components it's monitoring. It queries theirgetState()
methods to see where they're at.C. Some abstraction between them
Use an event hub? Command pattern?D. Separate state objects that reference components
State Pattern is used. Separate state objects are created, which activate/deactivate a set of components. State machine switches between state objects.I'm looking at components as implementations of aspects. They do everything that's needed internally to make that aspect happen. It seems like components should function on their own, without relying on other components. I know some dependencies are necessary, but state machines seem to want to control all of my components.
Answer
The overview is fairly light, but check out these slides from a presentation I did for New Game Conf last year:
https://docs.google.com/presentation/d/110MxOqut_y7KOW1pNwIdcccisIA3ooJwVR-xm-ecuc4/view
(see pertinent images below)
The gist of the technique is to combine the action list pattern (explained -- somewhat poorly -- in the slides) with behavioral state machines that act upon a component-based game entity.
It is in essence the same as creating a special composition system just for AI behavior, geared towards the kinds of inter-behavioral integration you need for simpler AI systems.
My favorite part of that particular game was how we could create entirely new types of enemies by simply selecting from a list of pre-written behaviors, put them into the action list for the game object (residing in a BrainComponent) in the order of desired priority, and everything just worked. With an action list that allows for Blocking/NonBlocking actions, this can do some really cool things despite how simple it all is to implement.
Even behaviors like "stun" where really just a StunBehaviorAction pushed onto the top of action list stack; if the stun behavior become activated (after observing that the game object's EarsComponent heard a stunning shockwave attack) then it set its internal state to Stunned, told the AnimationComponent to play the stun animation, and set its action state to Blocking, and its timer to a stun timeout pulled from the game object's EnemyParametersComponent. Since it was Blocking and at the top of the action list, none of the other BehaviorAction's in the action list would get their update method called, so they would essentially be switched off. When the timeout expired, the StunBehaviorAction set its state back to Idle and its action state to NonBlocking.
The other behaviors we implemented were almost all implemented with a single internal state machine. The only two that didn't have state machines, in fact, were the PatrolPathBehaviorAction (it would push a series of PathAction's onto the action list if it was idle, which in turn pushed MoveAction's) and the GuardHomeBehaviorAction (always at the bottom of the action list, and would always just push a PathAction back to the enemy's home location). Every other behavior was a state machine.
No comments:
Post a Comment