A camera in a 2D game is just an offset applied to every draw call. Store the camera position as script-lifetime attributes and subtract it from world coordinates before drawing.
Basic Camera Follow
The simplest camera: center on the player every frame.
@cam_x :: script = 0.0
@cam_y :: script = 0.0
defp update_camera(target_x :: float, target_y :: float) do
sw :: frame = to_float(screen_width())
sh :: frame = to_float(screen_height())
@cam_x = copy_to_script(target_x - sw * 0.5)
@cam_y = copy_to_script(target_y - sh * 0.5)
end
defp world_to_screen_x(wx :: float) :: float do
return wx - @cam_x
end
defp world_to_screen_y(wy :: float) :: float do
return wy - @cam_y
endUse world_to_screen_x / world_to_screen_y on every draw call:
def on_draw() do
draw_rectangle(
world_to_screen_x(@player_x),
world_to_screen_y(@player_y),
16.0, 16.0, :white
)
endSmooth Follow
Hard-locking the camera to the player feels jarring. Lerp toward the target instead:
@cam_x :: script = 0.0
@cam_y :: script = 0.0
defp update_camera(target_x :: float, target_y :: float, dt :: float) do
sw :: frame = to_float(screen_width())
sh :: frame = to_float(screen_height())
goal_x :: frame = target_x - sw * 0.5
goal_y :: frame = target_y - sh * 0.5
speed :: frame = 6.0
@cam_x = copy_to_script(lerp(@cam_x, goal_x, speed * dt))
@cam_y = copy_to_script(lerp(@cam_y, goal_y, speed * dt))
endIncrease speed for a tighter follow, decrease it for a floatier feel.
Camera Shake
Store a shake magnitude and decay it each frame. Add a random offset to the camera when drawing.
@cam_x :: script = 0.0
@cam_y :: script = 0.0
@shake_mag :: script = 0.0
@shake_decay :: script = 8.0
defp add_shake(amount :: float) do
@shake_mag = copy_to_script(@shake_mag + amount)
end
defp update_shake(dt :: float) do
if @shake_mag > 0.0 do
@shake_mag = copy_to_script(@shake_mag - @shake_decay * dt)
if @shake_mag < 0.0 do
@shake_mag = copy_to_script(0.0)
end
end
end
defp shake_offset_x() :: float do
if @shake_mag <= 0.0 do return 0.0 end
return (random_float() * 2.0 - 1.0) * @shake_mag
end
defp shake_offset_y() :: float do
if @shake_mag <= 0.0 do return 0.0 end
return (random_float() * 2.0 - 1.0) * @shake_mag
endThen offset draw calls by shake_offset_x() / shake_offset_y() on top of the camera transform.
Camera Bounds
Clamp the camera so it never shows outside the world:
defp clamp_camera(world_w :: float, world_h :: float) do
sw :: frame = to_float(screen_width())
sh :: frame = to_float(screen_height())
max_x :: frame = world_w - sw
max_y :: frame = world_h - sh
@cam_x = copy_to_script(clamp(@cam_x, 0.0, max_x))
@cam_y = copy_to_script(clamp(@cam_y, 0.0, max_y))
endCall clamp_camera after update_camera each tick.