Complete AI System for Torque
by Dan Keller · 03/09/2008 (8:12 pm) · 16 comments
Well, it's done. I've finally finished my ai system, except for a few minor things. (Well, I actually finished it a while ago, but I've been too busy to post it up here.) My basic goals in this project have been to make an opponent ai that is flexible, extensible, and, once completed, easy to implement. This has probably been one of my most ambitious coding projects yet, and I'm glad to say, I'm quite satisfied with the results. It uses a three-layered system: At the top layer, a genetic algorithm causes the bot population as a whole to learn and adapt over time. In the middle layer, a each bot uses a neural net to evaluate relevant input information and make a decision as to what to do next. And at the bottom layer, an A* pathfinder tells the bots how to get where they want to go.
Now down to the nitty-gritty description. When the game starts, the AIManager is created. It loads the neural net gene pool from a file, or creates a blank pool. (It has to be randomized from the console because of certain annoyingnesses with global object constructors.) When an AIPlayer "bot" is created, the AIManager combines two random nets from the better half of the gene pool, and gives it to the bot.
Then, the thinking begins. Each bot's thinking function is called, every other tick, by the AIManager. (I'm considering, for efficiency's sake, doing this some other way.) The bot converts all the information is uses into values from -1 to 1. There are currently 7 inputs: Whether the bot is doing anything, how long it's been since it last saw the player, its health, the player's health, how far away the player is, what it's doing, and one blank input that's there for a very good reason that I forget. This information is fed through a feed-forward neural net. There are five outputs, and the bot uses the highest of the five to decide where it wants to go.
Now the A* part begins. Back when the map was loaded, another global object, AStar, used a bunch of special NavMesh SceneObjects to create a grid of navigation points spanning the map. I have a good picture of this in one of my previous blog posts (a picture is worth a thousand subclauses). When the bot decides where to go, it calls one of five functions. They are stopMove(), findPathTo(), sneakUpOn(), findCoverFrom(), and wander(). The AStar object uses variations on the classic a* algorithm, and returns a list of points for the bot to follow. The bots use the original AIPlayer path following code, however keeping track of paths has been moved to c++ for simplicity's sake.
Each time the bot inflicts damage on the player, the bot's fitness score is incremented. This is how the effectiveness of each net is kept track of. When the bot dies, the AIManager::removeBot() function is called. (and a string of passive voice sentences are written) This replaces the lowest scoring net in the gene pool with the bot's net. The gene pool is then saved back to a file.
Read it again, it might make more sense this time.
Now down to the nitty-gritty description. When the game starts, the AIManager is created. It loads the neural net gene pool from a file, or creates a blank pool. (It has to be randomized from the console because of certain annoyingnesses with global object constructors.) When an AIPlayer "bot" is created, the AIManager combines two random nets from the better half of the gene pool, and gives it to the bot.
Then, the thinking begins. Each bot's thinking function is called, every other tick, by the AIManager. (I'm considering, for efficiency's sake, doing this some other way.) The bot converts all the information is uses into values from -1 to 1. There are currently 7 inputs: Whether the bot is doing anything, how long it's been since it last saw the player, its health, the player's health, how far away the player is, what it's doing, and one blank input that's there for a very good reason that I forget. This information is fed through a feed-forward neural net. There are five outputs, and the bot uses the highest of the five to decide where it wants to go.
Now the A* part begins. Back when the map was loaded, another global object, AStar, used a bunch of special NavMesh SceneObjects to create a grid of navigation points spanning the map. I have a good picture of this in one of my previous blog posts (a picture is worth a thousand subclauses). When the bot decides where to go, it calls one of five functions. They are stopMove(), findPathTo(), sneakUpOn(), findCoverFrom(), and wander(). The AStar object uses variations on the classic a* algorithm, and returns a list of points for the bot to follow. The bots use the original AIPlayer path following code, however keeping track of paths has been moved to c++ for simplicity's sake.
Each time the bot inflicts damage on the player, the bot's fitness score is incremented. This is how the effectiveness of each net is kept track of. When the bot dies, the AIManager::removeBot() function is called. (and a string of passive voice sentences are written) This replaces the lowest scoring net in the gene pool with the bot's net. The gene pool is then saved back to a file.
Read it again, it might make more sense this time.
About the author
#2
03/09/2008 (8:39 pm)
Sounds very interesting Dan. Any plans on releasing it as a demo/resource?
#3
When do you plan on releasing it?
What engine is it for TGE ot TGEA?
03/09/2008 (8:42 pm)
Two things:When do you plan on releasing it?
What engine is it for TGE ot TGEA?
#4
03/09/2008 (9:05 pm)
Well that's fine and dandy with the A*, but does it keep track of moving objects as well? To keep them from running into eachother and what not.
#5
03/09/2008 (9:17 pm)
Sounds tasty.
#6
03/09/2008 (9:53 pm)
I would be very interested in this as well... nice work it seems.
#7
It sounds like you've gone for something very similar to what I did for my undergrad paper. Alas, I found the more I worked with NNs the less amazing I thought they were, especially in a game environment. I always thought I could have coded up the behaviours in a much better way myself - rather then relying on the NNs and GAs to do the learning.
This is not to even mention the fact that NNs are a closed box as far as debugging and getting any useful data back. Random behaviour (even guided with active supervised reinforement learning) is still random behaviour imo... who wants that? You may think random behaviour is awesome etc... but its unpredictable behaviour that you want. NNs simply don't provide this.
However, I have discussed with people before the possibilities of using NNs for complex physics functions in games... a better use imo.
03/10/2008 (6:45 am)
First off, please don't take this as busting your bubble, its just my thoughts as someone who has implemented something similar to yourself.It sounds like you've gone for something very similar to what I did for my undergrad paper. Alas, I found the more I worked with NNs the less amazing I thought they were, especially in a game environment. I always thought I could have coded up the behaviours in a much better way myself - rather then relying on the NNs and GAs to do the learning.
This is not to even mention the fact that NNs are a closed box as far as debugging and getting any useful data back. Random behaviour (even guided with active supervised reinforement learning) is still random behaviour imo... who wants that? You may think random behaviour is awesome etc... but its unpredictable behaviour that you want. NNs simply don't provide this.
However, I have discussed with people before the possibilities of using NNs for complex physics functions in games... a better use imo.
#8
@Morrie It's for tge, though it should port fairly easily.
@Craig One of the reasons I took the approach I did was to make random behavior essentially equivalent to unpredictable behavior. If the NN was connected directly to the bot's movement, a random net would make the bot move, well, randomly. Instead, the a* layer means the bot would charge, stand its ground, run away, etc randomly, and even a "stupid" net could be effective. Also, because these bots have no goals to accomplish besides killing the player, they don't need to be that intelligent. I've been killed several times by bots even immediately after randomizing the gene pool.
03/10/2008 (12:01 pm)
@Andy/Peter I haven't decided yet.@Morrie It's for tge, though it should port fairly easily.
@Craig One of the reasons I took the approach I did was to make random behavior essentially equivalent to unpredictable behavior. If the NN was connected directly to the bot's movement, a random net would make the bot move, well, randomly. Instead, the a* layer means the bot would charge, stand its ground, run away, etc randomly, and even a "stupid" net could be effective. Also, because these bots have no goals to accomplish besides killing the player, they don't need to be that intelligent. I've been killed several times by bots even immediately after randomizing the gene pool.
#9
You say how a "stupid" net could be effective because you use behaviours. Well isn't that just random behaviour selection if the NN isn't actually doing anything of of note that conveys a particular intelligence quality? I could do that with Math.rand()... See what I'm trying to get at here?
03/10/2008 (1:31 pm)
When I put NNs into Torque, I linked their outputs to a state machine. Effectively the NNs were simply making the decision about what behaviour to exhibit. (My states were flee, attack, heal, idle etc - basic behaviours pretty much)You say how a "stupid" net could be effective because you use behaviours. Well isn't that just random behaviour selection if the NN isn't actually doing anything of of note that conveys a particular intelligence quality? I could do that with Math.rand()... See what I'm trying to get at here?
#10
Also, haven't NNs been used successfully in games before?
03/10/2008 (2:20 pm)
The point is that it does eventually learn, and stops being random. The game would ship with a pre-taught gene pool, so the player doesn't have to go through the bots' "stupid" stage.Also, haven't NNs been used successfully in games before?
#11
My point is that the fitness functions often (read: pretty much always) aren't strong enough to give properly decent feedback. Also, there is no set formula for doing a fitness function, so its just guess work. Academic research needs to go into producing a ruleset for doing game orientated fitness functions for them to be at all viable.
Yea NNs have been used in games already - so has all manner of wonky tech :)
03/10/2008 (2:48 pm)
Of course they will learn, I never questioned that fact :PMy point is that the fitness functions often (read: pretty much always) aren't strong enough to give properly decent feedback. Also, there is no set formula for doing a fitness function, so its just guess work. Academic research needs to go into producing a ruleset for doing game orientated fitness functions for them to be at all viable.
Yea NNs have been used in games already - so has all manner of wonky tech :)
#12
How easy will it be to implement new behaviours? Distinguishing teams, using switches and the like?
Also, you seem to have missed Ramen's question. I'd like to know that as well.
03/16/2008 (11:11 am)
Dan, that's incredible. Looking forward to the release.How easy will it be to implement new behaviours? Distinguishing teams, using switches and the like?
Also, you seem to have missed Ramen's question. I'd like to know that as well.
#13
Keep in mind that neural nets are not well suited to accomplishing complex tasks in a game. I'm using them because they provide a simple, yet unpredictable, AI. So if you're making a capture the flag game or something similar, NNs are probably not for you. The a*, on the other hand, can be used in most any situation.
03/22/2008 (9:26 pm)
Well, currently it's been written entirely for a single player game, so it only keeps track of one enemy. Keeping track of an entire team's worth of players may be somewhat difficult. And in answer to Ramen's question, not yet.Keep in mind that neural nets are not well suited to accomplishing complex tasks in a game. I'm using them because they provide a simple, yet unpredictable, AI. So if you're making a capture the flag game or something similar, NNs are probably not for you. The a*, on the other hand, can be used in most any situation.
#14
03/22/2008 (9:32 pm)
I've pathed up Stonghold, and I'll have a video up soon. Here's a screenshot. There are 1207 nodes, the paths built in just under 1/2 second.
#15
03/26/2008 (1:02 pm)
Hm. Would you be willing to release a version of your resource with only the pathfinding so that we may define how our NPCs behave on our own?
#16
04/14/2009 (8:53 am)
Any new updates on how this is going? 
Associate Andy Hawkins
DrewFX