Golem Agent Language

From Oldunreal-Wiki
Jump to navigation Jump to search

The Golem Agent Language (GAL) is a fairly simple text-based scripting language that is used by the Agent objects in Golem Studio of Unreal II. It is required in order to create functional Golem Entity Script Agent objects.


Agent overview

An Entity Script Agent is an object that acts as an intermediary between an entity and its Entity Scripts objects. Note that GAL scripts should not be confused with Entity Scripts (the former controls Entity Script Agent object logic, the latter define a static set of animations). The name "Agent" stems from the fact that Entity Script Agents are a higher level of abstraction than Entity Scripts, and entities normally access scripts through agents, and not directly.

While agents are entirely optional for having a working entity, agents make animating entities a lot easier. They dynamically combine different static scripts to a whole dynamic set of animations. Using Golem Studio you can easily see the difference between using entity scripts directly and using agents by enabling and disabling Agent mode in the Render window.

Agents vs Entity Scripts

There are numerous advantages of using agents as opposed to entity scripts, and especially pure animations, directly. As a comparison, games like Unreal Tournament 2004 use pure animations for all the animation needs. Compared to that system, Entity Scripts are a low more powerful, as they not only play preset animations, but also allow you to combine these pure animations with dynamically generated animations, such as surface walkers, vertex explosion effects, lipsync curves, etc. (see Abilities for a complete list), and combine several of these static or generated animations into one, with specifically set timings and external events, if needed. That alone makes the system a lot more powerful than pure animation-based solutions. However, entity scripts are still inherently static - when replayed, they will look the same every time.

Agents, on the other hand, add even more power to the system by allowing to control several different scripts at the same time with added randomness, conditions, etc. With both pure animation and Entity script systems, making an entity that can walk to all eight sides would require you to create 8 different animations/scripts. If you want to have four weapon carrying poses? You have to make 32 animations/scripts. And making the entity blink naturally during those animations would be outright impossible. Agents, however, allow you to do all of this with relative ease. Plus, agents allow for defining random events - such as playing one of the different idle animations. In pure animation solutions, that functionality was put on the shoulders of UnrealScript coders, while with Agents it can be defined by animators themselves. And that's not all - agents also allow fine control over how different scripts interact with each other under different circumstances, such as blending two animations with each other if the first animation has played over 50% of its animation.

Overall agents allow for a much easier and more precise control over how an entity should animate, without even having to interact with the game logic itself. That in itself allows for more productive separation of work - coders are no longer required to bother with all this animation business, while animators get all the control they need.

Language complexity

While the notion of needing to learn a programming language just to animate an entity can seem daunting, it is not that difficult in terms of GAL scripting. GAL is centred around a single task, instead of being multi-purpose, therefore the overall complexity is reduced. And it sure is a lot less difficult than other artist-centred languages, like 3DS MAX's MAXScript or Blender's Python. As a matter of fact, looking over an example GAL script even without knowing the specifics should make it clear how the language works.

Text vs graphical interfaces

While Golem Studio was generally created as an abstraction that allows for a more intuitive way of handling animations than working with code directly (once again, like it is the case with games that depend on pure animations), it may come as a surprise why GAL was not made into a visual tool and instead kept in text form. As a matter of fact, it was attempted to make it visual, but it was deemed too impractical and subsequently the idea was dropped. The GAL language itself was created specifically for this task. The possible complexity of input with GAL scripts is unlimited, while with a graphical tool the system would get cluttered and confusing very fast, unless it was artificially limited. In the end, this question boils down to the text versus graphical programming debate, and for this purpose a text-based approach proved to work better.

Import process

Importing GAL script files to Entity Script Agent objects is done via the context menu in Golem Studio. See the corresponding page for more information about that.

During the import process, Golem Studio checks whether the GAL script in question are syntactically and semantically valid. You cannot import invalid files. In case of an error, it will be printed in the log window.

Note that these checks do not guard against logical errors, so you should thoroughly check your scripts for any mistakes. The test section can be used to ease this process. The Immediate action box in Golem Studio also allows for quick debugging of small script parts. Testing more inside Golem Studio allows for less time needed for the reimporting routine.

File structure

GAL script files are divided into several different sections - channels, inputs, one or more action, optional transition and optional test. A section is a logical block for doing a specific task.

GAL uses keywords, symbols, identifiers, numbers and "strings". Identifiers (user-defined names) are case-insensitive, but must start with either a letter or an underscore, and the rest of the identifier should be comprised of digits, letters and underscores only.

Whitespace is ignored. The symbols // are used for writing comments - anything on the line after them will be ignored. /* and */ are used to indicate block comments – everything in between them will be ignored.

Numbers can be in different forms - integer numbers (0, 1, 2), floating-point numbers (0.1, 1.6, 2.85) or boolean values (0 or 1). If a float is expected, an integer is accepted as well (it's typecast automatically). If a boolean is expected, only 0 (for false) and 1 (for true) are accepted.

Channels section


The first section is called channels. This section identifies the names and the order of the entity script processes used by the agent. Each agent must have at least one defined, and the order is important. Usually channels are used to isolate animations that don't interfere with other animations (so that the entity could blink while walking, for instance). Once defined, the channels can be seen in Golem Studio under the Processes tab, albeit with numbers instead of names.

After defining them, these channel names can then be used in set and force blocks (see below).



Inputs section

    inputname = inputvalue, inputvalue, ...;
    .localinputname = inputvalue, inputvalue, ...;
    inputname = inputvalue "stringalias", inputvalue "stringalias",  ...;
    .localinputname = inputvalue "stringalias", inputvalue "stringalias",  ...;

The inputs section is in essence the "variables" block of GAL scripts. You can define any number of inputs here. Inputs are in essence arrays of identifiers. Any inputs defined here will be shown in the Inputs subtab in the Agent tab in Golem Studio. Each input must have one name, and can have an unlimited amount of values. The order of the values matters in that the order will be presented as-is in Golem Studio, and the first value will be set by default.

Each value can have an optional string alias. They are defined just after the value (and enclosed in quotes, as it is a string and not an identifier). It is used by str blocks; see below.

As mentioned, inputs act very similarly to variables. Just like variables, they can be global or local. In case of GAL, there is an important distinction – global inputs can be written to only by the user in Golem Studio or the game logic, but not by anything within GAL scripts themselves (except for the test block, see below). Local inputs are the opposite, they cannot be written by the game logic, but can be written by assigning them within GAL scripts. As for reading them, both local and global inputs can be read anywhere at any time.

To define an input as local, you need to prefix its name with a period character (.). If there is no period character before an input name, it is automatically defined as a global input. Note that the period character is not part of the input name, you don't need to enter it if you are checking the value of local inputs.

Note that the inputs section is not strictly required for agents that really don't need any inputs.


    Speed = Walk, Run;                                             //Global (external) input, "Walk" is default
    Mood = Good, Bad, Ugly;                                        //Global (external) input
    .Flavor = Chocolate "Choc", Vanilla "Vanl", Strawberry "Swbr"; //Local (internal) input with string aliases
    .Stooge = Larry, Curly, Moe, Shemp;                            //Local (internal) input, access it by name "Stooge" and not ".Stooge"

Action sections

action actionname

The action sections are similar to the concept of functions in other languages, but, as with inputs, they are also slightly different. In these sections you can set what scripts are to be executed at what time, depending on the conditions defined. In Golem Studio, you can call individual actions by double-clicking on them in the Actions sub-tab of the Agent tab.

The amount of actions defined depend on the creator of the script, as they are optional. Except for one, called Default – it is the equivalent of a main function in C-like languages. That is the action that gets executed on every tick that the entity spends in Agent mode. All other actions are either called from this Default action, or called manually from the game or Golem Studio.

Much like you can (and often should) have utility functions to help keep code clean in other programming languages, you can have "utility actions" in GAL. However, make sure to name them appropriately, because all of the defined actions appear in Golem Studio as-is – you can't set an action to be local, and you don't want users trying to play utility actions by themselves, after all.

Actions contain a set of statements. They are similar to statements in C, as in they are delimited by either a semicolon or between curly braces. The possible statements are explained below.

Channel statements

set/force (optional int bindinglevel) channelname statement;
set/force (optional int bindinglevel) channelname { statement; statement; ... }

The set and force statements are one of the main statements used within action sections. They define which channel to apply changes to. You need to have most of the other code within one of these statements in order for them to be functional (otherwise it wouldn't be clear which channel you want the animations to be played or affected). These statements also have an optional integer parameter that defines a binding level. It can be zero or greater, and has to be enclosed in parentheses.

Code within these channel statements is not always executed, however. This is where binding levels come into play. A channel can either be bound (executing a script) or unbound (empty). If a channel is bound, then it also has a time for which it will remain bound (typically equal to the script length), and a binding level. If the binding time is zero, it will become unbound on the next tick.

When a channel is bound, generally any code within channel statements will not be executed (there is already an animation playing, after all, and you might not want to interrupt it). But GAL does allow interrupting them, if need arises. The set statement will override bound channels with a binding level lower than its own. The force command will also override bound channels with a binding level equal to its own (as well as lower ones). Therefore if there are two set statements with the same binding level one after another, the second one will be ignored; if there are two force statements with the same binding level one after another, the first one will be ignored.

Normally manually setting binding levels should not be necessary. Most animations would use a simple set statement. If there is a more important animation that should discontinue one that is already playing, it should use the force command. And if there is something even more important that should never be overridden, then binding levels could be used.


set AimHead script "P_HeadTrack"; //Plays "P_HeadTrack" on the "AimHead" channel

force (1) AnimAll //Interrupts all current animations in channels "AnimAll" and "AnimUpper" and plays "HatchSequence" in "AnimAll"
    script "HatchSequence";
    force AnimUpper {}

Command statements

When a channel block is entered, the associated channel is reset to its defaults (the script is reset to None and all the previous settings are forgotten). In order to set new ones, command statements are used. Note that if you just create an empty channel block, it will stay cleared until unbound.

There are many different command statements. They are listed in the table below.

Command Description
script string "ScriptName";   
Plays (executes) the Entity Script defined by ScriptName. Defaults to None.
blend float Time;
Sets the amount of time for blending between the script playing on the channel previously with the newly defined one. Defaults to 0.0.
blendin bool bEnabled;
Indicates whether the blending time should be used when blending in this script over the previous one in the channel. Defaults to 1.
blendnull bool bEnabled;
Indicates whether the blending time should be used when blending this script out to an empty channel. Defaults to 1.
rate float Rate;
Changes the framerate of the playing script. This is a multiplier, so if the original had 30 frames per second rate, setting this to 2.0 would change it to 60 frames per second. Defaults to 1.0.
duration float Duration;
Alternative to rate. It automatically calculates the rate necessary to play the script for the Duration in seconds. This command has to be provided after setting a script, as the length of the script must be known for the calculations to be successful. Defaults to rate 1.0.
startframe float Start float Stop;
Sets the script's start frame to a custom value, in percentage of the whole script length. The value is chosen randomly to be between Start and Stop. So a startframe 0 1 command will chose the starting frame from anywhere within the script, while startframe 0.5 1 will choose from the second half of the script. Setting Start and Stop to the same value eliminates the random element. Should not be used with syncchannel. Defaults to 0.0 0.0.
looping bool bEnabled;
Indicates whether the script should loop after it reaches the end. If set to 0, the animation freezes on the last frame after reaching the end. Defaults to 1.
restart bool bEnabled;
Sets whether the animation should start playing from the beginning if the new script matches the currently playing one. If set to 0, the current script is continued. Defaults to 0, as that way setting the same script every frame allows it to continue playing without interruptions (saving coding time). Should only be used in case of events that mandate such interruption.
waitblendin bool bEnabled;
Sets whether to suspend the script that is getting blended into during blending until blending is complete. If set to 0, scripts continue playing while they are being blended. Defaults to 0.
waitblendout bool bEnabled;
Sets whether to suspend the script that is getting blended out during blending until blending is complete. If set to 0, scripts continue playing while they are being blended. Defaults to 0.
waitblend bool bEnabled;
Obsolete, kept for backwards-compatibility. The same as waitblendin.
notify float When string "NotifyText";
Sends a notification to the game at When percentage of the script. Used for interfacing with UnrealScript actions (such as synchronising in-game events). Defaults to 0.0 None.
syncchannel identifier ChannelName;
Sets the starting frame of the given channel to match the frame (relatively to script length) of the script running on the channel indicated by ChannelName. Defaults to None.
resetchannel identifier ChannelName;
Unbinds the channel indicated by ChannelName, regardless of binding levels. Should be avoided unless absolutely necessary, as this command goes around all safeguards. Defaults to None.
keepset float Start float Stop;
Sets the binding time for the current channel. With the time set, it is ensured that during this time nothing is done to interrupt the channel (unless overridden), as during this time all the set blocks for this channel are ignored. The parameter meaning is context-sensitive. If the keepset command comes before a script command, the parameters are time in seconds. If the command comes after a script command, the parameters are multipliers of the script's length. Either way the actual binding time is chosen randomly to be a value between Start and Stop. If they match, randomness is eliminated. Using keepset 1 1 is common for force blocks during special events, as it ensures the channel stays bound for the whole duration of the script.
keepchance float Start float Stop;
Only works within random blocks (see below). Eliminates the randomness of the chance blocks by ensuring a constant seed that is the same as the previous one. This is often useful in Default-related idle processing, where keepset-like behavior should be exerted in random choices, without actually forcing a real binding time onto a channel like keepset itself does. The parameter usage is the same as inkeepset, including the context sensitivity.
timer float Length identifier ActionName;
Executes the action specified by ActionName after Length amount of seconds. Whether it always gets executed and how it affects the script flow is unknown. Defaults to 0.0 None.

Control-flow statements

In addition to Channel statements, GAL supports Control-flow statements that allow for more dynamic control of Entity script interaction.

If-Else statements

if ( expression ) statement;
else statement;
if ( expression ) { statement; statement; ... }
else { statement; statement; ... }

Possible expressions:

inputname = inputvalue
inputname != inputvalue
( expression ) or ( expression )
( expression ) and ( expression )

GAL supports the basic logical checking using if and else statements, as in most other programming and scripting languages. The syntax is a mix of C-like and Pascal-like, with the comparison operators and and or being written as words, and the comparison operator is = (like in Pascal or Lua), while the unequal operator is written as != and the block after the statements are enclosed by curly brackets (like in UnrealScript or C). It may be confusing at first, but it makes sense in context (words are easier to remember than symbols, and there is no assign operator to begin with, hence the Pascal-like parts).

GAL has a unique expression has and hasno that takes a script name as a parameter. If the entity has a script (not necessarily playing) of the specified name, the expression will evaluate to true if using has and to false if using hasno. This is useful for shared agents that are not guaranteed that a particular script will exist on each entity the agent is used on.

Naturally, chaining else and if statements is allowed.


if ((Speed = Walk) and (Mood != Good))
    set MainChannel script "SomeScript";
else if ((Speed = Run) and has("SomeOtherScript"))
    set MainChannel { script "SomeOtherScript"; rate 2.0; }
    set MainChannel script "YetAnotherScript";

Random-Chance statements

random { statement; statement; ... }

chance int Odds statement;
chance int Odds { statement; statement; ... }

One of the things that are frequently used within GAL is the ability to execute commands randomly. This is achieved by using random blocks and chance statements inside of them.

Each random block can contain both regular statements and chance statements. These chance statements take one parameter that reflects the odds of it being chosen. In a random block, all of the chance statements are summed and then one of the chance statements is picked. Note that the odds number is meaningful, as the GAL engine makes sure that over the total amount of iterations of the random block that matches the total odds, they would be reflected precisely. If you have a random block with statements chance 2 ... chance 1 ..., then you are guaranteed that every 3 iterations of the random block the second statement will be chosen exactly once. If you want to have more randomness, you should use higher numbers (in this case, something like chance 200 ... chance 100 ...).

If you want to keep the same choice being made over a certain timeframe, you should use the keepchance command as described above. It is generally useful for things like idle animations – you don't want the animation to change every few frames, yet using keepset is not an option as you want other animations to be able to overwrite the idle animation with ease.


    chance 4 set MainChannel script "SomeScript";
    chance 1
        if (Speed = Run)
            set MainChannel { script "SomeRareRunScript"; blend 0.2; }
            set MainChannel { script "SomeRareWalkScript"; blend 0.1; }

Call and Return statements

call ActionName;


As mentioned before, the concept of actions is similar to the concept of functions in other languages. Within an action, you can call another action by using a call statement. This will cause the indicated action to be executed before continuing the current action. Note that once an action is called, the new action inherits all local input values and even the scope of set and force blocks from the previous action. When the called action ends, the calling action continues executing.

You can also end actions prematurely by using a return statement. Unlike in most other languages, it cannot be used for passing information, as GAL doesn't support parameters for actions.

The ability to call and return makes action-based loops possible, although generally redundant as the execution of GAL code loops on every frame anyway.


action SomeAction
    if (MyInput = DontDoAnything)
    call ChildAction;
    if (MyInput = DoThingsTwice)
        call ChildAction;

Scope considerations

Although in the examples above the different statements are shown in isolation for clarity, you can mix and match them nearly any way you want. For instance, you can have set and force statements inside chance blocks, or a chance block inside a random block that is inside another chance block inside a random block. In the latter case, the chance blocks will interact with parent random blocks that are closest to them hierarchically. The only things that are not allowed are logically nonsensical constructs, like a chance block outside a random block or any of these statements outside an action block.

However, there is a special meaning for set/force blocks nested inside other set/force blocks. Child channel statements inherit the binding properties from the parent channel statements, and any commands for changing the binding time or level are ignored. That allows both of these channels to be synchronously bound – as soon as the parent is unbound, the child channels get unbound as well.


set EyeChannel //Synchronises EyeChannel and HandChannel and keeps them bound for the duration of the first script
    script "EyeHandCoordination";
    keepset 1.0 1.0;
    set HandChannel
        script "HandEyeCoordination";
        syncchannel EyeChannel;

Concatenated string form

In all of the above examples anything that required a string value as a parameter used a literal string (enclosed in quotation marks). But that is not the only way to do so – strings, or parts of them, can be generated and concatenated. For that, we use the str format:

str ( string/inputname, string/inputname, ... )

Each string/inputname is either a literal string or a valid input name (local or global). Since inputs act like variables, it makes sense to use them. When used, this form concatenates all of the parameters (if it's an input, it takes its value and converts it to a string) together into one string. For instance, if an input MyInput is set to MyValue, then the expression

str("First", MyInput, "Second") = "FirstMyValueSecond"

will evaluate to true.

If an input has a string alias defined (see the Inputs section above), the string alias will be used instead of converting the value into a string directly. Keeping with the example in the Inputs section, if Flavor is set to Chocolate, then the first expression evaluates to true and the second evaluates to false:

str("First", Flavor, "Second") = "FirstChocSecond"
str("First", Flavor, "Second") = "FirstChocolateSecond"

This feature allows for more automation and simplified scripts. Consider these actions, which are equivalent:

action PlayAnimationA
    if (Direction = Forward)
        set FirstChannel script "WalkForward";
    if (Direction = Backward)
        set FirstChannel script "WalkBackward";
    if (Direction = Left)
        set FirstChannel script "WalkLeft";
    if (Direction = Right)
        set FirstChannel script "WalkRight";

action PlayAnimationB
    set FirstChannel script str("Walk", Direction);

Local inputs

As mentioned before, inputs are similar to variables, and global inputs cannot be written, while local inputs can be. In order to assign a value to a local input, the command localbind is used:

localbind inputname = inputvalue;

Naturally, it does not work for global inputs.


if ((GlobalA = Set) and (GlobalB = Set))
    localbind LocalA = Set;
if (LocalA = Set) //Gets executed only if both "GlobalA" and "GlobalB" are set to the value "Set"

Transition sections

transition inputname
    from inputvalue = actionname;
    to inputvalue = actionname;
    from inputvalue to inputvalue = actionname;

There are many situations when a change in an input value should be accompanied by a transition between the two states. For instance, if the entity is crouching and stands up, it is desirable to play a "crouching to standing" animation. While it is possible to make these animations play with using actions only, it would require a fairy complex mechanism. The transition sections make these transitions a lot easier to handle by calling predefined actions on input change automatically.

Each transition section needs to have the name of the monitored input defined. Inside the block to and from statements are used. Using from, an indicated action will be executed every time the monitored input changes the value from the defined one. Using to, an indicated action will be executed every time the monitored input changes the value to the defined one. Using both from and to on the same line, an indicated action will be executed only if the monitored input changes from the first specified value to the second.

Note that the statement order is important – the later statements override the previous ones. For example:

    Posture = Standing, Crouching, Proning;

transition Posture
    from Standing = FirstAction;
    from Standing to Proning = SecondAction;

Here if the Posture changes from Standing to Proning, the SecondAction (and only the SecondAction) will be executed. If it changes from Posture to anything else (in this case, the only other value is Crouching), the FirstAction is executed.

The from and to statements support only one executed action, as the actions can call other actions using the call statement. Also note that the called action will see the monitored input as still having the old value, and it changes as soon as the action ends. That way it's possible to determine which value is being transitioned from (as the value to which it is transitioned is already known – a specified, specific action is being executed, after all).

Test section

onpress "keyname" call/bind;
onpress "keyname" { call/bind; call/bind;  ... }

onrelease "keyname" call/bind;
onrelease "keyname" { call/bind; call/bind;  ... }
call actionname
bind inputname = inputvalue

The test section is an optional section for testing and debugging the agent scripts within Golem Studio. The section contains onpress and onrelease blocks which are entered when an indicated key is pressed or released. Within these blocks, statements call and bind are used. The former calls a specified action, and the latter sets a global input to a specified value. The bind statement cannot write to local inputs, as the whole test section is not considered to be a part of the internal GAL script (as it is intended to quickly set options that you should be able to do manually with Golem Studio).

The keyname corresponds to a key on the keyboard or a button on the mouse. It is case-sensitive. Valid options are:

a b c d e f g h i j k l m n o p q r s t u v w x y z
1 2 3 4 5 6 7 8 9 0
f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12
- = [ ] \ ; ' , . / `
backspace tab enter escape space
leftarrow rightarrow uparrow downarrow
ins del home end pgup pgdn
numslash numstar numminus numplus numenter numperiod
num1 num2 num3 num4 num5 num6 num7 num8 num9 num0
mouse1 mouse2 mouse3 mwheelup mwheeldown

You can also append shift, ctrl and alt (separated by a space) to all of the above to get the respective keystrokes.


    onpress "num8" bind Direction = Forward;
    onpress "num2" bind Direction = Backward;
    onpress "num4" bind Direction = Left;
    onpress "num6" bind Direction = Right;
    onpress "num5" bind Direction = None;
    onpress "numstar" bind Speed = Walk;
    onrelease "numstar" bind Speed = Run;
    onpress "1" bind WeaponType = Small;
    onpress "2" bind WeaponType = SmallSwing;
    onpress "3" bind WeaponType = Large;
    onpress "4" bind WeaponType = Shoulder;
    onpress "5" bind WeaponType = FlameThrower;
    onpress "w" { call EventWave; call EventTaunt;  }
    onpress "shift r" call EventReload;


  • Most of the article is based on information provided by Chris Hargrove.