Character Simulation with ScriptX

A general-purpose framework for dynamic behavior
By Assaf Reznik

Dr. Dobb's Journal
Software Tools for the Professional Programmer
Volume 19, Issue 13, November 1994

Filmore T. Goose is an obsessively neat bird, the Felix Unger of the animal world, who continuously picks up after fellow animals. Irma La Sheep is a good-hearted quadruped, always ready to knit a sweater as a gift for any animal that happens to pass by. These are two of the characters in Playfarm, a prototype multimedia title for children implemented in ScriptX from Kaleida Labs. ScriptX is a platform-independent, object-oriented development environment for creating multimedia applications (see the accompanying text box, "Introducing ScriptX").

Playfarm consists of a virtual farm environment populated with some interesting and truly autonomous characters:


A scene from Playfarm. The hand at the bottom of the image is an intelligent-cursor object, part of the user-representation component.

Every character in the Playfarm simulation runs in its own thread and can interact with the other characters in a rich, complex, and unpredictable manner. Playfarm's open-ended structure allows you to dynamically add new characters into the environment while it is running. This article describes the architecture and implementation of Playfarm, which was designed in a general-purpose way so that its design and code can be reused in creating other simulations.

Design Goals

Many current multimedia titles are structured as a series of film clips stitched together in a static way that sometimes provides a convincing illusion of dynamic behavior - even though every sequence has been planned, scripted, and burned into code before execution time. However, we wanted our characters to interact in a more dynamic fashion, rather than in the usual, predetermined way. At each clock tick, characters sense, process, and react to their surroundings. Their reaction depends both on the state of the environment at a given time, and on their own internal state. This free-form response is implemented by a character's "activity engine," discussed later.

A related design goal was to create characters with real personalities which reveal themselves over time. Behavior must be continuous rather than "action bites." Characters respond to the environment and to the user. When Playfarm props and characters are added, removed, or otherwise changed, characters must continue to interact with their surroundings in a manner consistent with their personalities.

Another goal was to make the environment extensible, creating an experience that users could modify and control, if so inclined. An additional goal was to create a general-purpose, content-independent model for character-based simulation. By creating an abstract model that is decoupled from the presentation layer, this model can be used in other contexts. Our approach is guided by the well-known Model-View-Controller (MVC) paradigm, first used in Smalltalk-80 and now incorporated into modern class libraries, including ScriptX.

Playfarm Architecture

The Playfarm architecture consists of three components: simulation environment, character and prop, and user representation. The user-representation component is the simplest, consisting of a geometric space equal in size to the screen display, and an intelligent cursor object.

The simulation-environment component is a general-purpose framework for a character-based simulation. It is divided into the presentation space and the model space.


The layered structure of the Playfarm project distinguishes the modeling space from the presentation space.

The presentation space is roughly analogous to the View portion of the Smalltalk MVC. It contains all visible objects (except for the cursor, which belongs to the user-representation component), as well as objects that manipulate the visual aspects of the environment - such as the Panner object, which is responsible for panning or moving around the model space. Only the section of the farm that is visible at a given time is part of the presentation space. By contrast, the model space covers the whole span of the virtual farm.

The character-and-prop component is, as you might expect, the set of classes used to instantiate characters and props. Props are inanimate objects in the space, while characters have animated behavior. The next figure illustrates the structure of a Playfarm character, which consists of an animation object and a character shell.


The structure of a Playfarm character, which spans both modelingand presentation spaces.

The animation object contains all the raw animation sequences for a character. Each of these sequences is named so that the model object can refer to it. Whenever the model makes an animation request, the name of the requested animation sequence is added to the animation queue and then played in order.

The character shell contains the character's repertoire of activities and the logic describing the relationships between this set of activities and factors such as time and mood (which in itself has a relationship to the environment and to other characters). These relationships are modeled in the character's "activity engine." Excerpts from the Playfarm source code are shown in this listing.

The Model Space

Playfarm maps the virtual farm onto a simulation-modeling space. This is a geometric mapping. It consists of a web of triangles whose nodes form a series of equilateral triangles:
From each node, a character can potentially move in six directions: right, down/right, down/left, left, up/left, and up/right.

This kind of grid has several advantages over other designs. Unlike a traditional rectangular grid (with four directions of movement), it is not immediately obvious to the user that characters can only move in six possible directions. Also, unlike a three-dimensional model, the flat structure of the Playfarm grid significantly reduces the complexity of character-animation sequences:There are only two basic character orientations (left- and right-facing), obviating the need for four views of a character (back, front, left, right).

Although the farm is presented on a vertical (visually upright) screen from a frontal view point, the mapping is of an aerial view. Each row, then, represents a different z-plane in the virtual farm. This is translated to the presentation layer by setting the z instance variables of corresponding presenters in each row to reflect the depth. Thus we can achieve a two-and-a-half-dimensional perspective.

The class Grid consists of nodes which are instances of class Cell (both are available electronically). It contains the behavior objects of Playfarm - that is, prop shells and character shells. The Cell class, among other things, implements a basic set of services for moving from one cell to any of its six neighbors, for adding and deleting objects from a cell, and for accessing a cell's contents and that of its neighboring cells.

The Character Component

In designing a Playfarm character, the first step is creating some animation sequences. You can do this with tools such as Macromedia Director. Cells created with Director must be converted to ScriptX's internal object representation, Bento, using ScriptX's import facility. Bento is an object-storage format designed at Apple and used in other software systems such as OpenDoc and the Taligent Frameworks. There is a Bento file associated with each character.

Next, these individual frames must be combined into a meaningful, named sequence. The Playfarm function makeFrameSequence takes a Bento file, a direction in which a character moves while performing the animation, the number of nodes to move (0 or 1 in Playfarm), the bitmaps associated with that sequence, and an optional sound argument. This function returns a FrameSequence object that, when properly attached to a movement controller, animates through all cells of the animation and smoothly moves the character in the specified direction at a velocity synchronized with the flip animation. All sequences in Playfarm are created such that they either start and end at the same location or move the animated object exactly one cell. For more details on the Animation class, see the source code listing.

In the class SheepPresenter For Irma, sequences are defined for walking, facing, turning (all these in the six possible directions), as well as for knitting, eating, complaining, and so on. Then the animation object is told what the transition sequences are - in this case, turnLeft and turnRight - and which sequences a character can go to from each transition. The model-character object is unaware of the names of transition sequences; they are abstracted from the behavior layer. Thus, when the behavior component of the character decides to walk left when the character is walking right, the animation object contains the logic that a transition sequence needs to be played before the character can walk left. This completes the presentation aspect of the character.

Character Behavior

The class Shell is included in the source code listing, along with its derivative CharacterShell. Together, these two encapsulate the behavior aspect of a character: attributes, current state, direction, relation to the model grid, and relation to a target object with which it might be choreographed. The Shell class and its subclasses provide services for keeping model shells and shell presenters in sync with each other. The Shell class also provides services for adding new shells, with their presenters, to the scene. To create the behavior shell for Irma, we must inherit from CharacterShell. This wires the sheep object to the model space and to its presenter.

The meat, if you will, of the sheep shell is its logical actions. The sheep presenter's animation sequences are combined into actions that make sense by defining character actions in the init method for the Sheep object. These actions rely on action classes, which provide the general building blocks for creating meaningful activities for characters. Each action instance is one atomic thing a character does, such as "walk left" or "knit a sweater." Combining these atomic actions into sequences and branches results in interesting behaviors and coherent interactions between objects in the system. Actions are the wires that connect a character's activity engine with the model space and the animation class.

Actions are combined together by two mechanisms: nested actions and action sequences. In the simplest case, an action simply corresponds to one animation sequence. The animation object (sheep presenter) is referred to by the targetAction instance variable of the Action object. But targetAction can also refer to a nested action or action sequence. In this case, when the action is invoked, it will invoke yet another action after doing some processing of its own. Sequencing several actions together in a meaningful context creates an activity.

Irma possesses a wander-around activity, a knit-sweater activity, and a complain activity. complainActivity, for example, is a sequence of three wander actions and a random action, which is nested. Sixty percent of time that complainActivity is invoked, it calls complainAction (the rest of the time it returns with no effect).

The Character Engine

The last step in creating a character is to install character activities into the activity engine. The approach described here was influenced by Patie Maes' work at MIT Media Lab. The character engine is the main driver of the character component. Character-level decisions define the overall expression of characters. For example, a character engine may make a character decide to sleep when it is tired, clean when it is anxious, and knit sweaters when it encounters a friend. The character engine understands schedules (biological clocks), moods, and the character's sense of the environment. It weighs all these different values and makes changes in its activities as needed.

The character engine models a character's traits and then influences the choice of activities that the character undertakes over time. Each activity is associated with a weight. This weight changes dynamically through the experience. Weights are influenced by factors such as the schedule, motivations, and reactions to the environment. For example, consider a dog character whose activity repertoire consists of eat, sleep, run around, and chase a cat. Each of these activities is assigned an initial weight. Then, if the dog is old and lazy, we create a schedule that favors sleeping and eating activities most hours of the day (or what ever time unit is abstracted by the schedule). The tiredness motivation goes up each time the character ends up running or chasing a cat.

Schedules link the weights of certain behaviors to the passage of time. One example is a 24-hour clock that influences a different behavior for each hour of the day. In our dog example, the schedule may read: eat at 8 a.m., run around at 9, chase cat at 10, eat at 11, and so on. Possible patterns can be seasonal, circadian, or simple rhythms. At each cycle of the activity engine, appropriate activity for that time is selected. Then, that weight associated with it is added to the active weight for that activity.

Strengtheners can strengthen an activity or a schedule. For example, if the dog chases a cat whenever one is in the vicinity, we can use a strengthener on the schedule to check if the current activity is the scheduled one. If it isn't, the strengthener will magnify the scheduled activity even more, thereby enforcing the schedule.

Motivations are externally attached personality factors of the character. This is how a character can pick up the feeling of a locale or experience. Motivations are usually modeled as range values that can be lowered or raised - for example, our dog has a loner motivation, which is triggered by a sensor of too_much_commotion around our dog. Whenever the sensor reads that the dog is not alone (that is, other characters are around), the loner motivation is pushed higher. In this way the dog might choose behavior based on its desire to be alone.

Reactions are sets of activities triggered directly by events (signaled by sensors). This allows characters to be unconditionally responsive to events no matter what their mood is at the time; for example, when hit by lightning.

Importing a Character

One of Playfarm's most attractive features is that it allows end users to import characters and props at run time. After a user spends some time with Playfarm, it can evolve into the user's own creation.

The process of developing and importing an independent character is very simple: You must provide the system with the presenter definition, the character-shell definition, and the object store containing the media. These steps have all been described here. The only remaining steps are to put the new character in its initial location, add it to the scene, and start its activity engine.


Assaf is a senior multimedia designer at Kaleida Labs, where he developed Playfarm with Lisa Lopuck, Mike Powers, and other engineers. He can be contacted at assaf@well.com.


Copyright 1994 Assaf Reznik, assaf@well.sf.ca.us
Reprinted by permission. For more information contact: Dr Dobbs Journal, 411 Borel Avenue Suite 100, San Mateo CA 94402
Playfarm code: Copyright 1994 Kaleida Labs, Inc.