01219245/cocos2d/Actions

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
This is part of 01219245.

In this short tutorial, we shall not create a new game, but we will learn a few useful techniques for making our game more engaging.

Actions

Using a combination of states and update method is very useful when we want to (micro) manage our objects. However, sometimes, we just want to tell object to do something like jumping or falling and do not want to keep track of these action.

For example, when our flappy dot dies, we want it to fall down to the ground. If we use states, we might want to add code to move the sprite downwards slighly in update method and to keep track that the dot reaches the ground. It will be easier if you can just tell the dot to fall down, and then stop worrying about it as the object shall move accordingly by itself (or by some magics). In Cocos2d-html5, there is a mechanism for performing that kind of work, called actions (See reference here, type action in to the search box). (Read an overview for C++ version in Cocos2d-x here.)

We shall learn how to use actions by examples.

Falling dot

We shall let the player falls. In this case, we use cc.MoveTo (ref) action to tell the object to move to a specific location. After we create the action, we can tell the object to perform an action simply by calling method runAction.

Add method fall to class Player:

    fall: function() {
	var pos = this.getPosition();
	var fallAction = cc.MoveTo.create( 0.5, new cc.Point( pos.x, 0 ) );
	this.runAction( fallAction );
    }

This action tell the Player to move to the ground (where y co-ordinate = 0) in 0.5 seconds. You can call this method fall at an appropriate location in GameLayer (e.g., after the Player hits the pillars).

Gitmark.png Commit your change.

Flapping dot: animations and combining actions

We shall make the dot animate. The associated action is cc.Animate (ref), which an be created with cc.Animation (ref). (Note the action is cc.Animate, the animation data is created in cc.Animation object.)

Create another 40x40 image for the player animation. Save it as images/dot2.png. The image below shows example images for dot.png and the new dot2.png.

Dot-anim.png

To make an animation we shall repeatedly shows the first image and switch to the second image. To do so we create an cc.Animation and add these two images with appropriate time between the animation frame. The code is shown below. You can add this code to method Player.ctor.

	var animation = new cc.Animation.create();
	animation.addSpriteFrameWithFile( 'images/dot.png' );
	animation.addSpriteFrameWithFile( 'images/dot2.png' );
	animation.setDelayPerUnit( 0.2 );
	var movingAction = cc.Animate.create( animation );
	this.runAction( movingAction );

Try to run the game. The player will move once.

To make the player moves many times, we can call method setLoops in the cc.Animation object. However, if we want the player to move forever, we can create a special action.

Cocos2d-html5 provides a set of actions that combines other actions. These are, for example, cc.Sequence, cc.Repeat, and cc.RepeatForever. In this case, we will use cc.RepeatForever.

To create an action for our animation that runs forever, we use the previous <cc>cc.Animate action to create cc.RepeatForever action like this:

var movingAction = cc.RepeatForever.create( cc.Animate.create( animation ) );

Try and see our player moves.

Moving after the game starts

Our player starts moving before we even start the game. This might not be good. Let's make it start and stop at appropriate times.

Let's start by cleaning our code a bit. The code for creating an animate action in the constructor is long and is not in the same abstraction level as the other code; let's extract it and create a new method:

    createAnimationAction: function() {
	var animation = new cc.Animation.create();
	animation.addSpriteFrameWithFile( 'images/dot.png' );
	animation.addSpriteFrameWithFile( 'images/dot2.png' );
	console.log( animation.getDelayPerUnit() );
	animation.setDelayPerUnit( 0.2 );
	return cc.RepeatForever.create( cc.Animate.create( animation ) );
    }

We want to start and stop the animation, so let's save the action as the player's property. Put this Player.ctor in Player.ctor (replace the part where we create movingAction with a method call):

        this.movingAction = this.createAnimationAction();

We also remove the call the this.runAction.

We will start the animation when the player starts, so let's add this to Player.start method:

        this.runAction( this.movingAction );

Also, we will stop when the player stops. We can add this line to Player.stop method:

        this.stopAction( this.movingAction );
Gitmark.png Try to see if the animation works. Don't forget to commit your change.

Spritesheets

It is hard to maintain a lot of files in the resource. A typical approach for animation sprite frames is to pack them into a single image file. We will learn how to do this in this section. We will do that using cc.SpriteFrame and cc.SpriteFrameCache.

Various tutorials:

Two concepts: Sprite and SpriteFrame

There are two related concepts (two classes) that we have to understand, or at least be able to distinguish them: Sprite and SpriteFrame (ref).

The objects that Cocos2d-html5 draws on the screen must be cc.Node objects. Class cc.Sprite inherits from cc.Node so that its objects can be drawn on the screen. A cc.Sprite can be moved, scaled, rotated and transformed. What is actually drawn on the screen, in case of a cc.Sprite, is a cc.SpriteFrame. A cc.SpriteFrame is a rectangular section of an image (called texture). (Note that it can also be the whole image.)

Let's review what we have done previously.

When we create a cc.Sprite from an image file, we call:

this.initWithFile("images/ship.png");

This will implicitely create a cc.SpriteFrame for this sprite.

When we create an cc.Animation, recall that we add a cc.SpriteFrame with statements like:

animation.addSpriteFrameWithFile( 'images/dot.png' );

We then apply this animation (which contains many cc.SpriteFrame's) to animate a single cc.Sprite.

In summary, the object that is shown, moved, and transformed on the screen is a cc.Sprite; it is the object of the game that we manage. The images shown for that object are cc.SpriteFrame's.

SpriteFrame and SpriteFrameCache

We can create a cc.SpriteFrame from an image file. We can also create it from a rectangular section of that image. However, if we will use this cc.SpriteFrame for many times, we can make it more efficient by caching it in the cc.SpriteFrameCache (ref).

The game contains only one cc.SpriteFrameCache object (i.e., it is a singleton). To access it we call

cc.SpriteFrameCache.getInstance()

We can add a cc.SpriteFrame object with a specific name using method addSpriteFrame (ref) and retrieve it back with method getSpriteFrame. An example code is shown below.

  // save a SpriteFrame to the cache.
  var spriteFrameCache = cc.SpriteFrameCache.getInstance();
  var shipFrame = cc.SpriteFrame.create( 'images/ship.png', cc.rect( 0, 0, 100, 100 ) );
  spriteFrameCache.addSpriteFrame( shipFrame, 'ship_frame1.png' );

  // ...

  // get the SpriteFrame
  var frame = cc.SpriteFrameCache.getInstance().getSpriteFrame( 'ship_frame1.png' );

Since we can specify a rectangular section of an image to create a cc.SpriteFrame. We can pack a lot of images for many sprite frames into a single image file and load all these frames into the sprite frame cache in a single call. To be able to do that, we must be able to specify all the information of all sprite frames in the image file. Cocos2d-html5 uses plist file to keep the frame information.

Creating a plist file

We shall learn to pack all sprite images and create a plist file. To see an example of an image, go to textureTransparentPack.png from MoonWarriors, and to see an example of a plist file, take a look at textureTransparentPack.plist.

There are many software that you can use to pack the sprite images. TexturePacker is one that you can try. See its tutorial here, and here (for the later links, don't forget to set Data Format to Cocos2D).

There are other software that you can use, e.g., XXX. In this tutorial, we will use an open-source darkFunction editor. But the overall idea should be the same.

Loading sprite frames

Here are the steps to load the sprite frames:

  • Add plist file and the image file to the preload resource list. It will be good if you name both files with the same name.
  • Call method addSpriteFrames, e.g.:
cc.SpriteFrameCache.getInstance().addSpriteFrames( 'res/yourtexturefile.plist' );
  • If your plist file and the image file have different names, you should also specify the image file name, e.g.,
cc.SpriteFrameCache.getInstance().addSpriteFrames( 'res/yourtexturefile.plist', 'res/images/yourtexture.png' );
  • You can then get all the SpriteFrame's in that plist/png files, by referring to their names with these methods:
    • getSpriteFrame from cc.SpriteFrameCache (ref) to get the SpriteFrame.
    • cc.Sprite.createWithSpriteFrameName (ref) to create a sprite and initialize it to the specified sprite frame.

Bonus: AnimationCache

Sound effects

Follow these steps:

  • Create the media files. You can use .wav, .mp3, or .ogg files. Put the files in a separate directory, e.g., effects.
  • Add the effect files to resource.js preload the resource. E.g., (don't forget the commas)
    //effect
    {src: 'effects/fall.mp3' }
  • Get the AudioEngine and call it. You can call playEffect (ref)
cc.AudioEngine.getInstance().playEffect( 'effects/fall.mp3' );

cr call playMusic (ref):

cc.AudioEngine.getInstance().playMusic( 'someofyourmusicfile' );

You can even loop the effects or musics by setting loop parameter to true; e.g.,

cc.AudioEngine.getInstance().playMusic( 'someofyourmusicfile', true );