+48 votes

I couldn't find a built in screen-shake function, so I implemented the first bit o' logic I found on the internet. Since it's a pretty common feature in games these days, I figured I'd share it. Major features include:

  • Variable duration, frequency, and amplitude.
  • Maintains its own offset so it can be used in combination with other effects.

Since it extends Camera2D, all you have to do to use it is to instance this script instead of base Camera2D then call the shake function. I find that setting the duration, frequency, and amplitude to 0.2, 15, and 8 respectively gives a nice light shaking effect.

extends Camera2D

var _duration = 0.0
var _period_in_ms = 0.0
var _amplitude = 0.0
var _timer = 0.0
var _last_shook_timer = 0
var _previous_x = 0.0
var _previous_y = 0.0
var _last_offset = Vector2(0, 0)

func _ready():
    set_process(true)

# Shake with decreasing intensity while there's time remaining.
func _process(delta):
    # Only shake when there's shake time remaining.
    if _timer == 0:
        return
    # Only shake on certain frames.
    _last_shook_timer = _last_shook_timer + delta
    # Be mathematically correct in the face of lag; usually only happens once.
    while _last_shook_timer >= _period_in_ms:
        _last_shook_timer = _last_shook_timer - _period_in_ms
        # Lerp between [amplitude] and 0.0 intensity based on remaining shake time.
        var intensity = _amplitude * (1 - ((_duration - _timer) / _duration))
        # Noise calculation logic from http://jonny.morrill.me/blog/view/14
        var new_x = rand_range(-1.0, 1.0)
        var x_component = intensity * (_previous_x + (delta * (new_x - _previous_x)))
        var new_y = rand_range(-1.0, 1.0)
        var y_component = intensity * (_previous_y + (delta * (new_y - _previous_y)))
        _previous_x = new_x
        _previous_y = new_y
        # Track how much we've moved the offset, as opposed to other effects.
        var new_offset = Vector2(x_component, y_component)
        set_offset(get_offset() - _last_offset + new_offset)
        _last_offset = new_offset
    # Reset the offset when we're done shaking.
    _timer = _timer - delta
    if _timer <= 0:
        _timer = 0
        set_offset(get_offset() - _last_offset)

# Kick off a new screenshake effect.
func shake(duration, frequency, amplitude):
    # Initialize variables.
    _duration = duration
    _timer = duration
    _period_in_ms = 1.0 / frequency
    _amplitude = amplitude
    _previous_x = rand_range(-1.0, 1.0)
    _previous_y = rand_range(-1.0, 1.0)
    # Reset previous offset, if any.
    set_offset(get_offset() - _last_offset)
    _last_offset = Vector2(0, 0)
in Engine by (103 points)
Bookmarked! I'll try it later. Thanks for posting!

@vctr

Cool! Nice to see some 3D scripts passing around. I'm not that good yet at programming (in Godot), so this is welcome.
But how do I implement your script? Because I already have a camera script attached to my camera, to follow the car dynamically (base-script by Bastiaan Olij tutorial).

Since you can't attacht 2 scripts to 1 node, do I just copy your script and paste it with what I already have?
The function _process() doesn't kick in untill you use the function shake() somewhere, right?

Edit: maybe I found it. Is it possible that instead of using extends Camera I could use extends "res:://path/to/mainScriptCamera.gd" ??

Edit 2: still can't seem to get this to work. First edit doesn't work I guess, since the script then becomes the child of the script and I'm not able to call a function of that child (or maybe I'm doing it wrong?).
Now tried it with

var camExtScript = preload("res://Scripts/cam_Shake.gd").new()

in the main camera script at the top (set the shake script back to extends Camera) and then trying to call the function when boosting is true

if (follow_this.boost):
    currentFOV = boostFOV
    camExtScript.shake(0.2,15,8)
else:
    currentFOV = maxFOV

I'm also changing the FOV when the car is boosting. But can't get the cam to shake.

Cheers

Don't mind if I do. Zoink.

4 Answers

+1 vote

Thank you bro, I will use it for sure

by (47 points)
+4 votes

This script is awesome, but I found a small bug. The camera doesn't always return it's offset to origin (0,0), especially when the object the camera is parented to is colliding with something. Also, it processes when it doesn't have to. So I made a few tweaks to improve it for my own needs. Now it only only enables processing while shaking, and disables processing while not shaking. When it's done shaking, it resets the offset to (0,0). And lastly, I made it so you can't interrupt an ongoing shake. Here's the new script: http://pastebin.com/LY52qRE5

by (852 points)
edited by

Nice. There is one thing to be aware of: in your modification, by setting the offset to 0,0 at the end of a screenshake effect, you may be interrupting unrelated camera effects.

Imagine you have a system in which you hold Down to look below you, moving the offset some amount. If an explosion shakes the screen while you're looking down, the end of your modified screenshake would re-center the camera on the player unexpectedly.

Fortunately, it's easy enough to tailor the concept to one's specific use cases.

0 votes

I changed it only a little bit for my 3D game with orthogonal camera. Here's the script if you would like to use it. https://gist.github.com/jegor377/606da0d70c4089b1e2456d25b12af46d

by (31 points)
0 votes

I made a video tutorial on making a similar, but simpler node and script:

https://www.youtube.com/watch?v=eWhglfQFYOk&t=1s

by (608 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.