Camera
Cameras control what part of the world you see and how it maps to the screen. Mibo provides Camera2D for 2D games and Camera3D for 3D games. Both support single-camera and split-screen patterns. The fluent .beginCamera(...)/.beginCameraWith(...) members and the Camera2DConfig/Camera3DConfig modifiers share the same shape across backends; only the underlying camera struct's field layout is backend-specific.
What and Why
- Scroll and zoom — A 2D camera lets your game world be larger than the screen. Pan, zoom, and follow a player.
- Perspective — A 3D camera defines where you look from and where you look at.
- Coordinate conversion — Convert between screen pixels and world positions for mouse picking, UI placement, and debug tools.
- Multi-camera — Split-screen multiplayer, picture-in-picture minimaps, and HUD overlays on top of the game world.
When to use
Situation |
Use |
|---|---|
2D game with scrolling world |
|
2D game with split-screen or HUD |
|
3D game |
|
3D split-screen or picture-in-picture |
|
Mouse picking in 3D |
|
Culling off-screen objects |
|
2D cameras
Creating a camera
Camera2D.create centers the camera on a world position:
let camera = Camera2D.create (Vector2(400f, 300f)) 1.0f viewportSize
position— world position to center onzoom— zoom factor (1.0f= no zoom)viewportSize— screen size in pixels (used to compute the offset)
_NOTE — vector types._ Each backend's
Camera2D.create/Camera3Dtakes that backend's native vector type — raylib usesSystem.Numerics, MonoGame usesMicrosoft.Xna.Framework— so make sure the matching namespace isopen. (TheVector3(...)used byCamera3Dfollows the same rule.) Note that the Core layout APIs (CellGrid2D,LayeredGrid2D) always takeSystem.Numerics.Vector2and must be explicitly qualified in MonoGame projects; see the note on the 2D Layout Engine page.
Using in a view
Wrap your world-space draw commands between .beginCamera(...) and .endCamera(...). The layer parameter controls draw order — camera and content must share the same layer range.
buffer
.beginCamera(camera)
.fillRect(0f, 0f, 800f, 600f, Color.Green)
.fillCircle(playerPos, 16f, Color.Red)
.endCamera(layer = 999<RenderLayer>)
.text(font, "HUD", Vector2(10f, 10f), 20f, layer = 1000<RenderLayer>)
.drop()
_TIP_: Put UI draws after
.endCamera(...)on a higher layer so they render in screen space, not world space.
Camera movement
Use smoothFollow to lerp the camera toward a target, and clampTarget to keep it within world bounds. The call shape differs per backend: the raylib camera is a native mutable struct (mutated by reference), while the MonoGame camera has immutable fields (the helpers return a new camera).
// raylib — mutates the camera in place (note the &)
let mutable cam = Camera2D.create startPos 1.0f viewportSize
// In your update function, each frame:
Camera2D.smoothFollow &cam playerPos 0.1f
Camera2D.clampTarget &cam 0f 0f worldWidth worldHeight
// MonoGame — returns a new camera (no &)
let cam = Camera2D.create startPos 1.0f viewportSize
let cam = Camera2D.smoothFollow cam playerPos 0.1f
let cam = Camera2D.clampTarget cam 0f 0f worldWidth worldHeight
Coordinate conversion
Convert between screen pixels and world positions. screenToWorld / worldToScreen / viewportBounds are available on both backends — on raylib you pass the camera by reference (&) to avoid copying the native struct, on MonoGame the camera is an immutable value (no &):
// raylib
let worldPos = Camera2D.screenToWorld &camera mousePos
let screenPos = Camera2D.worldToScreen &camera enemyPos
let visible = Camera2D.viewportBounds &camera screenWidth screenHeight
// MonoGame
let worldPos = Camera2D.screenToWorld camera mousePos
let visible = Camera2D.viewportBounds camera screenWidth screenHeight
Use viewportBounds to get the visible world rectangle — useful for culling off-screen objects (it pairs with Culling.isVisible2D).
2D multi-camera
Camera2DConfig lets you control viewport, clear color, and rendering behavior per camera. Build one with Camera2D.render and chain with* modifiers.
Config modifiers
Modifier |
Description |
|---|---|
|
raylib: normalized screen coordinates (0–1); MonoGame: pixel |
|
Clear with this color before rendering |
Using a config in a view
let config =
Camera2D.render worldCamera
|> Camera2D.withClear Color.CornflowerBlue
buffer
.beginCameraWith(config)
// ... world content ...
.endCamera(layer = 999<RenderLayer>)
.drop()
Split-screen
Pre-built helpers for two-player split-screen. Each clears with the given color. On raylib the split-screen halves the full screen (normalized 0–1); on MonoGame you pass the parent viewport bounds in pixels (typically the window size) as the last argument.
let left = Camera2D.splitScreenLeft player1Camera Color.CornflowerBlue
let right = Camera2D.splitScreenRight player2Camera Color.DarkGreen
buffer
.beginCameraWith(left)
// ... player 1 content ...
.endCamera(layer = 99<RenderLayer>)
.beginCameraWith(right, layer = 100<RenderLayer>)
// ... player 2 content ...
.endCamera(layer = 199<RenderLayer>)
.text(font, "HUD", Vector2(10f, 10f), 20f, layer = 200<RenderLayer>)
.drop()
Available split-screen helpers:
Helper |
Viewport |
|---|---|
|
Left half (0, 0, 0.5, 1) |
|
Right half (0.5, 0, 0.5, 1) |
|
Top half (0, 0, 1, 0.5) |
|
Bottom half (0, 0.5, 1, 0.5) |
For a picture-in-picture view (e.g. a minimap), compose one yourself with
Camera2D.render + withViewport + withClear, and emit that camera after the
main one so it draws on top — there is no built-in overlay helper, and
layering is purely draw order.
3D cameras
Creating a camera
For 3D rendering, use Camera3D.create. It takes just three parameters — position, target, and field of view — with sensible defaults for everything else (up = Vector3.Up; MonoGame also defaults near = 0.1f, far = 1000f and computes aspect from the viewport at render time):
// raylib (FOV in degrees)
let camera = Camera3D.create (Vector3(0f, 10f, 20f)) Vector3.Zero 45.0f
// MonoGame (FOV in radians)
let camera = Camera3D.create (Vector3(0f, 10f, 20f)) Vector3.Zero (MathF.PI / 4f)
For third-person or inspection cameras, use Camera3D.orbit (both backends):
// raylib (FOV in degrees)
let camera = Camera3D.orbit Vector3.Zero yaw pitch radius 55.0f
// MonoGame (FOV in radians)
let camera = Camera3D.orbit Vector3.Zero yaw pitch radius (MathF.PI / 4f)
Camera modifiers
Chain with* modifiers to override the defaults:
// Custom up vector (both backends)
let camera = Camera3D.create pos target fov |> Camera3D.withUp customUp
// Orthographic projection (both backends; FovY is reinterpreted as view height)
let camera = Camera3D.create pos target 10f |> Camera3D.asOrthographic
// Custom near/far planes (MonoGame only — raylib manages these internally)
let camera = Camera3D.create pos target fov |> Camera3D.withNearFar 0.01f 5000f
_NOTE — backend difference._ Both backends share the same constructor surface (
create/orbit) and modifiers (withUp/asOrthographic). MonoGame addswithNearFar(raylib manages near/far internally viaBeginMode3D). The FOV unit differs: raylib uses degrees, MonoGame uses radians.
Using in a view
buffer
.beginCamera(camera)
.model(playerModel, playerTransform)
.addPointLight { Position = torchPos; Color = Color.White; Intensity = 1f; Radius = 10f; CastsShadows = false; ShadowBias = ValueNone }
.endCamera()
.drop()
3D config modifiers
Camera3DConfig controls viewport and clear color. Build with Camera3D.render and chain modifiers:
Modifier |
Description |
|---|---|
|
Viewport in normalized screen coordinates (0–1) |
|
Clear with this color before rendering |
let config =
Camera3D.render mainCamera
|> Camera3D.withClear Color.SkyBlue
buffer
.beginCameraWith(config)
.model(sceneModel, sceneTransform)
.endCamera()
.drop()
Split-screen (3D)
let left = Camera3D.splitScreenLeft player1Camera Color.SkyBlue
let right = Camera3D.splitScreenRight player2Camera Color.SkyBlue
buffer
.beginCameraWith(left)
// ... player 1 scene ...
.endCamera()
.beginCameraWith(right)
// ... player 2 scene ...
.endCamera()
.drop()
Mouse picking
Cast a ray from a screen position into the 3D scene with Camera3D.screenPointToRay (both backends):
// raylib — returns the native Raylib_cs.Ray (note the & on the camera)
let ray = Camera3D.screenPointToRay &camera mousePos
// ray.Position — origin point
// ray.Direction — normalized direction into the scene
// MonoGame — takes the Camera3D and viewport size, returns Mibo's Ray
let ray = Camera3D.screenPointToRay camera mousePos viewportWidth viewportHeight
See also: 2D Rendering Overview, 3D Rendering, Lighting & Shadows
Mibo