collider

package module
v0.6.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 34 Imported by: 0

README

# Collider

The fastest way to a playable 2D game in Go. Every game you build can be played by humans and by AI agents, out of the box.

Collider is a code-first 2D game engine built around one idea: games are made of objects that collide, and things that happen when they do. You import the library, describe your objects, attach events, and you have a game. No editor, no project files, no boilerplate game loop.

player.OnCollisionWith("enemy", func(e *engine.Object) {
    g.Sound("hit.wav")
    g.Go("gameover")
})

That covers the humans. For the agents, the very same binary you ship to players is also a deterministic headless environment for bots and CI, and an MCP server that any AI agent can connect to and play, with zero extra code in your game:

claude mcp add mygame -e COLLIDER_AGENT=mcp -- C:\path\to\mygame.exe

Zombie Night, one of the example games

What's in the box:

  • Collision-first core: enter-only collision events, tag rules, solid resolution, gravity, spatial hash broad phase
  • AI agents can play every game made with Collider, out of the box: headless and deterministic for bots and CI, over MCP for any AI agent (Claude, Cursor, or a plain script), or on-screen via autopilot; one example game is played by its own agent and even records its demo GIF itself
  • Scenes, timers, sprite-sheet animations, text with custom TTF fonts (with fallback fonts for any script, Chinese included) and line wrapping, sound and music (wav/ogg)
  • Fifteen example games across genres, from pong to a dungeon crawler, each one a template you can start from
  • Shipping built in: embed assets into a single .exe or build for the browser; one example is live on itch.io
  • A GIF recorder for demos, and generated placeholder assets so nothing here needs hand-drawn art to run

Design principles

  1. Fewest lines possible. A complete game with menus, music, sprites and logic should fit in one small file. Every line of required boilerplate is a bug.
  2. Collisions are the core. Clicks are point-vs-object collisions. Triggers, solid walls, bullets hitting enemies: one unified system with events.
  3. Sensible defaults. Sprites collide with their image bounds. Sounds just play. Scenes just switch. Configuration is always optional.
  4. Scenes are everything. A menu, a level and a game-over screen are all the same concept. Learn one thing, build every screen of your game with it.
  5. No magic. Plain Go, plain callbacks, go get and read the code.

Built on top of Ebitengine for windowing, rendering, input and audio. Collider is the game-object, collision and event layer that every Ebitengine project currently rebuilds by hand.


Install

go get github.com/LucasAntunesdeAlmeida/collider
import engine "github.com/LucasAntunesdeAlmeida/collider"

Hello, collision

The smallest complete program: two objects, one event.

go run ./examples/hello
package main

import (
	"fmt"

	engine "github.com/LucasAntunesdeAlmeida/collider"
)

func main() {
	g := engine.New("Hello, collision", 800, 600)
	play := g.Scene("play")

	box := play.Add(engine.Rect(64, 64, engine.Red).At(400, 300))
	player := play.Add(engine.Rect(48, 48, engine.Blue).At(100, 300))

	player.OnUpdate(func(dt float64) {
		if g.Key(engine.Right) {
			player.Move(200*dt, 0)
		}
		if g.Key(engine.Left) {
			player.Move(-200*dt, 0)
		}
	})

	player.OnCollision(func(other *engine.Object) {
		fmt.Println("hit!")
	})

	box.OnClick(func() {
		box.Destroy()
	})

	g.Run("play")
}

Example games

Fifteen complete games, each exercising a different part of the engine and each a starting point for your own. Run any of them from its own folder so the asset paths resolve:

cd examples/pong
go run .
Game Genre What it proves
Hello Smallest program Objects, movement, collision and click events
Pong Arcade / versus Built-in velocity, solid bounce, score text
Jumper Platformer Gravity, solid ground, Grounded(), pickups
Zombie Night Top-down shooter Runtime spawning, tags, scene collision rules, timers
Horde Survivor A world bigger than the window: camera follow, screen-fixed HUD, draw layers; drawing effects (flip, rotate, tint, flash, fade); area queries (auto-aim, contact damage); hitboxes smaller than the sprites; an overlay pause menu with number-key shortcuts (keys by name); a drag joystick and pause on focus loss; a mute key and separate music and sound volumes; full gamepad play with pad prompts; a QUIT entry where quitting is possible
Breakout Brick breaker Grid spawning, win conditions, mouse control, menus
Memory Point-and-click puzzle A game with zero movement, pure click events
Arena Survival Extending the engine: your own types embedding Object with custom methods
Caves Exploration Procedural generation: cellular automata map, BFS-guaranteed winnable
Runner Endless runner Sprite sheet animations (run/jump/death), moving-world auto-scroll
Dialog Visual novel Custom TTF fonts, text size and color, typewriter effect; speaks the player's language (g.Language), Chinese included, through a CJK fallback font; speech wrapped and left-aligned in a fixed text area, labels anchored by their measured Width
Dungeon Dungeon crawler Multiple screens: ASCII room layouts, edge transitions, key and lock
Catcher Arcade Save data that persists on desktop and in the browser: g.Save / g.Load; a record kept when the window closes mid-round: g.OnClose
Ship Arcade Publishing: embedded assets, fullscreen/resizable/icon, exe and browser builds
Gem Rush Arcade Agents only: no human input; played by its own pilot, headless bots, or MCP agents

Each example folder has the game's code, an explanation and a demo GIF. The placeholder assets are generated by go run ./tools/genassets; replace them with real art and the games look like games.

AI agents can play your game

Any game you build with Collider is agent-playable out of the box, with zero overhead until used. An agent sees the game as structured data, one observation per step: the scene, and every object with its tag, position, motion, size and text (so HUDs and scores are readable):

{"scene": "play", "objects": [
  {"tag": "player", "x": 400, "y": 520, "w": 48, "h": 48},
  {"tag": "comet", "x": 312, "y": 180, "vy": 260, "w": 24, "h": 24},
  {"text": "SCORE 12", "x": 64, "y": 24, "w": 96, "h": 16}
], "state": {"lives": 2, "wave": 3}}

Positions are world coordinates; objects pinned to the screen with Fixed (a HUD) report screen coordinates and carry "fixed": true. w and h are the collision box: an object's Hitbox when it has one, so agents dodge exactly what can hit them. While an overlay is open (a pause menu), "overlay" names it: it takes all input, and objects lists the frozen scene's objects, then the overlay's. Once the game calls Quit (its QUIT entry), "quit": true marks the run as over.

Three optional calls make any game, however complex, fully agent-playable and self-describing:

  • g.Controls(map[string]engine.Key{"jump": engine.Space, ...}) names your inputs; the MCP act tool accepts these names and lists them in its own description, so agents discover how to play on connect.
  • g.AgentState(func() any {...}) attaches game state (score, health, phase) to every observation as the state field above.
  • g.AgentDocs("...") serves your game's rules to agents when they connect (the MCP initialize instructions).

Four ways in:

  • Headless (bots, CI): g.Headless("play") then a loop of g.Step(engine.Action{Keys: ...}), each step returning the next observation. Deterministic: same actions, same results. Playtest your game with a bot on every commit.
  • MCP (AI agents): run any Collider game with COLLIDER_AGENT=mcp and it serves MCP on stdio (observe, act, reset tools). Any MCP client can connect and play your game; act accepts any keyboard key by name, or the names you declared with Controls, plus click or a held pointer (down) at x, y, and gamepad buttons (pad) and the left stick (stickX, stickY) for games played with a pad.
  • Windowed MCP (fight the AI): COLLIDER_AGENT=mcp-window opens the normal window running in real time while serving the same MCP tools. Agent input merges with the keyboard, so a person and an agent can play the same game together, and COLLIDER_RECORD captures the match.
  • Autopilot (watch it): g.Autopilot(fn) runs the game windowed while your agent function supplies the input each frame. Combine with the GIF recorder and an agent records your demo for you.

Connect any MCP client in one command

The game itself is the MCP server (stdio): there is nothing to install. With Claude:

claude mcp add mygame -e COLLIDER_AGENT=mcp -- C:\path\to\mygame.exe

Cursor, Windsurf, VS Code and every other MCP client take the same shape, a command plus one env var:

{"mcpServers": {"mygame": {
  "command": "C:/path/to/mygame.exe",
  "env": {"COLLIDER_AGENT": "mcp"}
}}}

The protocol is plain newline-delimited JSON-RPC, so even a Python script can drive a game through a subprocess.

Tag your player "player" so agents can find themselves. A game can opt out entirely with g.DisallowAgents(), which also makes it ignore COLLIDER_RECORD. The Gem Rush example is a game with no human input at all: its own pilot plays it, and its demo GIF is agent-recorded.

Publish your game

Collider games ship as a single self-contained .exe (assets embedded via engine.UseAssets and go:embed) or run in the browser via WebAssembly, ready for itch.io. PUBLISHING.md is the complete guide; the ship example has it all wired up, and it is actually published: play Ship in your browser on itch.io.

Performance

A frame's work scales with what is on screen and what actually touches, not with bookkeeping. The engine's own benchmark (go test -bench Scene -benchmem) runs a survivor-style scene of 1000 live objects: 400 enemies crowding the hero, 150 projectiles, 300 visual gems and 150 props and walls, with the camera following. On a Ryzen 7 5800H one frame of logic (motion, broad phase, collision events, solids) takes about 0.75 ms with zero allocations, and preparing the draw about 0.26 ms, since objects outside the view are skipped. That leaves nearly all of a 60 fps frame to your game.

Record a GIF of your game

Every Collider game can record itself, no tooling needed. Set the COLLIDER_RECORD environment variable to a file path, play, close the window, and the session is saved as an animated GIF (the demos above were made exactly this way):

$env:COLLIDER_RECORD="demo.gif"; go run .

A game that calls g.DisallowAgents() ignores COLLIDER_RECORD, so a shipped build never writes a GIF wherever its environment says; call it only in release builds (a build tag) to keep recording demos.


API cheat sheet

The complete public surface implied by the example games. If it is not here, it does not exist. That is the point.

Game

Call Meaning
engine.New(title, w, h) *Game Create the game/window
g.Scene(name) *Scene Create (or fetch) a scene
g.Go(name) Switch scenes (state preserved)
g.Restart(name) Switch scenes, resetting it to its initial state
g.Overlay(name) Show a scene on top (pause, menus): it runs, the scene under it draws frozen
g.CloseOverlay() Remove the overlay; the scene under it resumes where it froze
g.Run(name) Start the loop on a scene (blocks)
g.Key(k) bool Is this key held?
engine.KeyNamed(name) Key Any key by name, for keys without a constant: "1"/"Digit1" (number row), "Numpad1", "J", "F1", "Tab", "ShiftLeft"...; the names agents use; panics on an unknown name
g.Mouse() (x, y) Cursor position (a finger on a touch screen counts)
g.MouseDown() bool Is the left button (or any finger) held? Drags, virtual joysticks
g.Focused() bool Does the window have focus? Pause when the player switches away (always true for agents)
g.PadDown(b) bool Is this gamepad button held on any connected pad? engine.PadA, PadB, PadX, PadY, PadLB, PadRB, PadLT, PadRT (triggers pulled), PadBack, PadStart, PadLStick, PadRStick (sticks pressed in), PadUp, PadDown, PadLeft, PadRight (d-pad); standard layout by position: Xbox, PlayStation, Switch Pro, Steam Deck
g.PadAxis(a) float64 A stick, raw -1..1 with Y down: engine.PadLeftX, PadLeftY, PadRightX, PadRightY; no dead zone (ignore small tilts yourself); the pad pushed furthest answers
g.PadConnected() bool Is a gamepad connected? Switch on-screen prompts (agents: from their first pad action on)
g.Sound(path) Fire-and-forget sound effect (silent for agents and tests, see below)
g.Volume(v) Master volume 0..1 (0 mutes) for the playing music and every sound started after; scales both channels below
g.MusicVolume(v) Music volume 0..1, times the master: applies to the playing music at once (a settings slider)
g.SoundVolume(v) Sound effect volume 0..1, times the master, for every Sound started after (0: they do no work)
g.Save(key, v) error Persist a JSON-encodable value (file on desktop, localStorage in the browser, memory for agents and tests)
g.Load(key, &v) bool Read it back; false (v untouched) when missing or undecodable
g.Quit() End the game after this frame: the window closes and Run returns (does nothing in a browser; headless, the run ends)
g.CanQuit() bool Can Quit end the game here? False in a browser, where a page cannot close its tab: hide the QUIT entry
g.Language() string The player's language as a BCP 47 tag ("pt-BR", "zh-CN", "en-US"), "" when unknown: pick your translation at startup. "" for agents and tests; COLLIDER_LANG=pt-BR overrides it to try one
g.OnClose(fn) Run fn once when the player closes the window (X, Alt+F4, a launcher's exit), then end the game like Quit: save or bank progress there. Never runs headless, in a browser, or on Quit
g.Fullscreen(on) / g.Resizable(on) / g.Icon(path) Window polish
g.Headless(scene) / g.Step(action) / g.Observe() Agent play, headless and deterministic
g.Controls(map[string]Key) Name your inputs so agents can discover them
g.AgentState(fn) Attach game state to every observation
g.AgentDocs(text) Game rules served to agents on MCP connect
g.DisallowAgents() Lock a shipped game: no agent play (on by default), COLLIDER_AGENT and COLLIDER_RECORD ignored

Scene

Call Meaning
s.Add(obj) *Object Put an object in the scene (any time, even mid-game)
s.Music(path) Looping background music while the scene is active (silent for agents and tests)
s.Gravity(px_per_s2) Gravity for WithGravity() objects
s.Every(sec, fn) Repeating timer
s.After(sec, fn) One-shot timer
s.OnClick(fn(x, y)) Click anywhere in the scene
s.OnUpdate(fn(dt)) Runs every frame while the scene is active
s.OnCollision(tagA, tagB, fn(a, b)) Rule for every current and future pair
s.Count(tag) int How many objects with this tag are alive
s.Camera(x, y) Center the view on a world point (follow the player every frame)
s.Near(tag, x, y, r) []*Object Live objects with this tag whose box overlaps the circle, closest first (visual included, Fixed not)

Object: constructors and configuration (chainable)

Call Meaning
engine.Sprite(path) Object from an image (collider = image bounds)
engine.Rect(w, h, color) Colored rectangle object
engine.Text(str) Text object (clickable, but never collides)
engine.UseAssets(fs) Load all assets from an embedded filesystem (go:embed)
.At(x, y) Position (center); .AtEdge() picks a random screen edge
.Tag(name) Label for tag-based collision rules
.Solid() Engine resolves overlaps (walls, floors, paddles)
.WithGravity() Affected by the scene's gravity
.Visual() Decoration: drawn, never collides
.Fixed() Pinned to the screen: ignores the camera, never collides (HUD)
.Layer(n) Draw order: lower layers first, insertion order within a layer; clicks hit the top
.Size(w, h) Override the drawn size (also the collider, unless Hitbox is set)
.Hitbox(w, h) Collision box apart from the drawn size, centered: collisions, solids, Touching, Near and agents' w/h use it; drawing and clicks keep the drawn bounds
.Animation(name, strip, frames, fps) Define a sprite-sheet animation
.Font(paths...) Custom TTF/OTF font. More paths are fallbacks: the first font draws every glyph it has, the next ones fill in the rest (a CJK font behind a Latin pixel font), each loaded the first time a text needs it
.TextSize(px) / .TextColor(c) Text styling (size, color)
.Wrap(width) Break text into lines at most width px wide: at spaces, between CJK characters (never before ,。!?」 or after 「), inside a word only if it alone is wider; "\n" always breaks. The text's box becomes that column, still centered on its position
.TextAlign(a) How the lines line up in the text's box: engine.AlignCenter (default), engine.AlignLeft, engine.AlignRight
.LineHeight(px) Distance between the baselines of consecutive lines (default: the fonts' own line height)

Object: runtime

Call Meaning
o.X, o.Y, o.Vx, o.Vy Position and velocity; velocity applies every frame
o.Data Free any field for your game state
o.Move(dx, dy) Move (respects solid collisions)
o.MoveToward(x, y, dist) / o.VelocityToward(x, y, speed) Homing helpers
o.Grounded() bool Resting on a solid (platformers)
o.Width() float64 / o.Height() float64 The drawn size in px: the Size, the image (once added), the Rect, or the text as measured (fonts and fallbacks, TextSize, the Wrap column, lines LineHeight apart, a button's plate); current right after SetText, Font, TextSize, Wrap or LineHeight, no frame or window needed. Lay labels out by their edges, test that translations fit. Never the Hitbox
o.SetText(s) / o.SetSprite(path) Change content at runtime
o.Play(name) / o.PlayOnce(name) Switch animations (loop / hold last frame)
.Alpha(a) Opacity, 0 (invisible) to 1 (opaque, the default): fades, ghosts
.Tint(c) Multiply the colors by c (nil clears): color variants of one sprite
o.Flash(c, sec) Draw a solid silhouette of color c for sec seconds (hit flash)
.Rotate(rad) Draw turned around the center, clockwise; the collider stays upright
.FlipX(on) Draw mirrored horizontally (face the way you walk); drawing only
o.LifeTime(sec) Auto-destroy after n seconds
o.Destroy() Remove from scene (safe inside callbacks; applied at end of frame)
o.OnUpdate(fn(dt)) Per-frame logic
o.OnClick(fn) Clicked (point-vs-bounds collision)
o.OnCollision(fn(other)) Touching anything
o.OnCollisionWith(tag, fn(other)) Touching anything with this tag
o.Touching(tag) []*Object What overlaps o right now, with this tag (continuous contact; never visual)

Behavior you can rely on

  1. Destroy() is always safe, including inside any callback: the object is removed at the end of the frame, never mid-iteration.
  2. Collision events fire on enter. OnCollision fires once when contact begins, not on every frame of overlap. Continuous contact (standing on the floor) is one event. To act on every frame of contact (damage over time), ask o.Touching(tag) instead.
  3. Solid vs trigger is the whole physics story. Solid() objects push others out; everything else just reports contact. No forces, no torque, no restitution. If your game needs Angry Birds physics, this is not its engine.
  4. Text never collides. A score label overlapping the ball will not bounce it. Text objects render and take clicks, nothing else.
  5. Restart rolls a scene back to its setup. Objects added before the scene first runs are restored (even if destroyed); objects and timers spawned during play are dropped. Variables captured in your closures are yours to reset.
  6. A tap is a click. g.Mouse() and every OnClick read touch as well as the mouse, so a game built for a mouse works on a phone without a second input path. A lifted finger leaves the cursor where it was, so anything following the cursor stays put until the next touch. g.MouseDown() is true while the button or any finger is held.
  7. An overlay freezes the scene under it. While g.Overlay is open, only the overlay updates, ticks timers, takes clicks and collides; the scene under it still draws but does not advance at all. Opening and closing apply at the start of the next frame, like g.Go, and Go/Restart close any overlay.
  8. Quit ends the game after the frame. The frame that calls g.Quit() finishes (its updates, deferred destroys and draw), then the window closes and Run returns. In a browser a page cannot close its tab, so Quit does nothing there and g.CanQuit() is false. Headless, the run ends instead: every later Step advances nothing and returns the final observation with quit: true, until Headless (or the MCP reset tool) starts a new run. Closing the window runs the g.OnClose handler once (if the game set one) between two frames, then ends the game the same way; no frame runs after it.
  9. Audio never gets in the way. Agent and test runs (Headless, Autopilot, COLLIDER_AGENT=mcp, go test) are silent: Sound and Music do no audio work and the sound device is never opened. These are the same runs whose saves stay in memory. A COLLIDER_AGENT=mcp-window session is a real window a person watches and plays along with, so it has sound. A machine with no working audio device (a Linux box or container with no PulseAudio or PipeWire server and no ALSA device) plays silently after one log line, instead of failing to start.
  10. Agents and tests get the default language. g.Language() is "" in the same agent and test runs (Headless and Autopilot once called, COLLIDER_AGENT=mcp, go test), so a translated game plays in its default language and agents read the same text on every machine. The COLLIDER_LANG environment variable (pt-BR, zh-CN; C for none) overrides the answer everywhere, agent runs included, to try or test a translation.
  11. A fallback font never moves text it does not draw. With .Font(first, fallback), text the first font draws entirely measures and draws exactly as with that font alone. Glyphs from a fallback sit on the same baseline, and a line is as tall as the largest ascent plus the largest descent among the fonts it uses.
  12. Text lines are laid out predictably. "\n" always starts a new line. .Wrap(w) breaks only at spaces, between CJK characters (never before closing punctuation or after opening punctuation), or between the letters of a word wider than w on its own. A wrapped text's box is exactly w wide and as tall as its lines, and it stays centered on the text's position, so with TextAlign(AlignLeft) the lines start at X - w/2 whatever they say. Agents read the unwrapped text.
  13. Text is measured the moment it changes. o.Width() and o.Height() are current right after SetText, Font, TextSize, Wrap or LineHeight, in the same frame, headless and in go test with no window: they are the box the text draws in, fallback fonts and wrapped lines included (a Size overrides it; a Hitbox never changes it). A sprite has its image's size once it is in a scene.

Documentation

Overview

Package collider is a code-first 2D game engine focused on collisions and events.

You import the library, describe your objects, attach events, and you have a game: no editor, no project files, no boilerplate game loop.

g := collider.New("My Game", 800, 600)
play := g.Scene("play")

player := play.Add(collider.Rect(48, 48, collider.Blue).At(100, 300))
player.OnCollisionWith("enemy", func(e *collider.Object) {
	g.Go("gameover")
})

g.Run("play")

See README.md for the full spec and five complete example games.

Index

Constants

View Source
const (
	PadA      = ebiten.StandardGamepadButtonRightBottom // bottom face button
	PadB      = ebiten.StandardGamepadButtonRightRight  // right face button
	PadX      = ebiten.StandardGamepadButtonRightLeft   // left face button
	PadY      = ebiten.StandardGamepadButtonRightTop    // top face button
	PadLB     = ebiten.StandardGamepadButtonFrontTopLeft
	PadRB     = ebiten.StandardGamepadButtonFrontTopRight
	PadLT     = ebiten.StandardGamepadButtonFrontBottomLeft  // left trigger, pulled
	PadRT     = ebiten.StandardGamepadButtonFrontBottomRight // right trigger, pulled
	PadBack   = ebiten.StandardGamepadButtonCenterLeft       // View / Select / Share
	PadStart  = ebiten.StandardGamepadButtonCenterRight      // Menu / Start / Options
	PadLStick = ebiten.StandardGamepadButtonLeftStick        // left stick, pressed in
	PadRStick = ebiten.StandardGamepadButtonRightStick       // right stick, pressed in
	PadUp     = ebiten.StandardGamepadButtonLeftTop          // d-pad
	PadDown   = ebiten.StandardGamepadButtonLeftBottom
	PadLeft   = ebiten.StandardGamepadButtonLeftLeft
	PadRight  = ebiten.StandardGamepadButtonLeftRight
)
View Source
const (
	PadLeftX  = ebiten.StandardGamepadAxisLeftStickHorizontal  // -1 left .. 1 right
	PadLeftY  = ebiten.StandardGamepadAxisLeftStickVertical    // -1 up .. 1 down
	PadRightX = ebiten.StandardGamepadAxisRightStickHorizontal // -1 left .. 1 right
	PadRightY = ebiten.StandardGamepadAxisRightStickVertical   // -1 up .. 1 down
)

Variables

View Source
var (
	White  = color.RGBA{R: 0xF2, G: 0xF2, B: 0xF2, A: 0xFF}
	Black  = color.RGBA{R: 0x10, G: 0x10, B: 0x10, A: 0xFF}
	Red    = color.RGBA{R: 0xE5, G: 0x3E, B: 0x3E, A: 0xFF}
	Green  = color.RGBA{R: 0x3E, G: 0xB6, B: 0x58, A: 0xFF}
	Blue   = color.RGBA{R: 0x3E, G: 0x6F, B: 0xE5, A: 0xFF}
	Yellow = color.RGBA{R: 0xF2, G: 0xC9, B: 0x38, A: 0xFF}
	Orange = color.RGBA{R: 0xF2, G: 0x8C, B: 0x38, A: 0xFF}
)

Functions

func Shuffle

func Shuffle[T any](s []T) []T

Shuffle returns a shuffled copy of a slice. Handy for card decks and spawn tables.

func UseAssets added in v0.2.0

func UseAssets(f fs.FS)

UseAssets routes all asset loading (sprites, sounds, music, fonts) through a filesystem instead of the disk: pass an embed.FS and the game ships as a single self-contained binary.

//go:embed sprites audios
var content embed.FS

func main() {
	collider.UseAssets(content)
	...
}

Call it before creating objects. Without it, paths load from disk, which is what you want during development.

Types

type Action added in v0.2.0

type Action struct {
	Keys   []Key   `json:"-"` // keys held during the frame
	MouseX float64 `json:"mouseX"`
	MouseY float64 `json:"mouseY"`
	Click  bool    `json:"click"` // press the left button this frame
	// MouseDown holds the pointer at MouseX, MouseY this frame (drags,
	// virtual joysticks); Click implies it on its frame.
	MouseDown bool `json:"mouseDown"`
	// Pad holds gamepad buttons this frame, and StickX, StickY push
	// the left stick (-1..1, clamped; Y grows downward). The first
	// action that uses the pad connects it (PadConnected) for the rest
	// of the run.
	Pad    []PadButton `json:"-"`
	StickX float64     `json:"stickX"`
	StickY float64     `json:"stickY"`
}

Action is one frame of injected input.

type Align added in v0.6.3

type Align int

Align is how the lines of a text line up in its box (TextAlign).

const (
	AlignCenter Align = iota // each line centered: the default
	AlignLeft                // lines start at the box's left edge
	AlignRight               // lines end at the box's right edge
)

type Color

type Color = color.Color

Color is any standard library color. The palette below covers the common cases so examples never need to import image/color.

type Game

type Game struct {
	// contains filtered or unexported fields
}

Game owns the window, the scenes, the asset cache and the main loop.

func New

func New(title string, width, height int) *Game

New creates a game with a window title and size in pixels.

func (*Game) AgentDocs added in v0.3.0

func (g *Game) AgentDocs(docs string)

AgentDocs sets the game's agent-facing documentation: rules, goals, coordinate conventions, anything an agent should know before playing. It is returned as the MCP server's instructions on initialize. Optional.

func (*Game) AgentState added in v0.3.0

func (g *Game) AgentState(fn func() any)

AgentState attaches game-defined state to every observation: fn runs once per observation and its result appears as the "state" field. Use it for what object positions cannot express: score, health, phase, whose turn it is. Optional.

func (*Game) Autopilot added in v0.2.0

func (g *Game) Autopilot(fn func(Observation) Action)

Autopilot runs the game windowed while an agent function supplies the input: fn receives each frame's observation and returns the action to hold. Watch a bot play, or combine with COLLIDER_RECORD and let the agent record its own demo GIF. Like every agent run it plays no audio. Call before Run.

func (*Game) CanQuit added in v0.6.0

func (g *Game) CanQuit() bool

CanQuit reports whether Quit can end the game here: true on desktop and in headless play, false in a browser. Show a QUIT menu entry only when it is true.

func (*Game) CloseOverlay added in v0.4.0

func (g *Game) CloseOverlay()

CloseOverlay removes the overlay at the start of the next frame and the scene under it resumes where it froze. Safe to call with no overlay open. Go and Restart close the overlay too.

func (*Game) Controls added in v0.3.0

func (g *Game) Controls(controls map[string]Key)

Controls names this game's inputs for agents: action name to key, like {"jump": engine.Space, "p2-attack": engine.KeyNamed("Numpad1")}. The MCP act tool then accepts these names and lists them in its description, so any agent discovers how to play without reading the game's source. Optional; raw key names always work.

func (*Game) DisallowAgents added in v0.2.0

func (g *Game) DisallowAgents()

DisallowAgents locks a shipped game against outside control: Headless, Step, Autopilot and the MCP server refuse to run, and the COLLIDER_AGENT and COLLIDER_RECORD environment variables are ignored, so nothing can drive the game or make it write a GIF to a path of its choosing. Agent play (and recording) is allowed by default; call it only in release builds if you still want to record demos.

func (*Game) Focused added in v0.4.0

func (g *Game) Focused() bool

Focused reports whether the game window has the keyboard focus, so a game can pause itself when the player switches away. Always true in headless and agent play (Step, MCP, Autopilot), where there is no player to switch away.

func (*Game) Fullscreen added in v0.2.0

func (g *Game) Fullscreen(on bool)

Fullscreen switches fullscreen on or off. Callable any time, including from an input handler for an F11 toggle.

func (*Game) Go

func (g *Game) Go(name string)

Go switches to another scene at the end of the current frame. The scene keeps its state; use Restart to reset it. Any open overlay closes.

func (*Game) Headless added in v0.2.0

func (g *Game) Headless(scene string)

Headless prepares the game to be driven by Step instead of Run: no window, no audio, injected input, fixed 60 steps per second.

func (*Game) Height

func (g *Game) Height() float64

Height returns the window height in pixels.

func (*Game) Icon added in v0.2.0

func (g *Game) Icon(path string)

Icon sets the window icon from an image asset.

func (*Game) Key

func (g *Game) Key(k Key) bool

Key reports whether a key is currently held down.

func (*Game) Language added in v0.6.3

func (g *Game) Language() string

Language reports the player's language as a BCP 47 tag, such as "pt-BR", "zh-CN", "en-US" or "de", or "" when it is unknown: the moment to pick the game's translation, before or while building scenes (it needs only New). Match leniently: the primary subtag ("pt", "zh") picks the translation, the region or script only where it matters (zh-CN and zh-Hans are Simplified Chinese, zh-TW and zh-Hant Traditional), and anything unknown or "" falls back to the game's default.

Where it comes from: on Windows, the user's display language; on macOS, the first preferred language (System Settings), then the environment; on Linux and other Unix systems, LC_ALL, LC_MESSAGES or LANG ("pt_BR.UTF-8" becomes "pt-BR"; "C" and "POSIX" mean unknown), with GNU's LANGUAGE list taking precedence as it does for gettext; in a browser, navigator.language.

Agent and test runs (Headless, Autopilot, COLLIDER_AGENT=mcp, go test: the runs that are silent and keep saves in memory) get "", so the game plays in its default language and agents see the same text on every machine. Headless and Autopilot count from the moment they are called; a bot that builds the game first and then calls Headless should set COLLIDER_LANG.

The COLLIDER_LANG environment variable overrides all of this, agent runs included, to try a translation without changing the system ("COLLIDER_LANG=zh-CN go run ."); COLLIDER_LANG=C forces "". Like LANG, it only picks among the game's own strings, so it is honored even after DisallowAgents.

func (*Game) Load added in v0.4.0

func (g *Game) Load(key string, v any) bool

Load reads the value saved under key into v, which must be a pointer, and reports whether it did. A missing key (the first run) or data that does not decode into v returns false and leaves v untouched, so defaults set before Load survive. Fields missing from an older save keep the values v already had.

func (*Game) Mouse

func (g *Game) Mouse() (x, y float64)

Mouse returns the cursor position in game coordinates.

func (*Game) MouseDown added in v0.4.0

func (g *Game) MouseDown() bool

MouseDown reports whether the pointer is held: the left mouse button, or any finger on a touch screen. Drag, aim, charge, a virtual joystick: read it with Mouse every frame. Clicks (a press) still arrive through OnClick.

func (*Game) MusicVolume added in v0.6.0

func (g *Game) MusicVolume(v float64)

MusicVolume sets the music's own volume, 0 to 1 (the default), clamped like Volume. The music plays at this times the master Volume, starting with the track playing now. For a settings menu's music slider.

func (*Game) Observe added in v0.2.0

func (g *Game) Observe() Observation

Observe returns the structured state of the current frame without advancing it.

func (*Game) OnClose added in v0.6.2

func (g *Game) OnClose(fn func())

OnClose runs fn once when the player closes the window (the X button, Alt+F4, the taskbar, a launcher's exit), before the game ends: the moment to save progress or bank a run in progress. fn runs on the game loop, between two frames, like any other callback; then the game quits the way Quit does (the window closes and Run returns, saving the COLLIDER_RECORD GIF on the way). There is no veto: a close request always ends the game, since launchers and the OS expect it to.

Only a close request runs fn: Quit is the game's own decision and does not. Headless runs (Step, COLLIDER_AGENT=mcp) have no window, so fn never runs there. In a browser a page is closed by its tab, not by the game: fn never runs either (save as you go; saves there are written at once). OnClose(nil) restores the default: the window closes at once. Callable any time, before or after Run.

func (*Game) Overlay added in v0.4.0

func (g *Game) Overlay(name string)

Overlay shows a scene on top of the current one: a pause menu, an inventory, a level-up choice. The overlay runs (its timers, updates, clicks and collisions) and draws above, while the scene under it still draws but is frozen: no updates, no timers, no input. Like Go, it takes effect at the start of the next frame, so the key or click that opened it never reaches the overlay on the same frame. Opening one while another is open replaces it. The overlay keeps its state between openings (Restart it by hand if it should start fresh) and never changes the music.

func (*Game) PadAxis added in v0.6.0

func (g *Game) PadAxis(a PadAxis) float64

PadAxis returns a stick axis from -1 to 1, raw: no dead zone, so apply your own (a resting stick rarely reads exactly 0; ignoring lengths under 0.2 is typical). Y grows downward like the screen. With several pads, the one pushed furthest on this axis answers. Agents set the left stick through Action.StickX and StickY.

func (*Game) PadConnected added in v0.6.0

func (g *Game) PadConnected() bool

PadConnected reports whether a gamepad with a standard layout is connected, so a game can show pad prompts ("A" instead of "ENTER"). In headless and agent play it is false until an action first uses the pad, then true for the rest of the run.

func (*Game) PadDown added in v0.6.0

func (g *Game) PadDown(b PadButton) bool

PadDown reports whether a gamepad button is held on any connected controller with a standard layout (Xbox, PlayStation, Switch Pro, Steam Deck and most others). Agents hold pad buttons through Action.Pad.

func (*Game) Quit

func (g *Game) Quit()

Quit ends the game after the current frame: the frame finishes (its updates, deferred destroys and draw), then the window closes and Run returns, saving the COLLIDER_RECORD GIF on the way. Callable any time, from any callback, as often as you like.

In a browser Quit does nothing, since a page cannot close its tab; CanQuit reports it, so a game can hide its QUIT entry there. Headless, the run ends instead: every later Step advances nothing and returns the final observation, marked Quit; Headless starts a new run.

func (*Game) Resizable added in v0.2.0

func (g *Game) Resizable(on bool)

Resizable lets the player resize the window; the game keeps its logical resolution and scales.

func (*Game) Restart

func (g *Game) Restart(name string)

Restart switches to a scene after rolling it back to its initial state: setup objects restored, runtime spawns and timers dropped. Any open overlay closes.

func (*Game) Run

func (g *Game) Run(name string)

Run starts the game on the given scene and blocks until the window closes or Quit is called. If the COLLIDER_RECORD environment variable is set to a file path, the session is saved there as an animated GIF (unless the game called DisallowAgents).

Agent play (unless the game called DisallowAgents):

  • COLLIDER_AGENT=mcp runs headless as an MCP server on stdio; act steps the simulation deterministically.
  • COLLIDER_AGENT=mcp-window opens the window and runs in real time while serving the same MCP tools: agents and the person at the keyboard play together, and the session is watchable (and recordable with COLLIDER_RECORD).

func (*Game) Save added in v0.4.0

func (g *Game) Save(key string, v any) error

Save stores v as JSON under key, for the next run of the game: best scores, unlocks, settings. On desktop each key is a file in the user's config directory (under a folder named after the game title); in the browser it is an entry in localStorage. Games run by agents or tests (Headless, Autopilot, COLLIDER_AGENT=mcp, go test) keep saves in memory instead, so bots and CI never touch the player's data.

Keys are simple names like "best" or "settings"; characters other than letters, digits, '-', '_' and '.' are replaced.

func (*Game) Scene

func (g *Game) Scene(name string) *Scene

Scene returns the scene with this name, creating it on first use.

func (*Game) Sound

func (g *Game) Sound(path string)

Sound plays a short effect, fire and forget. Agent and test runs (Headless, Autopilot, COLLIDER_AGENT=mcp, go test) are silent: no audio work at all, the device is never opened. On a machine with no working audio device the game plays silently too, after one log line.

func (*Game) SoundVolume added in v0.6.0

func (g *Game) SoundVolume(v float64)

SoundVolume sets the sound effects' own volume, 0 to 1 (the default), clamped like Volume. Every Sound started afterwards plays at this times the master Volume; at 0, Sound does no work at all.

func (*Game) Step added in v0.2.0

func (g *Game) Step(a Action) Observation

Step advances exactly one frame with the given input and returns the resulting observation. Deterministic: same actions, same results. Once the game has called Quit, Step advances nothing and returns the final observation, with Quit set.

func (*Game) Volume added in v0.4.0

func (g *Game) Volume(v float64)

Volume sets the master volume from 0 (muted) to 1 (full, the default); values outside are clamped, NaN counts as 0. It scales both channels, MusicVolume and SoundVolume: it applies to the music that is playing right away, and to every sound effect and music started afterwards. Callable any time, including from an input handler for a mute key.

func (*Game) Width

func (g *Game) Width() float64

Width returns the window width in pixels.

type Key

type Key = ebiten.Key

Key identifies a keyboard key. The constants below cover common game controls; KeyNamed reaches every other key by name.

func KeyNamed added in v0.5.0

func KeyNamed(name string) Key

KeyNamed returns the key with this name, for keys beyond the constants: KeyNamed("1") for a card pick, KeyNamed("Tab"), KeyNamed("F1"). It accepts the same names agents send over MCP, case sensitive: letters "A" to "Z"; the number row as "0" to "9" or "Digit0" to "Digit9" (the keypad is "Numpad0" to "Numpad9"); "F1" to "F24"; the ebiten names of the rest ("ArrowLeft", "ShiftLeft", "Escape", "Backspace", "Tab", "Comma", "Minus", ...); and the aliases Left, Right, Up, Down, Space, Enter, Esc. An unknown name panics: a typo is a bug, so look keys up once, at setup.

type Object

type Object struct {
	X, Y   float64
	Vx, Vy float64

	// Data is a free field for game state (a card face, hit points, anything).
	Data any
	// contains filtered or unexported fields
}

Object is anything that lives in a scene: the player, a wall, a bullet, a button. Position is the object's center. Velocity applies every frame.

func Button added in v0.2.0

func Button(label string) *Object

Button creates a clickable plate with a centered label: menus, retry screens, anything a player presses. Like text, buttons never collide.

play := menu.Add(collider.Button("PLAY").At(400, 350))
play.OnClick(func() { g.Go("play") })

Size follows the label unless Size is called; Color sets the plate color (the light and dark edges are derived from it) and TextColor the label.

func Rect

func Rect(w, h float64, c Color) *Object

Rect creates a colored rectangle object.

func Sprite

func Sprite(path string) *Object

Sprite creates an object from an image file. The collider defaults to the image bounds; override with Size (drawing too) or Hitbox (collisions only). The image itself is loaded from the game's asset cache when the object is added to a scene.

func Text

func Text(str string) *Object

Text creates a text object. Text objects render on screen and can be clicked, but never take part in collisions: a score label overlapping the ball must not bounce it.

func (*Object) Alpha added in v0.4.0

func (o *Object) Alpha(a float64) *Object

Alpha sets the object's opacity, from 0 (invisible) to 1 (opaque, the default): fades, ghosts, translucent auras. Chainable, and callable any time.

func (*Object) Animation added in v0.2.0

func (o *Object) Animation(name, path string, frames int, fps float64) *Object

Animation defines a named animation from a horizontal sprite strip: the image is cut into `frames` equal slices played at `fps`. Chainable. Define several (run, jump, death...) and switch with Play.

func (*Object) At

func (o *Object) At(x, y float64) *Object

At places the object's center. Chainable.

func (*Object) AtEdge

func (o *Object) AtEdge() *Object

AtEdge places the object at a random point on a random screen edge when it is added to a scene. Chainable.

func (*Object) Color added in v0.2.0

func (o *Object) Color(c Color) *Object

Color sets the object's color: the fill of a Rect, the plate of a Button. Chainable.

func (*Object) Destroy

func (o *Object) Destroy()

Destroy removes the object at the end of the frame. Always safe to call from inside callbacks.

func (*Object) Fixed added in v0.4.0

func (o *Object) Fixed() *Object

Fixed pins the object to the screen: its position is in screen pixels, it ignores the scene camera, and clicks hit it where it is drawn. The HUD, a pause button, a virtual joystick. Like Visual objects, fixed ones never collide: they do not live in the world. Chainable.

func (*Object) Flash added in v0.4.0

func (o *Object) Flash(c Color, sec float64)

Flash draws the object as a solid silhouette of color c for sec seconds, then back to normal: the classic hit flash is o.Flash(engine.White, 0.1). Opacity still applies; a new flash replaces a running one.

func (*Object) FlipX added in v0.4.0

func (o *Object) FlipX(on bool) *Object

FlipX mirrors the object horizontally when on: a character facing the way it walks. Drawing only; applies to sprites, rectangles and text. Chainable, and callable any time.

func (*Object) Font added in v0.2.0

func (o *Object) Font(paths ...string) *Object

Font sets the text's font from TTF/OTF file paths, each read once and cached. With several, the first draws every glyph it has and the others, in order, fill in the rest: a pixel font for Latin and Cyrillic, then a CJK font for Chinese:

engine.Text(s).Font("fonts/pixel.ttf", "fonts/cjk.ttf")

Every font draws at the same TextSize, and every glyph of a line sits on one baseline. A line is as tall as the largest ascent plus the largest descent among the fonts the text actually uses, so text the first font draws entirely measures and draws exactly as with that font alone: adding a fallback never moves it. A fallback font is only read and parsed the first time a text needs one of its glyphs. Pixel fonts stay sharp at whole multiples of their design size; an 8px Latin font and a 12px CJK font are both sharp at 24 and 48.

The empty path is the built-in Go font, the default; Font() with no paths restores it. A path that does not exist panics here. Chainable.

func (*Object) Grounded

func (o *Object) Grounded() bool

Grounded reports whether the object is resting on a solid object. The platformer jump check.

func (*Object) Height added in v0.6.4

func (o *Object) Height() float64

Height is the object's drawn height in pixels, measured like Width. A text is as tall as its lines: from the first line's top to the last line's bottom, LineHeight apart.

func (*Object) Hitbox added in v0.5.0

func (o *Object) Hitbox(w, h float64) *Object

Hitbox sets the collision box apart from the drawn size: a w x h box centered on the object's position, used by collisions, solids, Touching and Near, and reported to agents as the object's w and h. Drawing, culling and clicks keep the sprite (or Size) bounds, since players click what they see. The fair-contact fix: a 42px hero with a 26px hitbox is only hurt when a chaser really reaches it. Without Hitbox the collider is the drawn size, as before. Chainable, and callable any time.

func (*Object) Layer added in v0.4.0

func (o *Object) Layer(n int) *Object

Layer sets the draw order: lower layers draw first, higher ones on top, and objects on the same layer keep the order they were added in. Clicks go to the topmost object. The default layer is 0; any int works, negative included. Chainable, and callable any time.

func (*Object) LifeTime

func (o *Object) LifeTime(sec float64) *Object

LifeTime destroys the object automatically after this many seconds. Chainable, and callable after Add too (bullets, particles).

func (*Object) LineHeight added in v0.6.3

func (o *Object) LineHeight(px float64) *Object

LineHeight sets the distance in pixels from one line's baseline to the next, for text with several lines. The default (0) is the fonts' own line height: the largest ascent plus the largest descent (plus any line gap) of the fonts in use, so lines of a pixel font touch; 1.5 times the text size is a comfortable paragraph. The box runs from the first line's top to the last line's bottom. Chainable, and callable any time.

func (*Object) Move

func (o *Object) Move(dx, dy float64)

Move shifts the object by a delta. Overlaps with solid objects are resolved at the end of the frame.

func (*Object) MoveToward

func (o *Object) MoveToward(x, y, dist float64)

MoveToward moves the object dist pixels toward a point, stopping exactly on it instead of overshooting.

func (*Object) OnClick

func (o *Object) OnClick(fn func())

OnClick fires when the object is clicked (a point-vs-bounds collision).

func (*Object) OnCollision

func (o *Object) OnCollision(fn func(other *Object))

OnCollision fires once when the object starts touching another object.

func (*Object) OnCollisionWith

func (o *Object) OnCollisionWith(tag string, fn func(other *Object))

OnCollisionWith fires once when the object starts touching an object carrying this tag.

func (*Object) OnUpdate

func (o *Object) OnUpdate(fn func(dt float64))

OnUpdate runs every frame with the elapsed time in seconds.

func (*Object) Play added in v0.2.0

func (o *Object) Play(name string)

Play switches to a looping animation. Playing the animation that is already active does nothing, so calling it every frame is fine.

func (*Object) PlayOnce added in v0.2.0

func (o *Object) PlayOnce(name string)

PlayOnce switches to an animation that runs once and holds its last frame: death, explosion, one-shot attacks.

func (*Object) Rotate added in v0.4.0

func (o *Object) Rotate(rad float64) *Object

Rotate turns the object to an absolute angle in radians, clockwise on screen, around its center: an arrow pointing where it flies. Drawing only: the collider stays the upright box. Applies to sprites, rectangles and text. Chainable, and callable any time.

func (*Object) SetSprite

func (o *Object) SetSprite(path string)

SetSprite swaps the object's image at runtime.

func (*Object) SetText

func (o *Object) SetText(str string)

SetText changes the text at runtime and re-measures the bounds.

func (*Object) Size

func (o *Object) Size(w, h float64) *Object

Size overrides the drawn size, which is also the collider unless Hitbox sets one apart. Chainable.

func (*Object) Solid

func (o *Object) Solid() *Object

Solid marks the object as solid geometry: the engine pushes non-solid objects out of it instead of letting them pass through. Chainable.

func (*Object) Tag

func (o *Object) Tag(name string) *Object

Tag labels the object for tag-based collision rules. Chainable.

func (*Object) TextAlign added in v0.6.3

func (o *Object) TextAlign(a Align) *Object

TextAlign sets how the lines of a text line up in its box: centered (the default), left or right. The box is the widest line, the Wrap column or the Size, and stays centered on the text's position; a single line without Wrap fills its box, so it only matters for text with several lines, or with Wrap. Chainable, and callable any time.

func (*Object) TextColor added in v0.2.0

func (o *Object) TextColor(c Color) *Object

TextColor sets the text color (default white). Chainable.

func (*Object) TextSize added in v0.2.0

func (o *Object) TextSize(px float64) *Object

TextSize sets the font size in pixels. Chainable.

func (*Object) Tint added in v0.4.0

func (o *Object) Tint(c Color) *Object

Tint multiplies the object's colors by c: one white sprite becomes a red, blue or pale variant, a scene goes cold under a blue tint. nil clears it. Chainable, and callable any time.

func (*Object) Touching added in v0.4.0

func (o *Object) Touching(tag string) []*Object

Touching returns the objects with this tag whose box (the Hitbox, when set) overlaps this one's right now, in scene order: continuous contact, for damage over time or standing in a zone, next to the enter-only OnCollision events. Like collisions, it ignores visual, text and Fixed objects (and returns nothing when called on one), dead objects, and the object itself. The slice is the caller's to keep.

func (*Object) VelocityToward

func (o *Object) VelocityToward(x, y, speed float64)

VelocityToward points the object's velocity at a target with the given speed. The bullet helper.

func (*Object) Visual added in v0.2.0

func (o *Object) Visual() *Object

Visual marks an object as decoration: it is drawn but never takes part in collisions. Use it for backgrounds and scenery so they do not clutter the collision world. Chainable.

func (*Object) Width added in v0.6.4

func (o *Object) Width() float64

Width is the object's drawn width in pixels, the box it is drawn in and clicked by: its Size when set, else a Rect's width, a sprite's image width (known once the object is in a scene), or a text's measured width, with its fonts (fallbacks included), TextSize and Wrap (a wrapped text is its column wide), plus the plate margins of a button. A Hitbox never changes it. Text is measured the moment it changes, so Width is current right after SetText, Font, TextSize, Wrap or LineHeight, in the same frame, with no window and nothing drawn yet: lay out a label by its edge, or test in go test that every translation fits its box.

func (*Object) WithGravity

func (o *Object) WithGravity() *Object

WithGravity makes the object fall with the scene's gravity. Chainable.

func (*Object) Wrap added in v0.6.3

func (o *Object) Wrap(width float64) *Object

Wrap breaks the text into lines no wider than width pixels, at spaces in Latin and Cyrillic text and between any two CJK characters, never before closing punctuation (,。!?」 , . ! ?) or after opening punctuation (「( ( ), and never inside a word unless the word alone is wider than the line (then between its letters). A "\n" always starts a new line; spaces at the end of a wrapped line are dropped.

The text's box becomes a column exactly width wide (for clicks, and agents' w) and as tall as its lines, centered on the text's position like every object: with TextAlign(AlignLeft) the lines start at X - width/2 whatever they say. The object's text (and what agents read) stays the unwrapped string. For a fixed text area (a dialog box) whose first line stays put however many lines follow, give the text a Size too: its lines then start at the top of that box. 0 turns wrapping off. Applies to buttons' labels too. Chainable, and callable any time.

type ObjectObs added in v0.2.0

type ObjectObs struct {
	Tag   string  `json:"tag,omitempty"`
	X     float64 `json:"x"`
	Y     float64 `json:"y"`
	Vx    float64 `json:"vx,omitempty"`
	Vy    float64 `json:"vy,omitempty"`
	W     float64 `json:"w"`
	H     float64 `json:"h"`
	Solid bool    `json:"solid,omitempty"`
	Text  string  `json:"text,omitempty"`
	// Fixed objects (the HUD) report screen coordinates; everything
	// else is in world coordinates, which differ once the scene moves
	// its camera.
	Fixed bool `json:"fixed,omitempty"`
}

ObjectObs describes one live object. Tag is the game's own label (tag your player "player" so agents can find themselves); Text is set for text objects, so HUDs and scores are readable. W and H are the collision box: the object's Hitbox when it has one, else its drawn size.

type Observation added in v0.2.0

type Observation struct {
	Scene   string      `json:"scene"`
	Overlay string      `json:"overlay,omitempty"`
	Objects []ObjectObs `json:"objects"`
	State   any         `json:"state,omitempty"`
	// Quit is set once the game has called Quit (a QUIT menu entry):
	// the run is over, and further steps advance nothing.
	Quit bool `json:"quit,omitempty"`
}

Observation is the structured view of the current frame: the scene name, every object with its position, motion and label, and whatever extra state the game attached with AgentState. While an overlay is open (a pause menu), Overlay names it and Objects lists the frozen scene's objects first, then the overlay's.

type PadAxis added in v0.6.0

type PadAxis = ebiten.StandardGamepadAxis

PadAxis identifies a stick axis in the standard layout.

type PadButton added in v0.6.0

type PadButton = ebiten.StandardGamepadButton

PadButton identifies a gamepad button in the standard layout (an Xbox style controller: A at the bottom of the face buttons). Nintendo and PlayStation pads map by position, so PadA is Cross on a PlayStation pad and B on a Nintendo one.

type Scene

type Scene struct {
	// contains filtered or unexported fields
}

Scene is a screen of the game: a level, a menu, a game-over screen. It holds objects and runs their events every frame.

func (*Scene) Add

func (s *Scene) Add(o *Object) *Object

Add puts an object into the scene. It is safe to call at any time, including from inside callbacks; the object joins this frame.

func (*Scene) After

func (s *Scene) After(sec float64, fn func())

After runs fn once, sec seconds from now, while the scene is active.

func (*Scene) Camera added in v0.4.0

func (s *Scene) Camera(x, y float64)

Camera centers the view on a world point: every object draws shifted so that (x, y) lands in the middle of the screen, and clicks land in the world where they appear to. Call it every frame to follow the player. Objects marked Fixed ignore the camera. The default camera looks at the screen center, where world and screen coordinates are the same.

func (*Scene) Count

func (s *Scene) Count(tag string) int

Count reports how many objects with this tag are alive.

func (*Scene) Every

func (s *Scene) Every(sec float64, fn func())

Every runs fn repeatedly, every sec seconds, while the scene is active.

func (*Scene) Gravity

func (s *Scene) Gravity(g float64)

Gravity sets downward acceleration in pixels per second squared for objects marked WithGravity.

func (*Scene) Music

func (s *Scene) Music(path string)

Music sets looping background music (wav or ogg) that plays while the scene is active. Like Sound, it stays silent in agent and test runs and on a machine with no working audio device.

func (*Scene) Near added in v0.4.0

func (s *Scene) Near(tag string, x, y, r float64) []*Object

Near returns the live objects with this tag whose box (the Hitbox, when set) overlaps the circle of radius r around (x, y), closest center first (ties keep scene order). Visual objects count, Fixed ones (screen space) never do. The auto-aim and pickup-magnet query: s.Near("enemy", p.X, p.Y, 300). The slice is the caller's to keep.

func (*Scene) OnClick

func (s *Scene) OnClick(fn func(x, y float64))

OnClick fires on every click anywhere in the scene, before any object-level click handling. The position is in world coordinates, so under a moving camera it is where the click lands in the world.

func (*Scene) OnCollision

func (s *Scene) OnCollision(tagA, tagB string, fn func(a, b *Object))

OnCollision declares a rule for every current and future pair of objects with these tags. The callback receives the objects in tag order: the tagA object first.

func (*Scene) OnUpdate added in v0.2.0

func (s *Scene) OnUpdate(fn func(dt float64))

OnUpdate runs every frame while the scene is active, before object updates. The place for scene-wide input like a fullscreen toggle.

Directories

Path Synopsis
examples
agent command
Gem Rush: a game with NO human input.
Gem Rush: a game with NO human input.
agent/bot command
The headless run: the same Decide pilot plays Gem Rush with no window at all, via Headless and Step.
The headless run: the same Decide pilot plays Gem Rush with no window at all, via Headless and Step.
agent/game
Package game is Gem Rush: collect all gems before the timer runs out.
Package game is Gem Rush: collect all gems before the timer runs out.
arena command
Arena: survive the chasers.
Arena: survive the chasers.
arena/scripts
Package scripts holds this game's custom types.
Package scripts holds this game's custom types.
breakout command
Breakout: clear fifty bricks with three lives.
Breakout: clear fifty bricks with three lives.
catcher command
Catcher: catch falling gems with the basket for 30 seconds.
Catcher: catch falling gems with the basket for 30 seconds.
caves command
Caves: explore a procedurally generated cave and collect every gem.
Caves: explore a procedurally generated cave and collect every gem.
caves/scripts
Package scripts holds the map generation for Caves: pure algorithms, no engine types.
Package scripts holds the map generation for Caves: pure algorithms, no engine types.
dialog command
Dialog: a visual-novel style conversation with drawn portraits, a framed dialog box, a typewriter effect with letter blips, and a menu.
Dialog: a visual-novel style conversation with drawn portraits, a framed dialog box, a typewriter effect with letter blips, and a menu.
dungeon command
Dungeon: four connected rooms, a key, a locked door, a treasure chest.
Dungeon: four connected rooms, a key, a locked door, a treasure chest.
dungeon/scripts
Package scripts holds the dungeon's world: a grid of rooms drawn as ASCII layouts.
Package scripts holds the dungeon's world: a grid of rooms drawn as ASCII layouts.
hello command
Hello, collision: the smallest complete Collider program.
Hello, collision: the smallest complete Collider program.
horde command
Horde: survive the night on a field far bigger than the window.
Horde: survive the night on a field far bigger than the window.
jumper command
Jumper: climb the platforms, grab every coin, avoid the spikes.
Jumper: climb the platforms, grab every coin, avoid the spikes.
memory command
Memory: flip cards two at a time and find the eight pairs of pixel icons.
Memory: flip cards two at a time and find the eight pairs of pixel icons.
pong command
Pong: first to five points.
Pong: first to five points.
runner command
Runner: an endless runner with a drawn sprinter.
Runner: an endless runner with a drawn sprinter.
ship command
Ship: a small dodge game built to be PUBLISHED.
Ship: a small dodge game built to be PUBLISHED.
zombie-night command
Zombie Night: survive the horde.
Zombie Night: survive the horde.
internal
assets
Package assets loads and caches game content: images, sound effects and music.
Package assets loads and caches game content: images, sound effects and music.
mcps
Package mcps is a minimal MCP (Model Context Protocol) server over stdio: newline-delimited JSON-RPC 2.0 with the initialize, tools/list and tools/call methods.
Package mcps is a minimal MCP (Model Context Protocol) server over stdio: newline-delimited JSON-RPC 2.0 with the initialize, tools/list and tools/call methods.
physics
Package physics holds collision primitives: AABB tests now, the spatial hash broad phase in M4.
Package physics holds collision primitives: AABB tests now, the spatial hash broad phase in M4.
record
Package record captures gameplay frames and writes an animated GIF.
Package record captures gameplay frames and writes an animated GIF.
tools
cover command
Command cover generates the repository's social preview image (.github/social-preview.png, 1280x640) from real project assets: the pixel font, the engine palette and gameplay frames pulled straight from the example demo GIFs.
Command cover generates the repository's social preview image (.github/social-preview.png, 1280x640) from real project assets: the pixel font, the engine palette and gameplay frames pulled straight from the example demo GIFs.
genassets command
Command genassets draws every example game's art and sound from code: pixel art as ASCII grids (sprites.go), backgrounds and tiles as small generators (art.go), sounds as synthesized waveforms, plus a copy of the Press Start 2P font (with its OFL license) into each game that uses text.
Command genassets draws every example game's art and sound from code: pixel art as ASCII grids (sprites.go), backgrounds and tiles as small generators (art.go), sounds as synthesized waveforms, plus a copy of the Press Start 2P font (with its OFL license) into each game that uses text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL