Table of Contents

Actions are a handy way to animate properties with just a few lines of code. They can also be a way to call a function or dispatch an event after a delay. You can make actions happen in a paticular order, all at the same time, or some combination of the two. Let's take a look.

Learning Objectives

  • Property Actions
  • Delay Actions
  • Call Actions
  • ActionSequences
  • ActionGroups
  • Looped Actions

Level Setup

What Are Actions

Often in games we find that we wish to smoothly transition a property from its existing value to another target value. This is often done by calculating interpolated values on logic update between an the existing value and the target value using a function such as Math.Lerp. While there are times when it is advantageous to perform these operations in an update function it is more common to want to begin an interpolation operation and have it finish on its own. Actions can do this and more and act as a generic structure to organize in game actions that need to occur in sequence or parrallel.

Property Actions

The first and most common action type is a Property Action which allows us to interpolate a property from its current value to a target value, over a specific duration, at a rate defined by an easing function. We can break down the construction of a property action parameter by parameter.

Actions.Property(this.Owner.Actions,
                 @this.Area.Size,
                 this.TargetSize,
                 this.Duration,
                 Ease.Linear);
Action Property Parameters
this.Owner.Actions ActionSet The ActionSet which will update this action
@this.Area.Size PropertyDelegate Delegate of the property to be modified by the action
this.TargetValue Property type of the PropertyDelegate passed into the previous parameter (i.e. Real3 in this example) Value to which the action will interpolate the given property
this.Duration Real The duration over which the action will be completed
Ease.Linear Ease The Ease function used to calculate the rate of interpolation

Using a Property Action

Now that we've covered the basics of what a property action is let's try using one:

  • Update the SpriteFader script:
class SpriteFader : NadaComponent
{
  [Dependency] var Sprite : Sprite;
  [Dependency] var Area : Area;
  
  //Target scale to interpolate towards  
  [Property]
  var TargetSize : Real2 = Real2(2, 2);
  
  //Duration of the interpolation
  [Property]
  var Duration : Real = 1.0;
  
  function Initialize(init : CogInitializer)
  {
    //Call the animate function
    this.Animate();
  }
  
  //Interpolate the object's scale to a target value over a duration
  function Animate()
  {
    Actions.Property(this.Owner.Actions,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
  }
}

ScaleOnly

Here you can see the property action interpolating the title scale over the given period of one second.

Action Sequences

You may already be wondering how to chain actions together, well this is the purpose of an ActionSequence.

  • Add the following property to SpriteFader:
  [Property]
  var TargetColor : Real4 = Real4(1,0,0,1);
  • Update the Animate function in SpriteFader script:
  function Animate()
  {
    //create a sequence driven by the object
    var actionSequence = Actions.Sequence(this.Owner.Actions);
    //put the scaling property action first in the sequence
    Actions.Property(actionSequence,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //put the color interpolation property action second in the sequence
    Actions.Property(actionSequence,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
  }

ScaleThenColor

Here we can see sequences allow up to play actions in a linear order. This is very helpful, but what if we want to make these actions happen at the same time?

Action Groups

Similiar to how an ActionSequence will let us perform actions in sequence an ActionGroup will let us perform actions in parallel (at the same time).

  • Update the Animate function in SpriteFader script:
  function Animate()
  {
    //Create a group driven by the object
    var actionGroup = Actions.Group(this.Owner.Actions);
    //Put the scaling property  in the group
    Actions.Property(actionGroup,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(actionGroup,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
  }

ScaleAndColor

Now that the property actions are in a group instead of in a sequence they happen at the same time.

Combining Sequences and Groups

So far we have seen sequences and groups working seperately but what if we wanted to combine them. That would mean we could make sequences of grouped actions, or groups of sequenced actions. We can use the SpriteFader component to test this.

  • Add the following variables to SpriteFader:
  var OriginalSize : Real2;
  
  var OriginalColor : Real4;
  • Update the Initialize function in SpriteFader:
  function Initialize(init : CogInitializer)
  {
    //Capture original values
    this.OriginalSize = this.Area.Size;
    this.OriginalColor = this.Sprite.VertexColor;
    
    //Call the animate function
    this.Animate();
  }
  • Update the Animate function in SpriteFader:
  function Animate()
  {
    //Create a group driven by the object
    var actionGroup = Actions.Group(this.Owner.Actions);
    //Create the sequence to the text up and down
    var scaleSequence = Actions.Sequence(actionGroup);
    //Put the scale up property action in the sequence
    Actions.Property(scaleSequence,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the scale down property action in the sequence
    Actions.Property(scaleSequence,
                     @this.Area.Size,
                     this.OriginalSize,
                     this.Duration,
                     Ease.Linear);
    
    //Create the sequence to interpolate
    var colorSequence = Actions.Sequence(actionGroup);
    //Put the interpolate to black property action in the color sequence
    Actions.Property(colorSequence,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
    //Put the interpolate to white property action in the color sequence
    Actions.Property(colorSequence,
                     @this.Sprite.VertexColor,
                     this.OriginalColor,
                     this.Duration,
                     Ease.Linear);
  }

GroupedSequences

It should be noted that this.Owner.Actions is an ActionGroup so in this simple case we could have just passed it to the constructor for the action sequences. We could also invert the group and sequence nesting to achieve the same affect from a different approach.

  • Update the Animate function in SpriteFader:
  function Animate()
  {
    //Create a group driven by the object
    var actionSequence = Actions.Sequence(this.Owner.Actions);
    //Create the group to scale up and black out the text
    var scaleUpGroup = Actions.Group(actionSequence);
    //Put the scaling property  in the group
    Actions.Property(scaleUpGroup,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleUpGroup,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
    
    //Create the group to scale down and white out the text
    var scaleDownGroup = Actions.Group(actionSequence);
    Actions.Property(scaleDownGroup,
                     @this.Area.Size,
                     this.OriginalSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleDownGroup,
                     @this.Sprite.VertexColor,
                     this.OriginalColor,
                     this.Duration,
                     Ease.Linear);
  }

SequencedGroups

Now we can see both property actions in the scaleUpGroup complete and then the two actions in the scaleDownGroup run.

Call Actions

Another type of action is the call option. This allows you to put calls to functions in your sequences or groups. Test this out by printing at the beginning and end of the title animation sequences.

  • Add the following Print function to SpriteFader:
  function Print(str : String)
  {
    Console.WriteLine(str);
  }
  • Update the Animate function in SpriteFader:
  function Animate()
  {
    //Create a group driven by the object
    var actionSequence = Actions.Sequence(this.Owner.Actions);
    Actions.Call(actionSequence, this.Print, "Animation sequence started");
    
    //Create the group to scale up and black out the text
    var scaleUpGroup = Actions.Group(actionSequence);
    //Put the scaling property  in the group
    Actions.Property(scaleUpGroup,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleUpGroup,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
    
    //Create the group to scale down and white out the text
    var scaleDownGroup = Actions.Group(actionSequence);
    Actions.Property(scaleDownGroup,
                     @this.Area.Size,
                     this.OriginalSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleDownGroup,
                     @this.Sprite.VertexColor,
                     this.OriginalColor,
                     this.Duration,
                     Ease.Linear);
    
    Actions.Call(actionSequence, this.Print, "Animation sequence complete");
  }

SequencedGroupsWithPrint

SpriteFader scaling up and changing the color of the menu title at the same time using a group in a sequence then reversing the actions with another group. The animation is followed by the following console output

Animation sequence started
Animation sequence complete

Now you can see the print function executed at the beginning and end of the sequence. This can be very helpful with debugging the behaviors of actions but it also has a slightly more complex application.

(NOTE)Call Action Function Parameters: Action.Call is overloaded to take multiple parameters for the function passed to it. As seen above you pass the values that should be passed to the given function as parameters to the call action constructor itself.

Action Loops

  • Add the following property to SpriteFader:
  var LoopedSequence : ActionSet;
  • Update the Animate function in SpriteFader:
  function Animate()
  {
    if(this.LoopedSequence != null)
      this.LoopedSequence.Cancel();
    
    //Create a seqeunce driven by the object
    this.LoopedSequence = Actions.Sequence(this.Owner.Actions);
    Actions.Call(this.LoopedSequence, this.Print, "Animation sequence started");
    
    //Create the group to scale up and black out the text
    var scaleUpGroup = Actions.Group(this.LoopedSequence);
    //Put the scaling property  in the group
    Actions.Property(scaleUpGroup,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleUpGroup,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
    
    //Create the group to scale down and white out the text
    var scaleDownGroup = Actions.Group(this.LoopedSequence);
    Actions.Property(scaleDownGroup,
                     @this.Area.Size,
                     this.OriginalSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleDownGroup,
                     @this.Sprite.VertexColor,
                     this.OriginalColor,
                     this.Duration,
                     Ease.Linear);
    
    //Call animate at the end of the sequence to loop
    Actions.Call(this.LoopedSequence, this.Print, "Animation sequence complete");
    Actions.Call(this.LoopedSequence, this.Animate);
  }

Looped

SpriteFader scaling up and changing the color of the menu title at the same time using a group in a sequence then reversing the actions with another group, then looping the sequence

Now we can see the action sequence call the function that originally set it up. This create a loop in the sequence of actions which will repeat. You should also notice that we started storing the action sequence in a member variable of SpriteFader instead of a local variable. This does not change the behavior of the action sequence, but it does allow us to examine, pause, and cancel the sequence while it is running. This usually good practice for all action sets. We actually already introduced a small example of how one may use an ActionSet member variable when we introduced these two lines at the top of animate.

  if(this.LoopedSequence != null)
      this.LoopedSequence.Cancel();

By checking if this.LoopedSequence is not null we can determine if there is already a sequence in existence intended to animate the sprite. If the Animate function were to be called elsewhere without this check it could result in multiple sequence performing the same scaling and color interpolation operations on the same object independently and simultainiously. This can result in so very undesirable bugs where actions overlap each other in unintended ways. By cancelling any existing LoopedSequence before we start another we can avoid this issue almost entirely.

Delay Actions

Delay actions allow the insertion of a time based delay in an action sequence. When put in a group they can prevent the group completing but will not keep other actions in the group from running.

  • Update the Animate function in SpriteFader:
  function Animate()
  {
    if(this.LoopedSequence != null)
      this.LoopedSequence.Cancel();
    
    //Create a group driven by the object
    this.LoopedSequence = Actions.Sequence(this.Owner.Actions);
    Actions.Call(this.LoopedSequence, this.Print, "Animation sequence started");
    
    //Create the group to scale up and black out the text
    var scaleUpGroup = Actions.Group(this.LoopedSequence);
    //Put the scaling property  in the group
    Actions.Property(scaleUpGroup,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleUpGroup,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
    
    //Create the group to scale down and white out the text
    var scaleDownGroup = Actions.Group(this.LoopedSequence);
    Actions.Property(scaleDownGroup,
                     @this.Area.Size,
                     this.OriginalSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleDownGroup,
                     @this.Sprite.VertexColor,
                     this.OriginalColor,
                     this.Duration,
                     Ease.Linear);
    
    Actions.Call(this.LoopedSequence, this.Print, "Animation sequence complete");
    //Delay the animation loop
    Actions.Delay(this.LoopedSequence, this.Duration);
    Actions.Call(this.LoopedSequence, this.Print, "Animation loop delay complete");
    Actions.Call(this.LoopedSequence, this.Animate);
  }

LoopedDelayed

SpriteFader scaling up and changing the color of the menu title at the same time using a group in a sequence then reversing the actions with another group, then looping the sequence after a short delay

Now we have the title animating in a loop with a short delay in the sequence before the next itteration of the loop.

Event Actions

The final action type is the event action. This allows you to instantiate an even object as if you were about to dispatch it, but instead it pass it to the action to be dispatched later.

  • Add the following in the SpriteFader class within SpriteFader:
  sends SpriteFaded : NadaEvent;
  • Update the Initialize function in SpriteFader:
  function Initialize(init : CogInitializer)
  {
    //Capture original values
    this.OriginalSize = this.Area.Size;
    this.OriginalColor = this.Sprite.VertexColor;
    
    Zilch.Connect(this.Space, Events.SpriteFaded, this.OnSpriteFaded);
    
    //Call the animate function
    this.Animate();
  }
  • Add the following function to SpriteFader:
  function OnSpriteFaded(event : NadaEvent)
  {
    Console.WriteLine("Sprite Faded");
  }
  • Update the Initialize function in SpriteFader:
  function Animate()
  {
    if(this.LoopedSequence != null)
      this.LoopedSequence.Cancel();
    
    //Create a group driven by the object
    this.LoopedSequence = Actions.Sequence(this.Owner.Actions);
    Actions.Call(this.LoopedSequence, this.Print, "Animation sequence started");
    
    //Create the group to scale up and black out the text
    var scaleUpGroup = Actions.Group(this.LoopedSequence);
    //Put the scaling property  in the group
    Actions.Property(scaleUpGroup,
                     @this.Area.Size,
                     this.TargetSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleUpGroup,
                     @this.Sprite.VertexColor,
                     this.TargetColor,
                     this.Duration,
                     Ease.Linear);
    
    //Create the group to scale down and white out the text
    var scaleDownGroup = Actions.Group(this.LoopedSequence);
    Actions.Property(scaleDownGroup,
                     @this.Area.Size,
                     this.OriginalSize,
                     this.Duration,
                     Ease.Linear);
    //Put the color interpolation property action the group
    Actions.Property(scaleDownGroup,
                     @this.Sprite.VertexColor,
                     this.OriginalColor,
                     this.Duration,
                     Ease.Linear);
    
    //Delay the animation loop
    Actions.Delay(this.LoopedSequence, this.Duration);
    Actions.Event(this.LoopedSequence, this.Space, Events.SpriteFaded, NadaEvent());
    Actions.Call(this.LoopedSequence, this.Animate);
  }

LoopedDelayedEvents

SpriteFader scaling up and changing the color of the menu title at the same time using a group in a sequence then reversing the actions with another group, then looping the sequence after a short delay

Related Materials

Manual

Reference

Classes

Nada Base Types

Commands

Tasks

  • T782