It is possible to export some animated properties of objects in Blender, that is location, rotation and scale.
This results in what is called SceneNode Animation (https://ogrecave.github.io/ogre/api/latest/_animation.html#SceneNode-Animation) in Ogre.
This kind of SceneNode animation is very useful to animate the movement of a camera, a character patrolling in a game and any other kind of animation one can think of.
The advantage over skeletal animation is that it is more performant due to its simplicity, and the effect (translation/rotation/scale) is applied to the Node and its children (if setInherit()
is true).
The exported animation data goes into the .scene file describing the whole Scene crafted in Blender, there isn't a serializer for SceneNode Animation data, so this data has to be processed by the DotScene plugin.
Refer to the DotScene documentation (https://github.com/OGRECave/ogre/tree/master/PlugIns/DotScene) on how to load the .scene file into your Ogre application.
The animation is being exported frame by frame with Blender doing the interpolation between keyframes. This has the advantage that any tuning done to the F-Curves is preserved in the exported animation. Another advantage is that it is not necessary to choose an interpolation method other than IM_LINEAR on the Ogre side, making the animation more performant.
The disadvantage is that the .scene file is now less human-readable due to the chunk of data from all the animation frames and there is no control of the animation in the code.
Another disadvantage is that the timing of the frames will be interpreted according to Blender's current FPS setting which is by default 24 fps.
This setting can be changed in the Render Tab
, Dimensions Panel
, there is a drop-down list named Frame Rate
where you can select a value or create a new one with custom.
In a similar fashion to the exporting of Skeletal Animation, every action should go into an NLA Track to have it exported (the name of the exported animation being that of the action).
The reason for using NLA Tracks is to have better control over what actions should be exported as animations.
Here is a short video on how to animate objects in Blender: How To Move Animated Objects (Blender Tutorial)
We will use the default cube to simplify things.
Just select the cube and toggle the Transform
properties with the keyboard shortcut N
.
Make sure that the current frame is 1
and with the mouse cursor over one of the Location
properties, press I
.
This has the effect of automatically creating an action called CubeAction
and inserting a Keyframe at frame 1
.
Do the same for Rotation
and Scale
.
Then jump to frame 30
and change Location.Z
to 2m
, Rotation.Z
to 45º
and Scale.XYZ
to 1.5
.
Hover the mouse cursor on Location
, Rotation
and Scale
pressing I
to insert a Keyframe at frame 30
.
Play the animation and you will see the cube go upwards, increase in size 1.5 times and rotate 45º degrees to the right.
Don't forget to Push Down
the action from the Action Editor
to put it into the NLA stack before exporting.
Here is a short video on how to have an object follow a path in Blender Create Path For Any Object To Follow - Blender (BEGINNERS)
For this simple tutorial, we will use the default cube to follow a path in Blender.
Create a new scene and select the default cube.
Press Shift-A
to open the Add
menu and add a circle (Curve -> Circle
).
Scale the circle to four times its size by pressing S
and then move the mouse to increase the size of the circle so you have a decent-sized path.
Now, select the cube and go to the Constraints
tab and click on Add Object Constraint
to add a new constraint.
Select the Follow Path
constraint to have the cube following the circle as a path.
Now, set the "BezierCircle" as the Target
, click on Follow Curve
and Fixed Position
.
With meshes not as symmetrical as the cube you might have to fiddle with the Forward Axis
to have the meshes front facing the direction of the path.
To have an animation of the cube following the path, hover the mouse over the Offset
slider and press I
to insert a Keyframe at frame 1.
Select frame 60 and change the value of the Offset
slider to 1.0 and press I
to insert a Keyframe at frame 60.
The cube follows the path but the speed is not constant, to have a constant speed it is necessary to modify the F-Curve
and set it to linear interpolation.
To do this, go to the Graph Editor
and select Key->Interpolation Mode->Linear
, now the cube will follow the path at a constant speed.
To have a path that is more sinusoidal than circular it is possible to also use a Bezier Curve
as the Target
of the Follow Path
constraint.
If you want this path to loop on itself press Alt-C
when editing the Bezier Curve
in edit mode (also E
adds more segments to the curve).
To export a .scene with the animated cube following the path, the last step is to add an NLA Track so that the exporter knows which action to export.
Go to the Dope Sheet
, change the mode to Action Editor
and select Push Down
to add the action to the NLA stack.
To have an animated character follow a path there are a couple of things to take into account.
It is very important to parent the animated character armature to an empty object.
Add the empty object with Shift-A->Empty->Plain Axes
, then select the armature and afterwards select the empty by pressing Shift
to have both objects selected in the proper order.
Press Ctrl-P
to get the Parent
menu and select Object (Keep Transform)
.
Having the Armature being a child of the empty object allows us to have the path following be independent of the Armature's actions.
The second advantage of having the Armature parented to an empty is that it is possible to control the scale of the animated character without breaking the animations.
Just set the desired scale of the empty object and the armature will inherit that scale, the exported .scene will have an Ogre SceneNode with the name of the empty as a parent of the Armature SceneNode.
To have a camera following a path and tracking a target we will be combining the things learned in the previous sections.
Create a new scene and delete the default cube.
Add three empty objects (3 x Shift-A->Empty->Plain Axes
) and name them as follows: Tracked, CameraBase and CameraArm.
Parent the empty CameraArm to CameraBase so that the arm is a child of the base, and then parent the camera to CameraArm.
Clear the camera location and rotation by pressing Alt-G
, Alt-R
.
Add a Monkey mesh with Shift-A->Mesh->Monkey
to have some objects for the camera to look at.
Then add a BezierCircle to have a path to follow, scale it and name it CameraPath.
Select the CameraBase empty and add a constraint to follow the CameraPath bezier curve with Forward:
-X and Up:
Z (also check Follow Curve
and Fixed Position
).
Select the CameraArm empty and move it upwards so that the camera is looking at the object from a vantage point and not the floor.
Then select the camera and add a constraint of type Locked Track
, with Target:
Tracked, To:
-Z and Lock:
X.
The purpose of having the camera track the Tracked empty object instead of the Monkey mesh is that now it is not locked to the center of the object.
If the Monkey object is going to move as well, then the Tracked empty object can be made a child of the Monkey object or use more empty objects to move the monkey.
It is a good general rule to separate movements into independent nodes as was done with CameraBase and CameraArm which are generally used to separate the yaw and pitch of the camera.
Now, select CameraBase and repeat the process as in the previous tutorial to have the CameraBase empty follow the circular path, in the process the camera will keep on tracking the Tracked empty object.
It should be noted that Ogre has a setAutoTracking()
function for the SceneNodes, which would be a preferable way to track objects.
Please consult the manual for more details: https://ogrecave.github.io/ogre/api/latest/class_ogre_1_1_scene_node.html#ae4c8588895d3623bbe0007ea157af1a4
If you are using DotScene to load the .scene into your Ogre app, by default the animations won't play because they are disabled by default.
Besides enabling the animations it is also required to addTime()
to their AnimationStates, we will use a controller for that.
int main()
{
...
auto& controllerMgr = Ogre::ControllerManager::getSingleton();
for (auto animationState : mSceneMgr->getAnimationStates())
{
// Print the animation name
std::cout << "animationState: " << animationState.second->getAnimationName() << std::endl;
// Enable the Animation State (they are disabled by default)
animationState.second->setEnabled(true);
// Set the animation to looping (if your node animation loops)
animationState.second->setLoop(true);
// Create a controller to pass the frame time to the Animation State, otherwise the animation won't play
// (this is a better method than using animationState->addTime() in your main loop)
controllerMgr.createFrameTimePassthroughController(Ogre::AnimationStateControllerValue::create(animationState.second, true));
}
}
Consult the Ogre manual for further information:
- https://ogrecave.github.io/ogre/api/latest/class_ogre_1_1_scene_manager.html
- https://ogrecave.github.io/ogre/api/latest/class_ogre_1_1_animation_state.html
- https://ogrecave.github.io/ogre/api/latest/class_ogre_1_1_scene_node.html
- https://ogrecave.github.io/ogre/api/latest/class_ogre_1_1_controller_manager.html
- https://ogrecave.github.io/ogre/api/latest/class_ogre_1_1_controller.html
Some tips for troubleshooting:
- Make sure that the actions/animations you want to export have their corresponding NLA tracks associated with the object.
- To use Skeletal Animation combined with the path following remember to have the armature be a child of an empty to avoid problems.
- Check the .scene file to see if the animation was exported as expected, the problem might not be in the exported data.
If you have many actions it can become tiresome to push every one of them into the NLA stack to have them exported.
Here is a very simple Blender script to add every action as an NLA track, any action which should not be exported can simply be deleted from the list of NLA Tracks
Switch to the Scripting
layout and copy the following script:
import bpy
def add_NLA_strips(object):
for action in bpy.data.actions:
track = bpy.data.objects[object].animation_data.nla_tracks.new()
track.strips.new(action.name, action.frame_range[0], action)
track.name = action.name
# Add actions from a single object:
add_NLA_strips('Cube')
# Add actions for every object:
for object in bpy.data.objects:
add_NLA_strips(object)