Unigine::Skinner
Skinner is a visual scripting system for animations. It allows blending different animations together to create complex movements of skinned characters. It also possible to include the physical ragdoll into played animation (for the whole the character or per bone).
The Skinner is based on Unigine::Schemer. Skinner scripts can be found under data/core/systems/skinner folder.
The predefined Skinner blocks that are available by default are contained in the data/core/systems/skinner/blocks/skinner.blocks file.
How To Use Skinner Graph
Skinner is built based on Unigine::Schemer. It uses the same entities (see Schemer overview) and shares blocks with it. That means, you can add any of the Schemer block to be used in Skinner graph.
- Skinner graph is created in the same way as Schemer one (see the how-to guide).
- The skinned mesh that Skinner controls is specified in the script (see the details below).
- By default, the Skinner graph should start with schemer.entry named update (see the details below).
- It is possible to create entry point for playing a single animation event. It can be used to create a momentary action, for example, a punch or an action to pick up an item, instead of playing a looped animation (see details below).
- All Skinner nodes in the graph, just like Schemer ones, are triggered by dragging a path to their input. After thy has finished their work, an output path is used to trigger next following nodes, if any.
For a start, do the following to create a simple graph that sets an animation to the mesh:
- Create schemer.entry named update as the start-up point for the graph.
- Add skinner.animation node that will load the animation. Connect schemer.entry output with the input of skinner.animation by dragging a joint. By doing this, you indicate what node to execute after the graph has been run.
- Add schemer.file node that will specify the skinned animation file name. Connect it to the name anchor of skinner.animation node.
- Add skinner.set node that will play the animation (i.e. set animation to the mesh). This node needs an animation buffer that contains bones transformation data. Connect skinner.animation buffer to the skinner.set buffer. Connect skinner.animation output to skinner.set input to indicate that this is the next node to be executed.
- After the Skinner graph has been created or modified, click Run button. A script will be generated out of the graph, compiled on-the-fly and run.
- You can also save the created script into *.script file via Save button for it to be loaded and edited later or executed at runtime.
Here is how this simple Skinner graph would look like. Nodes will be executed in the specified order.
Available Skinner Blocks
The following blocks are the predefined Skinner blocks loaded from data/core/systems/skinner/blocks/skinner.blocks file.
skinner.add | Sums two animations together, for example, to combine two arms swinging animations. This function should be used when some bones are used in both animations at the same time. In case of separate animations, use skinner.combine. This node multiplies two animation buffers.
|
---|---|
skinner.animation | Loads an animation file and sets animation data to the animation buffer. After that, it can be set to the mesh or used as an input buffer for combining animations, etc.
|
skinner.animation2 | Loads two animation files to blends animations. The resulting interpolated animation data is set to the output animation buffer. After that, it can be set to the mesh or used as an input buffer for combining animations, etc.
|
skinner.animation3 | Loads three animation files to blends animations. The resulting interpolated animation data is set to the output animation buffer. After that, it can be set to the mesh or used as an input buffer for combining animations, etc.
|
skinner.blend | Mixes two animations together, each in the required proportion. The resulting blended animation data is set to the output animation buffer (for example, to be set to the mesh after that). The blender should be connected to two skinner nodes that set data into two animation buffers of the blender (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the blender. The second animation loop is the same, except that out 1, in 1 and buf 1 are used. After getting two buffers with animation data, the combiner combines them into one and outputs as the resulting buffer. Interpolation weights used for blending of each animation are added together.
|
skinner.blend3 | Mixes three animations together, each in the required proportion. The resulting blended animation data is set to the output animation buffer (for example, to be set to the mesh after that). The blender should be connected to three skinner nodes that set data into three animation buffers of the blender (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the blender. Two other animation loops are the same, except that out 1 and out 2 (as well as other anchors) are used. After getting three buffers with animation data, the combiner combines them into one and outputs as the resulting buffer. Interpolation weights used for blending of each animation are added together.
|
skinner.blend4 | Mixes four animations together, each in the required proportion. The resulting blended animation data is set to the output animation buffer (for example, to be set to the mesh after that). The blender should be connected to four skinner nodes that set data into four animation buffers of the blender (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the blender. Other animation loops are the same, except that out 1, out 2 and out 3 (as well as other anchors) are used. After getting four buffers with animation data, the combiner combines them into one and outputs as the resulting buffer. Interpolation weights used for blending of each animation are added together.
|
skinner.blend5 | Mixes five animations together, each in the required proportion. The resulting blended animation data is set to the output animation buffer (for example, to be set to the mesh after that). The blender should be connected to five skinner nodes that set data into five animation buffers of the blender (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the blender. Other animation loops are the same, except that out 1, out 2 and out 3 (as well as other anchors) are used. After getting five buffers with animation data, the combiner combines them into one and outputs as resulting buffer. Interpolation weights used for blending of each animation are added together.
|
skinner.bone | Sets a name of the bone. After setting a bone, it can be used for partial ragdoll animation or for custom transformation.
|
skinner.combine | Combines two separate animations together, for example, to combine arms animation with legs animation. This function should be used when there are no bones that are used in both animations at the same time; otherwise, use skinner.add. The combiner should be connected to two skinner nodes that set data into two animation buffers of the combiner (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from it enters into buf 0 of the combiner. The second animation loop is the same, except that out 1, in 1 and buf 1 are used. After getting two buffers with animation data, the blender blends them into one and outputs as the resulting buffer that can be set to mesh.
|
skinner.combine3 | Combines three separate animations together, for example, to combine arms animation with legs animation and torso animation. This function should be used when there are no bones that are used in animations at the same time; otherwise, use skinner.add. The combiner should be connected to three skinner nodes that set data into two animation buffers of the combiner (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from it enters into buf 0 of the combiner. The second and the third animation loops are the same, except that out 1 and out 2 (as well as other anchors) are used. After getting three buffers with animation data, the combiner combines them into one and outputs as the resulting buffer that can be set to mesh.
|
skinner.combine4 | Combines four separate animations together, for example, to combine arms animation with legs animation, torso animation and head animation. This function should be used when there are no bones that are used in animations at the same time; otherwise, use skinner.add. The combiner should be connected to four skinner nodes that set data into two animation buffers of the combiner (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from it enters into buf 0 of the combiner. Other animation loops are the same, except that out 1, out 2 and out 3 (as well as other anchors) are used. After getting four buffers with animation data, the combiner combines them into one and outputs as the resulting buffer that can be set to mesh.
|
skinner.combine5 | Combines five separate animations together. This function should be used when there are no bones that are used in animations at the same time; otherwise, use skinner.add. The combiner should be connected to five skinner nodes that set data into two animation buffers of the combiner (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from it enters into buf 0 of the combiner. Other animation loops are the same, except that out 1, out 2, out 3 and out 4 (as well as other anchors) are used. After getting five buffers with animation data, the combiner combines them into one and outputs as the resulting buffer that can be set to mesh.
|
skinner.difference | Calculates the difference between the reference frame of the loaded animation and all other frames in this animation. It calculates the difference in bone transformations for each frame of the played animation relative, for example, the bind pose (0 frame). After that, this data is output to the buffer and can be added to another animation, for example, to combine breathing animation (calculated via difference) with other animation that contains torso movement.
|
skinner.get | Creates the buffer out of the skinned animation frame that is currently played. It is used for blending between skinned and ragdoll animation. This node should be connected to the node that sets animation onto the mesh, frame-based or ragdoll one. Usually, it is connected through a number of nodes that set animation buffers to skinner.set at the end of the graph branch.
|
skinner.inverse | Inverts the animation. This node should be connected to another skinner node that sets data into animation buffer (for example, skinner.animation node). The animation loop exits on right output, enters another skinner node and returns from it on the right input. The buffer from a skinner node enters into right buffer. The block works according tho the following formula:
|
skinner.lerp | Linear interpolation between two animations in the required proportion. This node should be connected to two skinner nodes that set data into animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the interpolator. The second animation loop is the same, except that out 1, in 1 and buf 1 are used. After getting two buffers with animation data, the interpolator interpolates them into one and outputs as the resulting buffer.
|
skinner.lerp3 | Linear interpolation between three animations in the required proportion. This node should be connected to three skinner nodes that set data into animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the interpolator. Other animation loops are the same, except that out 1, and out 2 (as well as other anchors) are used. After getting three buffers with animation data, the interpolator interpolates them into one and outputs as the resulting buffer.
|
skinner.lookat | Allows for the head direction control. For example, it can be used to control in at what point in 3D space the character looks. This node should be connected to a skinner node that sets data into the animation buffer (for example, skinner.animation nodes). The animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a skinner node enters into buf 0 of the Lookat node.
|
skinner.ragdoll | Enables physics-driven ragdoll ("death") animation for the skinned mesh. It can be set either for the whole mesh (if no bone is specified) or for a separate bone.
|
skinner.rotation | Rotates the bone along a specified axis by the given angle. This node should be connected to a skinner node that sets data into the animation buffer (for example, skinner.animation nodes). The animation loop exits on the right output, enters another skinner node and returns from it on the right input. The buffer from a skinner node enters into right buffer of the Lookat node.
|
skinner.sequence | Loads the animation and sets it to the animation buffer. This animation is played only once when the node is triggered via the play input. When the animation playback is almost over, the sequencer triggers the next following node connected via the stop output. After that, it plays the animation to its end. stop output should always return to the right input (after passing other nodes, if necessary) for the sequencer to work properly.
|
skinner.set | Sets the animation to the mesh and plays it. This node should be connected to another node that loads the animation from a file and sets data into animation buffer (for example, skinner.animation nodes). The animation loop exits on the right output, enters another skinner node and returns from it on the right input. The animation data from a Skinner node enters into buffer.
|
skinner.smooth | Allows for smooth change of value according to the current framerate. This block is used to avoid unnecessary spikes when calculating animation parameters. It calculates the change in parameter value (delta) and limits it by "values per second".
|
skinner.sub | Subtracts the second animation from the first one. The subtractor should be connected to two skinner nodes that set data into two animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the blender. The second animation loop is the same, except that out 1, in 1 and buf 1 are used. After getting two buffers with animation data, the subtractor subtracts the first one from the second one and outputs the result into buffer.
|
skinner.switch | Allows for switching between two different animations depending on the trigger value. If the trigger value is equal to 0, the first animation is played. If it is equal to 1, the second animation is chosen. When the trigger indicates that transition between animation states should be done, animations are smoothly interpolated. Blending into the new animation state is performed with given speed. A switcher can be used, for example, to switch between file-based animation and a physical-driven ragdoll. The switcher should be connected to two skinner nodes that set data into two animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the switcher. The second animation loop is the same, except that out 1, in 1 and buf 1 are used. After getting two buffers with animation data, the switcher chooses the animation to be output or blends animations together in necessary proportion and outputs the result into buffer.
|
skinner.switch3 | Allows for switching between three different animations depending on the trigger value. If the trigger value is equal to 0, the first animation is played. If it is equal to 1, the second animation is chosen. If it is 2, the third one is played. When the trigger indicates that transition between animation states should be done, animations are smoothly interpolated. Blending into the new animation state is performed with given speed. The switcher should be connected to three skinner nodes that set data into three animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the switcher. Other animation loops are the same, except that out 1 and out 2 (and other anchors) are used. After getting three buffers with animation data, the switcher chooses the animation to be output or blends animations together in necessary proportion and outputs the result into buffer.
|
skinner.switch4 | Allows for switching between four different animations depending on the trigger value. If the trigger value is equal to 0, the first animation is played. If it is equal to 1, the second animation is chosen, etc. When the trigger indicates that transition between animation states should be done, animations are smoothly interpolated. Blending into the new animation state is performed with given speed. The switcher should be connected to four skinner nodes that set data into four animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the switcher. Other animation loops are the same, except that out 1, out 2 and out 3 (and other anchors) are used. After getting four buffers with animation data, the switcher chooses the animation to be output or blends animations together in necessary proportion and outputs the result into buffer.
|
skinner.switch5 | Allows for switching between five different animations depending on the trigger value. If the trigger value is equal to 0, the first animation is played. If it is equal to 1, the second animation is chosen, etc. When the trigger indicates that transition between animation states should be done, animations are smoothly interpolated. Blending into the new animation state is performed with given speed. The switcher should be connected to five skinner nodes that set data into five animation buffers (for example, skinner.animation nodes). The first animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from a Skinner node enters into buf 0 of the switcher. Other animation loops are the same, except that out 1, out 2, out 3 and out 4 (and other anchors) are used. After getting five buffers with animation data, the switcher chooses the animation to be output or blends animations together in necessary proportion and outputs the result into buffer.
|
skinner.transform | Allows setting a custom transformation to the bone, for example, to rotate it and output the whole animation buffer with transformed node. As the input value, this nodes receives a matrix. The transformer should be connected to a skinner node that sets data into an animation buffer of the transformer (for example, skinner.animation node). The animation loop exits on out 0, enters another skinner node and returns from it on in 0. The buffer from it enters into buf 0 of the transformer. After that, it can transform the specified bone and output the result with the rest of animation data as a modified buffer.
|
How to Run Skinner Script
To initialize the Skinner (here, without the visual editor), you need to add the code as in the following example. First of all, a mesh to be controlled by the Skinner is specified. After that, similar to running a Schemer, you need to call update() function. However, it must be called from within world script update() and this function requires to be passed the inverse FPS value to play animations. Update function will trigger execution of all nodes in the Skinner graph one by one starting from the schemer.entry named update and further in the specified order.
#include <core/scripts/utils.h>
#include <core/systems/skinner/skinner.h>
#include <samples/common/common.h>
Unigine::Skinner::Skinner skinner;
Unigine::Skinner::SkinnerMesh skinner_mesh;
/*
*/
int init() {
// Use Skinner namespace.
using Unigine::Skinner;
// Load the skinned mesh and add it into the editor.
ObjectMeshSkinned mesh = add_editor(node_load("samples/skinner/meshes/agent.node"));
// Create a Skinner.
skinner = new Skinner();
// Create SkinnerMesh that will manage the executable script compiled from a graph.
skinner_mesh = new SkinnerMesh(skinner,mesh);
// Load a graph that scripts character behavior.
skinner_mesh.loadScript("samples/skinner/scripts/agent_00.script");
return 1;
}
int update() {
float ifps = engine.game.getIFps();
// Update Skinner mesh.
skinner_mesh.update(ifps);
return 1;
}
In case of using ragdolls, render() function of the world script instead of update() can be used as a starting point for a graph. It will allow to get animations transformation in the current frame and use this data to simulate physics-driven ragdoll. (See details on render().) In this case, schemer.entry named render.
int render() {
float ifps = engine.game.getIFps();
// Run the Skinner graph script from "render" entry.
skinner_mesh.render(ifps);
}
return 1;
}
How to Run Event
To create a one-time action, you can call event() function of the Skinner (found in data/core/systems/skinner/skinner_mesh.h). It will simply call an update function for Skinner script created from a graph with the passed name.
int id = skinner_mesh.getEventID(entry_name);
skinner_mesh.event(id);