11 hours ago, SyncViews said:
Is there a simpler architecture that allows each AI "feature" to be self contained and reusable, while still being suitable for large numbers of units? Iv'e seen a lot of talk about FSM's but not seeing how to get away from this central blob. Is an FSM suitable at all for the more complex behaviours?
Yes, it is very suitable.
Describing it sounds more complex than it really is. Here is an article with a few variations, the last example with animals running around is closest to what you describe.
Several of the games I've worked on included variations of nested state machines mixed with some degree of utility functions.
Hopefully they don't scare you off with descriptions. The individual state machines can be self contained and composed neatly. One state machine goes through all the actions it needs to complete its goal. Actions are made available as part of a pool of actions associated with the entity, and behaviors are chained together through state machines both by nesting and by utility functions.
You're talking about a large-scale game with many unit types, this type of system tends to grow rapidly and is best suited for teams of programmers and artists/animators.
In your example you've got attack, patrol along a route, move, take off, and more. Each one of those can be broken down into smaller bits. You probably will have several move actions: walk, run, search, crawl, fly, etc. Those can be encapsulated by a bigger command like MoveTo(), that would accept a target to move to along with a preferred moving style. Infantry may have available actions of walk, run, search, and crawl. Tanks may have a "walk" that moves slowly but retains the ability to fire, and "run" that moves quickly but cannot fire. Aircraft may only have fly, which may require transitions for landing and launching. MoveTo() would look at the unit's available actions and choose an appropriate movement to get somewhere. If moving somewhere requires moving along waypoints it could run one move action to the first point, then another move action to another point, then another move action to another point. MoveTo() could also be smart about distances, if a unit has a short distance to travel it could choose the walk action, if it has a long distance to travel it could the run action. Some units may have complex forms of movement, such as tanks or aircraft that can accurately fire while moving.
Now you can compose that MoveTo() into bigger behaviors. Patrolling means running MoveTo() with a particular set of parameters, plus on every update also search for enemies. Attacking can check for distance, and if they're too far away they can call MoveTo().
After that you can compose behaviors out of a longer series of actions. "Collect resource" behavior means the action of identifying a nearby resource, moving to it, harvesting the resource, traveling back to the storage location, and depositing the resource.
Occasionally you'll want units to look for other actions to do. Generally you don't want this to be continuous so the work load is distributed over time. A patrolling soldier may re-evaluate if it should do something else every time it completes a walk cycle. A unit playing the unit's idle could wait until a full animation has completed before re-evaluating.
Having a "keep doing this behavior" is an important behavior for chaining. When the unit is done harvesting a resource it might repeat the behavior. But it is important that actors are constantly re-evaluating what they are currently doing, and they generally need a way to stop doing whatever they are doing. For example, units that are harvesting resources or that are patrolling should have the ability to stop and re-evaluate their actions when they're attacked.
When re-evaluating the system can make a short list of all available actions. Exactly how you do that will depend on the game, maybe attaching actions to game objects like enemy actors or to invisible spawn points, maybe having free actions that search for proximity around characters, or maybe through some other way that works for your game. Use a utility function to see how much the character wants to do something. Actions like idle have very low desire, actions like "defend myself" are very high desire. When you want units to behave nearly autonomously you want many options with high levels of desire. In life simulators like The Sims the actors should have a long list of potentially interesting activities to choose from. In military simulations where characters do very little unless commanded, the action should usually be "keep doing whatever I was doing before".
As these types of games grow, they typically start out with a small number of behaviors and a rapidly-growing number of actions. Already mentioned there will be several actions for moving between places. Aircraft will have different move actions than tanks, which will have different move actions than infantry. Moving while carrying resources may be different than moving while unloaded. As the number of actions increases you gain the ability to build more complex behaviors out of them. As mentioned, harvesting resources involves at least four actions in a chain. Building a structure may involve several steps as well.