BLOG

May
17
2013
GameDev: How To Implement A "Camera Shake" Effect

Intro

In modern videogames, a camera, or "screen", shake can add some really nice aestetic 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: shake.demo.zip

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 miliseconds

     * @param {int} frequency The frequency of the shake in Hertz

     */

    var Shake = function(duration, frequency)

    {

        // The duration in miliseconds

        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 time since the start of the shake in miliseconds

     */

    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 amplitide

        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 descreet 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 miliseconds

     */

    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 dont see the need for it here. A screen shake is a violent and haphazzard 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.


Comments Are Disabled

Go die in a hole somewhere you Re-Captcha solving slave drivers.