Wednesday, September 14, 2011

Repost: Simplifying Behaviour Tree Logic

Link: http://altdevblogaday.com/2011/09/12/simplifying-behaviour-tree-logic/

I have #AltDevBlogADay in my massive RSS subscription collection. I like to skim over the articles during my lunch break. Sometimes you can find some interesting articles on there that'll teach you something new or get you thinking about something old.

This posting by Jesse Cluff the other day was a good refresher on the finer points of Behavior Trees over [H]FSMs for behavior selection. There was one particular excerpt that I completely agreed with and wants to discuss here.

"So why use behaviour trees at all? Finite state machines are actually pretty decent (useful, well understood, and very importantly, intuitive). They have one critical weakness though. All the transitions between states are explicit."

I think he nailed it with this statement. Given a choice between a tree or a state machine, I'll always side with the tree for just this very simple reason. Most engines I've come across that have taken the route of FSM for behavior selection have suffered from the same basic problem: Inability to scale over time. I hear the argument a lot: "This NPC will be very simple and won't have a lot of state changes, so why go through all the trouble of making a large tree structure for only a few states?" If you're ever asked this, remember the appropriate response. "In 6 months time, he'll have a lot more state changes than you're thinking right now. And your 'quick, easy solution' is going to become so muddled and unmanageable, you'll want to rewrite it again and again."

The behavior FSM tends to make the "state" be the "behavior". Remember, a behavior is basically a form that takes input from the world (perception events, gameplay triggers, timers, etc.) and determines what the best way to handle that new information is. But what is required in the machine's state is also the transition rules. In the case of a behavior FSM, those transition rules are better defines as "the conditions that have an NPC change from one behavior to the next" e.g., the player has moved within 5m of me, so I need to stop attacking and start running away.

This means to satisfy the requirements of a behavior FSM, your behaviors will include the inputs needing attention as well as the conditions to change behavior. Sometimes those conditions will be "what must be true to enter this other behavior." Sometimes those conditions will be "what must be true to enter THIS behavior." But in either case, your behavior will have some set of conditions written into it, as well as a list of all other behaviors it can transition in to or out from.

And that means your behaviors aren't going to be module enough to be reused. So when that NPC starts growing and becoming more complex, you'll be modifying all of your behaviors over and over to incorporate that new complex logic. And that problem is going to scale linearly over time, becoming more and more troublesome.

Further on down the road, when you need to make a new type of that NPC (one that doesn't run away because he's braver, and he doesn't want to take cover because he has thick body armor), you're going to be tempted to reuse most of those behaviors. But to do that, you'll be having a lot of fun putting conditional checks around all those transition links and what-not to stop him from running away and taking cover.

The behavior tree on the other hand? It keeps all the transition logic outside of your "behavior" by rule of thumb, freeing your behaviors to be what they're meant to be: input/output decision lists. Need to make a new one? Just find where best it fits in the tree. No need to touch the other behaviors around it. Need to make a new archetype? Just make a new tree and reuse the behaviors you need, omitting the ones that aren't called for.

The tree is superior when you consider the practical ramifications of typical development cycles.

Kevin

Tuesday, September 13, 2011

Long Week, Some Progress

It's been a long past week at the office. Some of the things we're working on there are pretty darn exciting to me, but also take up a lot of my energy. It's at that level where you keep thinking up new things to do with it, each equally or more awesome sounding than the last, and you start to hate time for being too short. I haven't felt that in awhile; it's good.

It also means that I'm zapped by the time I get home.

I've managed to get a good deal of the Runtime Compile framework integrated into the engine. It's been relatively pain free so far, and I've managed to understand enough of what's going on in its core to figure out how it works and what I can do with it. I'm excited to start using it soon as I get my rendering on and make my tile maps. It's been a really long time since I wrote any graphics code. I feel like I need to kick myself here and (re)learn it all again.

Hopefully I'll be able to post a video soon to show off some progress.

Kevin

Monday, September 5, 2011

Runtime-Compiled C++

Check me out!

It's a framework whose contributors are friends and ex-colleagues of mine from Crytek. It enables you to recompile specially-crafted code in your project without having to restart the executable. It supports crash protection (if the come you compile in crashes, it'll just stop executing that new code and keep the application alive) and the framework itself looks to be quite non-intrusive.

I want to incorporate this from the get-go, because I think it'll prove to be invaluable later on. The promise it gives, is that I can implement my new ideas rapidly without fear of the simulation crashing down if something doesn't go right. This sounds ideal for AI development.

If I can use this to tweak and extend a new behavior, or a new pathfinding system, or a new conditions wrapper, without having to restart the simulation each time or rely on the shoddy edit-and-continue feature of Visual Studio, it seems to me I'll save a lot of time and energy. In the past, I've achieved this same result (to an extent) using external scripting like Lua. But that always came with its own set of problems. If I were to put the behavior of the AI in Lua - that is, catch the inputs from perception and events from the rest of the engine, and determine the next output for the AI, like where to run to or where to look and shoot - then I would need to expose all of these inputs and outputs into the Lua language. It's a lot of extra baggage and it means every time I add something new or change how something works, I also have to deal with the Lua API. If you encounter an error in a Lua script, that Lua chunk stops executing and your simulation might become stale or, worse yet, your application might crash from something not being executed at the right time. You've also got your standard performance issues, your distribution issues and your multi-platform issues. Blech!

But if I could get all the benefits without having to use another language - if I can get the "rapid prototype" benefits of Lua within C++, sitting right next to all of my inputs/outputs/whatever-puts in the Game Engine, so I don't have to do any additional work to get there, then sign me up!

I might have missed it in the documentation somewhere... but one thing I think I'll want to add support for in it is responding to assertions in the same way it handles crashes. It'd be great if, on assert, I can ask it to stop executing that code until the next recompile. Crashes can sometimes happen too late, and a properly-placed assert can help you understand what bad things are about to happen long before it comes to that point. To stop the code from degrading and leading to that future crash, I think, will help me out down the road.

Kevin

Getting started

Hello world!

My name is Kevin, and I'm a Game AI programmer. I started this blog as a means to motivate myself into continuing my research and exploration into newer methods and examples of making strong game AI.

I have worked on AAA titles at large studios including Ubisoft and Crytek, and while I find the challenges there rewarding and practical, I also believe it's important to think about problems which might not be relevant to the "current project". Let's face it, with tight deadlines and well-defined visions, we developers often find ourselves asking those what-if questions when we ponder those problems immediate to the day's tasks. But more often than not, those questions are quickly answered with a "not relevant to this game" or "we don't need to worry, we can just do X and move on."

Those what-if questions are what I like to explore more when I'm not in the studio, because while I might be able to skirt around the problem there, to not explore it further here could mean a lot of new opportunities and tangents missed - those which might result in a clearer understanding of the more-relevant problems or possibly better answers to them. Simply put: To not explore, is to not grow.

OmegAI is my own little sandbox, or at least that's what I want it to become. Truth is, I have only just started working on it today. I want it to be a tight 2D tile-based engine, clean of bloat, and capable of supporting my throwing on top new ideas as I come up with them. I've already got a lot of ideas I have been wanting to try, but there's still a lot of work to be done before I am at that point.

I want to use this blog to share those ideas when they come up. I also want to use this blog as a means of cataloging what work I've put in to OmegAI and these ideas. Who knows, I might even use it to point out something relevant that I stumble upon on them internets and find interesting.

Feel free to join in at any time on the things I share here. And thanks for reading. :)

Kevin