 Camera Shake or "Much Damage, Such WoW"

I was watching a GDC talk about camera shakes and figured it would be a fun thing to implement the camera shake. Enjoy http://server1.lazy-kitty.com/tests/camera_shake_2019_08_09/ 2 Likes

Is your implementation Open Source?
It just occured that there is an explosion in my tunnel now which causes a light to fall down. I think a little camera shake goes nicely with that Hey @weiserhei, glad that you’re interested. For now my engine is closed source. However, I plan to release it as open-source eventually. If I gave you the code - you would probably have to spend more time to adapt it than to write a shake from scratch yourself. That being said, here’s the relevant piece of code that might give you some pointers:

import Vector3 from "../../core/geom/Vector3.js";

import SimplexNoise from 'simplex-noise';
import { clamp, makeCubicCurve, seededRandom } from "../../core/math/MathUtils.js";
import { Behavior } from "../../engine/intelligence/behavior/Behavior.js";
import { BehaviorStatus } from "../../engine/intelligence/behavior/BehaviorStatus.js";
import Quaternion from "../../core/geom/Quaternion.js";

export class CameraShakeTraumaBehavior extends Behavior {

/**
*
* @param {CameraShakeBehavior} shakeBehavior
* @param {number} decay amount by which trauma decays per second
*/
constructor({
shakeBehavior,
decay = 1,
}) {
super();

this.decay = decay;
this.trauma = 0;

this.shakeBehavior = shakeBehavior;

this.formula = makeCubicCurve(0, 0.1, 0.1, 1);
}

tick(timeDelta) {
const shake = this.formula(clamp(this.trauma, 0, 1));

this.trauma = clamp(this.trauma - timeDelta * this.decay, 0, 1);

this.shakeBehavior.strength = shake;

return BehaviorStatus.Running;
}
}

export class CameraShakeBehavior extends Behavior {
/**
*
* @param {number} maxPitch
* @param {number} maxYaw
* @param {number} maxRoll
* @param {number} maxOffsetX
* @param {number} maxOffsetY
* @param {number} maxOffsetZ
* @param {number} strength
* @param {TopDownCameraController} controller
*/
constructor(
{
maxPitch = 0,
maxYaw = 0,
maxRoll = 0,
maxOffsetX = 0,
maxOffsetY = 0,
maxOffsetZ = 0,
strength = 0,

controller
}
) {
super();

/**
*
* @type {TopDownCameraController}
*/
this.controller = controller;

this.time = 0;

this.timeScale = 1;

this.strength = strength;

this.shake = new CameraShake();

this.shake.limitsRotation.set(maxPitch, maxYaw, maxRoll);
this.shake.limitsOffset.set(maxOffsetX, maxOffsetY, maxOffsetZ);

this.__target = new Vector3();
this.__rotation = new Vector3();
}

initialize() {
super.initialize();

//remember controller transform
this.__rotation.set(this.controller.pitch, this.controller.yaw, this.controller.roll);
this.__target.copy(this.controller.target);
}

tick(timeDelta) {
this.time += timeDelta * this.timeScale;

const offset = new Vector3();
const rotation = new Vector3();

//read out shake values
this.shake.read(this.strength, this.time, offset, rotation);

const q = new Quaternion();

q.fromEulerAngles(this.__rotation.x, this.__rotation.y, this.__rotation.z);

offset.applyQuaternion(q);

//update controller
this.controller.target.set(
this.__target.x + offset.x,
this.__target.y + offset.y,
this.__target.z + offset.z,
);

this.controller.pitch = this.__rotation.x + rotation.x;
this.controller.yaw = this.__rotation.y + rotation.y;
this.controller.roll = this.__rotation.z + rotation.z;

return BehaviorStatus.Running;
}
}

/**
* Based on a 2016 GDC talk by Squirrel Eiserloh "Math for Game Programmers: Juicing Your Cameras With Math"
*/
export class CameraShake {
constructor() {

this.time = 0;

/**
* Shake rotational limits, yaw, pitch and roll
* @type {Vector3}
*/
this.limitsRotation = new Vector3();

/**
* Shake offset limits
* @type {Vector3}
*/
this.limitsOffset = new Vector3();

const r = seededRandom(1);

this.noiseRotataion = new SimplexNoise(r);
this.noiseOffset = new SimplexNoise(r);

}

/**
*
* @param {number} value between 0 and 1
* @param {number} time
* @param {Vector3} offset
* @param {Vector3} rotation
*/
read(value, time, offset, rotation) {

const t = time;

const nR = this.noiseRotataion;

rotation.set(
this.limitsRotation.x * value * (nR.noise2D(t, 1) * 2 - 1),
this.limitsRotation.y * value * (nR.noise2D(t, 2) * 2 - 1),
this.limitsRotation.z * value * (nR.noise2D(t, 3) * 2 - 1),
);

const nO = this.noiseOffset;

offset.set(
this.limitsOffset.x * value * (nO.noise2D(t, 1) * 2 - 1),
this.limitsOffset.y * value * (nO.noise2D(t, 2) * 2 - 1),
this.limitsOffset.z * value * (nO.noise2D(t, 3) * 2 - 1)
);
}

}

Or you could just use a Sine wave on the camera’s rotational axes if I recall correctly. Please correct me if I’m wrong. Yeah, you can. Noise is better because it is not regular, like a sine function. There are a ton of simplex/perlin/whatever noise NPM packages, just have a look and pick one you like. I used “simplex-noise” package. It’s quite small and has a robust implementation.

1 Like

Thanks @Usnul, thats some quality code! Although I cant just drop it in, which means its going on the todo list 