As I said last time, I modified the test to include a basic hunger clock: Heroes start with 100 rations, rations can be bought at the rate of 10/coin and they are consumed at the rate of 1/tile (diagonals cost 1.41). I added the appropriate bits in the behavior tree, added some utility-specific stuff and voila, the heroes actually survive for a while using utility AI + behavior trees!

... but the heroes keep starving! That is a sign of an AI bug, such as a badly programmed curve/input. But I'm not completely to blame (obviously): Using the normalized inputs for the utility AI, it is quite awkward to combine them. For example:

  • MyRations input: Min/Max I care about: [0, 30]
  • MyDistanceToClosestCity input: Min/Max I care about: [0, 15]

Now if I want to figure the ratio: rations/city_distance, all I have at this point are these normalized values. The ratio of the normalized values is useless, unless I de-normalize it using the parameters, but that's just redundant. So what now?

One answer (that I'm pursuing) is to do the following:

  • Keep in the blackboard both normalized (for utility AI) and unnormalized values (for general use)
  • Have generic calculators for blackboard variables
  • Have dependency graphs for blackboard variables
    • e.g. MyRationsNormalized depends on MyRations
    • e.g. MyRationsOverCityDistance depends on MyRations and MyDistanceToClosestCity

Now when utility AI requests MyRationsOverCityDistance, then we'll try to grab MyRations and MyDistanceToClosestCity from the blackboard. If they are not there, then they will be calculated -- if during those calculations they need any more variables, the chain goes on. The dependency graph comes into play because the blackboard variables have a dirty flag: we don't want to calculate a variable every time we want to access its value. Now when we update a variable, using the dependency graph, we mark all the dependents dirty, and that's it!

Decisions and Instancing

Utility AI can be used to select for example:

  • Go shopping?
  • Go dungeoneering?
  • Go heal?
  • Go for groceries?

but it can also be used for:

  • Go to dungeon_0?
  • Go to dungeon_1?
  • ...
  • Go to dungeon_N?

So, the above is fairly dynamic in terms of the dungeons to compare. So it seems reasonable to abstract:

for( auto& d : dungeons)
{
	blackboard.set("current_dungeon", d);
	scores.push_back( choice.eval() );
}
auto iChoice = select_best_score( scores );

In the above, the considerations will always use the "current_dungeon" property to evaluate the inputs that will pass through the response curves. This is the point where this functionality plays well with the dependency graph! Every time the current_dungeon property is changed, the whole blackboard variable subgraph that depends on that variable needs to be marked as dirty.