Difference between revisions of "Animation System"

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search
m
(Interpolator (class {{DoxygenClass|Interpolator|0.7}}))
 
(13 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{VersionBadge|0.7}} {{VersionAtLeast|0.7.2}}
+
{{VersionBadge|0.7}} {{VersionBadge|0.8}}
 +
{{VersionAtLeast|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 ==
 
== Basic concepts ==
Line 6: Line 10:
 
== Purpose of animation classes ==
 
== Purpose of animation classes ==
  
=== Animation Definition (class CEGUI::Animation) ===
+
=== Animation Definition (class {{DoxygenClass|Animation|0.7}}) ===
 
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.
 
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 CEGUI::Affector) ===
+
=== Affector (class {{DoxygenClass|Affector|0.7}}) ===
 
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.
 
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 CEGUI::KeyFrame) ===
+
=== Key Frame (class {{DoxygenClass|KeyFrame|0.7}}) ===
 
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.
 
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 CEGUI::AnimationInstance) ===
+
=== Animation Instance (class {{DoxygenClass|AnimationInstance|0.7}}) ===
 
This class uses animation definition to animate Window. It holds animation position and playback speed.
 
This class uses animation definition to animate Window. It holds animation position and playback speed.
  
=== Animation Manager (class CEGUI::AnimationManager ===
+
=== Animation Manager (class {{DoxygenClass|AnimationManager|0.7}}) ===
 
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).
 
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 CEGUI::Interpolator) ===
+
=== Interpolator (class {{DoxygenClass|Interpolator|0.7}}) ===
 
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).
 
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 ==
 
== Defining animations ==
Line 28: Line 51:
 
=== Via code ===
 
=== 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.
 
This sample animation takes 0.3 seconds and in that time, it fades the window and rotates it 10 degrees around Y axis.
<code><cpp/>
+
<source lang="cpp">
 
{
 
{
 
   CEGUI::Animation* anim = CEGUI::AnimationManager::getSingleton().createAnimation("Testing");
 
   CEGUI::Animation* anim = CEGUI::AnimationManager::getSingleton().createAnimation("Testing");
Line 38: Line 61:
 
     // this affector changes YRotation and interpolates keyframes with float interpolator
 
     // this affector changes YRotation and interpolates keyframes with float interpolator
 
     CEGUI::Affector* affector = anim->createAffector("YRotation", "float");
 
     CEGUI::Affector* affector = anim->createAffector("YRotation", "float");
     // at 0.0 seconds, the animation should set YRotation to 10.0 degrees
+
     // at 0.0 seconds, the animation should set YRotation to 0 degrees
 
     affector->createKeyFrame(0.0f, "0.0");
 
     affector->createKeyFrame(0.0f, "0.0");
     // at 0.3 seconds, YRotation should be 0 degrees and animation should progress towards this in an accelerating manner
+
     // 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);
 
     affector->createKeyFrame(0.3f, "10.0", CEGUI::KeyFrame::P_QuadraticAccelerating);
 
   }
 
   }
Line 48: Line 71:
 
     // this affector will again use float interpolator
 
     // this affector will again use float interpolator
 
     CEGUI::Affector* affector = anim->createAffector("Alpha", "float");
 
     CEGUI::Affector* affector = anim->createAffector("Alpha", "float");
     affector->createKeyFrame(0.0f, "1.0"); // at 0.0 seconds, set alpha to 0.5
+
     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 1.0 seconds, set alpha to 1.0, now decelerating!
+
     affector->createKeyFrame(0.3f, "0.5", CEGUI::KeyFrame::P_QuadraticDecelerating); // at 0.3 seconds, set alpha to 0.5, now decelerating!
 
   }
 
   }
 
}
 
}
</code>
+
</source>
  
=== Via Falagard looknfeel XML ===
+
=== Via [[Falagard]] looknfeel XML ===
<code>
+
<source lang="xml">
 
<WidgetLook ...>
 
<WidgetLook ...>
 
...
 
...
Line 69: Line 92:
 
   </AnimationDefinition>
 
   </AnimationDefinition>
 
</WidgetLook>
 
</WidgetLook>
</code>
+
</source>
 +
 
 +
Animating colours example, with [http://www.cegui.org.uk/docs/current/xml_animation.html#xml_animation_keyframe KeyFrame sourceProperty] and the colour [http://www.cegui.org.uk/docs/current/xml_animation.html#xml_animation_affector interpolator]:
 +
<source lang="xml">
 +
  <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>
 +
</source>
  
 
=== Via separate animation list XML (AnimationManager::loadAnimationsFromXML) ===
 
=== Via separate animation list XML (AnimationManager::loadAnimationsFromXML) ===
<code>
+
<source lang="xml">
 
<Animations>
 
<Animations>
 
   <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
 
   <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
Line 85: Line 118:
 
   </AnimationDefinition>
 
   </AnimationDefinition>
 
</Animations>
 
</Animations>
</code>
+
</source>
  
 
== Instantiating 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).
+
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).
  
<code><cpp/>
+
<source lang="cpp">
 
CEGUI::AnimationInstance* instance = CEGUI::AnimationManager::getSingleton().instantiateAnimation(anim);
 
CEGUI::AnimationInstance* instance = CEGUI::AnimationManager::getSingleton().instantiateAnimation(anim);
 
// after we instantiate the animation, we have to set its target window
 
// after we instantiate the animation, we have to set its target window
Line 97: Line 130:
 
// at this point, you can start this instance and see the results
 
// at this point, you can start this instance and see the results
 
instance->start();
 
instance->start();
</code>
+
</source>
  
 
== Connecting events and animations ==
 
== Connecting events and animations ==
Line 104: Line 137:
 
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.
 
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.
+
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.
<code>
+
<source lang="xml">
 
<AnimationDefinition name="Testing" duration="0.3" replayMode="once">
 
<AnimationDefinition name="Testing" duration="0.3" replayMode="once">
 
   ...
 
   ...
 
   <Subscription event="MouseEntersArea" action="Start" />
 
   <Subscription event="MouseEntersArea" action="Start" />
 
</AnimationDefinition>
 
</AnimationDefinition>
</code>
+
</source>
  
 
Doing this in code is much more powerful (you can subscribe to a window that's different than target window) but also more messy:
 
Doing this in code is much more powerful (you can subscribe to a window that's different than target window) but also more messy:
<code><cpp/>
+
<source lang="cpp">
 
windowWithEvent->subscribeEvent(CEGUI::Window::EventMouseEntersArea, CEGUI::Event::Subscriber(&CEGUI::AnimationInstance::handleStart, instance));
 
windowWithEvent->subscribeEvent(CEGUI::Window::EventMouseEntersArea, CEGUI::Event::Subscriber(&CEGUI::AnimationInstance::handleStart, instance));
</code>
+
</source>
  
 
You can also do this from a Lua script:
 
You can also do this from a Lua script:
<code>
+
<source lang="lua">
 
CEGUI.WindowManager:getSingleton():getWindow("WindowName"):subscribeEvent("MouseEnter", "luafunctionname")
 
CEGUI.WindowManager:getSingleton():getWindow("WindowName"):subscribeEvent("MouseEnter", "luafunctionname")
</code>
+
</source>
 
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.
 
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.
  
Line 127: Line 160:
 
=== animations.xml example ===
 
=== 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.
 
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.
<code>
+
<source lang="xml">
 
<Animations>
 
<Animations>
 
   <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
 
   <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
Line 140: Line 173:
 
   </AnimationDefinition>
 
   </AnimationDefinition>
 
</Animations>
 
</Animations>
</code>
+
</source>
 
Possible values for this property, in XML, are:
 
Possible values for this property, in XML, are:
 
* "linear": (default value) Simple linear transition. Same rate of progression for every frame.  
 
* "linear": (default value) Simple linear transition. Same rate of progression for every frame.  
Line 152: Line 185:
 
=== animations.xml example: ===
 
=== 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.
 
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.
<code>
+
<source lang="xml">
 
<Animations>
 
<Animations>
 
   <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
 
   <AnimationDefinition name="Testing" duration="0.3" replayMode="once">
Line 165: Line 198:
 
   </AnimationDefinition>
 
   </AnimationDefinition>
 
</Animations>
 
</Animations>
</code>
+
</source>
 
Possible values for this property, in XML, are:
 
Possible values for this property, in XML, are:
 
* "absolute": (default setting) The interpolated value is set as the new property value.
 
* "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": 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.
 
* "relative multiply": The interpolated value is multiplied with the starting value of the property.
 +
 +
[[Category:Manuals]]

Latest revision as of 01:19, 9 July 2014

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.