Action Lists

Action lists are one of my favorite data structures for games. But from what I’ve found, they don’t seem to be very well known outside of the small community of students at DigiPen Institute of Technology. An action list is simply a list of actions which updates sequentially. An action can block the action behind it, preventing others to run until it’s finished, or can run concurrently. This provides a simple yet powerful interface that can be used for AI, animation, UI, and many other things.

Here is a quick demo using the Unity3D Web Player to give you an idea of what action lists can achieve:

Everything the green guy does is done using action lists; each rotation and movement is an action.

Actions

As you can see, actions can take many different forms. They could scale an object, change its color, play a sound, call a function – really they can be almost anything. So let’s start out by looking at an example action interface:

The action interface is really simple. It just provides a few virtual methods that can be overloaded and a bunch of booleans that denote the status of the action. When an action’s _finished bool is set, it will get shutdown by the action list and removed from the list. If an action is blocking, actions behind it in the sequence won’t be able to run until the blocking action is finished. And if an action is paused, it won’t update. Actions lists can be simplified without a pausing functionality but it can be nice to have.

Action List

As the name implies, the action_list is a list of actions. It maintains ownership of the actions and handles calling their startup, update, and shutdown methods.

Here, I’m using a std::list for the underlying container of the action_list. But this could easily be a std::vector or an array, just keep in mind that actions tend to run in a first in, first out fashion.

You may have noticed that action_list inherits from action. This is the composite design pattern, and allows an entire action_list to be added to another action_list as an action.

Most of the methods of action_list are fairly clear and are mostly dependent on the underlying choice of container. So, we’ll just look at the details of the update function:

The update iterates over the list of actions and updates them until the end of the list or until a blocking action is reached. It also handles calling the startup and shutdown methods when necessary.

Action Lanes

You’ll often want to have multiple actions operating on an object that don’t necessarily relate to each other. But doing so can sometimes be impossible when you want two sequences of blocking actions to run concurrently. You could have multiple action lists each running separate sequences, but it makes more sense to have one action list for one object. To do this, we can add the concept of lanes to action_list. A lane is essentially a self-contained action list that gets updated separately from the other lanes. This can be easily achieved by creating a new container made up of multiple action_containers from before. Here’s an action_list interface using a std::map as the container of action_containers:

This is mostly the same as before, with the added option of providing a lane_id when adding an action to the list, and of course, the lane_map. The update function is also very similar as before, but now iterating over each lane then each action within the lane.

Action lanes are of course not absolutely necessary but do provide a a bit of cleanliness when working with many actions. You could define a lane as the animation lane while having a separate lane maintain the actual behavior of the object. This provides a very clear and clean way of maintaining actions that don’t necessarily relate to each other.

Summary

As you can see, action lists are fairly simple to implement, but they provide a lot of power. They give an interface for behaviors to get updated in a given sequence or concurrently. They also allow a lot of flexibility and can be used for a wide range of different tasks.

» Read Part 2