Skip to content

Archive

Category: Behavioral Models

In my previous article, I drafted/debugged basic combat behavior in a game prototype. A simple combat system probably needs to account for the following – if only to get animations to sync correctly if character actions are independent.

  • Impact Frame – unless you have fine grained collision detection, the impact frame determines when the opponent actually gets hit.
    I am adding per action impact frames – each action can define it’s own frame/time offset. I’ll probably use a time offset instead later, but that will be part of a general effort on my part to move from frame based to time based modelling.
  • Passive reaction – When an actor gets hit, they cannot enforce their decisions.
  • Impact condition. A simple model can take into account the orientation relative to, and distance from, the target. My model already has this but that seems a little buggy.

Writing a class to hold impact frames is nothing, although you might legitimately want to store your impact frame information anywhere else:

#import “ImpactFrames.h”

#import “Definitions.h” // used for the ‘act’ typedef which is just a NSString*

@implementation ImpactFrames

static NSMutableDictionary* map;

+(void)putFrameOffset:(int)frameOffset forAction:(act)action{

if (map==nil)map=[[NSMutableDictionary alloc]init];

[map setObject:[NSNumber numberWithInt:frameOffset] forKey:action];

}

+(int)getFrameOffset:(act)forAction{

return [((NSNumber*)[map objectForKey:forAction]) intValue];

}

+(void)clear{

[map release];

map=nil;

}

@end

Passive reactions can be implemented using three tags: action, affect and activity

  • Activity represents the current action.
  • Affect represents a passive reaction to an external action
  • Action represents the actor’s decision.

Then each evaluation can be structured as follows:

  1. If an affect is defined, override action with affect
  2. If the current activity matches the current action, continue.
    1. If we have not reached the last frame for this action, continue to the next frame.
    2. If we have reached the last frame, clear the affect and action labels.
  3. If the current activity is the ‘rip state’ return immediately.
  4. If the current action is different from the current activity, initialize this action.

I had to patch my actions a little because I manage motion and activity independently, so I need to replicate some of the logic.

I have this little character that goes on to hit an NPC for a test, and the NPC dies on the spot. Actually, now that I remember, I have an animation for the death of my NPC. What I want to make happen:

  • The PC getting hit
    • A NPC needs to aim their action at the PC.
    • User interaction needs to be disabled while the PC register the hit.
    • An animation needs to be played.
    • Other effects may be seen, e.g. lose life points
  • The NPC not chasing after the PC while dying (that’s a bug)

I’d rather use my behavior (pseudo)scripting API instead of just hacking away. Conceptually, integrating player decisions with standard NPC scripting fits fairly well, so I should put my code where my brains are.

Design bites back, the dog doesn’t.

My ‘biting dog’ script doesn’t seem to work as well as it used to be. This relates in part to design issues with my framework:

  • In my script, the dog would walk to the player and start to bite. If the player moves away, the dog starts chasing the player. This is correct since the player has moved within ‘chase range’ and is now out of biting range. However, at that point the biting action is not reset and that has reached frame zero. I need to reset an action whenever it completes, so I have introduced a ‘reset’ method.
    >> actions are instantiated too early. The action descriptor should be used to regenerate the action whenever the condition is filled. This will avoid relying on a ‘reset’ call. In other words, the condition should be armed and attached to the agent; the action descriptor should be attached to the condition. When the condition is fulfilled, a new instance of the matching action should be created.
  • I used to rely on the agent having non zero speed to determine the agent state is ‘walking’. This is more than wrong. There are various factors that can affect an agent speed. I have corrected this but forgot to update the action tag in Approach, Avoid, MoveTo, etc…

Let the player be hurt

I add a couple of lignes to my script (in fact i add a player script since I have none) for the player to be ‘hurt’. Hurt is just a label here, but that could bind an animation.

Roughly speaking, things work out pretty good, but not good enough. If the player targets the dog, the PC and NPC end up rushing each other. I can’t clearly see which one hits the other first. The player seems to have the upper hand.

Bite me, Stab me

I still need to ensure two things:

  • I seem to have a targeting problem.
    • There is a limit angle for a hit to register – an actor needs to be facing the target. This shouldn’t, but seems to be a problem.
  • Both the player and the dog should be able to hit each other in turn.
    • As far as I know there is no lag time between hits.
    • Also, I have no ‘impact frame’ defined. In other words, while an actor is acting, the actor is being hit – these are ‘linked states’ rather than an action and reaction.

Whenever I bind a script using my Interpreter, the following occurs:

  1. For each type definition in the script (a type definition is associated with an actor’s label, bindType is invoked.
  2. bindType, creates both a reflex map and an affect map.
  3. Actions are added to the reflex map ; interactions are added to the affect map.

This means that action/interaction couples are grouped per actor in the main transition. Although not ideal, this seems to work fine:

(Aim) player aims action:stab

(AffectAction) player targetted by:hurt

(BasicActorUpdates) resolve Actor player {…} to hurt

(BasicActorUpdates) resolve Actor dog {…} to bite

What the log indicates is that even though the player aims the stab action, it is being hurt, which overrides the player’s intent.

If I look at AffectBinding (which causes both the dog to bite the dust and the player to be hurt when attacked), I see that states are linked at every frame. In retrospect, this is an obviously poor design. It would be way more efficient and probably more useful to just trigger the affect once. But what happens in my case is that the affect triggers over and over until the activity stops.

So in this case, for as long as the dog is in ‘bite state’, the player state is ‘hurt’. Since the dog bites in a loop, the player can never counter-attack, because affects (passive actions) override decisions.

In ActorAffectCondition (a condition that triggers an affect, such as being hurt, when another actor is performing an action on the target), i have a checkAgainst: method. There, I add a condition specifying an ‘impact frame’ for the source action.

The result is immediate (and visually obvious) – instead of  sticking to the ‘hurt state’ the PC now blips between hurt and normal. This also means that the PC can counter-attack.

R.I.P

I still have this problem with the ‘zombie dog’. The death state shouldn’t allow wagging dog tail and biting a victorious player.

In theory I could add a capacity switch to the dog’s stab response. But there’s nothing to check for capacities just yet. Since being dead is a peculiar state, I will hack this once.

Activity change is ultimately driven by an actor’s step: method. This iterates activities and cycles them. For now, this allows all activities to be interrupted.

I could force all activities to complete before another starts. Trying this is interesting but for now, not very effective, causing more inconsistent behavior.

Instead I only force the ‘rip state’ to be sticky.

Oh my. This dog is still running around though. Motion is controlled independently from action… so I wire another hack into my
[BasicActorUpdates updateActorLocation:]
method.

Conclusion

This is my first debugging session on the behavior engine since I wrote the original draft and validated the test suite. Since the test suite was so simple – testing really basic cases – surprises tend to occur.

The design of behavior implementation isn’t good. I’ll make another implementation later I guess, including only current activities in the loop and adopting an event driven approach for higher level behaviors. This will be cleaner, more intuitive, and reduce processing load.

Meanwhile, there are also shortages in action descriptions:

  • An action should at least have a flag (maybe with a default) indicating whether this action can be interrupted. Passive responses shouldn’t be interruptible. Many actions (e.g., walk cycle) work out better if they can be interrupted.
  • An action should specify an impact frame, and whether the effect is punctual or continuous.
  • An action should ideally have a lag time. This could somehow be made integral to impact frames.

At higher level, a working combat model should take into account the following:

  1. The speed of an attack depends on the length of the attack animation. If the attack has an ‘impact frame’ then a longer attack means attacking less often.
  2. If the attack can be interrupted, then the later the impact frame, the easier it is for the player to anticipate the attack.
  3. The speed of recovery determines the ability to counter-attack. If recovery is slow, then the opponent may be able to attack another time.

Tomorrow, I need to:

  • Allow specifying impact frames.
  • Test with actual animations
  • Allow specifying which actions can be interrupted, and which cannot.

At this stage, my Script class includes about 35 statement forms. A lot of this is a little redundant. The aim was to cover many significant cases related to decision, action and interaction.

A test suite

Today I am writing a test suite for the Script class and the underlying APIs. This is necessary because testing production graphs is, as we’ve seen before, expensive.

I won’t get into unit testing using XCode/Cocoa. Instead, I’ll just write my own tests from scratch – this is because I need to invest a little time and effort into making scripts easier to test.

This will be a fairly large test suite (for me, because i’m not terribly fond of testing), however, all cases are very simple and I can take advantage of some design features for the behavior API.

  1. Given model/view separation, I need only setup the model, not the view.
  2. There is no need to setup timers to test behavior, we can just iterate the action graph as far as we need and check the data model to test the output.

Writing the tests

Macros came in really handy to keep the tests concise, but also to avoid having to create ad-hoc data structures. Each test is about 10 lines long and I have 34 tests. Here’s a sample:

// this defines a function and stores the test name
TEST(test_observe_actor,"test observe actor")

// setup a stage and actors, with B 100 units away from A
SETUP_A_B(100);

// the actual script to test
[s type:ACTOR_A];
   [s observe:ACTOR_B];
[s xType];

// bind the script to the stage and iterate the system once.
BIND; STEP(1);

// some code to check the output
Vector* u=[[Vector alloc]initWithX:1 y:0 z:0];
Vector* v=a.orientation;

// this checks the specified condition and prints the result
CHECK([Vector angle:u with:v]<0.01);

// just a macro defining a closing bracket, because a dangling
// bracket would be odd.
END_TEST

Granted this is not a very clean use of macros, and won’t scale up to a really large test suite – but we’re not into ‘test-first’ development here – just running a few tests.

I hacked a function firing up the tests into the main.m file associated with my XCode project. Another time I’ll look into a nicer way of doing all that stuff. (need to move the framework to a library project and create a separate project for the test suite).

Pending, sample output looks like this:

2009-11-16 22:14:55.837 Generic2DBoardGame[5191:207] X test observe actor

(X tells us that the test failed :( )

Debugging

Unsurprisingly (given the way I drafted my code), a lot of tests are failing. I even had to quarantine 6 tests that seem to get into infinite loops. Since I have many tests, this is a good time to add logging. The first logs I want to produce are snapshots of the system showing the state of each entity. I will only display logs for failed tests.

After debugging a couple of test cases, I find that primarily, diagnosis falls into three broad categories:

  • An event isn’t generated. Because events often occur given conditions related to an actor, I print the condition leading to such actor being rejected by TargetSelector
  • An action isn’t being applied. I don’t expect to see this too often.
  • An action isn’t effective.

These broad categories allow me to channel log output and enable/disable channels to conclude the diagnosis.

That’s about it. I’ve got 11 pass cases so I’m about 1/3 in and should be done tomorrow.

In my previous article I’ve suggested a few scripting statements that could be used to specify interactions between actors and the board. In this post I am investigating a solution to bind these statements to the action graph in my underlying API.

Implementing interactions

If you followed the previous articles, you already know how ‘pseudo-scripted’ statements get into the action graph. First an object tree representing the script is created. I have added the following classes to support this:

  • InteractionDef – Defines an interaction as associating an EventDef with several responses.
  • EventDef – Base class for interaction events
    • ActorEventDef – Occurs when an occur performs a specified action.
    • EnvironmentEventDef – Occurs when an actor occupies/enters a given tile
    • CollisionEventDef – Occurs when an actor collides with an actor or prop.
  • ActorResponseDef – A passive reaction (typically rendered as an actor animation)
  • CapacityModifierDef – Used to capture disabling/enabling a capacity
  • RoleModifierDef – Used to capture disabling/enabling a role
  • PropertyModifierDef – Used to capture an increase/decrease to a property

The class hierarchy  could be a little more structured. Also, it seems that we may easily compress the same hierarchy later by considering similar node types. However, this is enough to render the current set of scripting statements provided by the Script class.

The next step is to feed those into the Interpreter class that will actually setup the action graph. I decided to provide ad-hoc classes to render interactions to the action graph. Compared to the design I earlier produced to model actor decisions, classes for interaction are more specialized – many of these nodes just can’t be put anywhere in the graph. This is more a change in the design than something that is specific to the problem being solved, so I guess I would take an opportunity to clean the overall design of the action graph API a little later on – it seems that using more particular types helps keep the action graph small and simplifies some of the design while offering straightforward optimization hooks. On the other hand, large, structured nodes offer less opportunities for reuse.

The class hiearchy for this looks like this:

  • AffectMap – a list of affect bindings, i.e. event/response pairs (subclass of Action)
  • AffectBinding – a list of event/response pairs
  • AffectCondition – specialized subclasses implement interaction events
  • Affect – specialized subclasses realize actor responses.

All reflex maps (see previous articles) should be processed before affect maps. Notwithstanding, it’s OK to use several reflex maps, either for each actor or for the same actor.

Unlike with ReflexMap, I decided to hold an Actor instance directly in the map and pass it to binding, condition and affect method calls, this prevents these ‘support classes’ from implementing Action, but saves memory (no need to store Actor pointers in each node) and  seems, overall, a better choice.

With this, my original draft for a behavior programming API and pseudo scripting interface is complete. Next time I’ll write a small test suite for this and hopefully I’ll then find time to discuss the overall design.

I guess it would take a pretty good physics engine to model Yakuzas saber-fighting on motorcycles.

Interactions

Pending, this post is about interactions.

So far, I have developed a model and APIs for scripting the decisions taken by NPCs in a variety of situations. Interaction is about how such decisions resolve into a physical environment, with effect on other characters. This may include the following:

  1. Motion
  2. Collisions
  3. Getting beaten up, Burned and magically transformed.

Scripting for interaction

I am adding the following statement forms to my Script class.

// interaction events
-(void)on:(tag)actor;
-(void)on:(tag)actor targets:(act)behavior;
-(void)on:(tag)actor targets:(act)behavior with:(tag)item;
-(void)on:(tag)actor performs:(act)behavior;
-(void)on:(tag)actor performs:(act)behavior with:(tag)item;
-(void)colliding:(tag)entity at:(float)speed angle:(float)angle;
-(void)within:(tag)environment;
// interaction modifiers
-(void)unless:(tag)condition;
// interaction result;
-(void)act:(act)passiveActivity;
-(void)as:(tag)role is:(BOOL)enabled;
-(void)become:(tag)type during:(int)duration;
-(void)let:(act)behavior be:(BOOL)enabled;
-(void)let:(act)behavior be:(BOOL)enabled during:(int)duration;
-(void)modify:(tag)property by:(float)amount;

Interaction events: these are straightforward. targets indicates that the event source should be facing the target actor. performs indicates that the source needn’t face the target, just be within a reasonable range.
The unless statement allows excluding a target based on a role, item or status tag.

Interaction results: . act: designates a passive activity that will typically translate to an animation. as:is: allocates/deallocates a role (e.g: “secretary”) ; finally, become: specifies a complete, temporary transformation (‘become superman!’). modify:by: allows incrementing, decrementing one of the actor’s stats.

Discussion

This model is no less arbitrary than the decision model. Games do not often provide complex decision models for NPCs. In contrast, many games provide modally specific interaction and complex stat management. Although I don’t mean to provide a ‘catch-all’ scripting interface, I had a hard time anticipating over interaction resolution. Some comments about this (pseudo-)scripting interface:

  • Given as a high level ‘scripting interface’ it does not specify interaction fully and completely. This is consistent with the intent of the Script class – it is be used ‘with a game designer cap’. How easily behavior can be refined or varied is down to the design of the underlying APIs.
  • As a ‘pseudo-scripting’ class, The Script class can only provide handy, readable shortcuts. The power of a genuine scripting language isn’t to be expected.
  • If I really wanted complex stat manipulations, I’d probably code them directly.

The next step is to see how these will be implemented.

In my previous article, I introduced character actions based on material transfers (giving, stealing, etc…). Some of these actions require detecting and reaching items placed on designated tiles on the game board. Overall, an actor designates the target they want to reach using a tag that may refer either to a tile or another actor. Tags may identify either one or several tiles/actors, so we retrieve the nearest element bearing the specified tag.

Some performance issues

The above design requirement can lead to performance issues:

  • Code within the action graph cannot allocate memory every time it runs. This will choke memory. In this case, I have a method that generates a location vector for a tile and I need to either optimize or avoid this call.
  • On large boards, we definitely cannot iterate all tiles every time. This will kill processing time.

Taken in isolations, these issues are not hard to resolve, but the issues are not isolated: I have a design and an API, and I can’t optimize everything arbitrarily, since the action graph can be modified at runtime.

OK, less identify the memory bottlenecks first.

The problem will usually occur when Board getLocation:(Cell*) is invoked. This call is terribly costly first because it generates a Vector instance every time, secondly because given a Cell pointer, the Board needs to determine the actual row/column for the cell. There is a disappointing and short sighted, yet simple solution if the board size is not too large: store the coordinates in the cell.

Debugging issues

Upon testing material transfer actions, I ran into several bugs:

  • Incorrectly initialized objects – because I use @property a lot and the API calls are receding in the background (versus script calls), I forgot to provide acceptable constructors. Proof that knowing the rules doesn’t dispense from applying them.
  • Boundary conditions:
    • The Approach action that lets a character move towards a location can be programmed in various ways. I ran into a boundary condition there because my character stopped approaching exactly when within range. Upon checking again whether to trigger Approach, the reflex map would trigger Approach again. This would loop forever.
      To fix this, I made conditions over distance more restrictive but…
    • My Pick action will only trigger if the character is right over the target object. Since I had made distance conditions more restrictive, a condition stating dMin=dMax=0 would never be satisfied.
      Wonder why I set such a restrictive threshold? Well, I don’t want to specify a ‘within’ range while scripting picking actions. Later on I’ll have to define a default picking range as part of Actor attributes.

Debugging is getting more expensive as nodes are added to action graphs. To allow efficient debugging, I started logging output, but this won’t be enough. This is because:

  • I use very few classes, and those classes (‘objectified functions’) are versatile.
  • The same methods are executed over and over at a high frequency.

Just adding NSLog statements to trace flow isn’t very practical as it generates a lot of unwanted output.

Typically, questions that need to be answered while debugging are something like:

  • Why a trigger condition doesn’t fire?
  • Why does an action never complete.
  • Why is an action behaving incorrectly.

To improve debugging I’ll need to trace only specified nodes in the action graph. Doing this requires tagging such nodes when creating them. I’m planning on adding this facility to my pseudo-scripting API.

Interacting with tiles and props

In the data model supporting my engine, I distinguish three base classes:

  • Actors
  • Cells – cells are ’tiles’ on the game board. A cell defines a terrain (an id representing grass, sand, etc…), the solid property which determines whether an actor can go through the cell, a prop
  • Props. A prop is an accessory associated with a cell. This could be use for trees, walls, etc…

For performance reasons, it might be better for actors not to interact with cells and props. Also we could work around the problem using actors. I leave this question as an implementation detail. For now, I want to consider a few scenarios which might require interacting with the stage and props:

  • There used to be games based on a ‘color all tiles’ concept. We’d like to change the environment property of a tile given preconditions.
  • Suppose an actor picks a key and uses it to open a door. I would tend to define the door as a prop, and the key as either a prop or an actor.
  • Now suppose an actor activates a lever, this might open a door as well, or change something else

a. Specifying tile and prop interactions

Let’s see how this can be specified using our pseudo-scripting. I add the following methods to the Script class:

-(void)let:(act)reaction when:(tag)kind doing:(act)action within:(float)range has:(tag)object;
-(void)let:(act)reaction when:(tag)kind within:(float)range has:(tag)object;
-(void)let:(act)reaction when:(tag)kind is:(tag)status;
-(void)let:(act)reaction when:(tag)kind within:(float)range is:(tag)status;

This doesn’t require redefining the statement class, all that is new so far is that we can add conditions over the target that triggers an action – carrying an item or bearing a given status. So I extend the Statement class accordingly.

I have added methods checking for statuses and items to the Actor class; the actor class forwards hasStatus:(tag)status and hasItem:(tag)item to the hasTag:(tag)tg method; I don’t allocate new pointers at this point – I’ll be relying on an actor subclass if I need more flexibility.

b. Enabling tile and prop interaction

At this point, scripts using the new statement could work, but they would only work with actors:

  • The Interpreter doesn’t recognize tiles and props as type-binding targets
  • Tiles and props don’t have tags associated to them anyway

I modify Interpreter to allow type-binding using tiles. I won’t define per-tile tags. Each tile has an integer coding the kind of terrain used for the tile. I map these ids to tags in the Board class. Maybe later I can extend board to tag special tiles irrespective of terrain if needed.

The interpreter works by associating a TargetSelector to a Decision. Decision is tightly bound to the Actor class, so I define a BoardDecision class for tiles. TargetSelector only requires an actor’s location, so I updated this class to support tiles as well.

For now BoardDecision only recognizes changes to the solid/empty state of a tile (can actors pass through?) and wholesale changes to terrain type.

Finally, I decide I won’t support prop interactions after all. using an Actor as substitute for a prop is much easier.

Picking, dropping, exchanging and snatching objects

I still don’t have anything to support picking and dropping objects. Maybe a good place to implement picking is within BasicActorUpdates:

  • If an actor is on a tile with a pickable object, they can automatically pick an object with a suitable ‘auto-pick’ tag
  • If an actor is on a tile with a pickable, and they use a PICK action, they will also pick that object.

There are many issues related to picking, dropping, exchanging and snatching (forcefully acquiring) objects. To get started, however, I will only use the following statements, adding them to the script class:

-(void)drop:(tag)item at:(tag)location;
-(void)pick:(tag)item at:(tag)location;
-(void)pick:(tag)item;
-(void)give:(tag)item to:(tag)receiver;
-(void)steal:(tag)kind from:(tag)owner;

This allows a game designer to specify basic flows for goods collection, storage and exchange. Note that all of these are high level actions compared to what I have defined so far – given the opportunity, a character is expected to effect these actions, moving to reach the target when detected.

These actions all break down into a couple of actions, one to reach the target and one to effect the corresponding ‘material transfer’ (give/take/pick an object). This is reflected in my implementation of the new Script methods:

  • Each of the above functions generates an approach: statement as already defined by the Script class.
  • A required/desired item condition is attached to the generated statement.
  • A lower priority statement is added to specify the actual transfer.

I have tied up a quick implementation – for now the items to give/take and the intended target are bound to the target actor as a side effect of selecting target actors/locations. I’ll try to put together something more adequate later, but for now, let’s see if that works.

Minor changes?

There is an incidental difference between picking and other behaviors I worked with so far: the pick methods defined in Script may imply moving to a location defined by a cell on the board.
So far, all my tests only required actors to move towards/away from other actors. However, because I decided to rely on existing script statements to implement picking, the requirement that a target tag may refer either to a tagged tile or a tagged actor propagated.
I’ve stubbornly followed suit, for the simple reason that it was legitimate to want to move towards/away from a given tile rather than a given actor. Meanwhile…

  • I have a serious performance issue to take care of, because cells in the data model don’t define a location vector. I have a method that generates a location vector every time, but it is unthinkable to have this run for many tiles at every frame, as it would kill runtime memory in a very, very short time.
  • While it is legitimate to attempt reaching an item on a tagged tile, the algorithm for selecting a tagged tile containing an item is also a concern.

Next time I might look into this in some detail – probably we can improve all this a lot by defining a base class for tiles and actors; also, I surely need to run a fun script to test picking, giving and snatching.

This is my third post on programming NPC behavior in a prototype game engine. I summarize my design (it’s working!). Next I’ll look into other features I might want to add to the prototype.

Summary of design so far

The Script class provides methods to setup a script using simple, statement-like function calls. The Script class also realizes a data structure for the resulting script. Classes:

  • Script
  • Type
  • ScriptDef
    • Selector
    • Statement

The Interpreter class binds a script to a scene by setting up an action graph

An action graph is defined as a hierarchy of actions and conditions. The root of the action graph (typically a Transition instance) is invoked at every model update. Classes used in the action graph:

  • Action – defines the apply method
    • Transition – a simple action group
      • XTransition – extends transition with the ability to replace an action by an alternative if failed, or a next action if complete.
    • PrepareActors
    • BasicActorUpdates
    • XAction – adds the (BOOL)failed and (BOOL)completed methods along with definition for alternative/subsequent action
      • ReflexMap – Select and apply the first action that matches an associate trigger condition
      • Decision – Base class for actor actions (all decisions define an agent)
        • Do – general purpose action (these actions typically match to a sprite animation)
        • LookAt – make an actor look at another actor
        • Approach – come closer to another actor
        • Follow – come closer to an actor if not within a given threshold
        • Avoid – go away from an actor if within a given threshold
  • Condition – defines the (BOOL)check method; used in reflex maps
    • NearestTarget – evaluate the nearest target given an agent and a list of actors
  • TargetSelector – a complex action used to select one or several actors based on several criteria (min/max distance, actor tags, actor activity)

In my tests, I create an XTransition instance as root (this is invoked by an NSTimer) and add the following nodes in order:

  1. PrepareActors. This is used to clear some actor parameters. In my first tests, the speed vector associated with an actor wasn’t cleared. This would be OK if speed was redefined every time, but some actions (e.g: Avoid) are modifiers rather than setters.
  2. An XTransition node, used to hold actor decisions.
  3. An instance of BasicActorUpdates. BasicActorUpdates allows resolving conflicting decisions; for example, BasicActorUpdates prevents actors from entering a solid cell.

I have tested this design with a simple script and (sure, after some debugging) it works quite well. I’ll try to post a picture or video later on.

Main design features

  • The action graph separates game behavior from game data. It also allows specifying complex behaviors at runtime by adding and removing nodes. At compile time, the action graph requires the data model.
  • Tags are string or integer labels used to identify actors indirectly (used in place of data pointers). This allows defining both class and character specific behaviors.
  • Scripts are defined independently from either the action graph or the scene. Scripts have no compile time dependencies with either the data model or the action graph, so they allow simple, highly portable game behavior definitions – An interpreter is used to bind a script to an actual action graph and scene.
  • Separate decision and action. This is not required by the API, but simplifies behavior programming a lot. First all actors evaluate ‘what they would like to do’, next interactions are resolved locally, taking conflicts into account.

In my the last article I solved a technical issue related to my action graphs. Now I want to make it easier to write powerful scripts for my actors. Let’s recall a fragment from the previous example:

Actor* dog=[[Actor alloc]initWithX:0 y:0 z:0 tag:@"dog"];
OnApproach* inner=[[OnApproach alloc]initWithSource:dog target:@"actor" range:30.0f];
XAction* chase=[[MoveToActor alloc]initMovingActor:dog to:inner.target];
ReflexMap* reflexes=[[ReflexMap alloc]init];
[reflexes add:chase on:inner];

This isn’t practical, for several reasons:

  • The script requires defining an actual actor. Often, we’d like to specify ‘class behavior’ instead – maybe we want to describe general behaviors associated with dogs.
  • All this [[Actor alloc]init… stuff is about OK for framework level stuff. For behavioral scripts, it is too heavy.

What we’d really want to write is something like (check the previous article for reference)

class Dog{
  - bark at actors within 60 metres
  - chase actors within 30 metres
  - bite actors within 1 metre
}

I don’t want to get into the writing/binding of a scripting language and interpreter, but we can surely clean things up and get it closer to the above.

A Script class

I’ve specified a Script class with convenience methods. With these methods added, the ‘guarding dog’ script now looks like:

Script* s=[[Script alloc]init];
//
[s type:DOG];
	[s let:BITE when:HUMAN within:10];
	[s approach: HUMAN whenWithin:30];
	[s let:BARK when:HUMAN within:60];
[s xType];

type: and xType: tell the script to start/end a class declaration. The other functions are meant to be translated into an action graph. The next step is to decide how this is done. In the meantime, this is worth a couple of comments:

  • We no longer need defining a dog actor. The DOG tag allows us to define class behavior. This doesn’t prevent us from defining ad-hoc behaviors for a given actor (use the tag only once). I hope that tags will prove very useful helpers in writing concise, yet powerful scripts.
  • Script methods are somewhat ad-hoc. However, this is probably OK. I’m trying to simplify 90% of scripting. Having said that, the current script spec still leaves out a lot of the (structural/modal) complexity of the action graph.

Implementing the script class

So far my script class is just semantics. We want to do something with our semantics:

  • We want to store the script in some form.
  • We want to be able to make a script current on a given ‘stage’. In other words, when we attach the script to a scene or virtual worlds, we want our actors to behave according to that script.

The classes provided by my framework so far are not meant to describe a script. They are normally instantiated as runtime nodes in an action graph. At best, I can use them as prototypes, and this is exactly what I tried first. Here is a sample function implementation:

-(void)observe:(tag)kind{
	NearestActor* condition=[[NearestActor alloc]initWithAgent:proto target:kind];
	XAction* response=[[LookAt alloc]initWithAgent:proto lookingAt:cond.target];
	[map add:response on:condition];
}

This can potentially get very untidy. Say we had an actor with a given tag. we’d have to go back and check for all rules including an instance of the same-tag prototype and duplicate the rule so it becomes applicable to that actor. The reason why this process can become messy is that neither conditions nor actions enforce the object paradigm. The Script class, on the other hand, ‘starts with typing’. it assumes an actor as the context of any script statement.

As a result, it is probably wiser to define separate constructs used to store behavior definitions associated with a tagged actor. This is simply because doing ensures we track typing information at least until we actually bind a runtime actor to its behavior. Given an on-stage actor, we can then collect definitions for each of the actor’s tags and generate reflex maps accordingly. At this point, none of the reflex maps will be associated to the actors per se, which may cause other problems. This, however, goes down to a little of book-keeping.

Script structure – draft

I used the following classes to store script nodes:

  • Type - A type is associated with a tag name and (for now), a top level selector.
  • ScriptDef – ScriptDef is the base type for script elements, in this case ‘selector’ and ‘statement’
  • Selector – A selector specifies a ReflexMap (should be explained in my previous article)
  • Statement - For now, my statements are structured, conditional statements. Probably I need to break this down later, but for now this form should cover most of the interactions I wish to describe using the scripts.

Interpreter

I then defined an Interpreter class responsible for setting up a script given a Stage and Transition. This reads the script; for each class, I retrieve the actors bearing the matching tag name and build an action graph based on the class definition. I muddled a little more with my existing Condition and Action subclasses, but somehow managed to tie everything together.

Next time I’ll do some testing to check how everything works, and try to summarize things a little.

Recently, I started designing a game engine with a few simple goals in mind:

  1. My game engine is concerned with managing a virtual world model. It is not an animation engine or a sequence/transition manager.
  2. Although I developed a rough and ready animation API, the model must be separate from that API. This is so that I can later rewrite view using a 3D engine or whatever else.

I’ve been muddling a little bit with behavior programming. After ‘going dark’ for a few days I’ve decided to share my experience as I believe this could illuminate some design points.

Behavior programming – a draft API

A few important classes from the draft API:

  • Condition – a condition is an object that returns a boolean value, so it has a (BOOL)check method.
  • Action – an action is an object with a (void)apply method.
  • ReflexMap – an action mapping a trigger (a Condition) to a response (an Action). When apply is invoked, the reflex map iterates the current action, if any. If no action is currently selected, ReflexMap iterates its list of triggers. The first satisfied trigger condition causes it’s response to become current.

As you can see, the API is based on objectified functions. This is a choice, maybe I’ll get a chance to explain why another time.

An example

Here is sample code used to program simple behavior for a guarding dog:

Actor* dog=[[Actor alloc]initWithX:0 y:0 z:0 tag:@"dog"];
//
Condition* outer=[[OnApproach alloc]initWithSource:dog target:@"human" range:60.0f];
Condition* inner=[[OnApproach alloc]initWithSource:dog target:@"human" range:30.0f];
Condition* close=[[OnApproach alloc]initWithSource:dog target:@"human" range:10.0f];
//
XAction* bark=[[ActorGesture alloc]initWithActor:dog action:BARK];
XAction* bite=[[ActorGesture alloc]initWithActor:dog action:BITE];
XAction* chase=[[MoveTo alloc]initMovingActor:dog to:nil];
//
ReflexMap* reflexes=[[ReflexMap alloc]init];
[reflexes add:bite on:close];
[reflexes add:chase on:inner];
[reflexes add:bark on:outer];

The @”foo” bits just represent strings (in case you’re not too familiar with Objective C). I use those strings as tags, so the @”human” tag allows OnApproach to only target human intruders (not rabbits, leprechauns and so forth…)

As illustrated by this code, although the idea of defining rules using conditions and actions seems OK at first, the design isn’t. The problem is that a Condition (in this case OnApproach) can be fulfilled in various ways. When the condition is fulfilled, we need to pass data to the action to effect it correctly. So in this case onApproach is fulfilled with a given target, but MoveTo cannot bind the target detected by OnApproach. Let’s try to fix this.

Binding precondition output to postcondition input – 1: using arbitrary parameters

My first impression is that the OnApproach condition should be replaced by an event call processed by a listener, so we’d have something like:

on:(Actor*)actor approaches:(Actor*) agent{...}

The downside is that doing this will require that we re-implement the event handling function for every trigger/response pair. We don’t want to write a new class for that every time. We do need to bind the output of the trigger to the input of the matching action. We might then change change the Action and Condition classes:

@interface Condition
-(BOOL)check; // unchanged. we need to return a boolean value anyway.
-(NSArray*)result; // OnApproach will place a reference to the approaching actor in this array.
@end

@interface Action
-(void)apply:(NSArray*)param // allows injecting data into the triggered action.
@end

We still need to tell ReflexMap which parameters to inject into MoveTo. So we change a function in ReflexMap:

add:(Action*)action on:(Condition*)trigger;

Now becomes:

add:(Action*)action with:(int[])params on:(Condition*)trigger;

And our reflexes are then setup as follows:

int[] args={0}; // addresses the only output from OnApproach
[reflexes add:bite with:args on:close];
[reflexes add:chase with:args on:inner];
[reflexes add:bark with:args on:outer];

This solves the problem, but lacks clarity because conditions and actions now return/accept arrays of arbitrary length and content.

Binding precondition output to postcondition input – 2: sharing placeholders

The next idea is as follows: what if we could arrange that whatever target defined by OnApproach is addressable using MoveTo?
To do this requires modifying both OnApproach and MoveTo. On the bright side, we needn’t change the Action and Condition signatures:

  1. I defined an ActorRef class. ActorRef owns an Actor property (I could use a pointer to pointer, but that would be a little confusing).
  2. OnApproach defines targetRef as an ActorRef instance. When a target is detected, targetRef.actor is assigned.
  3. MoveTo also defines a targetRef property. When MoveTo is initialized, we pass the ActorRef instance owned by our OnApproach condition.

We can now change the original example as follows (skipping the bits that didn’t change):

OnApproach* inner=[[OnApproach alloc]initWithSource:dog target:@"actor" range:30.0f];
XAction* chase=[[MoveToActor alloc]initMovingActor:dog to:inner.target];

This seems to solve our problem nicely:

  • Type safety isn’t compromised.
  • The core interfaces remain simple
  • The binding process doesn’t involve ReflexMap
  • The code used to define our test script is simple and readable, compared to solution (1).

OK, enough for today then. Next time I’ll look into a way to further simplify behavioral scripts, as they are still a little heavy.