This function adds an *animation step* to the character's *animation queue*. An animation queue is just a sequence of animation steps. Its length is set to a fixed value, perhaps 10 steps. So if you add a step 11, then the oldest step is automatically discarded.
An animation step is a list of key-value pairs. The tangible.animate above would most likely create an animation step with the following key-value pairs in it:
```
action: "jump"
height: 3.0
xyz= (100,100,0)
plane= "earth"
facing= 50
```
So obviously, the "action" and "height" key-value pairs were taken directly from the *tangible.animate* command. But where did "xyz", "plane", and "facing" come from? Notice that in the key-value pairs above, I used a colon (:) next to action and height, but I used an equal-sign (=) next to xyz, facing, and plane, to make it clear that they are different from each other.
The values with the equal-sign are persistent values. All persistent values are automatically copied from one animation step to the next, unless they are manually altered in the *tangible.animate* command. Each time you do a tangible.animate, those persistent values will get propagated forward. They will never change unless you explicitly change them in the *tangible.animate* command. In contrast, the values with the colon are transient values: they do not get propagated to the next animation step.
Normally, the persistent values are xyz, facing, and plane. However, it is possible to create a tangible with additional persistent values. For example, you could make a chest of gold, with a persistent state "open=true" to indicate that the chest is open. The chest would persist the value open=true until you issue a tangible.animate with open=false in it. After that, it would persist the open=false.
When you create a new tangible, the animation queue is initialized with a single animation step containing initial values for all of the persistent key-value pairs.
Unreal monitors the animation queue of every tangible. When it sees a new animation get added to the animation queue, it plays that animation. The rest of this document will explain how we have written unreal blueprints to handle animation queues and animation steps.
## Reasons that Animation Queues can Change in Unexpected Ways
The Lua function *tangible.animate* appends a step to the animation queue. The majority of updates to the animation queue consist of appending. So mostly, Unreal can expect animation queues to grow monotonically.
However, there are certain cases when something other than an append happens to an animation queue. There are three primary reasons that this can happen:
- For interactive walking, if we were to append a new animation step every time somebody moves a few inches, then we would be appending a dozen steps per second. Since animation queues have fixed lengths, this would very rapidly push even fairly recent animations out the back end of the queue. This is undesirable. So the interactive walking code sometimes updates the XYZ of the most recent step, rather than appending a new step.
- It is possible for the lua code to reinitialize the animation queue of a tangible. It might do that, for example, when changing the class of that tangible. Or, it might do that if it decides that the animation queue of the tangible is "messed up." When reinitializing the animation queue, all existing animations are discarded.
- The difference transmitter can make corrections to the animation queue of the client. When it does, anything can change in any way. Animations can appear, disappear, and reappear. Animations can be altered in subtle ways. Animations can be inserted between existing animations. Anything can happen.
Unreal monitors the animation queue of every tangible. Unreal must be prepared to deal with all of these atypical changes to the animation queue. Unreal can assume that *usually*, modifications to the animation queue are append-only, but occasionally, there will be exceptions.
## How Animation Queues get Transferred from Luprex to Unreal
Animation queues are stored inside of Luprex. They are part of the world model. Unreal needs to see the animation queues, in order to be able to perform the animations. Therefore, an API is needed that lets us transfer animation queues from Luprex to Unreal.
Animation queues can be serialized. We made the design decision that the API that transfers animation queues from Luprex to Unreal would use the serialized representation of the animation queue. So when we transfer the animation queue of one tangible from Luprex to Unreal, we're not transferring a complicated data structure: we're just transferring a string. This greatly simplifies the API.
So, what follows is the actual API that transfers the animation queues, this is part of enginewrapper.hpp. You provide a list of tangible IDs, it returns a list of strings, one for each specified tangible. Each string is the serialized representation of an animation queue:
```cpp
// Get the animation queues for the specified tangibles.
//
// You must supply an array of tangible IDs. For each tangible, returns the
// animation queue as a serialized string. The serialized format is
// documented in header file animqueue.hpp.
//
// You must also supply a buffer for the string lengths, and a buffer for
// the string pointers. Both buffers must have space for number of
// tangibles specified in the call.
//
// The returned character pointers remain valid until the next call to
Notice that in this API, no string copies are being made. This is important, because this API is used every single frame. Unreal is being given a pointer to strings that reside within the world model.
But think about that a little more: in order to be able to transfer the string without making a string-copy, the serialized string representation must already exist within the world model: it must already be there. So, in order to facilitate this API, we made another design decision: that animation queues would be stored in Luprex in serialized format. That means that whenever Luprex wants to manipulate an animation queue, it has to deserialize it, manipulate the deserialized representation, and then reserialize it. That's OK. We very likely will only do a handful of tangible.animate commands per second, and animation queues are fairly small data structures anyway (maybe a few kilobytes). Optimizing the API above, on the other hand, is extremely important because we use it every frame at 60fps, on every tangible.
If Unreal had to deserialize every animation queue at every frame, that would be expensive. Fortunately, that's not necessary, because of another trick we use: animation step hash values.
Each animation step has a hash value, which is computed from the content of the animation step mixed with the hash value of the previous step. The hash function we use is very good, the likelihood of a collision is very low. In the serialized representation of the animation queue, the most recent animation step is serialized first. And when serializing an animation step, the hash value is written first. So that means that the first 8 bytes of the serialized animation queue are always the hash value of the most recent animation step. Unreal can check whether the animation queue has changed by simply comparing the first 8 bytes of the serialized animation queue to the hash value it received the previous frame. If the hash value is the same, then the animation queue hasn't changed. In that event, unreal doesn't need to decode anything. Only when the animation queue actually changes does unreal need to decode it.
For each tangible, Unreal stores a decoded, deserialized form of the animation queue. When the serialized representation retrieved from Luprex *does* change, then Unreal decodes the serialized representation and updates its own decoded, deserialized representation.
This decoded, deserialized representation is a C++ data structure that lives within our Unreal project. It is updated by our Unreal-based C++ code. From there, it can be read by our Blueprints. Much of the rest of this paper discusses how our Blueprints fetch data from this animation queue representation.
## What the Tangible Actor Blueprint Classes Are
We have written a Blueprint Class TangibleStaticMesh. By "Blueprint Class," I mean that it is not written in C++. It is written in Unreal's visual scripting language, blueprint. TangibleStaticMesh's main job is to execute the animation queue. It reads the queue, and then performs the animations specified in the queue.
TangibleStaticMesh only supports tangibles that are static meshes. They can "moveto", they can "warpto", and they can switch from one static mesh to a different one, but they don't have much more behavior than that. They can't play recorded animations because they're static meshes.
We also provide other classes, TangibleSkeletalMesh and TangibleCharacter. Collectively, TangibleStaticMesh, TangibleSkeletalMesh, and TangibleCharacter are called the "Tangible Actor" classes.
These three classes provide a lot of common functionality. But eventually, the scripter will have to write his own Tangible Actor class. For example, suppose the game scripter wants a cannon that can fire cannonballs at a target specified in the *tangible.animate* command. Nothing in our code can do anything like that. So the scripter will have to write his own Tangible Actor class. He may be able to derive from one of our classes, however.
TangibleStaticMesh is the simplest of the Tangible Actor classes. It follows "conventional" rules:
- Animations are played one at a time.
- Animations are played in the order that they appear in the queue.
- When an animation is done, the next one begins.
Those are the rules used by TangibleStaticMesh. Most other tangible actors will follow the same rules, but some tangible actors use different rules. It is possible to write a tangible actor that executes animations out of order, or that executes more than one animation at a time. In fact, there is no stipulation about how a Tangible Actor interprets the queue. "Tangible Actor" is a very open-ended job description: "a blueprint class that does whatever it wants to in response to the animation queue."
Reasoning about that is difficult, so we're going to start by explaining TangibleStaticMesh, the simplest of the Tangible Actor classes. Later, we'll talk about Tangible Actors that do unconventional things.
## What Unreal Adds to the Animation Queue
Unreal's copy of the animation queue is mostly a direct copy of what's in Luprex. But there are a few additions that Unreal makes to the animation queue.
Unreal adds a finished bit to every step of the animation queue. This bit will be used to record which animation steps have already been played, so that our blueprints don't try to play them again. Again, the finished bit is not present in Luprex's copy of the animation queue, it only exists within Unreal's copy. Naturally, when a new animation step shows up in the animation queue, Unreal sets the finished bit for that animation step to false.
Some tangibles have an idle animation. They play the idle animation when there's nothing else to do. In order to make this easier for tangible actors, the Unreal C++ code synthesizes an "idle" step which is always present at the end of the animation queue. Doing this makes it possible for the tangible actor to treat the "idle" animation as just another step in the animation queue. The blueprint doesn't need to have any special "idle state" that it keeps track of. It just keeps playing the steps in the animation queue, forever. The idle step cannot be marked *finished*, any attempt to mark it as finished will simply be ignored. That ensures the invariant that there's always an idle step to play, if there's nothing else to play.
## How Tangible Actor Blueprints Read the Animation Queue
Tangible actor blueprints will be examining the animation queue. This section explains the blueprint functions that allow blueprints to fetch animation steps from the animation queue, and then fetch Key→Value pairs from the animation steps.
The most important function to fetch an animation step from the queue is *GetCurrentAnimation*. This scans the animation queue, and returns the first animation step whose *finished* bit is not set. That's usually the animation step which is most important for a tangible actor's purposes.
The animation step is returned in the form of a *struct lxAnimationStep*. This is a C++ struct which is also a valid type in blueprint language. It contains the Key→Value pairs of the animation step, and also the animation step's hash value. In blueprint language, structs are passed by value, not by reference, therefore, the struct which is returned is a *copy* of some step in the animation queue. You can make blueprint variables of type *lxAnimationStep*, so animation steps are first class values in blueprint language.
The function *GetCurrentAnimation* is one of many accessors that can fetch animation steps from the animation queue. We plan to write a full library of accessors: functions to get the Nth step, functions to scan the queue for steps with particular attributes, and so forth. The intent is that the blueprint is meant to have unlimited access to the entire animation queue. Most of this is not implemented yet, but it is important that blueprints can read the entire queue, and they need to be able to find what they want to find as efficiently as possible.
Once the blueprint has an animation step in the form of a lxAnimationStep, the next thing it needs to do is read the Key→Value pairs in it. The blueprint programmer can do that using accessors like these:
```
Action = AnimationStepGetString(step, "action")
XYZ = AnimationStepGetVector(step, "xyz")
Plane = AnimationStepGetString(step, "plane")
Model = AnimationStepGetString(step, "model")
Facing = AnimationStepGetFloat(step, "facing")
```
Of course, blueprint language is a visual scripting language. The code above looks pretty simple when written in a textual form, but it takes fifteen little rectangles in the event graph just to do the five assignment statements above. That's half a screen full of code. In order to make that more concise, we've provided the accessor *UnpackAnimationStep*. Here's how it works. You add variables like this to your blueprint:
```
Aq Action (string)
Aq XYZ (vector)
Aq Plane (string)
Aq Model (string)
Aq Facing (float)
```
Notice that all the variable names have a prefix, "Aq". Then, you use a single command, UnpackAnimationStep, to assign values to those variables:
```
UnpackAnimationStep(step, prefix="Aq", into=self)
```
This pulls the Key→Value pairs out of the animation step, and stuffs them into the blueprint variables whose name begins with "Aq." Using a variable name prefix ensures that *UnpackAnimationStep* won't overwrite variables that it isn't supposed to be touching.
Using *UnpackAnimationStep* is entirely equivalent to fetching all the values one by one. The two versions of the code above fetch exactly the same values, and the results are exactly the same.
Here's an animation step that I mentioned earlier in this paper:
```
action: "jump"
height: 3.0
xyz= (100,100,0)
plane= "earth"
facing= 50
```
This contains a pair "height: 3.0". But in code above which uses simple accessors, there's no call to *AnimationStepGetFloat(step, "height").* So that block of code would ignore the height. Meanwhile, in the version of the code that uses *UnpackAnimationStep*, there's no variable "Aq Height." Either way, height gets ignored.
But what if the blueprint code that implements the "jump" animation needs to know the height? The blueprint author has two options. He can add a variable "Aq Height," and let *UnpackAnimationStep* populate it. His other option is to add a call to *AnimationStepGetFloat(step, "height")* at the point in the code where the height is needed. Either option is perfectly valid.
You might also notice that there's a variable "Aq Model," but the animation step above doesn't contain a "model=value" pair. So *UnpackAnimationStep* will set variable "Aq Model" to its default value, which for string variables is the empty string. Likewise, the accessor *AnimationStepGetString(step, "model")* will return empty string.
So now you know how a blueprint can read the animation queue. It has unlimited access to the queue, it can read the whole queue. Next, we'll tell you what a TangibleStaticMesh, our simplest tangible actor, does with the queue.
## How TangibleStaticMesh gets Notified when the Animation Queue Changes
TangibleStaticMesh needs to examine the animation queue to play the animations in it. But we don't want TangibleStaticMesh to examine the animation queue every "tick." Remember, TangibleStaticMesh is written in blueprint language, which is an interpreted language that is not particularly fast. Therefore, we only want the blueprint to look at the animation queue when the animation queue changes.
To that end, class TangibleStaticMesh implements a function *AnimationQueueChanged*. This function is written in blueprint, and it is automatically called by the C++ code whenever the animation queue changes in any way. It is called immediately after Unreal fetches an animation queue from Luprex and detects a change.
In Unreal, to call from C++ code into Blueprint code, you need to use what is called a "blueprint interface." Therefore, *AnimationQueueChanged* is part of a blueprint interface called *lxTangibleInterface*. All Tangible Actor classes implement the interface, and our C++ code that calls *AnimationQueueChanged* uses the interface to call it. When looking for the function *AnimationQueueChanged* in the blueprint editor, you will find it in the left hand panel under *Interfaces*, under *Tangible Functionality*.
## How TangibleStaticMesh Initiates Animations
When the C++ code calls *AnimationQueueChanged*, the first thing that *AnimationQueueChanged* does is to call the function *GetCurrentAnimation*. This scans the animation queue for the first animation step whose *finished* bit is not set. This is the animation step that TangibleStaticMesh wants to be playing.
*GetCurrentAnimation* returns the animation step in the form of a *struct LxAnimationStep*.
Then, TangibleStaticMesh checks to see if it is already playing that animation step. TangibleStaticMesh accomplishes this by storing the currently-playing animation step in a variable *CurrentAnimation*, of type *LxAnimationStep*. If the animation step returned by *GetCurrentAnimation* matches the one stored in *CurrentAnimation*, then *AnimationQueueChanged* doesn't need to do anything. It can return immediately.
If *GetCurrentAnimation* returns an animation step that doesn't match the variable *CurrentAnimation*, then TangibleStaticMesh theoretically has to abort the old animation. In reality, you won't find any code to abort anything, because TangibleStaticMesh is so trivial: it can't actually play animations other than "moveto" and "warpto," so there's nothing to clean up or abort. If you were a scripter writing a more complicated Tangible Actor, then it might be necessary to do some cleanup steps here.
After aborting the old animation, *AnimationQueueChanged* stores the new animation's *LxAnimationStep* in the variable *CurrentAnimation*. Then, it unpacks the step using *UnpackAnimationStep*. The Key→Value pairs are transferred into blueprint variables, to make it easy for the blueprint to access them. The variables used have the variable name prefix "Aq." The Aq variables contain the same animation step as *CurrentAnimation*, in an unpacked form.
Next, AnimationQueueChanged checks if "Aq Model" has changed. If so, it switches the static mesh to a different mesh.
Finally, *AnimationQueueChanged* actually initiates the new animation. In TangibleStaticMesh, you will find a "Call Function By Name" that calls *Initiate Action: Moveto*, *Initiate Action: Turnto*, or some other tick-function depending on whether *Aq Action* contains "moveto", "turnto", or some other value.
In TangibleStaticMesh, the routines that initiate different actions are extremely simple. For example, to initiate a "moveto," all we have to do is store the target XYZ in a variable. But that has already been done: it's in the variable "Aq XYZ." The actual movement will be handled by a tick-function, which I'll explain in a moment. So the routine *Initiate Action: Moveto* is actually a no-op, because everything that needs to be done has already been done. In TangibleStaticMesh, the other routines that initiate animations are all equally trivial.
This is the process by which TangibleStaticMesh initiates new animation steps.
## How Some Animations involve Tick Functions
After TangibleStaticMesh initiates a "moveto" action, the next thing that has to happen is that every frame, the actor location needs to move a little bit toward its target. This movement is handled by a tick-function.
The event graph of TangibleStaticMesh has a "tick" event. That tick-event uses "Call Function by Name" to call *Tick Action: Moveto*, *Tick Action: Turnto,* or some other tick function depending on whether *Aq Action* contains "moveto", "turnto", or some other value.
The function *Tick Action: Moveto* adds a small increment to actor location, moving it a tiny bit at a time toward Aq XYZ. At the end of *Tick Action: Moveto* it checks whether the tangible has reached its destination. If so, it calls the function *FinishedAnimation.* In the next section, we will explain what *FinishedAnimation* does, and how it leads to the next animation getting played.
That's how TangibleStaticMesh uses tick functions to update the state of ongoing animations.
In blueprint code, tick functions are discouraged, because blueprint code is interpreted code and it isn't that fast. It is conceivably possible that you could write a version of "moveto" that doesn't require a tick-function. Perhaps there's some Unreal builtin functionality that can move an actor toward a destination automatically: I don't know. If so, you would have to tell Unreal to do the movement automatically, and you would have to tell Unreal to invoke a callback when the movement is done. The callback would be responsible for calling *FinishedAnimation*.
When TangibleStaticMesh initiates a new animation, it first has to abort any already-playing animation. Currently, there's not anything that TangibleStaticMesh has to do when it "aborts" the existing animations. But if you implemented "moveto" by telling Unreal to perform the movement automatically, then any code to abort already-playing animations would need to be able to abort these automatic movements.
## What TangibleStaticMesh does when Animations Finish
In the code that implements the "moveto" animation, in the function *Tick Action: Moveto* there is an if-statement that checks if the tangible has reached its destination. If so, it calls the function *FinishedAnimation*.
All *FinishedAnimation* does is set the finished bit in a specified step in the animation queue. Setting the finished bit counts as a modification of the animation queue. This, in turn, guarantees that next frame, the C++ code will call *AnimationQueueChanged*.
In TangibleStaticMesh, the first thing that *AnimationQueueChanged* does is call *GetCurrentAnimation*. GetCurrentAnimation returns the first animation in the queue whose *finished* bit is not set. So it won't return the step that was just finished. It will return the step after that.
So here's the chain of events that gets triggered, summarized:
- Something in the blueprint calls *FinishedAnimation*. This sets a *finished* bit.
- The *finished* bit is a change to the animation queue. This triggers *AnimationQueueChanged*.
- *AnimationQueueChanged* calls *GetCurrentAnimation*, as it always does.
- *GetCurrentAnimation* returns a new animation, because the old one's *finished* bit is set.
- The new animation gets initiated.
Through this chain of events, the *FinishedAnimation* in *Tick Action: Moveto* triggers the playing of the next animation in the queue.
It is perfectly safe to call FinishedAnimation from anywhere. You can call it from a tick-function, you can call it from a callback, whenever you figure out that an animation has finished, you can call it from wherever you are in the blueprint code.
Some animations are instantaneous, such as "warpto." They take zero frames to complete. The following sequence of events occurs: the lua code pushes the "warpto" animation into the queue. The Unreal C++ code sees this and calls *AnimationQueueChanged*. This, in turn, calls *Init Action: Warpto*. That routine actually performs the warpto, and then calls *FinishedAnimation* immediately. So it's even fine to call *FinishedAnimation* directly from the routine that initiates an animation, if you want the animation to be finished as soon as it starts.
The routine FinishAnimation takes an *lxAnimationStep* as a parameter. This is so that it knows which animation step to mark as finished. TangibleStaticMesh always passes in the variable *CurrentAnimation*.
*FinishAnimation* also takes three boolean parameters: Auto Update XYZ, Auto Update Plane, and Auto Update Facing. These require some explanation. Suppose that the lua programmer pushes an animation step that looks like this:
Since "play an emote" isn't a travel command like "moveto" or "warpto", the lua scripter shouldn't have specified a destination XYZ, but he did so anyway, and now we have to recover from the mistake. Specifically, the tangible actor really does need to move to the new XYZ coordinate, otherwise bad things happen.
The convention that we have adopted is that to recover from this type of mistake, it is considered acceptable to just play the emote in-place (ie, without moving the actor), then, when the emote is fully finished, the blueprint warps the player to the specified XYZ coordinate. In other words, every animation step is treated as if it has an *implicit* "warpto" at the end of it. This rule guarantees that if the lua programmer sets the xyz, facing, or plane in an animation step, the character will end up at the desired xyz, facing, and plane, no matter what the animation step is.
That's why *FinishAnimation* has those three boolean flags: Auto Update XYZ, Auto Update Plane, Auto Update Facing. If those are all true – and they almost always should be – then *FinishAnimation* will implement the implicit "warpto" for you. I cannot think of a situation where you would want these flags to be false, but I have left the option, in case somebody wants to do something odd in a Tangible Actor.
## How Tangible Actors handle Warping Away
Suppose that in your game, players can teleport from one place to another by casting a "teleport" spell. Suppose that there is a spellcasting animation with lots of sparkles whenever somebody teleports. You would think that this would be the same as any other animation, but in fact, there is some special-case logic to deal with this case.
Here is the tricky scenario: player T, the teleporter, is standing in front of player V, the viewer. Player V can see player T. Then, player T casts the teleport spell, and executes this lua code:
Luprex has no idea how long these animations take to execute. As far as Luprex is concerned, they are instantaneous. As soon as player T executes that code, Luprex thinks that player T has moved to {1000000, 0, 0}, a million miles away from player V.
Every frame, Unreal asks Luprex what tangibles are within the view radius. When player V's Unreal client does a *scanradius*, the scan no longer returns player T, because Luprex thinks that player T is a million miles away. Yet, player V's Unreal client hasn't even begun to play the sparkle animation. How is it supposed to play the sparkle animation if Luprex is no longer even reporting the existence of tangible T?
The answer is that Tangible Actor T was already visible to player V the previous frame, and our Unreal code doesn't allow Tangible Actors to blink out of existence that easily. Once Tangible Actor T exists in Unreal, that Tangible Actor will continue to exist in Unreal, and Unreal will continue to query its animation queue from Luprex. Luprex can still report the animation queue: even though T isn't in player V's scanradius any more, T still exists and still has an animation queue. So Luprex can still report the animation queue to Unreal.
Since Tangible Actor T hasn't played the sparkle animation or the warpto animation yet, Tangible Actor T has an "actor location" that is still in front of player V. That means there's a disconnect: the Luprex tangible is already at {1000000, 0, 0}, far away. But the Unreal Tangible Actor T is still located in front of player V. It will remain there for however long it takes to play the sparkle animation.
When Tangible Actor T is done with the sparkle animation, only then does it play the "warpto" animation. At that point, it updates its actor location to {1000000, 0, 0}, using Unreal's built-in function, "Set Actor Location" or something similar. Once it has done that, the Tangible Actor T is finally at the same location as Luprex's tangible T.
In order for Unreal to actually discard a Tangible Actor, two conditions must be met. First, the tangible has to no longer be appearing in the Luprex *scanradius*. In other words, it has to be "far away according to Luprex." Second, it has to have an actor location that is far away from the viewer. In other words, it has to be "far away according to Unreal." When both of these are true, the tangible actor is garbage collected. So on Player V's client, Tangible Actor T will get automatically deleted by the C++ code one frame after the "warpto" animation is played.
Now, let's consider a small variation of the lua commands:
Here, instead of changing the XYZ of the player, the warpto changes the plane of the player. Unreal engine doesn't have the concept of "planes." What is the blueprint supposed to do? The answer is that we've added the concept of "plane" to Unreal at the C++ level. We have provided an API function *SetTangiblePlane*, that takes a string. When the tangible actor executes the "warpto" animation, that's all it has to do. The C++ code will make sure to only render tangibles that are on the same plane as the player.
## How TangibleCharacter implements Interactive Movement
Blueprint class TangibleCharacter is meant to implement bipedal characters, which can be interactively controlled using a gamepad or keyboard. TangibleCharacter is derived from Unreal's built-in "Character" class.
TangibleCharacter has two modes that it switches between: scripted mode, and interactive mode. When lua uses *tangible.animate* to push an animation onto the queue, TangibleCharacter switches into scripted mode, and plays the scripted animation. But when there are no more *tangible.animate* commands left to play, TangibleCharacter switches into interactive mode.
In scripted mode, TangibleCharacter works just like TangibleStaticMesh. There's a function *AnimationQueueChanged*, which calls *GetCurrentAnimation*. Just like TangibleStaticMesh, there's a function *Initiate Action: Moveto*, there's a function *Tick Action: Moveto*, and so forth. The code is a little more complex because we have to control the animation blueprint (more on that later), but it's all structured in exactly the same way.
Interactive mode is activated when there are no more tangible.animate commands left to play. Specifically, it is activated when *AnimationQueueChanged* calls *GetCurrentAnimation*, and *GetCurrentAnimation* returns the idle animation. Interactive mode is implemented entirely by the functions *Initiate Action: Idle* and *Tick Action: Idle*.
When we enter into interactive mode (in *Initiate Action: Idle*), we enable Unreal's built-in "Character Movement Component." This is the part of Unreal that implements interactive movement for characters. Enabling the movement component causes the gamepad and keyboard to become active. Pushing the gamepad stick will cause the actor to move. This movement follows all of Unreal's built-in movement rules: you can't walk through walls, you stay on the ground, you move at the speed specified in the character blueprint parameters, and so forth. When we switch to scripted movement, the Character Movement Component is disabled: it is only used for interactive movement.
When you move interactively, Luprex needs to be notified. In *Tick Action: Idle*, the code checks to see whether the character has moved a significant distance from where Luprex thinks it is. If the distance is large enough, *Tick Action: Idle* invokes the Lua function *engio.move*. This Lua function calls *tangible.animate* to add a moveto animation to the queue, to reflect the interactive movement that recently occurred.
The next time Unreal fetches the animation queues from Luprex, it will see the animation that *engio.move* just added to the queue. We don't want Unreal to switch into scripted mode and play the animation that just appeared in the queue, because it *already* performed that movement. In order to prevent this from happening, after invoking *engio.move*, the blueprint calls *SetAutoFinish*. This anticipates the appearance of the "moveto" in the animation queue, and asks our Unreal C++ code to automatically mark that "moveto" as *finished* as soon as it appears. This will prevent the switch to scripted mode.
TangibleCharacter is used for the player character, and also other players and non-player characters. For characters other than the player, interactive mode is disabled. Instead, we just play an idle animation.
## How TangibleCharacter interfaces to its Animation Blueprint
The Unreal distribution includes a sample character, Manny, which uses an animation blueprint, ABP_Manny. We use a slightly-modified version of ABP_Manny for TangibleCharacter. I will explain how ABP_Manny works, and what we have altered.
Every frame, ABP_Manny fetches four parameters from Manny's Character Movement Component:
- Actor Location
- Velocity Vector
- "Is Falling" flag
- "Is Accelerating" flag
Using these four parameters, ABP_Manny automatically switches between walking, running, falling, jumping, and idling animations. These are roughly the rules it uses:
- If Manny "is Falling" and his velocity is downward, transition into the "falling" pose.
- If Manny "is Falling" and his velocity is upward, transition into the "jumping" pose.
- If Manny is not falling and his velocity is zero, play the "idle" animation.
- If Manny is not falling and his velocity is nonzero, put him in the "walking/running" blend.
- The "is Accelerating" flag tweaks the walking/running blend.
- If Manny's feet are close to the ground, use IK to snap his feet to the ground.
I may have gotten some details wrong. The important thing to know, however, is that ABP_Manny takes care of all of this. That means that the Manny Blueprint doesn't have to implement animation. It can leave it all up to ABP_Manny.
For example, when Manny is walking along flat ground, Manny's Character Movement Component is using "Set Actor Location" every frame to continuously move Manny along a trajectory. It does not have to reason about his body shape, his skeleton, or his footsteps. It just moves him along a linear, straight path in a smooth, continuous way.
Manny's Character Movement Component does have to pay attention to collisions: it thinks of Manny as a capsule, it makes sure the capsule does not penetrate a wall. It also makes sure the capsule doesn't pass through the floor, or rise off the ground. This functionality is all built-in to the Character Movement Component, so no blueprint code is needed to implement it.
What TangibleCharacter does differently from Manny is as follows. TangibleCharacter has two modes, scripted and interactive. By contrast, Manny only has interactive mode. TangibleCharacter's two movement modes are implemented separately: interactive mode uses the Character Movement Component (like Manny), scripted mode does not. In both modes, the four key parameters must be provided to the animation blueprint:
- Actor Location
- Velocity Vector
- "Is Falling" flag
- "Is Accelerating" flag
In interactive mode, these four parameters are taken directly from the Character Movement Component. But in scripted mode, TangibleCharacter has to calculate them. In the original code for ABP_Manny, it used to reach directly into the Character Movement Component to fetch these values. In our updated version of the code, the animation blueprint fetches these values using a formal interface. That's the only change we have made to ABP_Manny.
In scripted mode, we ignore collisions, because we want the character to do what the script says, no questions asked. If a *tangible.animate* says that the TangibleCharacter should walk through a wall, he walks right through. So really all that the scripted "moveto" routine in TangibleCharacter has to do is move the Actor Location in a smooth, continuous line toward the destination location. Of course, it also has to report the velocity vector (trivial), and that he's not falling. That's it. Our modified version of ABP_Manny handles the rest.
## Interpreting the Animation Queue in Nontraditional Ways
In TangibleStaticMesh, animations are played one at a time. But you can imagine a Tangible Actor that plays more than one animation at a time.
For example, imagine a "fireworks launcher" object that launches rockets, and which can launch many rockets simultaneously. The rockets are owned by the launcher: it's actually just one Tangible Actor, the launcher and its rockets. Now imagine that the fireworks launcher is controlled using animate commands like this:
Our rocket launcher has the following unusual behavior: when it sees a sequence of animation steps with action="launch", it launches them all at the same time. So in response to the *tangible.animate* commands above, our launcher would launch two "burst" rockets simultaneously. Then, it would wait for both of those rockets to finish, however long that takes. Then, it launches two "sparkle" rockets simultaneously. Then it waits for the two sparkles to finish.
This illustrates the principle that a Tangible Actor is allowed to interpret its animation queue in whatever way it wishes. Most Tangible Actors will play animations one at a time, but as you can see from the fireworks launcher, there's no rule against playing multiple animations at once.
You can think of a Tangible Actor as a sort of a script interpreter, and the animation queue is the script that it interprets. This is a flexible way of doing things: if there's something that one of our existing Tangible Actors can't do, you can write a new one, and you can design a simple "scripting language" to control it that makes it possible to achieve the behaviors you want.
It's interesting to consider how the rocket launcher class would be implemented. A lot of the code structure would be the same as in TangibleStaticMesh. But there are a few things that would have to be done differently.
For example, in class TangibleStaticMesh, aborting animations is simple: it just aborts the old animation whenever a new animation begins. But in the fireworks launcher, it needs to do something more complicated, because there is more than one animation in flight at a time. It must keep a list of the rockets that are in-flight, and for each one, it needs to record the *lxAnimationStep* that launched the rocket. In *AnimationQueueChanged*, it should run over this list and for each rocket in-flight, it should make sure that the animation step that launched the rocket is still present in the animation queue. If not, it needs to abort that rocket.
One interesting question is what to do with the *finished* bit in the animation queue. Should a launch step be marked "finished" as soon as it is launched, or should it be marked "finished" when the rocket is completely done exploding? The answer is that there is no right answer. In fact, the fireworks launcher isn't required to *ever* set the finished bit. The *finished* bit is just a convenience designed to make it easy to implement "the usual." If the blueprint wants to keep its own records of what it has played, and ignore the *finished* bit, that's valid. In practice, it's probably most convenient for the launcher to mark a rocket-launch step finished as soon as the launcher has launched the rocket. That way, it can immediately use *GetCurrentAnimation* to get the next rocket in the sequence.
In general, we don't expect the game developer to have to write custom blueprints like this all the time. The provided Tangible Actor classes (TangibleStaticMesh, TangibleSkeletalMesh, and TangibleCharacter) should be general enough for many of the objects in the game. But we expect that there will occasionally be an object which needs more interesting behavior, so we expect that the game developer will from time to time need to write his own blueprint classes that do things that our Tangible Actors don't do.
When the game developer writes his own Tangible Actor classes, it may be possible to derive from our Tangible Actor classes. Right now, that isn't very feasible, because our Tangible Actor classes are spaghetti code. That's the case because I'm not very good at blueprint programming yet. My plan is that in a few months, when I'm better at blueprint programming, I'll refactor the Tangible Actors to make them good classes to derive from.
But, even then, it may be simpler to write new Tangible Actors from scratch. That will especially be the case if I provide a well-designed library of utility functions to make it easy to write Tangible Actors. I think this may be the best approach: rather than encouraging people to derive from our classes, encourage them to study our classes and then write their own from scratch.