In any part of your game, as the design gets more complex you’ll find it increasingly hard to build the logic to implement it. Whether it’s animation control, movement, individual orders, squad behaviors, level scripting, or game logic you’ll find the same problems as the system gets bigger.
The question is, how do you manage the interaction between all these layers of logic in the game? More to the point, how do you make sure each layer stays manageable too? As Ian Morrison noted in this thread of the forums (registration and introduction required):
“One thing I’ve noticed, though, is that a lot of AI development ties the animation VERY closely to the decision making, such that the animation is capable of interfering with decisions!”
Layering is certainly a solution to this kind of problem, but certain approaches are better than others.
Giving the Right Kind of Orders
The trick is to let the lower-level handle the complexity, but still retain enough control at a higher-level when necessary. It’s very simple:
“Orders to lower-level systems should consist of a finite task that terminates, then leads into a looping state that requires no supervision.”
This is a very powerful idea, and almost too easy to dismiss or ignore. Yet I’m willing to bet a majority of design problem you have in your game could be solved by applying this idea to part of the logic.
Figure 1: The anatomy of a perfect order as a finite task then a looping state.
Of course, if you want to leave even more responsibility to the underlying system, you can let it figure out part of the order, for example:
Deduce the most appropriate task(s) based on the current state and the requested state.
Induce the best state to use from an order consisting of a task only.
The higher level system should be willing to give up freedom whenever it can, but this approach also allows it to request very specific results.
These ideas are applicable anywhere, but these are some of the most useful:
Animation — Trigger a transition (i.e. a gesture like ducking) into a looping cycle (i.e. a posture like crouching).
Movement — Request locomotion (e.g. walking) to a destination point where a repeating behavior can be played (e.g. standing).
Behavior — Ask to advance using cover to a target and then defend it by patrolling.
Groups — Order leap-frog to fall backwards and then hold the position around a waypoint.
Levels — Setup waves of attackers then a quiet period waiting for the next “phase.”
In fact, setting up an interface that supports this task / state combination is a great way to start the development of any logic in a large system!
Here’s what starts to happen when you design systems using this approach:
You don’t baby sit the lower-level system in any way.
You give it orders and let it run, potentially for a while without having to worry.
You don’t keep track its state or mirror its logic at a higher-level.
You let the lower-level system remember the state variable for you!
You work by reacting to changes in the lower-level system only.
I’ll purposefully keep this article short emphasize that this is a very simple trick that’s very widely applicable. Remember: the best orders consist of finite tasks followed by looping states.
Have you used this trick to help design your behaviors? Do you think it could help you structure your logic better? An for a bonus question, how do goal-driven system help with such a design?