May

17

2013

17

2013

GameDev: How To Implement A "Camera Shake" Effect

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!

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.

```
#camera-shake canvas {
border: 1px solid #DDDDDD;
margin: 0;
}
#camera-shake canvas.timeline {
width: 360px;
height: 120px;
}
#camera-shake div.form {
clear:both;
}
#camera-shake div.form fieldset{
border:medium none;
margin:0;
padding:0;
}
#camera-shake div.form legend{
margin:0;
padding:0;
}
#camera-shake div.form div{
float:left;
vertical-align:text-top;
}
#camera-shake div.form div.form-break {
clear: both;
}
#camera-shake div.form div.input {
margin: 5px 0;
}
#camera-shake div.form div.submit {
border:0 none;
clear:both;
margin-top:20px;
}
#camera-shake div.form input[type="submit"]{
font: bold 11px/13px Arial,Helvetica,sans-serif;
padding: 4px 8px;
background:#e6e49f;
background: -webkit-gradient(linear, left top, left bottom, from(#f1f1d4), to(#e6e49f));
background-image: -moz-linear-gradient(top, #f1f1d4, #e6e49f);
color:#333;
border:1px solid #aaac62;
-webkit-border-radius:8px;
-moz-border-radius:8px;
border-radius:8px;
text-decoration:none;
text-shadow: #fff 0px 1px 0px;
min-width: 0;
}
#camera-shake div.form input[type="submit"]:hover {
background: #f0f09a;
background: -webkit-gradient(linear, left top, left bottom, from(#f7f7e1), to(#eeeca9));
background-image: -moz-linear-gradient(top, #f7f7e1, #eeeca9);
cursor: pointer;
}
```

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

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; };

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.