Help us shape the future of Go! Take the 2026 Go Developer Survey

gram

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: MIT Imports: 2 Imported by: 0

README ΒΆ

gram

gram logo
Go Version GoDoc License Codecov Coverage Go Quality Check

gram is a modular 2D game engine for Go. A game is a set of named Stages, each with its own entity-component world and its own Scenes; an engine drives the active Stage through Ebitengine's Update/Draw/Layout loop on the goke ECS. Everything beyond the tick loop is a plugin: the built-in ones give a Stage a world of moving boxes, collisions, sight, a board with terrain, pathfinding and mouse selection, and a game adds its own the same way. Formerly gokebiten.

Features Β β€’Β  Installation Β β€’Β  Example Β β€’Β  Demos Β β€’Β  Model Β β€’Β  Architecture Β β€’Β  Benchmarks Β β€’Β  Documentation

Design Goals

  • One extension point. Everything beyond the tick loop is a plugin.Plugin, built-in or yours, installed from a Stage's Init with ctx.Use. There is no registry, no lookup by name and no install-order retry: a plugin that needs another takes it as a constructor argument.
  • A Stage owns its ECS. Each Stage gets a fresh world the moment it is entered, so a menu Stage sits idle with no gameplay entities until the player starts.
  • Behaviors are plain functions. Game logic reacting to what a plugin finds is registered on that plugin and run inside its own pass; the payload type says whose it is, and a plugin refuses another's, so registering in the wrong place is an error, never a silent no-op.
  • Kinds say what an entity is. A kind is the list of components its entities carry, each constant or read from the entity's own row; it also tells save files what to expect.
  • Saves survive change. Persisted resources are matched by name, never by position, so a save survives plugins being added, removed or reordered between versions.
  • Contract in the open, orchestration inside. What a game implements (game, plugin, render) is public and documented; only the engine that drives it is internal.

πŸ“¦ Installation

go get github.com/kjkrol/gram

Prerequisites: Go 1.27+ and the Ebitengine system dependencies (a C compiler and a few system libraries; Ebitengine uses cgo on most platforms).

✨ Key Features

Capability Package What you get
Stages and Scenes game Named Stages with their own ECS and lifecycle (Init/Restore/Spawn/Update); Scenes with layered renderers and input; a live Composition of what is shown and which Scene is active
Plugins and behaviors plugin The one extension contract; Between and Each behaviors hosted in a plugin's own pass
World plugins/world Every entity's Base (position, velocity, kind, capabilities); movement under stop, wrap or open edges; the shared spatial index and camera; spawning from kinds
Kinds plugins/world/kind Define a kind from a Spec of Const and Load components; Entry rows onto the roster
Collisions plugins/collision A Detector over the world's space: Collider to take part, Physics to bounce and be pushed apart, a ShapeTest to refine, Meeting/Struck for behaviors
Sight plugins/vision A Sight cone scanned each tick into Seen, nearest first; Sighting behaviors per observer; drawn outlines
Board and navigation plugins/board, plugins/navigation Square or hex grid with terrain and occupancy; MoveOrder paths that re-route when terrain changes
Selection plugins/selection Click, marquee drag and shift-add into a Selected tag, with a highlight renderer
Persistence game.Persistence Save, load and list the active Stage's ECS and every tracked value by name
Camera and rendering camera, render A wrap-aware camera with zoom and pan; an atlas baked at Close, quad batching, cached and telemetry renderers

Example

The smallest game that does something: one Stage with a torus of bouncing boxes, a collision plugin counting their contacts, and one Scene drawing them. This is examples/minimal; run it with make demo-minimal.

package main

import (
	"image/color"
	"math/rand/v2"
	"time"

	"github.com/hajimehoshi/ebiten/v2"
	"github.com/kjkrol/aabbworld"
	"github.com/kjkrol/aabbworld/geom"
	"github.com/kjkrol/goke/v3"
	"github.com/kjkrol/gram"
	"github.com/kjkrol/gram/control"
	"github.com/kjkrol/gram/game"
	"github.com/kjkrol/gram/plugin"
	"github.com/kjkrol/gram/plugins/collision"
	"github.com/kjkrol/gram/plugins/collision/behavior"
	"github.com/kjkrol/gram/plugins/world"
	"github.com/kjkrol/gram/plugins/world/kind"
	"github.com/kjkrol/gram/render"
)

const (
	screenWidth, screenHeight = 640, 480
	boxSize                   = 12
	boxCount                  = 300
)

func main() { gram.Run(&Game{}) }

// Game is the game itself: window props and one Stage.
type Game struct{ stage arena }

func (g *Game) Props() game.Props {
	return game.Props{Title: "gram minimal", ScreenWidth: screenWidth, ScreenHeight: screenHeight, TargetTPS: 60}
}

func (g *Game) Stages() (map[string]game.Stage, string) {
	return map[string]game.Stage{g.stage.Name(): &g.stage}, g.stage.Name()
}

// box is the row every entity spawns from: where it starts and how it moves.
type box struct {
	pos world.Position
	vel world.Velocity
}

// arena is the one Stage: a torus of bouncing boxes.
type arena struct {
	world     *world.Plugin
	collision *collision.Plugin
	boxes     kind.Of[box]
	stats     behavior.ContactStats
	scenes    game.Scenes
}

func (a *arena) Name() string       { return "arena" }
func (a *arena) Stack() game.Scenes { return a.scenes }

// Init installs the plugins and defines what a box is.
func (a *arena) Init(ctx game.Initializer) error {
	a.world = ctx.UseWorld(world.Config{
		Space:    world.SpaceCfg{Width: screenWidth, Height: screenHeight, Edges: aabbworld.Torus},
		Entities: world.EntitiesCfg{MaxCount: boxCount, MinSize: boxSize, MaxSize: boxSize},
	})
	a.boxes = kind.Define[box](a.world.Kinds(), "box", kind.Spec{
		kind.Load(func(b box) world.Position { return b.pos }),
		kind.Load(func(b box) world.Velocity { return b.vel }),
		kind.Const(collision.Collider{}),
		kind.Const(collision.Physics{Restitution: 1}),
	})

	a.collision = collision.NewPlugin(a.world)
	if err := a.collision.RegisterBehavior(
		plugin.Between[plugin.Anything, plugin.Anything](behavior.CountContacts(&a.stats)),
	); err != nil {
		return err
	}
	if err := ctx.Use(a.collision); err != nil {
		return err
	}

	scenes, err := game.NewStack(&view{arena: a, tps: ctx.TPS()})
	if err != nil {
		return err
	}
	a.scenes = scenes
	scenes.Composition().Show("view")
	return ctx.Track(scenes.Composition())
}

// Restore has nothing to restore from: this game keeps no saves.
func (a *arena) Restore(game.Persistence) (bool, error) { return false, nil }

// Spawn scatters the boxes on a grid, each heading somewhere at random.
func (a *arena) Spawn() error {
	rng := rand.New(rand.NewPCG(1, 2))
	placement := world.NewGridPlacement(screenWidth, screenHeight, boxSize)
	entries := make([]kind.Entry, boxCount)
	for i := range entries {
		var vel world.Velocity
		vel.SetDelta(geom.NewVec(rng.Float64()*200-100, rng.Float64()*200-100))
		entries[i] = a.boxes.Entry(box{pos: placement.Place(i, boxCount), vel: vel})
	}
	a.world.Seed(entries...)
	return nil
}

// Update is one tick: move, then collide.
func (a *arena) Update(ctx goke.RunCtx, d time.Duration) {
	a.world.RunPlan(ctx, d)
	a.collision.RunPlan(ctx, d)
	ctx.Sync()
}

// view is the one Scene: the boxes over a dark background, with a telemetry line.
type view struct {
	arena *arena
	tps   *game.TPS
}

func (v *view) Name() string    { return "view" }
func (v *view) Focusable() bool { return true }

func (v *view) Layers() []render.Renderer {
	atlas := render.NewAtlas()
	atlas.RegisterAt(v.arena.boxes.SpriteID(), boxSize, render.Solid(color.RGBA{R: 90, G: 200, B: 110, A: 255}))
	atlas.Close()
	v.arena.world.WithRenderer(atlas)

	count := func() int { return v.arena.world.Res.Telemetry.Count }
	return []render.Renderer{
		render.SolidBackground{Color: color.RGBA{R: 30, G: 30, B: 30, A: 255}},
		v.arena.world.Renderer(),
		render.NewTelemetryRenderer(&v.tps.Ticks, count, &v.arena.stats.Counter),
	}
}

func (v *view) HandleEvents(events *control.InputEvents, runtime game.Runtime, _ game.Composition) {
	for _, k := range events.KeyEvents {
		if k.Action == control.ActionPress && k.Key == ebiten.KeyEscape {
			runtime.Quit()
		}
	}
}

Demos

examples/collision-demo is the same idea at scale: thousands of colliding boxes at a fixed 120 TPS, with save and load on F5.


Stats: 2306 colliding AABBs | 120 TPS | 50 collisions/tick

Stats: 524 colliding AABBs | 120 TPS | 15 collisions/tick

Demo Shows Run
minimal One Stage, one Scene, a world with collisions: the README example make demo-minimal
collision-demo Thousands of bouncing boxes of many kinds, hit overlays, telemetry, save and load make demo-collision
scenes-demo A menu Stage switching into a gameplay Stage, a modal Scene over the ticking world, a non-focusable HUD make demo-scenes
navigation-demo A board with terrain, units selected by click and marquee, right-click move orders along re-routing paths make demo-navigation
vision-demo Entities keeping out of each other's way by sight, and a hunter living off the ones that fail make demo-vision

Every demo opens a window, so go test cannot exercise it; each ships its own tests of the logic underneath.

Model

Stage and Scene

A game.Game supplies Props (window, tick rate) and its Stages by name. A Stage is one self-contained context β€” a menu, the gameplay β€” with its own goke ECS, built fresh the moment Runtime.SwitchStage enters it. Its lifecycle is Init (install plugins), Restore (resume from a save, or report there is none), Spawn (seed the initial state, only when nothing was restored) and Update (one tick, running the plugins' RunPlan in the order the game needs).

Within a Stage, a Scene is one thing it can show: its renderers, built once, and its input handling. The Stage's Scenes registry is static; the Composition over it is live β€” which Scenes are visible, in what order, and which is active, the topmost focusable one and the only Scene whose HandleEvents runs. A HUD that is not focusable can sit on top and never steal input. Runtime (pause, quit, switch Stage, persistence, camera) is one interface that reaches a Stage and every Scene alike.

Plugins and behaviors

A plugin's Install only queues ECS wiring; the engine flushes it all in one ecs.Setup after the Stage's Init, which is what lets Restore decide fresh-spawn or restore before the ECS commits to either. Game logic that reacts to what a plugin finds is a behavior: plugin.Between[A, B](fn) for every pair the plugin meets where one entity carries tag A and the other B (plugin.Anything as the wildcard), plugin.Each[T](fn) for every entity carrying T. Ready-made ones live in plugins/collision/behavior and plugins/vision/behavior; which tags they run between is the registration's to say.

Kinds, spawning and saves

kind.Define[Row](world.Kinds(), "name", kind.Spec{...}) says what an entity is: each component kind.Const(v) (the same for all) or kind.Load(func(row Row) T) (read from that entity's row). Spawn puts entries on the world's roster with Seed; the engine spawns them only when Restore loaded nothing. Attach and Detach are the mid-game counterparts of Const. Kinds tell save files every component type their entities carry, so a game's own tags and state survive a save without being registered anywhere else.

Architecture

The packages form a strict acyclic graph; each imports only the layers below it. Every package has a doc.go describing what it brings.

Package Responsibility
camera The view onto a world: screen conversion, culling, move and zoom; wrap-aware
control The input vocabulary: InputEvents, KeyEvent, ClickEvent, EventHandler
render Drawing primitives: Renderer, Atlas baked at Close, QuadBatch, sprite drawers, cached and telemetry renderers
plugin The extension contract: Plugin, Installer, Tick, Between/Each behaviors and the hosts that run them, Serializable, PostLoader, Populator
plugins/world/kind What an entity is: Spec, Const/Load, Define, Of, Registry
plugins/world The foundation: Base, the shared Space and camera, movement under the edge rules, kinds, Seed/Populate, Attach/Detach, the entity renderer
game What a game implements and receives: Game, Stage, Scene, Scenes, Composition, Initializer, Runtime, Persistence
plugins/collision The Detector over the world's space; Collider, Physics, ShapeTest, Meeting, Struck
plugins/collision/behavior CountContacts, ShowHits with HitOverlay, LogContacts
plugins/vision Sight cones into Seen; Sighting behaviors; SightOutline drawn
plugins/vision/behavior Flee, Chase, and the Predator/Prey/Skittish/Threat tags
plugins/board A square or hex grid with terrain kinds and occupancy over the world
plugins/navigation MoveOrder paths across a board, re-routing when terrain changes; right-click commands; route drawing
plugins/selection Click, marquee and shift-add into Selected; highlight renderer
internal/engine The Engine: the Ebitengine loop, one active Stage, persistence, input capture
gram (public) Run; the package you import. The root doc.go carries the concepts and the full package graph
camera ──► render ──► plugin ──► plugins/world/kind ──► plugins/world ──► game ──► internal/engine ──► gram
control β”€β”€β”€β”˜                                              β”‚  β–²
                                                          β–Ό  β”‚
                     plugins/{board, collision, selection, vision} ──► plugins/navigation, plugins/*/behavior

Outside the module: goke is the ECS every Stage runs on, aabbworld the space, collisions and line of sight under the world, Ebitengine the loop and the drawing, astar the pathfinding.

⏱️ Performance

One tick of the built-in plugins on an Intel i5-8265U (see BENCHMARKS.md), 0 allocs/op throughout once warm:

Tick Scene Cost
World: move every entity, rebuild the space 5,000 boxes, 20Γ—20 each, on a 4000Γ—4000 torus 199 Β΅s
World + collisions 524 boxes, 20Γ—20 each, covering 20% of a 1024Γ—1024 torus 134 Β΅s
World + collisions 8,388 boxes, 5Γ—5 each, covering 20% of the same torus 2.9 ms
Vision: every observer scans its cone 500 observers, 60Β° cones of radius 200 503 Β΅s

Deep dive: every scene, what each benchmark measures, and how to reproduce, in BENCHMARKS.md.

make bench

Relationship to goke and aabbworld

gram began as goke's Ebitengine example and was extracted so the ECS stays free of GUI dependencies while this integration evolves and versions on its own. The world, collisions and line of sight are aabbworld's; gram is where they meet an ECS and a screen.

πŸ“– Documentation

License

MIT

Documentation ΒΆ

Overview ΒΆ

Package gram is a modular 2D game engine for Go: a user-implemented game.Game β€” a named set of game.Stage values, each with its own entity-component world and its own [game.Scene]s β€” driven through Ebitengine's Update/Draw/Layout loop by an engine that wraps the goke ECS. Everything beyond the tick loop is a plugin.Plugin: the built-in ones give a Stage a world of moving boxes, collisions, sight, a board with terrain, pathfinding and mouse selection; a game adds its own the same way. Run is the whole public surface of this package.

Game, Stage and Scene ΒΆ

A Game supplies game.Props (window, tick rate) and its Stages by name, plus which one starts. A Stage is one self-contained context the game can be in β€” a menu, the gameplay, a map screen β€” with its own goke ECS, built fresh the moment game.Runtime.SwitchStage enters it, so a menu Stage sits idle with no gameplay entities until the player starts. Its lifecycle is Init (install plugins through a game.Initializer), Restore (resume from a save, or report there is none), Spawn (seed the initial state, only when Restore found nothing) and Update (one tick).

Within a Stage, a Scene is one thing it can show: its renderers (Layers, built once on entering the Stage) and its input handling. The Stage's game.Scenes is the static registry of its Scenes; the live game.Composition over it says which are visible, in what order, and which is active β€” the topmost focusable one, the only Scene whose HandleEvents runs. A Stage has no input handling of its own. game.Runtime is one undivided interface β€” pause, quit, switch Stage, persistence, the camera β€” that reaches a Stage and every Scene alike.

Plugins and behaviors ΒΆ

A plugin.Plugin is installed from Stage.Init through ctx.Use. Its Install only queues ECS wiring; the engine flushes it all in one ecs.Setup after Init returns, which is what lets Restore decide fresh-spawn or restore before the ECS commits to either. A plugin needing another plugin takes it as a constructor argument β€” construction order in the game's code is the dependency order; there is no registry, no lookup by name and no install-order retry.

Game logic that reacts to what a plugin finds is a behavior, registered on the plugin it concerns and run inside that plugin's own pass: plugin.Between of a payload for every pair of entities the plugin meets, one carrying tag A and the other B; plugin.Each of a payload for every entity carrying T. The payload type says whose the behavior is β€” a Meeting is collision's, a Sighting is vision's β€” and a plugin refuses one made for another, so registering in the wrong place is an error, never a silent no-op.

Kinds and spawning ΒΆ

What an entity is comes from a kind, defined in Stage.Init with package plugins/world/kind: a Spec lists the components every entity of the kind carries, each the same for all (Const) or read from that entity's own row (Load). Define hands back the kind; its Entry puts one entity on the world's roster (Seed), and the engine spawns the roster (Populate) only when nothing was restored. Kinds also tell save files which component types to expect, so a game's own tags and state survive a save without being registered anywhere else.

Tick ΒΆ

Props.TargetTPS is the engine's one fixed step. Ebitengine runs one Update per frame; a frame that falls behind runs at most five steps and drops the rest, so the game slows down instead of spiralling. Each step calls the active Scene's HandleEvents, then Stage.Update, where the game runs its plugins' RunPlan in the order it needs β€” world first, then whatever reads the world's space (collision, vision, ...), as the examples do.

Persistence ΒΆ

game.Persistence saves and loads the active Stage's ECS together with every tracked value's state: a plugin's Serializable, or anything a Stage handed to ctx.Track, such as its Scene Composition. Resources are matched by name β€” a plugin's Name, or the Go type name of anything else β€” never by position, so a save survives plugins being added, removed or reordered between game versions. PostLoader and Restorer are the hooks run after a load.

Package dependencies ΒΆ

The packages form a strict acyclic graph; each imports only layers below it:

Layer 0   camera              β€” a Camera over a world: screen conversion, culling, move and zoom
          control             β€” the input vocabulary: InputEvents, KeyEvent, ClickEvent, EventHandler
Layer 1   render              β€” drawing primitives: Renderer, Atlas, QuadBatch, sprites          (β†’ camera)
Layer 2   plugin              β€” the extension contract: Plugin, Installer, Tick, Between and Each,
                                PairHost and EachHost, Serializable, PostLoader, Populator      (β†’ control, render)
Layer 3   plugins/world/kind  β€” what an entity is: Spec, Const and Load, Define, Of, Registry    (β†’ render)
Layer 4   plugins/world       β€” the foundation: Base (Position, Velocity, Caps), the Space,
                                movement, kinds, Seed and Populate, Attach and Detach, Camera   (β†’ camera, control, plugin, kind, render)
Layer 5   game                β€” what a game implements and receives: Game, Stage, Scene, Scenes,
                                Composition, Initializer, Runtime, Persistence, Props, TPS       (β†’ camera, control, plugin, world, render)
          plugins/board       β€” a grid with terrain over the world                              (β†’ world, …)
          plugins/collision   β€” the Detector over the world's Space; Collider, Physics, Meeting, Struck (β†’ world, …)
          plugins/selection   β€” mouse input into a Selected tag                                 (β†’ world, …)
          plugins/vision      β€” a Sight cone into Seen, Sighting, SightOutline                   (β†’ world, …)
Layer 6   plugins/navigation  β€” MoveOrder paths across a board                                   (β†’ board, selection, world, …)
          plugins/collision/behavior, plugins/vision/behavior β€” ready-made reactions              (β†’ their plugin, world, plugin)
Layer 7   internal/engine     β€” the Engine: the Ebitengine loop, one active Stage, persistence   (β†’ game, plugin, world, camera, control, render)
Layer 8   gram                β€” Run; the package you import                                     (β†’ game, internal/engine)

Expressed as a directed graph (arrow = "is imported by"), showing the spine:

camera ──► render ──► plugin ──► plugins/world/kind ──► plugins/world ──► game ──► internal/engine ──► gram
control β”€β”€β”€β”˜                                              β”‚  β–²
                                                          β–Ό  β”‚
                     plugins/{board, collision, selection, vision} ──► plugins/navigation, plugins/*/behavior

Outside the module: goke/v3 is the ECS every Stage runs on, aabbworld the space, collisions and line of sight under the world, ebiten/v2 the loop and the drawing, astar the pathfinding, and uid the entity identifiers.

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Run ΒΆ

func Run(g game.Game)

Run launches a Game

Types ΒΆ

This section is empty.

Directories ΒΆ

Path Synopsis
Package camera is the view onto a world: screen conversion, culling, the visible window and its control.
Package camera is the view onto a world: screen conversion, culling, the visible window and its control.
Package control is the input vocabulary: what the engine captures each frame and what a handler is told.
Package control is the input vocabulary: what the engine captures each frame and what a handler is told.
examples
collision-demo command
minimal command
Command minimal is the smallest gram game that does something: one Stage with a world of bouncing boxes, a collision plugin counting their contacts, and one Scene drawing them.
Command minimal is the smallest gram game that does something: one Stage with a world of bouncing boxes, a collision plugin counting their contacts, and one Scene drawing them.
navigation-demo command
scenes-demo command
vision-demo command
Command vision-demo shows ten entities keeping out of each other's way by sight, and one red hunter that lives off the ones who fail at it.
Command vision-demo shows ten entities keeping out of each other's way by sight, and one red hunter that lives off the ones who fail at it.
Package game is what a game implements and what it is handed back: the Game the engine drives, its [Stage]s and their [Scene]s, and the Initializer, Runtime and Persistence a Stage or Scene works through.
Package game is what a game implements and what it is handed back: the Game the engine drives, its [Stage]s and their [Scene]s, and the Initializer, Runtime and Persistence a Stage or Scene works through.
internal
engine
Package engine is the driver behind gram.Run: the Ebitengine loop, one active Stage and its ECS at a time, and the unexported implementations of the contracts package game declares.
Package engine is the driver behind gram.Run: the Ebitengine loop, one active Stage and its ECS at a time, and the unexported implementations of the contracts package game declares.
Package plugin is the extension contract: what a Plugin is, what it is handed at install time, and how game logic is hosted inside a plugin's own pass as a behavior.
Package plugin is the extension contract: what a Plugin is, what it is handed at install time, and how game logic is hosted inside a plugin's own pass as a behavior.
plugins
board
Package board lays a square or hex grid over the game world, with per-cell terrain (passability, movement cost, sprite) and occupancy tracking.
Package board lays a square or hex grid over the game world, with per-cell terrain (passability, movement cost, sprite) and occupancy tracking.
collision
Package collision detects overlaps between world entities each tick and records what each struck on its Collider.
Package collision detects overlaps between world entities each tick and records what each struck on its Collider.
collision/behavior
Package behavior holds ready-made reactions to contacts, each a plain function handed to plugin.Between or plugin.Each: CountContacts for telemetry, ShowHits with HitOverlay to keep a hit visible, LogContacts to write a line per contact.
Package behavior holds ready-made reactions to contacts, each a plain function handed to plugin.Between or plugin.Each: CountContacts for telemetry, ShowHits with HitOverlay to keep a hit visible, LogContacts to write a line per contact.
navigation
Package navigation moves entities along a MoveOrder's path across a board, re-pathing automatically when terrain along the route changes.
Package navigation moves entities along a MoveOrder's path across a board, re-pathing automatically when terrain along the route changes.
selection
Package selection turns mouse input into a Selected tag on world entities: left-click to select, drag for a marquee, shift to add.
Package selection turns mouse input into a Selected tag on world entities: left-click to select, drag for a marquee, shift to add.
vision
Package vision gives entities a narrowed view of the world: a Sight cone sees what falls inside it, within range and not hidden behind something nearer.
Package vision gives entities a narrowed view of the world: a Sight cone sees what falls inside it, within range and not hidden behind something nearer.
vision/behavior
Package behavior holds ready-made reactions to what an entity sees, each a plain function of a vision.Sighting handed to plugin.Between: Flee gives way to what is on a collision course and runs from a Threat, Chase goes after the nearest and searches when it sees none.
Package behavior holds ready-made reactions to what an entity sees, each a plain function of a vision.Sighting handed to plugin.Between: Flee gives way to what is on a collision course and runs from a Threat, Chase goes after the nearest and searches when it sees none.
world
Package world is the foundation for a Stage with moving, drawable entities: a Position and Velocity each, a shared spatial index other plugins query, and motion integrated each tick, never further than Position.MaxStep.
Package world is the foundation for a Stage with moving, drawable entities: a Position and Velocity each, a shared spatial index other plugins query, and motion integrated each tick, never further than Position.MaxStep.
world/kind
Package kind is how a game says what its entities are.
Package kind is how a game says what its entities are.
Package render is the drawing side of gram: pure primitives that know nothing of Stages, Scenes or plugins.
Package render is the drawing side of gram: pure primitives that know nothing of Stages, Scenes or plugins.

Jump to

Keyboard shortcuts

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