Camera
The Camera2D node, follow modes and pans, parallax layers, camera zones, smooth zoom and camera views.
Files: camera.odin, camera_tools.odin.
File overviews
Section titled “File overviews”camera.odin
Section titled “camera.odin”2D camera support for scenes.
A camera is a node: it looks at its own world position, and its world rotation turns the view.
Parent it to a player and the view follows the player with no code; or leave it top-level and
move it with Camera2D_Follow (smoothed) or Camera2D_MoveTo (timed pans). A scene draws every
layer not listed in scene.screenLayers through its current camera (scene.camera); layers in
scene.screenLayers (HUD, menus) are drawn in screen space.
screen = zoom * rotate(world - cameraWorldPosition, -cameraWorldRotation) + offsetRotating the camera clockwise turns the view clockwise, so the world appears to rotate counter-clockwise on screen.
Example
package mygame
import e "engine:."import sdl "vendor:sdl3"
WORLD_SIZE :: [2]f32{3840, 2160}HUD_LAYER :: e.RenderLayer.Layer8
Player :: struct { using node: e.Node,}
CreateLevelScene :: proc(game: ^e.Game) -> ^e.Scene { scene := game->createScene("Level") scene.screenLayers = {HUD_LAYER} // Optional: the HUD ignores the camera
player := e.CreateNode(scene, Player) player.position = WORLD_SIZE * 0.5 player.update = Player_update player.draw = Player_draw
// Option 1: a camera that's rigidly attached to the player // camera := e.Scene_CreateCamera(scene, parent = player)
// Option 2: a top-level camera that eases after the player and stays inside the level camera := e.Scene_CreateCamera(scene) camera.follow.mode = .SmoothDamp camera.follow.smoothTime = 0.3 camera.follow.deadzone = {200, 120} e.Camera2D_SetBounds(camera, {0, 0}, WORLD_SIZE) e.Camera2D_SnapTo(camera, player.position) return scene}
Player_update :: proc(node: ^e.Node, events: []sdl.Event, deltaTime: f64) { camera := node.scene.camera for &event in events { #partial switch event.type { case .MOUSE_WHEEL: camera.zoom = clamp(camera.zoom + event.wheel.y * 0.1, 0.5, 3) case .MOUSE_BUTTON_DOWN: // Event positions are in screen space; convert to find the clicked world point node.position = e.Scene_ScreenToWorld(node.scene, {event.button.x, event.button.y}) case .KEY_DOWN: // Cinematic pan to the center of the level. Following pauses until it ends if event.key.scancode == .C do e.Camera2D_MoveTo(camera, WORLD_SIZE * 0.5, 1.5, .InOutCubic) } } e.Camera2D_Follow(camera, e.Node_GetWorldPosition(node), deltaTime)}
Player_draw :: proc(node: ^e.Node, renderer: ^sdl.Renderer) { // Local coordinates: the node's transform and the camera are applied for us e.DrawCircle(renderer, {0, 0}, 24, {120, 200, 255, 255})}camera_tools.odin
Section titled “camera_tools.odin”Camera tools: parallax layers (backgrounds that move slower than the world, for depth), camera zones (rooms the camera stays inside), smooth zoom, and camera views (what another camera sees, in a rectangle on the screen: split screen, a minimap, picture in picture).
- ParallaxLayer (
ParallaxSettings): its image (texture) and children move with the camera byfactorof its motion: 1 like the world, 0.5 half as fast (far away), 0 not at all (the sky).repeatX/repeatYtile the image across the screen that way. Its position is where it is when the camera is where the scene starts it (ParallaxLayer.base); scripts movebase. - CameraZone (
sizefrom its position,transitionseconds): while the scene camera’s position is inside a zone, the camera’s bounds are the zone, so it can’t show past the room; entering another zone slides the bounds over to it. No zone: the bounds the camera had. - Smooth zoom:
Camera2D_ZoomTo(camera, zoom, seconds). - CameraView (
size,camera: a path to a Camera node,zoom,border): draws what that camera sees into its rectangle (put it on a screen layer). Each player’s camera in a split screen, a zoomed-out camera for a minimap. Other cameras than the scene’s follow by being children of what they follow.
CameraFollowMode
Section titled “CameraFollowMode”CameraFollowMode :: enum { // Jump straight to the position every frame. No easing Snap, // Close a fraction of the remaining distance every frame: fast at first, slowing as it arrives. // Controlled by `CameraFollow.smoothing` Exponential, // Critically damped spring: accelerates and decelerates smoothly with no overshoot. // Feels the most natural for player cameras. Controlled by `CameraFollow.smoothTime` SmoothDamp, // Move toward the position at a constant speed. Controlled by `CameraFollow.speed` Linear,}How a camera tracks a moving position in Camera2D_Follow.
enum. Source: camera.odin:81
CameraFollow
Section titled “CameraFollow”CameraFollow :: struct { // How it eases toward the position mode: CameraFollowMode, // .Exponential: how tightly to follow. Higher is tighter; around 4-10 feels natural smoothing: f32, // .SmoothDamp: roughly how many seconds the camera takes to catch up smoothTime: f32, // .Linear: world units per second speed: f32, // Caps how fast the camera can move, in world units per second (not in .Snap). 0 = no limit maxSpeed: f32, // Size of a box, in world units and centered on the camera, that the followed position can // move inside without moving the camera. {0, 0} disables it deadzone: [2]f32,}Settings for Camera2D_Follow. Each mode only reads its own strength setting;
deadzone applies to every mode, maxSpeed to every mode but .Snap (which always jumps).
struct. Source: camera.odin:98
Camera2D
Section titled “Camera2D”Camera2D :: struct { // The node part: transform, hierarchy, hooks using node: Node, // Screen position the camera's target appears at, usually the center of the screen offset: [2]f32, // Scale factor. 1 is unscaled, 2 shows everything twice as big, 0.5 half as big zoom: f32,
// When true, the camera's target is clamped every frame so the view stays inside `min..max`. // Set with `Camera2D_SetBounds`. Rotation is not taken into account, so a rotated view can // show slightly past the bounds at its corners bounded: bool, // Top-left corner of the world boundary, used when `bounded` is true min: [2]f32, // Bottom-right corner of the world boundary, used when `bounded` is true max: [2]f32,
// How `Camera2D_Follow` eases toward the followed position follow: CameraFollow,
// Internal state; read it if useful but don't modify it
// Current follow velocity in world units per second (.SmoothDamp) velocity: [2]f32, // Active `Camera2D_MoveTo` pan move: CameraMove, // Active `Camera2D_Shake` shake: CameraShake, // Active `Camera2D_ZoomTo` (the zoom in `from.x` / `to.x`: camera_tools.odin) zoomMove: CameraMove, // A `Camera2D_MoveTo` pan reached its end (not when replaced or cancelled) onPanFinished: EventSubject `event:"A timed pan (Camera2D_MoveTo) reached its end"`, // A `Camera2D_Shake` settled onShakeFinished: EventSubject `event:"A shake (Camera2D_Shake) settled"`,}A camera node. It looks at its world position (Camera2D_GetTarget), and its world rotation
turns the view. Create one with Scene_CreateCamera.
A zero-initialized Camera2D also works as a plain value outside a scene: a zoom of 0 is treated as 1 and following snaps.
struct. Source: camera.odin:129
CameraShake
Section titled “CameraShake”CameraShake :: struct { // Screen pixels at the start, seconds in all, seconds left strength, duration, left: f32, // The view's offset this frame, in screen pixels offset: [2]f32,}State of a Camera2D_Shake
struct. Source: camera.odin:173
CameraMove
Section titled “CameraMove”CameraMove :: struct { // A pan is going active: bool, // Start and end of the pan, in world pixels from, to: [2]f32, // Seconds the pan takes, and seconds into it duration, elapsed: f32, // The pan's curve (easing.odin) ease: EaseType,}State of a Camera2D_MoveTo pan
struct. Source: camera.odin:181
CameraToolSettings
Section titled “CameraToolSettings”CameraToolSettings :: struct { // ParallaxLayer: how much it moves with the camera (1: like the world, 0: fixed on screen) factor: [2]f32, // ParallaxLayer: its image tiles across the screen this way repeatX, repeatY: bool, // CameraZone: seconds the camera's bounds slide to it transition: f32, // CameraView: zoom (times its camera's) zoom: f32, // CameraView: its frame's width in pixels (0: none) border: f32,}A ParallaxLayer’s and CameraZone’s and CameraView’s settings (SceneNodeData.cameraTools)
struct. Source: camera_tools.odin:26
ParallaxLayer
Section titled “ParallaxLayer”ParallaxLayer :: struct { using node: Node, settings: CameraToolSettings, // Its image (nil: just its children) texture: ^sdl.Texture, // Where it is when the camera is where it started (its scene file position) base: [2]f32, // The camera's position when the scene started (the parallax is relative to it) cameraStart: [2]f32, started: bool,}A background layer moving slower than the world: see the file’s overview
struct. Source: camera_tools.odin:43
CameraZone
Section titled “CameraZone”CameraZone :: struct { using node: Node, // Its size from its position (its top-left corner) size: [2]f32, settings: CameraToolSettings,}A room the camera stays inside: see the file’s overview
struct. Source: camera_tools.odin:56
CameraView
Section titled “CameraView”CameraView :: struct { using node: Node, // Its rectangle's size, from its position (its top-left corner) size: [2]f32, settings: CameraToolSettings, // The camera it shows: a path from it (node arena) cameraPath: string, target: ^RenderTarget,}What another camera sees, on screen: see the file’s overview
struct. Source: camera_tools.odin:64
CameraZoneState
Section titled “CameraZoneState”CameraZoneState :: struct { // The zone the camera is in (zero: none) zone: EntityId, // The bounds sliding from, to, and how far (0 to 1) over `duration` fromMin, fromMax, toMin, toMax: [2]f32, elapsed, duration: f32, sliding: bool,}Bounds sliding from one zone to another (Scene.cameraZones)
struct. Source: camera_tools.odin:75
Procedures
Section titled “Procedures”Scene_CreateCamera
Section titled “Scene_CreateCamera”Scene_CreateCamera :: proc(scene: ^Scene, parent: ^Node = nil) -> ^Camera2DCreates a camera node in a scene and makes it the scene’s camera.
A top-level camera starts at the center of the game resolution, so world and screen coordinates line up until it moves. A camera with a parent starts at the parent’s origin and moves with it.
Inputs
- scene: The scene
- parent: Node to attach the camera to, or nil for a top-level camera
Returns
- The new camera, also stored in
scene.camera
Example
camera := e.Scene_CreateCamera(scene)camera.zoom = 2procedure. Source: camera.odin:210
Scene_SetCamera
Section titled “Scene_SetCamera”Scene_SetCamera :: proc(scene: ^Scene, camera: ^Camera2D)Makes a camera the one the scene draws through (scene.camera). Pass nil to draw without a
camera.
Inputs
- scene: The scene
- camera: The camera (normally one of the scene’s nodes), or nil
procedure. Source: camera.odin:232
Camera2D_GetTarget
Section titled “Camera2D_GetTarget”Camera2D_GetTarget :: proc(camera: ^Camera2D) -> [2]f32The world position the camera looks at (its world position).
Inputs
- camera: The camera
Returns
- The position, in world pixels
procedure. Source: camera.odin:245
Camera2D_SetTarget
Section titled “Camera2D_SetTarget”Camera2D_SetTarget :: proc(camera: ^Camera2D, target: [2]f32)Moves the camera to look at a world position, whatever its parent is. Unlike Camera2D_SnapTo,
a pan or follow velocity going on is left alone.
Inputs
- camera: The camera
- target: The world position to look at, in world pixels
procedure. Source: camera.odin:257
Camera2D_SetBounds
Section titled “Camera2D_SetBounds”Camera2D_SetBounds :: proc(camera: ^Camera2D, min, max: [2]f32)Restricts the camera so the visible area stays inside a world rectangle.
When the camera is the scene’s camera, the bounds are applied automatically each frame after the scene’s nodes update. If the bounds are smaller than the view on an axis, the camera is centered on that axis instead.
Inputs
- camera: The camera to restrict
- min: Top-left corner of the allowed world area
- max: Bottom-right corner of the allowed world area
Example
// Given: camera: ^e.Camera2De.Camera2D_SetBounds(camera, {0, 0}, {3840, 2160})procedure. Source: camera.odin:277
Camera2D_ClearBounds
Section titled “Camera2D_ClearBounds”Camera2D_ClearBounds :: proc(camera: ^Camera2D)Removes the boundaries set with Camera2D_SetBounds, letting the camera move freely.
Inputs
- camera: The camera
procedure. Source: camera.odin:289
Camera2D_ViewTransform
Section titled “Camera2D_ViewTransform”Camera2D_ViewTransform :: proc(camera: ^Camera2D) -> Transform2DThe transform from world coordinates to screen coordinates for this camera, including any shake. Uses the camera’s interpolated (drawn) transform.
Inputs
- camera: The camera
Returns
- offset × zoom × rotate(-world rotation) × translate(-world position)
procedure. Source: camera.odin:303
Camera2D_WorldToScreen
Section titled “Camera2D_WorldToScreen”Camera2D_WorldToScreen :: proc(camera: ^Camera2D, world: [2]f32) -> [2]f32Converts a world position to the screen position it is drawn at.
Inputs
- camera: The camera
- world: A position in world pixels
Returns
- The screen position, in game coordinates
Example
// Given: camera: ^e.Camera2D, enemy: ^e.Node// Draw a screen-space marker over a world objectmarkerPos := e.Camera2D_WorldToScreen(camera, e.Node_GetWorldPosition(enemy))procedure. Source: camera.odin:327
Camera2D_ScreenToWorld
Section titled “Camera2D_ScreenToWorld”Camera2D_ScreenToWorld :: proc(camera: ^Camera2D, screen: [2]f32) -> [2]f32Converts a screen position (e.g. from a mouse event) to the world position under it.
Inputs
- camera: The camera
- screen: A screen position, in game coordinates
Returns
- The world position, in world pixels
Example
// Given: camera: ^e.Camera2Dworld := e.Camera2D_ScreenToWorld(camera, e.Input_GetMousePosition())_ = worldprocedure. Source: camera.odin:346
Camera2D_Follow
Section titled “Camera2D_Follow”Camera2D_Follow :: proc(camera: ^Camera2D, position: [2]f32, deltaTime: f64)Eases the camera toward a world position using the settings in camera.follow.
Call once per frame, e.g. from the update of the node being followed.
Does nothing while a Camera2D_MoveTo pan is running, so pans aren’t interrupted.
Frame rate independent in every mode. A camera parented to the followed node doesn’t need this.
Inputs
- camera: The camera to move
- position: World position to follow, e.g.
Node_GetWorldPosition(player) - deltaTime: Seconds since the last frame
Example
// Given: player: ^e.Nodee.Camera2D_Follow(node.scene.camera, e.Node_GetWorldPosition(player), deltaTime)procedure. Source: camera.odin:366
Camera2D_MoveTo
Section titled “Camera2D_MoveTo”Camera2D_MoveTo :: proc(camera: ^Camera2D, position: [2]f32, duration: f32, ease := EaseType.InOutCubic)Pans the camera to a world position over a fixed time, following an easing curve.
The pan is advanced automatically by the scene whose camera it is, after its nodes update.
While it runs, Camera2D_Follow does nothing; afterwards following resumes from wherever the
pan ended. Starting a new pan replaces the current one.
Inputs
- camera: The camera to pan
- position: World position to pan to
- duration: Seconds the pan takes. 0 or less jumps immediately
- ease: Easing curve for the pan
Example
// Given: camera: ^e.Camera2D, door: ^e.Node// Show the exit door, then return to the playere.Camera2D_MoveTo(camera, e.Node_GetWorldPosition(door), 1.2, .InOutSine)procedure. Source: camera.odin:429
Camera2D_SnapTo
Section titled “Camera2D_SnapTo”Camera2D_SnapTo :: proc(camera: ^Camera2D, position: [2]f32)Moves the camera to look at a world position immediately, cancelling any pan and easing. Use it when a scene starts or the player respawns.
Inputs
- camera: The camera
- position: The world position to look at, in world pixels
Example
// Given: camera: ^e.Camera2D, spawn: [2]f32e.Camera2D_SnapTo(camera, spawn)procedure. Source: camera.odin:456
Camera2D_IsMoving
Section titled “Camera2D_IsMoving”Camera2D_IsMoving :: proc(camera: ^Camera2D) -> boolTells whether a Camera2D_MoveTo pan is in progress.
Inputs
- camera: The camera
Returns
- true while it pans
procedure. Source: camera.odin:471
Camera2D_Shake
Section titled “Camera2D_Shake”Camera2D_Shake :: proc(camera: ^Camera2D, strength, duration: f32)Shakes the view: it jumps around its place by up to strength screen pixels, less and less over
duration seconds (a hit, an explosion). A stronger shake replaces a weaker one; a weaker one
started during a stronger one is ignored. The shake only moves what’s drawn (the view transform),
never the camera node, so following, bounds and scripts reading its position are unaffected.
The game advances it in real time (Camera2D_UpdateShake, from Game_Update), so it keeps going
through a hitstop (Game_HitStop).
Inputs
- camera: The camera
- strength: Largest offset, in screen pixels (game coordinates)
- duration: Seconds until it settles
Example
e.Camera2D_Shake(node.scene.camera, 4, 0.2)procedure. Source: camera.odin:519
Camera2D_ClampToBounds
Section titled “Camera2D_ClampToBounds”Camera2D_ClampToBounds :: proc(camera: ^Camera2D, viewSize: [2]f32)Adjusts the camera so the visible area stays inside its bounds. Does nothing if the camera is
not bounded. Called by Camera2D_Update.
Inputs
- camera: The camera to clamp
- viewSize: Size of the visible area in screen coordinates, normally
game.size
procedure. Source: camera.odin:553
Scene_ScreenToWorld
Section titled “Scene_ScreenToWorld”Scene_ScreenToWorld :: proc(scene: ^Scene, screen: [2]f32) -> [2]f32Converts a screen position to world coordinates using the scene’s camera. Without a camera, screen and world coordinates are the same.
Inputs
- scene: The scene
- screen: A screen position, in game coordinates
Returns
- The world position, in world pixels
procedure. Source: camera.odin:588
Scene_WorldToScreen
Section titled “Scene_WorldToScreen”Scene_WorldToScreen :: proc(scene: ^Scene, world: [2]f32) -> [2]f32Converts a world position to screen coordinates using the scene’s camera. Without a camera, screen and world coordinates are the same.
Inputs
- scene: The scene
- world: A position in world pixels
Returns
- The screen position, in game coordinates
procedure. Source: camera.odin:604
Scene_GetMouseWorldPosition
Section titled “Scene_GetMouseWorldPosition”Scene_GetMouseWorldPosition :: proc(scene: ^Scene) -> [2]f32The world position currently under the mouse cursor, for code that polls the mouse instead of handling events.
Inputs
- scene: The scene (its camera and game)
Returns
- The world position, in world pixels
Example
target := e.Scene_GetMouseWorldPosition(scene)e.Debug_Circle(scene, target, 4)procedure. Source: camera.odin:623
CreateParallaxLayer
Section titled “CreateParallaxLayer”CreateParallaxLayer :: proc(scene: ^Scene, texture: ^sdl.Texture, factor := [2]f32{0.5, 0.5}) -> ^ParallaxLayerCreates a ParallaxLayer and adds it to the scene (at the start of the next update).
Inputs
- scene: The scene
- texture: Its image (nil: just its children)
- factor: How much it moves with the camera (1: like the world, 0: fixed on screen)
Returns
- The layer
Example
// Given: hills: ^sdl.Texturefar := e.CreateParallaxLayer(scene, hills, {0.3, 0})far.settings.repeatX = trueprocedure. Source: camera_tools.odin:100
CreateCameraZone
Section titled “CreateCameraZone”CreateCameraZone :: proc(scene: ^Scene, size: [2]f32) -> ^CameraZoneCreates a CameraZone and adds it to the scene (at the start of the next update).
Inputs
- scene: The scene
- size: Its width and height, from its position (its top-left corner)
Returns
- The zone
Example
room := e.CreateCameraZone(scene, {640, 360})room.position = {640, 0}procedure. Source: camera_tools.odin:130
CreateCameraView
Section titled “CreateCameraView”CreateCameraView :: proc(scene: ^Scene, size: [2]f32, cameraPath: string) -> ^CameraViewCreates a CameraView and adds it to the scene (at the start of the next update).
Inputs
- scene: The scene
- size: Its rectangle’s size
- cameraPath: The Camera node it shows (a path from it)
Returns
- The view
Example
minimap := e.CreateCameraView(scene, {200, 120}, "../MapCamera")minimap.settings.zoom = 0.2procedure. Source: camera_tools.odin:156
Camera2D_ZoomTo
Section titled “Camera2D_ZoomTo”Camera2D_ZoomTo :: proc(camera: ^Camera2D, zoom: f32, duration: f32, ease := EaseType.InOutCubic)Zooms a camera smoothly to a zoom over some seconds.
Inputs
- camera: The camera
- zoom: The zoom to reach (1: unscaled)
- duration: Seconds (0: at once)
- ease: The curve
Example
e.Camera2D_ZoomTo(scene.camera, 2, 0.5)procedure. Source: camera_tools.odin:183
Constants and variables
Section titled “Constants and variables”DEFAULT_CAMERA_FOLLOW
Section titled “DEFAULT_CAMERA_FOLLOW”DEFAULT_CAMERA_FOLLOW :: CameraFollow{ mode = .Exponential, smoothing = 6, smoothTime = 0.25, speed = 600,}The follow settings new cameras get: .Exponential with a smoothing of 6, no deadzone or speed cap
constant. Source: camera.odin:115
DEFAULT_CAMERA_TOOL_SETTINGS
Section titled “DEFAULT_CAMERA_TOOL_SETTINGS”DEFAULT_CAMERA_TOOL_SETTINGS :: CameraToolSettings{factor = {0.5, 0.5}, transition = 0.4, zoom = 1, border = 2}When a scene file sets none
constant. Source: camera_tools.odin:40
