Pretty Dumb AI

Published October 06, 2008
Advertisement
So, after much reading and thinking, I've finally settled on a plan for my AI. Rather than picking a specific AI implementation (like STRIPS planning, or hierarchical state machines), I'm going with a task-based AI framework which will (in theory) allow me to plug any combination of them together and to easily mix autonomous and scripted behaviour together. Before I get into the boring stuff, here's an AI "Black Triangle" moovie:

Disabled

So now let's dive under the hood ...

My design basically has two classes: an Agent, and a Task. An Agent represents any entity in the gameworld that can run AI, and the Tasks are the things you can get an Agent to do. So a task might be low level ("Moove", "Shoot"), high-level ("Fight", "PathFinding"), a state in a state machine ("Flee", "Attack"), a goal-based planner (e.g. a STRIPS system generating new subtasks), a sensor ("DamageSensor", "LineOfSight", "AudioSensor"), etc, etc. Any Agent can run zero or more Tasks (i.e. it can run Tasks in parallel) and new Tasks assigned to the Agent can either augment, interrupt or replace existing Tasks.

As well as managing and running the Tasks, the Agent provides an abstract interface to the game characters (or to use AI-speak, the Agent is the actuator of the AI). So rather than having the AI know how to attach a LookAt constraint to a game skeleton, the AI just asks the Agent to "LookAt" something, and it's the Agent implementation which knows how to make this type of game character look (e.g. using a Skeleton constraint, vs playing a blended animation, vs rotating the character, etc). This allows me to keep the AI code focused on AI, without knowing too much about the way the game world and characters are put together.

Now the nice thing about building the AI as Tasks on top of an Agent, is it allows me to totally mix and match control schemes. So I can have an enemy using some fancy-pants AI planning engine, but then have a game script which assigns him some simple scripted Tasks (e.g. go here, press this button, then say this) - and the Task framework will handle putting the high-level fancy AI to sleep while it carries out the scripted sequence. And this can work the other way too: I can give a game character some simple scripted motion, and then "call" some higher level combat AI task when it sees the player.

And most satisfyingly of all, the player input fits right it. There's a "ControlScheme" task which implements the game's control scheme (i.e. reading the joystick/keyboard input and turning into Agent calls). Not only does this allow me to re-use all the same Agent functions for controlling the main character - but it means you can script the player's character by simply assigning him some new tasks. Then when they end, the ControlScheme task takes back over and you get control back. But you could also do cool things like attach the ControlScheme task to other AI Agents in the game to let the player temporarily control other game characters or vehicles (as Vehicles are just another type of Agent that implement the Agent interface as a vehicle model rather than a character skeleton).

So that's my AI plan for world domination.

So far, I've refactored all my existing character and control classes to sit on top of the new Agent/Task architecture. I can control my cow by having him run the ControlScheme task, and I can assign a simple "Moove" task to the Elephants to make them run around in a circle. Strangely, just seeing the little Elephants run around crazily in a circle is very satisfying. If nothing else, just having a really clean decoupling between the control code and the character/agent makes me very happy (as there used to be a big mess of auto-push code mixed through the generic character code which always upset me).

The next goal is to add a basic PhysicsSensor so I can have the Elephants change direction when they bump into something. Unfortunately, this requires me to start working out how Task management is specified in the AI graph - something I don't have a nice solution to yet.

And just to wrap up, here's an in-development outtake where I forgot to lock some of the physics axes ...

Disabled


Cheers!
Previous Entry More Elephants
Next Entry Rule Based AI
0 likes 4 comments

Comments

Jotaf
Damn, that's what *I* would do if I was up to the point of implementing meaningful AI in any of my projects! Seems pretty solid, I like that architecture quite a lot. Flexible but not so much that it's just a glorified scripting language like so many state-machine implementations. Anyways a little question for ya: how do you handle conflicts between tasks? Is it like a subsumption architecture or what?
October 06, 2008 02:54 PM
Milkshake
Quote:Original post by Jotaf
Damn, that's what *I* would do if I was up to the point of implementing meaningful AI in any of my projects! Seems pretty solid, I like that architecture quite a lot. Flexible but not so much that it's just a glorified scripting language like so many state-machine implementations.


Good to know I'm not totally out to lunch with this =)

Quote:Original post by Jotaf
Anyways a little question for ya: how do you handle conflicts between tasks? Is it like a subsumption architecture or what?


That's a very good question Jotaf! And exactly what I'm currently wrestling with.

From the perspective of the task architecture itself, it doesn't care. So tasks can replace older tasks (subsumptive), suspend older tasks (interuptive), or augment existing tasks (multi-tasking). The hard bit is working out how this is specified/inferred in the task graph you create for an Agent. And this is the bit I'm currently thinking about.

Right now, the two observations I've made are:
1) any instance of a Task can only be running once (or else the multiple threads running it will trample each other's memory)
2) while it's the new task which specifies whether it should replace existing task[s], it's the existing tasks which specify whether it makes sense to suspend/resume them, or just forget them when they get interrupted.

So I'm trying to work out how these rules might sit on a graphically assembled task network to cleanly describe when to replace/suspend/multi-task. I'm hoping to err on the side of simplicity (i.e. single task) wherever possible, but still allow people to write multi-tasking AI when they specifically want it. The multi-tasking is pretty easy inside a single "program", as you just build parallel logic paths. It's harder when you want an interrupt (i.e. sensor) driven reaction to run along side an existing behaviour (and I mean, harder from the perspective of how you specify that's what you want to happen).

I think I'm going to start with the simplest/dumbest solutions to get things moving, and look at adding more sophisticated task management when and if it's needed.
October 06, 2008 04:00 PM
Jotaf
Quote:Original post by Milkshake
So tasks can replace older tasks (subsumptive), suspend older tasks (interuptive), or augment existing tasks (multi-tasking).
...
Right now, the two observations I've made are:
1) any instance of a Task can only be running once (or else the multiple threads running it will trample each other's memory)
2) while it's the new task which specifies whether it should replace existing task[s], it's the existing tasks which specify whether it makes sense to suspend/resume them, or just forget them when they get interrupted.


Ouch. I don't see anything wrong with that, but specifying whether they can be suspended or not is a kind of a "lock" and that's concurrency for ya. Blocking should be kept to a minimum otherwise it gets hairy. That pretty much covers subsumptive and interruptive, leaving multi-tasking out.

However, thinking of this last one as an augmentation of existing tasks, I'm reminded of a software architecture that featured "entry-points" where you could plug other components, which in turn could contain other entry-points. These were nothing more than a slot for an optional function call that could handle something and possibly return something. If it didn't return anything it was just like an event notification, if it did, well, you get the idea. Communication between different tasks could be done like this, and a Generic Attacking AI makes this decision about who to attack, but it's such a nice task that it lets others override its decision and choose someone else :) So that's a rule for augmenting tasks. Maybe in a not-so-distant future.

Quote:I think I'm going to start with the simplest/dumbest solutions to get things moving, and look at adding more sophisticated task management when and if it's needed.


That's right, no need to solve the General Concurrency Problem with this one :)
My comments about multi-tasking shouldn't be taken too seriously, your 2nd rule is more than enough to start cracking subsumption and interruptions right?
October 07, 2008 07:34 PM
Milkshake
Quote:Original post by Jotaf
That's right, no need to solve the General Concurrency Problem with this one :)
My comments about multi-tasking shouldn't be taken too seriously, your 2nd rule is more than enough to start cracking subsumption and interruptions right?


I hope so. I'd like to come up with a general purpose task architecture - but it's obviously a very tough nut to crack (particularly when you want a fairly simple interface to it). I'm certainly going to need at least a bit of both of those - so you can have some temporary reaction to something (and then go back to your business), and so you can replace some/all of your logic (e.g. for state machine style AI).

October 08, 2008 02:06 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement

Latest Entries

Moose Sighting

1394 views

Constraints

1563 views

Moose

1296 views

Melon Golf

1856 views

Toon

1358 views

Spaceships

1110 views

Rendering Pt2

1201 views

Hardware Shaders

1241 views
Advertisement