Screen shake
Trauma decaying over time, sampled from FastNoiseLite into the offset and the roll of the camera. A blast fires every 1.2 seconds and the slider scales it.
The editor above is running this project. Change a line and it reloads.
Godot classes used
Camera2DFastNoiseLiteHSlider
The code
scripts/main.gd
extends Node
const MAX_OFFSET: float = 44.0
const MAX_ROLL: float = 0.07
const DECAY: float = 0.6
var camera: Camera2D
var noise: FastNoiseLite
var flash: Polygon2D
var bar: ColorRect
var readout: Label
var trauma: float = 0.0
var intensity: float = 1.0
var elapsed: float = 0.0
func _ready() -> void:
_add_background()
var view: Vector2 = get_viewport().get_visible_rect().size
noise = FastNoiseLite.new()
noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
noise.frequency = 0.5
_add_city(view)
flash = _make_disc(78.0, Color('#fc7f7f'))
flash.position = Vector2(view.x * 0.5, view.y * 0.62)
add_child(flash)
camera = Camera2D.new()
camera.position = view * 0.5
# ignore_rotation defaults to true and would drop the roll
camera.ignore_rotation = false
add_child(camera)
camera.make_current()
_add_hud(view)
var timer: Timer = Timer.new()
timer.wait_time = 1.2
timer.timeout.connect(_burst)
add_child(timer)
timer.start()
_burst()
print('a blast fires every 1.2s, click or press space for one now, the slider scales it')
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
_burst()
elif event is InputEventKey and event.pressed and (event as InputEventKey).keycode == KEY_SPACE:
_burst()
func _process(delta: float) -> void:
elapsed += delta
trauma = maxf(trauma - DECAY * delta, 0.0)
var shake: float = trauma * trauma * intensity
camera.offset = Vector2(noise.get_noise_2d(0.0, elapsed * 30.0), noise.get_noise_2d(500.0, elapsed * 30.0)) * MAX_OFFSET * shake
camera.rotation = noise.get_noise_2d(1000.0, elapsed * 30.0) * MAX_ROLL * shake
flash.scale = Vector2.ONE * (0.2 + trauma * 0.9)
flash.modulate.a = trauma * 0.75
bar.size.x = 260.0 * minf(trauma, 1.0)
readout.text = 'trauma %.2f intensity %.2f' % [trauma, intensity]
func _burst() -> void:
trauma = 1.0
func _on_intensity(value: float) -> void:
intensity = value
func _add_city(view: Vector2) -> void:
var ground: Polygon2D = Polygon2D.new()
ground.polygon = PackedVector2Array([Vector2(-600, view.y * 0.62), Vector2(view.x + 600, view.y * 0.62), Vector2(view.x + 600, view.y + 600), Vector2(-600, view.y + 600)])
ground.color = Color('#2a2f3d')
add_child(ground)
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.seed = 3
for i in 11:
var width: float = rng.randf_range(60, 110)
var height: float = rng.randf_range(120, 300)
var tower: Polygon2D = Polygon2D.new()
tower.polygon = PackedVector2Array([Vector2(0, -height), Vector2(width, -height), Vector2(width, 0), Vector2(0, 0)])
tower.color = Color('#8da5f3').lerp(Color('#12141a'), rng.randf_range(0.3, 0.65))
tower.position = Vector2(60 + i * 110, view.y * 0.62)
add_child(tower)
func _make_disc(radius: float, color: Color) -> Polygon2D:
var points: PackedVector2Array = PackedVector2Array()
for i in 32:
points.append(Vector2(cos(TAU * i / 32.0), sin(TAU * i / 32.0)) * radius)
var disc: Polygon2D = Polygon2D.new()
disc.polygon = points
disc.color = color
return disc
func _add_hud(view: Vector2) -> void:
var hud: CanvasLayer = CanvasLayer.new()
add_child(hud)
var frame: Panel = Panel.new()
var style: StyleBoxFlat = StyleBoxFlat.new()
style.bg_color = Color(0, 0, 0, 0)
style.border_color = Color('#8eef97')
style.set_border_width_all(3)
frame.add_theme_stylebox_override('panel', style)
frame.position = Vector2(20, 20)
frame.size = view - Vector2(40, 40)
hud.add_child(frame)
var slider: HSlider = HSlider.new()
slider.min_value = 0.0
slider.max_value = 2.0
slider.step = 0.05
slider.value = intensity
slider.position = Vector2(48, view.y - 90)
slider.size = Vector2(260, 24)
slider.value_changed.connect(_on_intensity)
hud.add_child(slider)
var track: ColorRect = ColorRect.new()
track.color = Color('#2a2f3d')
track.position = Vector2(48, view.y - 140)
track.size = Vector2(260, 14)
hud.add_child(track)
bar = ColorRect.new()
bar.color = Color('#fc7f7f')
bar.position = track.position
bar.size = Vector2(0, 14)
hud.add_child(bar)
readout = Label.new()
readout.add_theme_font_size_override('font_size', 20)
readout.add_theme_color_override('font_color', Color('#f3d48d'))
readout.position = Vector2(48, view.y - 180)
hud.add_child(readout)
var note: Label = Label.new()
note.text = 'the green frame is a CanvasLayer, it never shakes'
note.add_theme_font_size_override('font_size', 20)
note.add_theme_color_override('font_color', Color('#8eef97'))
note.position = Vector2(48, 44)
hud.add_child(note)
func _add_background() -> void:
var layer: CanvasLayer = CanvasLayer.new()
layer.layer = -1
add_child(layer)
var background: ColorRect = ColorRect.new()
background.color = Color('#12141a')
layer.add_child(background)
# ColorRect defaults to MOUSE_FILTER_STOP and would eat the click before _unhandled_input
background.mouse_filter = Control.MOUSE_FILTER_IGNORE
background.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)