Skip to content
This repository was archived by the owner on Nov 27, 2018. It is now read-only.

Actions

Ibon Tolosana edited this page Feb 20, 2015 · 18 revisions

Actions

Actions (cc.action.Action) are general object state modifiers though more commonly are used to change Node properties in a timely manner. For example, Actions can change a Node position by making it traverse a path for two seconds, or can animate its scale or color. But more generally act on any object, so they also can modify an Audio volume. The V4 actions implementation, which is 100% backwards compatible with Cocos2d-html5 V2 and V3, offers some advantages compared to V3 among which you could find:

  • New chainable API.
  • Full Action lifecycle exposure.
  • Simplified actions hierarchy.
  • Rebuilt actions model. Easing, Repeat, Speed, are Action attributes, and not Actions.

Action Types

The list of available actions in V4 are:

  • Alpha (cc.action.AlphaAction): modifies transparency.
  • Animate (cc.action.AnimateAction): modifies a Sprite SpriteFrame.
  • Move (cc.action.MoveAction): moves a Node across a line.
  • Path (cc.action.PathAction): moves a Node across a complex path. Optionally can tangentially rotate the Node.
  • Rotate (cc.action.RotateAction): changes a Node rotation angle.
  • Scale (cc.action.ScaleAction): changes a Node scale.
  • Tint (cc.action.TintAction): changes a Node Color.
  • Sequence (cc.action.SequenceAction): contains other Actions, creating a separate Action timeline. Contained actions can happen sequential or concurrently.
  • Property (cc.action.PropertyAction): modifies an arbitrary numeric property not necessary belonging to a Node. For example, it can modify a sound volume.
  • Jump (cc.action.JumpAction): modifies a Node position and makes it jump.

All this actions are the equivalent to V3 cc.ActionInterval action types. In V4, there's no difference between Instant and Interval actions. An Instant action is an Zero-length Interval action. For example to chain Actions and intercalate function calls, We have introduced full Action lifecycle.

Action lifecycle

Action expose full lifecycle events as follows:

  • start: called when the action first fires. Repeating actions will call this event only once.
  • end: called when the action finishes. Actions flagged with setRepeatForever don't end unless explicitly stopped.
  • repeat: fired each time the action repeats.
  • apply: fired each time the Action modifies an object (Node or other). The callback will receive the value that has just been set for the properties the action modifies. For example for a cc.action.RotationAction (or cc.rotate) the currently set rotation angle will be notified.
  • pause.
  • resume.

Expected callback functions are:

  • Start, End, Pause and Resume events: function( action, target )
  • Apply: function( action, target, value ). Value is an object of different types. For example:
    • Point for ScaleAction, MoveAction, PathAction, number for RotateAction, AlphaAction, cc.math.Color for TintAction, etc.
    • Object for PropertyAction
  • Repeat: function( action, target, repetitionCount ). If a callback is set it will be called even for Actions that don't repeat with repetitionCount=1 (first call).

The callbacks can be set like:

var action= cc.scaleBy( 2, 1.5, 3 ).
    onStart( function( action, node ) {
        ...
    }).
    onEnd( function( action, node ) {
        ...
    }).
    onRepeat( function( action, node, repetitionCount ) {
        ...
    }).
    onApply( function( action, node, valueSet ) {
        // scale will receive a `cc.math.Point` object in the valueSet parameter.
    });

The Action lifecycle can be applied to V3 style actions. The system guarantees that callbacks can be set for any Action type, event instant Actions like cc.CallFunc and that all callback functions will be called. For example, in a cc.CallFunc action, which does not have execution interval, the following callbacks will be called in a row: start -> apply -> repeat -> end

Delay before and after

The actions system allows to set a delay before and after the Action starts Applying. If the Action repeats, the delays will be reapplied.

// V3
cc.sequence(
   cc.delay( 0.5 ),
   cc.rotate( 2, 0, 360 )
);

// V4
cc.rotate( 2, 0, 360 ).
   setDelay( 0.5 );

// V3 = 3 actions, V4= 1 action.

The following methods control delays:

// d parameter for delay values will be either in seconds or 
// milliseconds depending on the selected time unit. 

action.setDelay(d);
action.setDelayAfterApplication(d);

About time units see: (Conventions).

Action attributes

In V4, the Actions system has been rebuilt from the ground up. In this implementation some Action types have been integrated as attributes of a base Action object. Conceptually, these Actions are understandable more as an attribute than an Action acting on another Action. The following Actions are now an Attribute:

cc.repeat

// V3 call:
cc.repeat( cc.rotate( 1.5, 0, 360 ), 4 );

// V4 call:
cc.rotate( 1.5, 0, 360 ).
    setRepeat( 4 );

cc.repeatForever

// V3 call:
cc.repeatForever( cc.rotate( 1.5, 0, 360 ), 4 );

// V4 call:
cc.rotate( 1.5, 0, 360 ).
    setRepeatForever();

cc.delayTime

// A common V3 delay usage scenario:
cc.Sequence.create(actionBy2, cc.DelayTime.create(0.25), actionBy2.reverse())

// V4
cc.Sequence.create(actionBy2.setDelay(0.25), actionBy2.reverse())

cc.speed

// V3 call:
cc.speed( cc.rotation( 1.5, 0, 360 ), .5 );

// V4
cc.rotation( 1.5, 0, 360 ).
    setSpeed( 0.5 );

cc.callFunc

// V3 call:
cc.sequence( cc.rotation( 1.5, 0, 360 ), cc.callFunc( function() {
    ...
});

// V4
cc.rotation( 1.5, 0, 360 ).onEnd( function() {
    ...
} );

As you can see, there's no need to create a new Action, and to pack different actions in a Sequence to have the callback called when the rotation Action ends.

All cc.ease<*>

This is an important different approach in V4 than V3. Easing has been promoted to a different Object, a cc.action.TimeInterpolator instead of an Action. Thus, Actions have an attribute of TimeInterpolator type which is just an interface of an object which is callable with a time parameter, and a reverse function that gets a reversed TimeInterpolator:

export interface TimeInterpolator {
        (time:number) : number;

        /**
         * Reverse the interpolator instance.
         */
        reverse() : TimeInterpolator;
    }

Some features of an Interpolator object is that they can be inverted, and/or have an Action applied in a ping-pong fashion. The ping-pong effect could be:

// V3
cc.sequence( cc.rotateBy( 2, 0, 360 ), cc.rotateBy( 2, 360, 0 ) );
// or another very common idiom:
var rot= cc.rotateBy( 2, 0, 360 ),
cc.sequence( rot, rot.reverse() );

// V4. false means: not inverted. true means: ping-pong effect.
cc.rotateBy( 2, 0, 360 ).
    setInterpolator( cc.action.Linear( false, true ) );

// or

cc.rotateBy( 2, 0, 360 ).pingpong();

// both code blocks give the same results.
// V4 code has many advantages, like that there's no need to duplicate or reverse the action.

** Important to note is that interpolators should not be applied to root Sequences, that is , top level sequence Actions. This is due to the fact that Action sequences have a lifecycle an interpolator different than linear will break.

Reverse

Until V4, the way of reversing an Action, either Instant, Interval, Sequence or Spawn was to call reverse(), which actually built a new Action, and internally had to apply some complex logic to apply values in reverse order. On the other hand, in V4 we realized there's no need to build new Actions, and modify the application parameters, but rather, apply the Action in reverse time order. To do so, cc.ease actions have been rebuilt as independent objects with reversing capabilities. This new approach is more maintainable and memory efficient.

var move1 = cc.MoveBy.create(3, cc.p(250, 0));
var move2 = cc.MoveBy.create(3, cc.p(0, 50));
var tog1 = cc.ToggleVisibility.create();
var tog2 = cc.ToggleVisibility.create();
var seq = cc.Sequence.create(move1, tog1, move2, tog2, move1.reverse());
var action = cc.Repeat.create( cc.Sequence.create(seq, seq.reverse()), 3 );

// V3
// to have action inversely applied, you can either (duplicate Actions):
var action_reverse = action.reverse();
// to have both: action and its reverse applied:
var sequence= cc.sequence( action, action.reverse() )

// V4
// or call:
// to have action inversely applied (true=reverse, false=ping-pong):
var action_reverse= action.setInterpolator( cc.action.Linear( true, false ) );
// action_reverse and action are the same action.

// to have both: action applied and its reverse applied:
var sequence= action.setReversed();

Chainable API

In V4 Node objects come with some advanced API features like enumerateChildren or a new Action definition API. The reason behind this is to enforce serialization.

The method Node.startActionChain() create an ActionChainContext object which allows for more compact action syntax like:

// V4

n.startActionChain().
    actionSequence().
        setRepeatForever().
        onEnd( function() {
            console.log("end");
        }).
        actionRotate().
            to(180).
            setRelative(true).
            setDuration(1).
        actionScale().
            to( {x:0, y:1 }).
            setRelative(true).
            setDuration(1.5).
        actionScale().
            to( {x:1, y:1}).
            setRelative(true).
            setDuration(1.5).
        actionTint().
            to( {r: -.5, g: -.5, b: -.5 } ).
            setRelative(true).
            setDuration( 2).
    endSequence();

// code equivalent to V3

var r = cc.repeat(cc.rotateBy(2, 180), 1);
var s00 = cc.scaleBy(1.5, 0, 1);
var s01 = cc.scaleBy(1.5, 1, 1);
var seq0 = cc.sequence(r, s00, s01, cc.tintBy(2, -128,-128,-128));

There's an option to work on Sequences like:

n.startActionChain().
        actionRotate().
            to(180).
            setRelative(true).
            setDuration(1).
        then().
        actionScale().
            to( {x:0, y:1 }).
            setRelative(true).
            setDuration(1.5).
        then().
        actionScale().
            to( {x:1, y:1}).
            setRelative(true).
            setDuration(1.5).
        then().
        actionTint().
            to( {r: -.5, g: -.5, b: -.5 } ).
            setRelative(true).
            setDuration( 2).

The then() call chains actions, deferring the start time to the correct execution time based on action's duration and delay before/after application.

Another what to initialize an Action, is by using their initializer object. In order to be fully serializable, each action (and mostly each object) has an initializer object, a companion JSON object which can be passed to constructor functions (one of the reasons to enforce single parameter constructors in V4) or saved from objects. For example:

node.runAction(cc.action.ParseAction( <ActionInitializer object instance> ));

Sequence and Spawn

In this API, unless a then() call is made the result is equivalent to a V3 cc.Spawn Action type. A .actionSequence() call creates a equivalent to a V3 cc.sequence. To make it behave as a cc.sequence or cc.spawn, you can call .setSequence(boolean).

actionTo/actionBy (relative)

V3 API makes clear distinction between relative and absolutely applied Actions. In the chainable API this is stated by calling .setRelative(bool).

serialization

The Actions subsystem is fully serializable throughout the contract:

getInitializer();             // returns an XXXXActionInitializer instance.
__createFromInitializer();    // called internally from deserializer.

Thus code like the following:

var r = cc.repeat(cc.rotateBy(2, 180), 1);
var s00 = cc.scaleBy(1.5, 0, 1);
var s01 = cc.scaleBy(1.5, 1, 1);
var seq0 = cc.sequence(r, s00, s01, cc.tintBy(2, -128, -128, -128));
var seq1 = seq0.reverse();
seq = cc.repeatForever(cc.sequence(seq0, seq1));


var seq2 = cc.action.ParseAction(
    JSON.parse(
        JSON.stringify( 
            // getInitializer will serialize out the SequenceAction
            seq.getInitializer(), null, 2 ) 
    )
);

// seq deep equals seq2

PropertyAction

Though all Action object types could be reduced to a PropertyAction object, for performance reasons they have been promoted to independent objects. Precisely, we avoid to call functions in object hash-mode like (someone shed some light on whether this is true or not):

// avoid
obj[property]= .5;

// in favor of
obj.property= .5;

On the other hand, If you want an action to act on an arbitrary object's property, this is the kind of object you are looking for. The PropertyAction object, receives arbitrary objects in the from and to action fields. This action interpolates values defined in the from field up to a matching value in the to field. For example:

// this code will interpolate 'x' property in an arbitrary object from 5 to 10.

var pa0= new cc.action.PropertyAction({
    duration: 2,
    from: {
        x: 5
    },
    to: {
        x: 10
    }
});

var obj = {
    x: 0
};

The PropertyAction will be smart enough to infer missing from or to properties declaration. For example:

// this code will infer missing from and/or to property. 
// The value is set when the action is initialized for a given target object

var pa0= new cc.action.PropertyAction({
    duration: 2,
    to: {
        x: 10
    }
});

var obj0 = {
    x: 1
};

var pa1= new cc.action.PropertyAction({
    duration: 2,
    from: {
        x: 3
    }
});

var obj1 = {
    x: 10
};

// by the time pa0.step( delta, obj0 ) is called, x initial value will be initialized to 1.
// by the time pa1.step( delta, obj1 ) is called, x final value will be initialized to 10.

Finally, the PropertyAction object is a very powerful beast. It can interpolate more than one property:

// a call to pa0.step(2, obj) will give obj.x= 100, obj.y= 10

var pa0 = new cc.action.PropertyAction({
    duration: 2,
    from : {
        y:7
    },
    to: {
        x: 10
    }
});

var obj = {
    x: 100,
    y: 200
};

And will also be able to act on deep nested properties:

var pa0 = new cc.action.PropertyAction({
    duration: 2,
    from : {
        "p0.y":7
    },
    to: {
        "p1.x": 10
    }
});

var obj = {
    p0 : {
        x: 100,
        y: 0
    },
    p1 : {
        x: 0,
        y: 200
    }
};

Action class hierarchy

The Action hierarchy compared to V3, has been over simplified. V4 API enforces composability instead of extension, and thus, the Action object hierarchy is that all Action types extends from an abstract Action object: cc.action.Action. Therefore, the object hierarchy for actions is flat, where concrete Actions like cc.action.RotateAction only override a few methods, basically to initialize itself.

Clone this wiki locally