Animation System

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search

Written for CEGUI 0.7


Works with versions 0.7.x (obsolete)

Written for CEGUI 0.8


Works with versions 0.8.x (stable)

Works with latest CEGUI stable!

Requires at least version
0.7.2

Prerequisites

You have to inject time pulses (see System::injectTimePulse), otherwise CEGUI has no idea time is passing and the animations will always be stuck at the position they started at.

Basic concepts

In UI lots of animations are likely to be shared (for example widget specific animation), so CEGUI splits the animation between definition and instance. You can have one animation definition animating large amount of Windows, having only one animation per window is also fine (one animation with only one instance). Animation definitions aren't tied to window type, the only condition is that the target must have all affected (target properties of all affectors inside the animation) and source properties (all source properties in key frames, if any).

Purpose of animation classes

Animation Definition (class Animation)

This class holds defining data for animations. Specifically duration, replay mode, auto start and list of Affectors. This class doesn't directly animate anything. You have to instantiate it via CEGUI::AnimationManager::instantiateAnimation.

Affector (class Affector)

Affector affects (changes the value of) one property. It holds application method (more about that later), target property, used interpolator and list of key frames.

Key Frame (class KeyFrame)

Key frame represents the value of affected property at given time position. It holds value, source property and time position. Key frames can use values of properties of the affected window. If source property is not empty, property is saved when animation starts and used as value for this key frame.

Animation Instance (class AnimationInstance)

This class uses animation definition to animate Window. It holds animation position and playback speed.

Animation Manager (class AnimationManager)

Singleton class. You use it to create new animation definitions and instantiate existing animation definitions. (You should not construct Animation definitions or Animation instances directly, always use Animation Manager).

Interpolator (class Interpolator)

To be able to animate smoothly between 2 discrete keyframes, interpolators had to be introduced. You don't have to use them directly, everything is done for you in the animation classes. You only need to understand their internals if you need custom interpolator (as all basic interpolators are included in CEGUI, if you find some property that can't be animated with stock interpolators, let us know).

The following stock interpolator types are available:

   "String"
   "float"
   "int"
   "uint"
   "bool"
   "Sizef"
   "Vector2f"
   "Vector3f"
   "Rectf"
   "Colour"
   "ColourRect"
   "UDim"
   "UVector2"
   "URect"
   "UBox"
   "USize"

Defining animations

Via code

This sample animation takes 0.3 seconds and in that time, it fades the window and rotates it 10 degrees around Y axis.

{
   CEGUI::Animation* anim = CEGUI::AnimationManager::getSingleton().createAnimation("Testing");
   anim->setDuration(0.3f); // duration in seconds
   anim->setReplayMode(CEGUI::Animation::RM_Once); // when this animation is started, only play it once, then stop
 
   // now we define affector inside our Testing animation
   {
    // this affector changes YRotation and interpolates keyframes with float interpolator
    CEGUI::Affector* affector = anim->createAffector("YRotation", "float");
    // at 0.0 seconds, the animation should set YRotation to 0 degrees
    affector->createKeyFrame(0.0f, "0.0");
    // at 0.3 seconds, YRotation should be 10.0 degrees and animation should progress towards this in an accelerating manner
    affector->createKeyFrame(0.3f, "10.0", CEGUI::KeyFrame::P_QuadraticAccelerating);
   }
 
   // animation can have more than one affectors! lets define another affector that changes Alpha
   {
    // this affector will again use float interpolator
    CEGUI::Affector* affector = anim->createAffector("Alpha", "float");
    affector->createKeyFrame(0.0f, "1.0"); // at 0.0 seconds, set alpha to 1.0
    affector->createKeyFrame(0.3f, "0.5", CEGUI::KeyFrame::P_QuadraticDecelerating); // at 0.3 seconds, set alpha to 0.5, now decelerating!
   }
}

Via Falagard looknfeel XML

<WidgetLook ...>
...
  <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
    <Affector property="Alpha" interpolator="float">
      <KeyFrame position="0.0" value="1.0" />
      <KeyFrame position="0.3" value="0.5" />
    </Affector>
    <Affector property="YRotation" interpolator="float">
      <KeyFrame position="0.0" value="0" />
      <KeyFrame position="0.3" value="10" />
    </Affector>
  </AnimationDefinition>
</WidgetLook>

Animating colours example, with KeyFrame sourceProperty and the colour interpolator:

  <AnimationDefinition name="NormalToHover" duration="0.2" replayMode="once">
    <Affector property="CurrentColour" interpolator="colour">
      <KeyFrame position="0.0" sourceProperty="NormalColour" />
      <KeyFrame position="0.2" sourceProperty="HoverColour" />
    </Affector>
  </AnimationDefinition>

Via separate animation list XML (AnimationManager::loadAnimationsFromXML)

<Animations>
  <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
    <Affector property="Alpha" interpolator="float">
      <KeyFrame position="0.0" value="1.0" />
      <KeyFrame position="0.3" value="0.5" />
    </Affector>
    <Affector property="YRotation" interpolator="float">
      <KeyFrame position="0.0" value="0" />
      <KeyFrame position="0.3" value="10" />
    </Affector>
  </AnimationDefinition>
</Animations>

Instantiating animations

If you created the animation in code or via separate animation definition file, you have to instantiate it (only Falagard instantiates and sets target automatically).

CEGUI::AnimationInstance* instance = CEGUI::AnimationManager::getSingleton().instantiateAnimation(anim);
// after we instantiate the animation, we have to set its target window
instance->setTargetWindow(targetWindow);
 
// at this point, you can start this instance and see the results
instance->start();

Connecting events and animations

You often want your animation to start when something happens (Mouse gets over the widget, new FrameWindow is opened, etc). You have two options to achieve this.

If you already are using XML to define animations, the easiest solution is to use the auto event subscription facility. You add pairs of Event name and action that should happen with the animation ("Start", "Stop", "Pause", "Unpause", etc.) and when target window is set, these subscriptions are automatically made for your convenienve.

Both Falagard and Animations.xml approaches are the same with regards to this. This XML snippet starts the animation when mouse enters the target window's area.

<AnimationDefinition name="Testing" duration="0.3" replayMode="once">
  ...
  <Subscription event="MouseEntersArea" action="Start" />
</AnimationDefinition>

Doing this in code is much more powerful (you can subscribe to a window that's different than target window) but also more messy:

windowWithEvent->subscribeEvent(CEGUI::Window::EventMouseEntersArea, CEGUI::Event::Subscriber(&CEGUI::AnimationInstance::handleStart, instance));

You can also do this from a Lua script:

CEGUI.WindowManager:getSingleton():getWindow("WindowName"):subscribeEvent("MouseEnter", "luafunctionname")

This code sets up the window specified in WindowName, the name of that window on creation or from the XML "Name" property, to react to the mouse entering it by calling the Lua function specified in luafunctionname. The Lua function name argument should be the name, without trailing (), to a function declared in Lua or exported to Lua from C.

Progression

As I created the implementation, I noticed that linear animations look really boring and predictable. Often times accelerating or decelerating anims will look much better and lively. To make creating those easier, I introduced progression to key frames. This enum tells the system how to progress towards the key frame that holds the progression (that means progression on the first key frame is never used!). The default is linear, meaning that progress towards the keyframe is completely linear. Try the other options yourself to see the differences.

animations.xml example

Here we have edited the example animation so that the alpha value uses the quadratic deceleration progression. This will make the alpha value drop quickly in the first frames but the speed of the change will decrease quickly and give a smooth finish to the transition.

<Animations>
  <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
    <Affector property="Alpha" interpolator="float">
      <KeyFrame position="0.0" value="1.0" />
      <KeyFrame position="0.3" value="0.5" progression="quadratic decelerating" />
    </Affector>
    <Affector property="YRotation" interpolator="float">
      <KeyFrame position="0.0" value="0" />
      <KeyFrame position="0.3" value="10" />
    </Affector>
  </AnimationDefinition>
</Animations>

Possible values for this property, in XML, are:

  • "linear": (default value) Simple linear transition. Same rate of progression for every frame.
  • "discrete": Abrupt progression, goes from one value straight to the next one.
  • "quadratic accelerating": Starts slow but accelerates to very fast towards the end.
  • "quadratic decelerating": Starts very fast but comes smoothly to a halt in the end.

Application methods

Affectors offer 3 different "application methods". One is absolute, meaning that the keyframe value is taken as absolute and is directly set to the property after interpolation. The other 2 are relative. That means that when the animation starts, the old values of properties are saved and later used when animating. AM_Relative means saved_value + interpolated_value, AM_RelativeMultiply means saved_value * interpolated_float.

animations.xml example:

In the following example we have changed the rotation affector to do relative rotation instead of absolute, which is default. This means that whatever rotation the window had when the animation started it will add the new rotation to that value. The absolute method would have reset the rotation to 0 in the first key frame and then proceeded to rotate it by 10 degrees during 0.3 seconds.

<Animations>
  <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
    <Affector property="Alpha" interpolator="float">
      <KeyFrame position="0.0" value="1.0" />
      <KeyFrame position="0.3" value="0.5" />
    </Affector>
    <Affector property="YRotation" interpolator="float" applicationMethod="relative">
      <KeyFrame position="0.0" value="0" />
      <KeyFrame position="0.3" value="10" />
    </Affector>
  </AnimationDefinition>
</Animations>

Possible values for this property, in XML, are:

  • "absolute": (default setting) The interpolated value is set as the new property value.
  • "relative": The interpolated value is added to the starting value of the property.
  • "relative multiply": The interpolated value is multiplied with the starting value of the property.