Tutorial : Cocos2d camera shake effect from unity (Updates)!

i found a way to make a cool camera shake effect which is inspired or i should say taken from unity!

here is the effect that we are trying to achieve:

and here is some explanations on how some of it works:

Warning: we are not just using the method explained above on its own we are using it with perlin noise which is not explained in the above topic.

i’ll start by saying that we aren’t really shaking the camera all though you could (i haven’t tried that) so any way what we are really doing is shaking the layer position by generating random values but not just any random values its smoothed random values generated by perlin noise algorithm/function and by using the layer schedule method.

so let’s get down to the code:

there are lots of perlin noise implementations out there and here are two (choose the one you like because i haven’t noticed any difference in the effect whether i used the first or the second implementation).

First implementation(longer) :
i found this implementation in here: http://www.dreamincode.net/forums/topic/66480-perlin-noise/

here it is with some tweaking and as you can see i replaced the interpolation() method with cocos2d MathUtil::lerp():

inline double findnoise(double x, double y)
{
	int n = (int)x + (int)y * 57;
	n = (n << 13) ^ n;
	int nn = (n*(n*n * 60493 + 19990303) + 1376312589) & 0x7fffffff;
	return 1.0 - ((double)nn / 1073741824.0);
}


double noise(double x, double y)
{
	double floorx = (double)((int)x);//This is kinda a cheap way to floor a double integer.
	double floory = (double)((int)y);
	double s, t, u, v;//Integer declaration
	s = findnoise(floorx, floory);
	t = findnoise(floorx + 1, floory);
	u = findnoise(floorx, floory + 1);//Get the surrounding pixels to calculate the transition.
	v = findnoise(floorx + 1, floory + 1);

	double int1 = MathUtil::lerp(s, t, x - floorx);
	double int2 = MathUtil::lerp(u, v, x - floorx);
	return MathUtil::lerp(int1, int2, y - floory);
}

Second implementation (shorter):

its from here: http://stackoverflow.com/questions/16569660/2d-perlin-noise-in-c

float noise(int x, int y) {
	int n = x + y * 57;
	n = (n << 13) ^ n;
	return (1.0 - ((n * ((n * n * 15731) + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0);
}

feel free to use any other implementation rather than what i provided if you like.!

and here where the magic goes, i used the scheduler method with lambda functions on touch like this:

bool TestScene::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {

// experiment more with these four values until you rest with something you like!
	float interval = 0.f;
	float duration = 0.5f;
	float speed = 2.0f;
	float magnitude = 1.0f;

	static float elapsed = 0.f;

	this->schedule([=](float dt) {
                float randomStart = random(-1000.0f, 1000.0f);(bug fixed)
		elapsed += dt;

		float percentComplete = elapsed / duration;

// We want to reduce the shake from full power to 0 starting half way through
		float damper = 1.0f - clampf(2.0f * percentComplete - 1.0f, 0.0f, 1.0f);

// Calculate the noise parameter starting randomly and going as fast as speed allows
		float alpha = randomStart + speed * percentComplete;

		// map noise to [-1, 1]
		float x = noise(alpha, 0.0f) * 2.0f - 1.0f;
		float y = noise(0.0f, alpha) * 2.0f - 1.0f;

		x *= magnitude * damper;
		y *= magnitude * damper;
		this->setPosition(x, y);

		if (elapsed >= duration)
		{
			elapsed = 0;
			this->unschedule("Shake");
			this->setPosition(Vec2::ZERO);
		}

	}, interval, CC_REPEAT_FOREVER, 0.f, "Shake");

	return true;
}

now all what you gotta do is put a background image in the layer or scene and fire the touch event and see the shaking effect.

Update:
when i experimented more with the example i found a small mistake which is that the randomStart variable should be inside the schedule method not outside to get a lot better stable shaking effect(like the video or even better) unlike what i explained the first time and in the end all of this resulted in a lot better smoother shaking effect then when the variable was outside.

if you see the randomStart variable inside the schedule method then the code is updated and i fixed the bug.

for a smooth shaking you can try these values:

float interval = 0.f;
float duration = 0.5f;
float speed = 2.0f;
float magnitude = 1.f;

and for a bit of more rough shaking you can try these values:

float interval = 0.f;
float duration = 0.5f;
float speed = 4.0f;
float magnitude = 2.f;

and you can always tweak it more for a more rough or smooth effect!

and here is the effect in a real world scenario(click the picture if you don’t see the effect):

8 Likes

here is the original unity C# code that i converted to cocos2d-x c++(if anyone is curious):

using UnityEngine;
using System.Collections;

public class PerlinShake : MonoBehaviour {
	
	public float duration = 0.5f;
	public float speed = 1.0f;
	public float magnitude = 0.1f;
	
	public bool test = false;
	
	// -------------------------------------------------------------------------
	public void PlayShake() {
		
		StopAllCoroutines();
		StartCoroutine("Shake");
	}
	
	// -------------------------------------------------------------------------
	void Update() {
		if (test) {
			test = false;
			PlayShake();
		}
	}
	
	// -------------------------------------------------------------------------
	IEnumerator Shake() {
		
		float elapsed = 0.0f;
		
		Vector3 originalCamPos = Camera.main.transform.position;
		float randomStart = Random.Range(-1000.0f, 1000.0f);
		
		while (elapsed < duration) {
			
			elapsed += Time.deltaTime;			
			
			float percentComplete = elapsed / duration;			
			
			// We want to reduce the shake from full power to 0 starting half way through
			float damper = 1.0f - Mathf.Clamp(2.0f * percentComplete - 1.0f, 0.0f, 1.0f);
			
			// Calculate the noise parameter starting randomly and going as fast as speed allows
			float alpha = randomStart + speed * percentComplete;
			
			// map noise to [-1, 1]
			float x = Util.Noise.GetNoise(alpha, 0.0f, 0.0f) * 2.0f - 1.0f;
			float y = Util.Noise.GetNoise(0.0f, alpha, 0.0f) * 2.0f - 1.0f;
			
			x *= magnitude * damper;
			y *= magnitude * damper;
			
			Camera.main.transform.position = new Vector3(x, y, originalCamPos.z);
				
			yield return null;
		}
		
		Camera.main.transform.position = originalCamPos;
	}
}

and yes i know it’s C# and i shouldn’t be putting it here but it’s just for the sake of anyone’s curiosity.

Thanks for the tutorial
I also create a repo with the example

And this is the result of the tutorial :smiley:

Thank you

6 Likes

Big Thanks for your contribution! @OscarLeif.
plus i hope you update the repo with the result now that i fixed the bug!!

Thanks for Joseph39 and OscarLeif.

it’s a nice tutorial.

I make a some modification.

a) less compute ( use classic perlin 1d noise function )
b) change it into action

example:
auto action = CShakeAction::create(1.0f, 60.0f, 16.0f); // duration, speed, magnitude
this->runAction(action);

enjoy ^^

1 Like

Please check the repo again
@Joseph39 I update your code now this shake animation is now an action
It’s the same code I only move it to a simple runAction(ActionShake::create())

this->runAction(ActionShake::create(duration,speed,magnitude));

It should work with any object that inherits from Node Class.

@designforplay It make me confuse a little bit, but is a great Idea to have this very very simple to use.

1 Like

@OscarLeif, i try u code, the target node is a isometric tilemap, and the result is “bad”…
do u try isometric tilemap to run ActionShake Action? thank u:)

I Put a sprite in center of screen ,but when run ActionShake, the sprite move to the left bottom of screen…

I think you should shake the layer that contains the TileMap…please tell more details (the code I guess).

@OscarLeif it work, thank u:)