funkey

A game engine for the terminal. Written in Rust. A game draws pixels and reads keys; funkey does the rest.

Rust Unlicense Linux | macOS Stay Amazing

What it is

A game is one type with two methods: update reads the keys and moves things, draw paints a frame of pixels. funkey runs the loop at a fixed rate and puts the frame on the terminal. That is the whole contract, and Doom fits it.

Pixels, two ways

Half blocks in any terminal: two pixels a cell, in full colour, only the cells that changed. Real pixels through the kitty graphics protocol where the terminal has it, over shared memory in glass.

Keys that go up

A terminal never says when a key is released. glass and kitty do, through the kitty keyboard protocol, so a held arrow walks and a tap takes one step. Elsewhere a key counts as held while its repeats come.

Sound

A mixer in the engine, piped to pw-play, paplay or aplay. WAV files, Doom lumps, or sounds made on the spot: tones, slides, noise, and tunes written as notes. Channels loop and change pitch while they play.

2D, 3D and Doom

Sprites from rows of characters or PNG files, tile maps with platformer physics, particles, two fonts. A software 3D rasterizer with a depth buffer and fog. A WAD loader and a sector renderer that draws Doom's own levels.

The games

Seven games come with the engine. Each has its own version, shown on its title screen and tagged in the repo; the crate version is the engine's. Every one runs with cargo run --release --example <name>, and with FUNKEY_PIXELS=kitty in glass or kitty for real pixels. funkeys shows them as cards and plays the one you pick; every release carries it and the games as binaries.

castle: the gatehouse and keep across the moat, banners flying

castle

A walk to a castle
castle-v1.0

From the hills, down the path, over the drawbridge and in through the gate. Stone, slate and wood as textures over the walls, the sun and its shadows baked into every corner, banners in the wind, torches, pines, mountains and clouds, painted on every core at 960 by 600. Nothing to win, only to look at.

cargo run --release --example castle
soar: mountains, water and clouds under a low sun

soar

A flight over fractal mountains
soar-v1.0

A height map ray-cast a column at a time, the way Comanche did it, with the sun and its shadows baked in, haze, water that glitters, and clouds drifting over. The autopilot flies; take the controls when you like. There is nothing to win, only to look at.

cargo run --release --example soar
Doom: a zombie approaches down a corridor, the pistol raised

doom

The game, from any Doom WAD
doom-v1.0

Every monster from both Dooms with Doom's own frame timings, all nine weapons, pickups, keys, doors, lifts, crushers, teleports, switches, the status bar with the face, the automap, the intermission, the sounds. Freedoom's WADs are free; Doom's own load the same way.

cargo run --release --example doom
jumpman: girders, ladders and bombs

jumpman

Platformer, Jumpman Junior in spirit
jumpman-v1.1

Five levels of girders, ladders and ropes. Take every bomb, jump the bullets, dodge the robots, and do not fall too far. One bomb reveals hidden ladders, one takes girders away. A title tune, a high score.

cargo run --release --example jumpman
invaders: five rows of eleven above four shields

invaders

Space Invaders in spirit
invaders-v1.0

Fifty-five of them, marching down to the four-note beat that quickens as the rows thin. Shields crumble pixel by pixel from either side, a mystery ship crosses the top, and each wave starts a row lower.

cargo run --release --example invaders
drive: a red car on a road over green hills

drive

Driving, on the 3D rasterizer
drive-v1.0

A car on a winding road over the hills. Fetch the packages before the clock runs out, then more of them with less time. An arrow points at the nearest. Leave the road and the grass slows you.

cargo run --release --example drive
climb: bricks, ladders and coins

climb

The first game, 200 lines
climb-v1.0

Ladders, coins, blobs. The smallest game on the engine, and the one to read first when you want to write your own.

cargo run --release --example climb

Doom, in the terminal

The game's own frames and sounds, written straight to video by a scripted run. Every pixel is what the terminal shows.

Make your own

A bouncing ball is the whole shape of a game. Give it a name, a Game impl, and run it.

use funkey::*;

struct Ball { x: f32, y: f32, vx: f32, vy: f32 }

impl Game for Ball {
    fn update(&mut self, input: &Input, dt: f32) -> Flow {
        if input.pressed(Key::Char('q')) { return Flow::Quit; }
        self.x += self.vx * dt;
        self.y += self.vy * dt;
        if self.x < 0.0 || self.x > 156.0 { self.vx = -self.vx; }
        if self.y < 0.0 || self.y > 96.0 { self.vy = -self.vy; }
        Flow::Continue
    }
    fn draw(&mut self, f: &mut Frame) {
        f.clear(0x102030);
        f.rect(self.x as i32, self.y as i32, 4, 4, 0xffcc00);
    }
}

fn main() {
    run(&mut Ball { x: 10.0, y: 10.0, vx: 60.0, vy: 40.0 }, Config { width: 160, height: 100, fps: 60 });
}
# Cargo.toml
[dependencies]
funkey = { git = "https://github.com/isene/funkey", package = "fe2o3-funkey" }

From there: Sprite::from_rows for art, Tilemap and Body for a level that can be walked, Audio with Sample::tone for a sound made of nothing, Raster for a third dimension. With FUNKEY_SCRIPT set, a game runs with no terminal and writes its frames, so it can be tested, and filmed.