Blog

GameDev: How to Implement a “Camera Shake” Effect

In modern videogames, a camera, or "screen", shake can add some really nice aesthetic touch and tactility to your game at a very low cost. When I went to research some concrete implementation details, however, I failed to find many examples to guide my efforts. I decided to venture forth and write up a quick article on how to do so for most 2D games. Towards the end of the article I will touch on how a camera shake might be implemented in a 3D environment. Onward!

How "Shaking" Works

I originally started thinking about this topic while driving home from work on the freeway. I noticed that my side view mirror was loose for some reason and the vibrations of the road made the image unstable. I began to ponder on the math behind a "shake". I began working on the theory that a shake is really just a large vibration. A vibration can be mathematically represented by sine and cosine waves, having a frequency and amplitude. In doing some research on earthquakes (usgs.gov - fact #34) I discovered that earthquakes too have a frequency, sweet! I was on the right track. A camera shake is much more violent and harsh than a cosine wave, so I figured it could be modeled with randomized amplitudes at a fixed frequency. Smooth interpolation also seemed like a bit overkill, so I went with linear interpolation. Lastly, so that the effect would eventually wear off, I added a decay function. Let's take a look at the live demo and then we can examine the code used to create the effect.

X-axis Timeline

Y-axis Timeline

Settings

 

 

As you can see by the demo above, we get a pretty flexible shaking effect by allowing for different frequencies, durations and amplitudes.

The Code

You can download the full demo code here:

Icon HTML5 Shake Demo (349.3 KB)

Here is the initialization code for generating a shake graph for 1D. By the end of the constructor, we should have an array filled with randomized samples that we will use for the motion.

 

	
	/**
 * @class Initializes a 1D shaking function
 * @param {int} duration The length of the shake in milliseconds
 * @param {int} frequency The frequency of the shake in Hertz
 */
var Shake = function(duration, frequency)
{
    // The duration in milliseconds
    this.duration = parseInt(duration);
 
    // The frequency in Hz
    this.frequency = parseInt(frequency);
 
    // The sample count = number of peaks/valleys in the Shake
    var sampleCount = (duration/1000) * frequency;
 
    // Populate the samples array with randomized values between -1.0 and 1.0
    this.samples = [];
    for(var i = 0; i < sampleCount; i++) {
        this.samples.push(Math.random() * 2 - 1);
    }
 
    // Init the time variables
    this.startTime = null;
    this.t = null;
 
    // Flag that represents if the shake is active
    this.isShaking = false;
};

 

To generate the graphs of the shake function, we need to be able to retrieve the amplitude at any time (t) during the duration of the shake. To do that, we have the "amplitude" function that can be passed an arbitrary time and retrieve the amplitude at that moment.

	
	/**
 * Retrieve the amplitude. If "t" is passed, it will get the amplitude for the
 * specified time, otherwise it will use the internal time.
 * @param {int} t (optional) The milliseconds since the start of the shake
 */
Shake.prototype.amplitude = function(t)
{
    // Check if optional param was passed
    if(t == undefined) {
        // return zero if we are done shaking
        if(!this.isShaking) return 0;
        t = this.t;
    }
     
    // Get the previous and next sample
    var s = t / 1000 * this.frequency;
    var s0 = Math.floor(s);
    var s1 = s0 + 1;
     
    // Get the current decay
    var k = this.decay(t);
     
    // Return the current amplitude
    return (this.noise(s0) + (s - s0)*(this.noise(s1) - this.noise(s0))) * k;
};

Inside the amplitude function we are doing some linear interpolation. What that basically means is that we are taking the discrete randomized samples that we got earlier and we are connecting them via straight lines. In order to do this, it requires that we get the two closest samples to the current time:

 

	
	//This is the current sample location as a floating point value.
var s = t / 1000 * this.frequency;

//Closest sample less than or equal to `s` as an integer
var s0 = Math.floor(s);

//The next sample
var s1 = s0 + 1;

 

Once we have these three values we can use a linear equation to find the current y. In the code it doesn't look much like a linear equation, but it is. Let's reorganize and the linear equation will be more apparent:

 

	
	   var m, x, b, y;

    // The slope is equal to the bottom sample value subtracted
    // from the top sample value
    m = (this.noise(s1) - this.noise(s0));

    // The `x` value is how far along from the initial sample we are
    x = (s - s0);

    // The x-intercept, `b`, is the bottom sample value
    b = noise(s0);
    
    // Now we have the linear equation
    y = m*x + b;

 

Lastly, we have the decay function, this is important as it is used to smoothly transition the shaking back to a still screen. It's simply a linear function of t that starts at 1.0 and ends at 0.0 when t = duration.

 

	
	/**
 * Get the decay of the shake as a floating point value from 0.0 to 1.0
 * @param {int} t The time since the start of the shake in milliseconds
 */
Shake.prototype.decay = function(t)
{
    // Linear decay
    if(t >= this.duration) return 0;
    return (this.duration - t) / this.duration;
};

 

Rand vs. Perlin

In my research for this article, I found a lot of posts online saying that randomized numbers couldn't produce an effective screen shake and that perlin noise is the only way to make it seem "natural". Well I love perlin noise, but I really don't see the need for it here. A screen shake is a violent and haphazard function of time and really the granularity that one might gain from a perlin noise function is lost. I found that, with the right settings, an array of randomized numbers does the trick just fine.

WANT TO TALK SHOP ?

I enjoy speaking to anyone who has a question or a thought about me or my projects. Or maybe you have a project of your own you would like to share?
Feel free to reach out on social media or contact me privately using my contact form.
I am looking forward to hearing from you!

Contact Me