How to create a shake action in Cocos2d-x (with source code)

While working on my next game Don’t Feed the Trolls, I was looking for a “shake effect”. When the player feeds a troll, I want the whole screen to shake to improve visual feedback.
A shake effect is very easy to implement, so I quickly hacked in my code to see if it was un interesting feature. After a quick test on the Android version of the game, I could see it really does improve the game. So I wrote a proper cocos2d-x action to integrate the effect in a clean way. I’m releasing the code here hoping other developers will find it useful for their own games.

The effect

The shake is a small random movement. It can be applied to any cocos2d node (CCNode). In Don’t Feed the Trolls, I applied the effect to my top-most node, therefore the whole screen is shaking.

The action

I derived my CCShake class from CCActionInterval. The action is initialized with two parameters:
time : the duration of the effect.
strength : the distance of the shake movement. You can have different strengths in X and Y if you wish.
You create the action with CCShake::actionWithDuration and send it as a parameter of CCNode::runAction.


// Shake node_to_shake for 5 seconds.
node_to_shake->runAction( CCShake::actionWithDuration( 5.0f, 10.0f ) );

When the action is completed, the node is moved back to its starting position.

Source code

The code is free for personal and commercial use. If you plan to use it, feel free to credit me, send me an email and/or follow me on twitter/facebook. This is of course not compulsory.

Header: CCShake.h


#ifndef __SHAKE_H__
#define __SHAKE_H__

#include "CCActionInterval.h"
using namespace cocos2d;

class CCShake : public CCActionInterval
{
	// Code by Francois Guibert
	// Contact: www.frozax.com - http://twitter.com/frozax - www.facebook.com/frozax
public:
	CCShake();

	// Create the action with a time and a strength (same in x and y)
	static CCShake* actionWithDuration(ccTime d, float strength );
	// Create the action with a time and strengths (different in x and y)
	static CCShake* actionWithDuration(ccTime d, float strength_x, float strength_y );
	bool initWithDuration(ccTime d, float strength_x, float strength_y );

	virtual void startWithTarget(CCNode *pTarget);
	virtual void update(ccTime time);
	virtual void stop(void);

protected:
	// Initial position of the shaked node
	float _initial_x, _initial_y;
	// Strength of the action
	float _strength_x, _strength_y;
};

#endif //__SHAKE_H__

Source: CCShake.cpp

// Code by Francois Guibert
// Contact: www.frozax.com - http://twitter.com/frozax - www.facebook.com/frozax
#include "cocos2d.h"
#include "CCShake.h"

// not really useful, but I like clean default constructors
CCShake::CCShake() : _strength_x(0), _strength_y(0), _initial_x(0), _initial_y(0)
{
}

CCShake* CCShake::actionWithDuration( ccTime d, float strength )
{
	// call other construction method with twice the same strength
	return actionWithDuration( d, strength, strength );
}

CCShake* CCShake::actionWithDuration(ccTime duration, float strength_x, float strength_y)
{
	CCShake *p_action = new CCShake();
	p_action->initWithDuration(duration, strength_x, strength_y);
	p_action->autorelease();

	return p_action;
}

bool CCShake::initWithDuration(ccTime duration, float strength_x, float strength_y)
{
	if (CCActionInterval::initWithDuration(duration))
	{
		_strength_x = strength_x;
		_strength_y = strength_y;
		return true;
	}

	return false;
}

// Helper function. I included it here so that you can compile the whole file
// it returns a random value between min and max included
float fgRangeRand( float min, float max )
{
	float rnd = ((float)rand()/(float)RAND_MAX);
	return rnd*(max-min)+min;
}

void CCShake::update(ccTime time)
{
	float randx = fgRangeRand( -_strength_x, _strength_x );
	float randy = fgRangeRand( -_strength_y, _strength_y );

	// move the target to a shaked position
	m_pTarget->setPosition( ccp( randx, randy) );
}

void CCShake::startWithTarget(CCNode *pTarget)
{
	CCActionInterval::startWithTarget( pTarget );

	// save the initial position
	_initial_x = pTarget->getPosition().x;
	_initial_y = pTarget->getPosition().y;
}

void CCShake::stop(void)
{
	// Action is done, reset clip position
	m_pTarget->setPosition( ccp( _initial_x, _initial_y ) );

	CCActionInterval::stop();
}

Possible improvements

This is really a simple class, for a simple effect. You can improve the effect in many ways. For instance, we could animate the strength of the shake over time.

That's it !

Have fun with it, feel free to discuss the source code in the comments, twitter or facebook.

Comments

  1. wpd

    Many thanks! I’m looking for this Action Effect.

    And I modify the update() method to suit my game.

    void CCShake::update(ccTime time)
    {
    float randx = fgRangeRand( -_strength_x, _strength_x );
    float randy = fgRangeRand( -_strength_y, _strength_y );

    // move the target to a shaked position
    m_pTarget->setPosition( ccpAdd(ccp(_initial_x, _initial_y),ccp( randx, randy)));
    }

  2. Sorry about the late reply, wpd. But you’re right, and your change is actually a bug fix. My version only works for objects placed on (0,0) (it was the whole screen in my case).

  3. vinicius

    Hi, I want to use it on my game, whats is the best way of crediting you?

    1. Hi,
      Anything will be fine : mention in the credits, or on the web game page if any, or a tweet… ;)

  4. phucanh

    This is great!
    But I saw an issue with your code. It always set position of Node to (0, 0) while shaking. With that, this action is suitable for shaking entire screen.
    To shake any Node, I think you should add this code in the update function

    Vec2 newPos = Vec2(randx, randy) + Vec2(_initial_x, _initial_y);
    m_pTarget->setPosition( newPos );

    BR

Comments are closed.