Golem Agent Language

From Oldunreal-Wiki
Revision as of 19:43, 14 July 2012 by GreatEmerald (talk | contribs) (→‎Command statements: finished the section)
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.

Overview

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

channels
{
    channelname;
    channelname;
    ...
}

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).

Example:

channels
{
    AnimAll;
    AnimUpper;
    AnimLeft;
    AnimRight;
    AnimBlink;
    AnimBrow;
    AnimCheeks;
    AimHead;
    AnimHead;
}

Inputs section

inputs
{
    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.

Example:

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
{
    statement
    statement
    ...
}

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.

Example:

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.
waitblend bool bEnabled;
Sets whether to suspend both scripts during blending until blending is complete. If set to 0, scripts continue playing while they are being blended. Defaults to 0.
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.

Control-flow statements