flan/examples/core-2d-camera.fln

107 lines
5.3 KiB
Plaintext

;;;; raylib [core] example - 2d camera
;;;;
;;;; examples/core/core_2d_camera.c. Needed no new binding at all, which is
;;;; the reason to port it: Camera2D, begin-mode-2d and end-mode-2d have been
;;;; in vendor/raylib/raylib.fln since the beginning and nothing in the
;;;; corpus had ever passed a whole Camera2D across the FFI on a frame's path.
;;;; Two Vector2s, a float and a float, by value, sixty times a second — the
;;;; acceptance table pins that layout with get-screen-to-world-2d, and this
;;;; is the same struct going into the call that actually draws with it.
;;;;
;;;; The hundred buildings are a `once` of fixed arrays rather than a Vec:
;;;; a global cannot hold a Vec (docs/PORTING.md §3) and does not need to here,
;;;; because the count is a constant in the C too. `array(n, T)` is the zeroed
;;;; fixed array, and the C's `= { 0 }` is exactly that.
;;;;
;;;; get-random-value comes from the generated half of the bindings. It is on
;;;; no frame's path — it runs once, before the loop — and its Flan face is
;;;; the C one, so docs/PORTING.md §1's rule does not reach it.
;;;;
;;;; One deliberate difference from the C: `camera.rotation - 1.0` and the
;;;; clamp after it are written with the prelude's `clamp` macro rather than
;;;; two ifs. It is the same arithmetic; the C spells it out because C has no
;;;; clamp.
import rl "vendor:raylib"
const screen-width = 800
const screen-height = 450
const max-buildings = 100
once buildings: [100 rl/Rectangle]
once build-colors: [100 rl/Color]
once player: rl/Rectangle
once camera: rl/Camera2D
fn main() -> ()
rl/init-window(screen-width, screen-height,
"raylib [core] example - 2d camera")
defer rl/close-window()
player = rl/Rectangle{.x 400.0, .y 280.0, .width 40.0, .height 40.0}
buildings = array(max-buildings, rl/Rectangle)
build-colors = array(max-buildings, rl/Color)
;; The skyline: each building is as wide as the last one left room for, so
;; the spacing accumulates and the row starts 6000 units to the left.
let spacing = 0
for i in range(max-buildings)
let w = f32(rl/get-random-value(50, 200))
h = f32(rl/get-random-value(100, 800))
buildings[i] = rl/Rectangle{.x -6000.0 + f32(spacing), .y (f32(screen-height) - 130.0) - h, .width w, .height h}
spacing += i32(w)
build-colors[i] = rl/Color{.r u8(rl/get-random-value(200, 240)) .g u8(rl/get-random-value(200, 240)) .b u8(rl/get-random-value(200, 250)) .a 255}
;; A fresh Camera2D is all zeroes and a zoom of 0 makes the transform
;; singular, so the zoom is the one field that must be set — raylib.fln's
;; own comment on the struct says so.
camera = rl/Camera2D{.offset rl/Vector2{.x f32(screen-width) / 2.0, .y f32(screen-height) / 2.0} .target rl/Vector2{.x player.x + 20.0, .y player.y + 20.0} .rotation 0.0 .zoom 1.0}
rl/set-target-fps(60)
until rl/window-should-close()
;; Update
;; The C's `if (RIGHT) ... else if (LEFT) ...`: both held cancels to the
;; right, which is the else-if and not an accident.
if rl/is-key-down(:key-right)
player.x += 2.0
elif rl/is-key-down(:key-left)
player.x -= 2.0
;; The camera follows the player's centre.
camera.target = rl/Vector2{.x player.x + 20.0, .y player.y + 20.0}
if rl/is-key-down(:key-a)
camera.rotation -= 1.0
elif rl/is-key-down(:key-s)
camera.rotation += 1.0
camera.rotation = clamp(camera.rotation, -40.0, 40.0)
camera.zoom += rl/get-mouse-wheel-move() * 0.05
camera.zoom = clamp(camera.zoom, 0.1, 3.0)
if rl/is-key-pressed(:key-r)
camera.zoom = 1.0
camera.rotation = 0.0
;; Draw
rl/with-drawing:
rl/clear-background(rl/raywhite)
;; Everything inside with-mode-2d is in world space and goes through
;; the camera. The ground and the crosshair lines are drawn far outside
;; the window on purpose: at zoom 0.1 they still have to reach the edges.
rl/with-mode-2d(camera):
rl/draw-rectangle(-6000, 320, 13000, 8000, rl/darkgray)
for i in range(max-buildings)
rl/draw-rectangle-rec(buildings[i], build-colors[i])
rl/draw-rectangle-rec(player, rl/red)
rl/draw-line(i32(camera.target.x), screen-height * -10,
i32(camera.target.x), screen-height * 10, rl/green)
rl/draw-line(screen-width * -10, i32(camera.target.y),
screen-width * 10, i32(camera.target.y), rl/green)
;; And everything after it is in screen space again — the frame drawn
;; around the window is how the example shows the difference.
rl/draw-text("SCREEN AREA", 640, 10, 20, rl/red)
rl/draw-rectangle(0, 0, screen-width, 5, rl/red)
rl/draw-rectangle(0, 5, 5, screen-height - 10, rl/red)
rl/draw-rectangle(screen-width - 5, 5, 5, screen-height - 10, rl/red)
rl/draw-rectangle(0, screen-height - 5, screen-width, 5, rl/red)
rl/draw-rectangle(10, 10, 250, 113, rl/fade(rl/skyblue, 0.5))
rl/draw-rectangle-lines(10, 10, 250, 113, rl/blue)
rl/draw-text("Free 2d camera controls:", 20, 20, 10, rl/black)
rl/draw-text("- Right/Left to move Offset", 40, 40, 10, rl/darkgray)
rl/draw-text("- Mouse Wheel to Zoom in-out", 40, 60, 10, rl/darkgray)
rl/draw-text("- A / S to Rotate", 40, 80, 10, rl/darkgray)
rl/draw-text("- R to reset Zoom and Rotation", 40, 100, 10, rl/darkgray)