Chapter 1: Odin't Know That, setup and a first look
This chapter:
- See what we're building and the philosophy behind it.
- Install Odin and confirm it works, including the fact that raylib is already inside it.
- Set up your editor.
- Open your first window with
odin run ..
Where we left off: nothing yet; this is the start. By the end, you'll have a working toolchain and a window on screen.
1.1 What we're building
Sköll is a side-view endless runner. A wolf runs left-to-right forever across a rolling Norse landscape. You press one key, jump, to clear rocks and leap over gaps in the ground, snatching glowing runes as you go. The longer you survive, the faster it gets. Fall into a gap or hit a rock and the run ends; your best score is saved for next time. Behind the wolf, layered hills drift past at different speeds, a sun arcs across a sky that slowly shifts from day to dusk to night, and every jump, pickup, and crash has a sound.
Here's the thing worth saying up front: there are no art files and no sound files in this project. The wolf is a handful of rectangles, triangles, and lines. The hills are circles. The runes are four-sided polygons. Every sound is a little maths: a sine wave shaped by an envelope, turned into raw audio samples at runtime. When you finish, the entire game is one executable file with nothing next to it, and it runs on any desktop.
That constraint isn't austerity for its own sake. Drawing everything from primitives means you understand every pixel; synthesising every sound means you understand the audio pipeline end to end; and shipping one dependency-free binary is a genuinely nice way to hand someone a game. It also keeps the focus where I want it here: on Odin.
Why raylib? Odin ships with bindings to a lot of libraries under
vendor:. For a small 2D game, raylib is the sweet spot: an immediate-mode library where you just say "draw this rectangle here" every frame, with input, timing, a 2D camera, and audio all in one. It does the tedious platform work so we can spend our pages on the language and on how the game feels. And because it's bundled with Odin, using it costs you exactly zero extra downloads.
1.2 What you need
- A desktop computer running Windows, macOS, or Linux.
- A terminal.
- A text editor.
No browser. No Node. No package manager. That's the whole shopping list.
1.3 Installing Odin
Follow the official installation guide: https://odin-lang.org/docs/install/. There are prebuilt releases for each platform, and instructions for building from source if you prefer. Odin depends on an LLVM installation for the backend; the guide covers the per-platform details, so work through it there rather than trusting a summary here that will drift out of date.
Once it's installed and on your PATH, confirm it:
odin version
You should see something like dev-2026-07:... (this book was written and verified against a dev-2026-07 build). For a fuller picture of what your toolchain thinks it's targeting:
odin report
Odin note: raylib is already here. You do not install raylib separately. It lives inside the Odin distribution under
vendor/raylib, alongside the standard library. In code, you'll reach it withimport rl "vendor:raylib", and it just works: the compiler knows wherevendor:is and links the right prebuilt raylib for your operating system. This is the whole reason I can promise "no downloads beyond Odin".
1.4 Setting up your editor
Two things make Odin pleasant to write:
- Syntax highlighting. Most editors have an Odin extension; the Odin site links the current ones.
- The language server,
ols.olsgives you go-to-definition, autocomplete, and inline errors. Install it and point your editor's LSP client at it. It is not required, since the compiler is the source of truth, but it makes exploring raylib's hundreds of procedures far easier.
The command you'll lean on most doesn't need an editor at all. From any package directory:
odin check .
This type-checks your code without producing a binary: fast, and the quickest way to catch a mistake. Run it often.
1.5 Hello, window
Enough setup. Make a directory for the project and create one file in it:
mkdir skoll
cd skoll
// main.odin
package main
import rl "vendor:raylib"
main :: proc() {
rl.InitWindow(960, 540, "Sköll")
defer rl.CloseWindow()
rl.SetTargetFPS(60)
for !rl.WindowShouldClose() {
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground(rl.Color{18, 18, 28, 255})
}
}
Run it:
odin run .
A 960×540 window titled Sköll opens, filled with a dark near-black colour, and it closes when you press Esc or click the close button. That's the milestone: your toolchain compiles, links raylib, and puts a window on screen.
Let's read it, because every line here comes back later.
package main: a directory is a package; the one containingmainis the program's entry point. The whole game will be this single package, split across several files.import rl "vendor:raylib": pull in the bundled raylib binding and give it the short local namerl. Every raylib call isrl.Something.rl.InitWindow(960, 540, "Sköll"): create the window and its OpenGL context. The"Sköll"title is acstring(a C string); raylib is a C library, and Odin passes string literals to it ascstringautomatically. We'll meetcstringproperly in Chapter 3, where it has a sharp edge.defer rl.CloseWindow():deferschedules a call to run when the enclosing procedure returns, no matter how it returns. PairingInitWindowwith a deferredCloseWindowright next to it means the teardown can never be forgotten. You'll see thisdefer-the-cleanup pattern constantly.rl.SetTargetFPS(60): ask raylib to pace the loop at 60 frames per second.for !rl.WindowShouldClose() { ... }: Odin has a singleforkeyword for all loops; with just a condition it's a while-loop.WindowShouldClose()returnstrueonce the user asks to quit.rl.BeginDrawing()/rl.EndDrawing(): everything you draw goes between these.EndDrawingswaps the finished frame to the screen. Again wedefertheEndso it always runs.rl.ClearBackground(rl.Color{18, 18, 28, 255}): paint the whole frame one colour.rl.Coloris four bytes: red, green, blue, alpha, each 0–255.
Gotcha: an empty loop still needs both
BeginandEnd. raylib expects exactly oneEndDrawingperBeginDrawingper frame.defer rl.EndDrawing()inside the loop body guarantees that even once we add earlycontinues later on.
1.6 The map of the journey
Here's the road from that blank window to the finished game:
- Ch. 2: a fast tour of exactly the Odin we'll use: types, arrays, structs, enums, pointers,
defer, and a first look atcontext. - Ch. 3: how Odin calls raylib (it's a C library), the immediate-mode loop in earnest, colours and timing, and the one interop trap:
cstringlifetimes. - Ch. 4: the wolf: reading the keyboard, gravity, and a jump that feels right.
- Ch. 5: a scrolling world with a 2D camera and parallax hills.
- Ch. 6: generating the endless course: seeded randomness and allocation-free entity arrays.
- Ch. 7: collisions, collecting runes, scoring, and the speed ramp: the game becomes a game.
- Ch. 8: juice: particles, screen shake, squash-and-stretch, a day→night sky.
- Ch. 9: the title screen, game-over screen, HUD, and the state machine tying them together.
- Ch. 10: sound, synthesised from scratch: one-shot effects and a live wind rumble.
- Ch. 11: saving the high score, and locking down deterministic, reproducible runs.
- Ch. 12: building a release binary and shipping it to all three desktop platforms.
The project grows one file at a time into this shape (all one package main):
skoll/
├── main.odin # window + audio bootstrap, the game loop
├── game.odin # tuning constants, game state, the update step
├── player.odin # the wolf
├── world.odin # scrolling, generation, collisions
├── entities.odin # data types + the obstacle table
├── draw.odin # everything you see
├── effects.odin # particles, shake, day→night
├── hud.odin # HUD + overlays
├── sound.odin # procedural audio
├── save.odin # high-score persistence
├── build.sh
└── run.sh
Checkpoint
odin run . opens a dark window titled Sköll and closes cleanly. Your compiler works, your editor is set up, and raylib links. That's everything Chapter 2 needs.
Exercises
- Change the background colour and window size. Rebuild and confirm your change.
- Change the window title to include a non-ASCII character (raylib's default font is limited, note what renders and what doesn't; we'll return to text in Chapter 9).
- Run
odin check .and then deliberately misspellrl.InitWindow. Read the error. Fix it.
Further reading
- Odin: install guide: https://odin-lang.org/docs/install/
- Odin: language overview: https://odin-lang.org/docs/overview/
olslanguage server: https://github.com/DanielGavin/ols- raylib: home and cheatsheet: https://www.raylib.com/ · https://www.raylib.com/cheatsheet/cheatsheet.html
- Sibling volume, Yo Odin (WebAssembly): https://nofuss.co.za/yo_odin/
- Understanding the Odin Programming Language: https://odinbook.com/