186 lines
8.6 KiB
Plaintext
186 lines
8.6 KiB
Plaintext
;;;; raylib [core] example - input virtual controls
|
|
;;;;
|
|
;;;; examples/core/core_input_virtual_controls.c. No new bindings.
|
|
;;;;
|
|
;;;; Split the way sand.flan is split: everything above the second banner is
|
|
;;;; arithmetic with no raylib calls in it, so test/programs/virtual-controls-
|
|
;;;; headless.flan can import this file as a package, drive the pointer over a
|
|
;;;; scripted path and hash where the player ended up. That is the only one of
|
|
;;;; the ten examples with a headless half, and it has one because it is the
|
|
;;;; only one whose interesting part — which D-pad button is under the pointer,
|
|
;;;; and what that does to the player — is a function of numbers rather than of
|
|
;;;; a frame buffer. The link follows what the program reaches, so the headless
|
|
;;;; driver pulls in neither a window nor libraylib.
|
|
;;;;
|
|
;;;; Two language notes, both visible below.
|
|
;;;;
|
|
;;;; **No `break`.** The C's nearest-button search is
|
|
;;;;
|
|
;;;; for (i = 0; i < BUTTON_MAX; i++) { if (...) { pressedButton = i; break; } }
|
|
;;;;
|
|
;;;; and `(break)` is refused: "break is not implemented yet (see the build
|
|
;;;; sequence in plan.org)". `return` from the function works and is what this
|
|
;;;; uses — which is why the search is its own `defn` and not written inline
|
|
;;;; the way the C has it. That is not a loss: the function is the thing the
|
|
;;;; headless case wants to call anyway. Where a loop genuinely cannot be a
|
|
;;;; function, the shape left is a flag and a compound loop condition.
|
|
;;;;
|
|
;;;; **No `fabsf`.** The prelude has sqrt-f32 and sign-f32 but no absolute
|
|
;;;; value for floats, and the comment there says why for integers. `(max v
|
|
;;;; (- 0.0 v))` is it, and unlike the integer case it has no edge: there is no
|
|
;;;; float whose negation is itself except -0.0, whose absolute value is 0.0
|
|
;;;; either way.
|
|
;;;;
|
|
;;;; The button geometry is `defconst` arrays of struct literals. That works —
|
|
;;;; and it is the only way a fixed array can be made inside anything but a
|
|
;;;; top-level `defonce`, since a `let` binding takes no type annotation.
|
|
|
|
import rl "vendor:raylib"
|
|
|
|
const screen-width = 800
|
|
const screen-height = 450
|
|
|
|
;; The C's PadButton enum. -1 is BUTTON_NONE and the four directions are 0..3,
|
|
;; in the order the arrays below are written. A `defenum` would not help: the
|
|
;; value is used as an index and an enum does not convert to an integer
|
|
;; ("i32 converts a number, found ..."), so it would have to be turned back
|
|
;; into one at every use.
|
|
const button-none = -1
|
|
const button-up = 0
|
|
const button-left = 1
|
|
const button-right = 2
|
|
const button-down = 3
|
|
const button-max = 4
|
|
|
|
const button-radius: f32 = 30.0
|
|
|
|
;; padPosition is {100, 350} and the buttons sit a radius and a half away from
|
|
;; it on each axis. The C computes these at run time from padPosition; they are
|
|
;; written out here because a defconst is compile-time and the numbers are.
|
|
const button-positions: [button-max rl/Vector2] = [rl/Vector2{.x 100.0 .y 305.0} ; up
|
|
; left
|
|
; right
|
|
; down
|
|
rl/Vector2{.x 55.0 .y 350.0}
|
|
rl/Vector2{.x 145.0 .y 350.0}
|
|
rl/Vector2{.x 100.0 .y 395.0}]
|
|
|
|
const player-speed: f32 = 75.0
|
|
|
|
;;;; ── The part with no raylib in it ──────────────────────────────────
|
|
|
|
once player: rl/Vector2
|
|
|
|
fn reset-player() -> ()
|
|
player = rl/Vector2{.x f32(screen-width) / 2.0, .y f32(screen-height) / 2.0}
|
|
|
|
fn abs-f32(v: f32) -> f32 = max(v, 0.0 - v)
|
|
|
|
;; The C's search, with `return` where it had `break`. Manhattan distance and
|
|
;; not Euclidean — the C adds the two axis distances and compares against the
|
|
;; radius — so the hit area is a diamond, which is deliberate: it makes the
|
|
;; four buttons tile without overlapping.
|
|
fn nearest-button(pos: rl/Vector2) -> i32
|
|
for i in range(button-max)
|
|
let b = button-positions[i]
|
|
dist-x = abs-f32(b.x - pos.x)
|
|
dist-y = abs-f32(b.y - pos.y)
|
|
if dist-x + dist-y < button-radius
|
|
return i
|
|
button-none
|
|
|
|
;; The C's switch on the pressed button. Nothing moves for button-none, which
|
|
;; is the `default: break`.
|
|
fn move-player(button: i32, dt: f32) -> ()
|
|
let step = player-speed * dt
|
|
if button == button-up
|
|
player.y -= step
|
|
elif button == button-left
|
|
player.x -= step
|
|
elif button == button-right
|
|
player.x += step
|
|
elif button == button-down
|
|
player.y += step
|
|
else
|
|
()
|
|
|
|
;; FNV-1a over the player's two floats, so the headless driver has one number
|
|
;; to compare. The floats are read through their bit patterns rather than
|
|
;; rounded to integers: rounding would hide exactly the kind of drift — a
|
|
;; crossed x and y, a step applied twice — the case exists to catch.
|
|
const fnv-offset: u64 = 0xcbf29ce484222325
|
|
const fnv-prime: u64 = 1099511628211
|
|
|
|
fn hash-f32(h: u64, v: f32) -> u64
|
|
;; No float-to-bits cast in the language, so the value is scaled and
|
|
;; truncated instead. 1024 keeps three decimal places of a screen
|
|
;; coordinate, which is finer than any real difference between two correct
|
|
;; runs and coarser than the last bit of an f32 — so this is reproducible
|
|
;; across targets where a raw bit pattern would depend on the FPU.
|
|
let n = i64(v * 1024.0)
|
|
g = h
|
|
for b in range(8)
|
|
g = g ^^ u64(n >> i64(b * 8) && 255)
|
|
g *= fnv-prime
|
|
g
|
|
|
|
fn hash-player() -> u64 = hash-f32(hash-f32(fnv-offset, player.x), player.y)
|
|
|
|
;;;; ── The raylib front-end ───────────────────────────────────────────
|
|
|
|
;; The arrowheads, one triangle of three points per button, in the winding
|
|
;; raylib wants — counter-clockwise, or it culls them and draws nothing. A
|
|
;; [4 [3 rl/Vector2]]: a fixed array of fixed arrays of a struct, which is the
|
|
;; deepest shape any of these ten examples asks for and which works.
|
|
const arrow-tris: [button-max [3 rl/Vector2]] = [[rl/Vector2{.x 100.0 .y 293.0} rl/Vector2{.x 91.0 .y 314.0} rl/Vector2{.x 109.0 .y 314.0}]
|
|
[rl/Vector2{.x 64.0 .y 341.0} rl/Vector2{.x 43.0 .y 350.0} rl/Vector2{.x 64.0 .y 359.0}]
|
|
[rl/Vector2{.x 157.0 .y 350.0} rl/Vector2{.x 136.0 .y 341.0} rl/Vector2{.x 136.0 .y 359.0}]
|
|
[rl/Vector2{.x 91.0 .y 386.0} rl/Vector2{.x 100.0 .y 407.0} rl/Vector2{.x 109.0 .y 386.0}]]
|
|
|
|
const label-colors: [button-max rl/Color] = [rl/Color{.r 253 .g 249 .b 0 .a 255} ; yellow, up
|
|
; blue, left
|
|
; red, right
|
|
; green, down
|
|
rl/Color{.r 0 .g 121 .b 241 .a 255}
|
|
rl/Color{.r 230 .g 41 .b 55 .a 255}
|
|
rl/Color{.r 0 .g 228 .b 48 .a 255}]
|
|
|
|
fn main() -> ()
|
|
rl/init-window(screen-width, screen-height,
|
|
"raylib [core] example - input virtual controls")
|
|
defer rl/close-window()
|
|
reset-player()
|
|
rl/set-target-fps(60)
|
|
until rl/window-should-close()
|
|
;; Update. Touch first, mouse as the desktop stand-in — and on the desktop
|
|
;; the left button has to be held, or the player would follow the cursor
|
|
;; wherever it went.
|
|
let touching = rl/get-touch-point-count() > 0
|
|
let input =
|
|
if touching then rl/get-touch-position(0) else rl/get-mouse-position()
|
|
let pressed =
|
|
if touching or rl/is-mouse-button-down(:mouse-left)
|
|
nearest-button(input)
|
|
else
|
|
button-none
|
|
move-player(pressed, rl/get-frame-time())
|
|
;; Draw
|
|
rl/with-drawing:
|
|
rl/clear-background(rl/raywhite)
|
|
rl/draw-circle-v(player, 50.0, rl/maroon)
|
|
for i in range(button-max)
|
|
rl/draw-circle-v(button-positions[i], button-radius,
|
|
if i == pressed then rl/darkgray else rl/black)
|
|
let t = arrow-tris[i]
|
|
rl/draw-triangle(t[0], t[1], t[2], label-colors[i])
|
|
rl/draw-text("move the player with D-Pad buttons", 10, 10, 20,
|
|
rl/darkgray)
|
|
;; Not in the C: which button the search picked, as a number, so the
|
|
;; headless case and the window agree about the same thing.
|
|
rl/draw-text("button: ", 10, 34, 20, rl/lightgray)
|
|
rl/draw-text(str(i64->bytes(i64(pressed))),
|
|
10 + rl/measure-text("button: ", 20), 34, 20, rl/lightgray)
|
|
;; The number drawn this frame was formatted into the temp allocator;
|
|
;; this hands that memory back once the frame is drawn.
|
|
free-temp()
|