Odin & raylib · Reference
raylib Quick Reference
The raylib procedures a 2D game actually uses, with their real Odin signatures.
Signatures below are taken from the bindings that ship with the compiler
(/usr/lib/odin/vendor/raylib/raylib.odin, raylib 6.0). rl is the conventional alias
for import rl "vendor:raylib".
The fastest lookup you have
Nothing on this page beats grepping the bindings directly. Every procedure carries its original raylib comment.
grep -n "Draw.*Circle" /usr/lib/odin/vendor/raylib/raylib.odin
grep -n "Collision" /usr/lib/odin/vendor/raylib/raylib.odin
The skeleton
package main
import rl "vendor:raylib"
main :: proc() {
rl.SetConfigFlags({.VSYNC_HINT}) // before InitWindow
rl.InitWindow(960, 540, "Title")
defer rl.CloseWindow()
rl.InitAudioDevice() // only if you need sound
defer rl.CloseAudioDevice()
for !rl.WindowShouldClose() {
dt := rl.GetFrameTime()
// update
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.RAYWHITE)
// draw
free_all(context.temp_allocator)
}
}
Core types
Vector2 :: [2]f32 // an array, so + - * / work component-wise
Color :: distinct [4]u8 // r, g, b, a
Rectangle :: struct { x, y, width, height: f32 } // x,y is the TOP-LEFT corner
Camera2D :: struct {
offset: Vector2, // where `target` lands on screen
target: Vector2, // world point the camera looks at
rotation: f32, // degrees
zoom: f32, // 1.0 is unscaled — a zoom of 0 draws nothing
}
Built-in colours: RAYWHITE WHITE BLACK BLANK LIGHTGRAY GRAY DARKGRAY
YELLOW GOLD ORANGE PINK RED MAROON GREEN LIME DARKGREEN SKYBLUE
BLUE DARKBLUE PURPLE VIOLET DARKPURPLE BEIGE BROWN DARKBROWN MAGENTA.
Build your own with rl.Color{40, 42, 54, 255}, or fade one with
rl.Fade(rl.RED, 0.5).
Window and timing
| Procedure | Signature |
|---|---|
InitWindow |
proc(width, height: c.int, title: cstring) |
CloseWindow |
proc() |
WindowShouldClose |
proc() -> bool |
SetConfigFlags |
proc(flags: ConfigFlags) — a bit_set, call before InitWindow |
SetTargetFPS |
proc(fps: c.int) |
GetFrameTime |
proc() -> f32 — seconds since last frame |
GetTime |
proc() -> f64 — seconds since InitWindow |
GetScreenWidth / GetScreenHeight |
proc() -> c.int |
IsWindowResized |
proc() -> bool |
Useful ConfigFlag members: .VSYNC_HINT .WINDOW_RESIZABLE .MSAA_4X_HINT
.FULLSCREEN_MODE .BORDERLESS_WINDOWED_MODE .WINDOW_HIGHDPI.
Input
| Procedure | Signature |
|---|---|
IsKeyDown |
proc(key: KeyboardKey) -> bool — held |
IsKeyPressed |
proc(key: KeyboardKey) -> bool — the frame it went down |
IsKeyReleased |
proc(key: KeyboardKey) -> bool |
IsMouseButtonDown / IsMouseButtonPressed |
proc(button: MouseButton) -> bool |
GetMousePosition |
proc() -> Vector2 |
GetMouseWheelMove |
proc() -> f32 |
The enum type is inferred, so write rl.IsKeyDown(.SPACE). Key names are the raylib ones
with KEY_ stripped: .LEFT .RIGHT .UP .DOWN .SPACE .ENTER .ESCAPE .A
.Z .ZERO .NINE .LEFT_SHIFT .F1. Mouse buttons are .LEFT .RIGHT .MIDDLE.
IsKeyDown vs IsKeyPressed is the distinction that causes the most bugs. Movement
wants Down. Jumping, shooting, opening a menu want Pressed.
Drawing shapes
| Procedure | Signature |
|---|---|
ClearBackground |
proc(color: Color) |
DrawRectangleV |
proc(position, size: Vector2, color: Color) |
DrawRectangleRec |
proc(rec: Rectangle, color: Color) |
DrawRectangleLinesEx |
proc(rec: Rectangle, lineThick: f32, color: Color) |
DrawCircleV |
proc(center: Vector2, radius: f32, color: Color) |
DrawCircleLinesV |
proc(center: Vector2, radius: f32, color: Color) |
DrawLineV |
proc(startPos, endPos: Vector2, color: Color) |
Everything draws from the top-left and the y-axis points down. To centre a
PLAYER_SIZE box on pos, draw at pos - PLAYER_SIZE/2.
Text
| Procedure | Signature |
|---|---|
DrawText |
proc(text: cstring, posX, posY, fontSize: c.int, color: Color) |
MeasureText |
proc(text: cstring, fontSize: c.int) -> c.int |
DrawFPS |
proc(posX, posY: c.int) |
LoadFont |
proc(fileName: cstring) -> Font |
DrawTextEx |
proc(font: Font, text: cstring, position: Vector2, fontSize, spacing: f32, tint: Color) |
Drawing a number requires a cstring, and core:fmt has one built for exactly this:
import "core:fmt"
label := fmt.ctprintf("Score: %v", score)
rl.DrawText(label, 8, 8, 20, rl.BLACK)
ctprintf allocates from the temp allocator, so call
free_all(context.temp_allocator) once at the end of every frame.
Textures
| Procedure | Signature |
|---|---|
LoadTexture |
proc(fileName: cstring) -> Texture2D — after InitWindow |
UnloadTexture |
proc(texture: Texture2D) |
DrawTextureV |
proc(texture: Texture2D, position: Vector2, tint: Color) |
DrawTextureEx |
proc(texture: Texture2D, position: Vector2, rotation, scale: f32, tint: Color) |
DrawTexturePro |
proc(texture: Texture2D, source, dest: Rectangle, origin: Vector2, rotation: f32, tint: Color) |
DrawTexturePro is the one that does everything: source picks a sub-rectangle of the
image (this is how sprite sheets work), dest places and scales it, origin sets the
pivot for rotation. Use rl.WHITE as tint to draw unmodified.
Textures need a GPU context, so load them after InitWindow, not before.
Collision
| Procedure | Signature |
|---|---|
CheckCollisionRecs |
proc(rec1, rec2: Rectangle) -> bool |
CheckCollisionCircles |
proc(center1: Vector2, radius1: f32, center2: Vector2, radius2: f32) -> bool |
CheckCollisionCircleRec |
proc(center: Vector2, radius: f32, rec: Rectangle) -> bool |
CheckCollisionPointRec |
proc(point: Vector2, rec: Rectangle) -> bool |
GetCollisionRec |
proc(rec1, rec2: Rectangle) -> Rectangle — the overlap |
GetCollisionRec is how you resolve a collision rather than just detect it: the returned
rectangle tells you how deep the overlap is on each axis, and you push out along the
shallower one.
Camera
camera := rl.Camera2D{ zoom = 1 } // zoom = 0 draws nothing. Set it.
camera.target = player // follow the player
camera.offset = {WINDOW_WIDTH/2, WINDOW_HEIGHT/2}
rl.BeginMode2D(camera)
// world-space drawing
rl.EndMode2D()
// screen-space drawing (HUD) goes here, outside the camera
GetScreenToWorld2D(position, camera) converts a mouse position into world space —
essential the moment the camera moves. GetWorldToScreen2D goes the other way.
Audio
| Procedure | Signature |
|---|---|
InitAudioDevice / CloseAudioDevice |
proc() |
LoadSound |
proc(fileName: cstring) -> Sound |
PlaySound |
proc(sound: Sound) |
UnloadSound |
proc(sound: Sound) |
LoadMusicStream |
proc(fileName: cstring) -> Music — streamed, needs UpdateMusicStream each frame |
Short effects are Sound (decoded into memory). Long tracks are Music (streamed).
Vector maths
The bindings ship a raymath.odin, but prefer Odin’s own core:math/linalg. Because
Vector2 is just [2]f32, linalg works on it directly, and around a third of raymath’s
procedures are now marked deprecated in favour of a linalg equivalent — rl.Vector2Lerp
compiles with a warning telling you to use linalg.lerp.
import "core:math"
import "core:math/linalg"
dir := linalg.normalize0(target - player) // 0 version is safe on a zero vector
dist := linalg.length(target - player)
mid := linalg.lerp(a, b, 0.5)
ang := math.atan2(dir.y, dir.x)
x := clamp(player.x, 0, WINDOW_WIDTH) // builtin, no import
Where to look things up
- raylib cheatsheet — every function, one page.
- raylib examples — ~150 runnable programs in C.
- pkg.odin-lang.org/vendor/raylib — generated Odin docs.
Related: Odin cheatsheet · Lesson 1 · Lesson 2