107 lines
5.3 KiB
Plaintext
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)
|