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.
I originally started thinking about this topic while driving home from work on the freeway. I noticed that my side view mirror was loose and the vibrations of the road made the image unstable. I began working on the theory that a shake is really just a large vibration — representable by sine and cosine waves with a frequency and amplitude. 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, linearly interpolated, with a decay function so the effect eventually wears off.
As you can see by the demo above, we get a pretty flexible shaking effect by allowing for different frequencies, durations and amplitudes.
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)
{
this.duration = parseInt(duration);
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);
}
this.startTime = null;
this.t = null;
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:
Shake.prototype.amplitude = function(t)
{
if(t == undefined) {
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 (this.noise(s0) + (s - s0)*(this.noise(s1) - this.noise(s0))) * k;
};amplitude() calls out to noise(), which is nothing more than a lookup into the samples array populated by the constructor above — with one important detail: it returns 0 once you ask for a sample past the end of the array, rather than throwing or clamping to the last value. That’s what makes the interpolation in amplitude() taper smoothly to 0 right at the tail of the shake, on top of the separate decay() multiplier below.
Shake.prototype.noise = function(s)
{
// Retrieve the randomized value from the samples
if(s >= this.samples.length) return 0;
return this.samples[s];
};Lastly, the decay function — important as it smoothly transitions 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.
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 don’t see the need for it here. A screen shake is a violent and haphazard function of time and the granularity one might gain from a perlin noise function is lost. With the right settings, an array of randomized numbers does the trick just fine.
Want to poke at the code directly? The demo above is also published as a standalone,
dependency-free page — download it and open index.html with no build step, no server:
view source / run locally →