willow

package module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Feb 21, 2026 License: MIT Imports: 20 Imported by: 0

README

Willow

Go Reference Go Report Card

A retained-mode 2D game framework for Ebitengine - scene graph, batching, cameras, interaction, and effects. Inspired by Starling, Flash display lists, and PixiJS - adapted for Go's strengths.

Status: Functional and actively developed. Core systems are working and used across all included examples. API may change before v1.0.0.

Shapes demo Masks demo Shaders demo Watermesh demo

New here? Check out the examples - runnable demos with no external assets required.


What is Willow?

Willow is a 2D game framework built on Ebitengine. It utlizies the retained-mode design pattern, which means you create a tree of nodes representing your game objects, and Willow handles passing the draw commands down to the Ebitengine. A main focus of Willow is performance - it is designed to minimize heap allocations and maximize batching, with features like subtree command caching for static content, and a zero-allocation-per-frame contract on the hot path. While managing the display tree does have a slight runtime cost, in some instances Willow can be faster than raw Ebitengine draw calls due to better batching and caching strategies.

It sits between Ebitengine and your game:

Your Game             - gameplay, content, logic
Willow                - scene graph, rendering, interaction
Ebitengine            - GPU backend, window, audio, platform

Why does Willow exist?

Ebitengine is an excellent, minimal 2D engine for Go - but every project beyond a prototype ends up building the same infrastructure from scratch: transform hierarchies, draw-call batching, hit testing, camera viewports, text layout, sprite atlases.

Willow exists so you don't have to rebuild that foundation every time.

It was created with a specific belief: Go deserves a clean, structured way to build 2D games. A framework that handles the rendering infrastructure so you can focus on gameplay.

Inspired by Starling, Flash display lists, and PixiJS - scene graph architectures that powered millions of 2D games - adapted for Go's strengths: simplicity and performance.


What Willow Is Not

  • Not a full game engine - no built-in physics, networking, or asset pipelines
  • Not a UI layout framework (a companion willowui library is planned)
  • Not a replacement for Ebitengine - it builds on top of it

Willow focuses on structured rendering and scene composition. You bring the gameplay and domain logic.


Goals

  1. Structure without handcuffs. Willow provides hierarchy, transforms, and batching without imposing game architecture. Any genre, any pattern, any scale.
  2. Performance as a contract. Zero heap allocations per frame on the hot path. 10,000 sprites at 120+ FPS on desktop, 60+ FPS on mobile and web. Verified with compiler escape analysis and benchmark suites.
  3. Wrap Ebitengine, never fight it. Willow uses Ebitengine's lifecycle hooks, image types, and threading model directly.
  4. Minimal public API. Every exported symbol earns its place. Fewer concepts, less to learn, less to break.
  5. Cross Platform. Windows, macOS, Linux, iOS, Android, WebAssembly - wherever Ebitengine runs.

Use Cases

Willow is well suited for:

  • 2D games requiring structured layering and scene composition
  • Games with worlds with large tile maps, cameras, and movement
  • Game tooling and level editors built on a display tree
  • Prototyping game rendering architectures on top of Ebitengine

Quick start

go get github.com/phanxgames/willow@latest

For quick setup, call willow.Run(scene, config) and Willow handles the window and game loop. For full control, implement ebiten.Game yourself and call scene.Update() and scene.Draw(screen) directly - both paths are first-class.

package main

import (
	"log"

	"github.com/phanxgames/willow"
)

func main() {
	scene := willow.NewScene()

	sprite := willow.NewSprite("hero", willow.TextureRegion{})
	sprite.ScaleX = 40
	sprite.ScaleY = 40
	sprite.Color = willow.Color{R: 0.3, G: 0.7, B: 1, A: 1}
	sprite.X = 300
	sprite.Y = 220
	scene.Root().AddChild(sprite)

	if err := willow.Run(scene, willow.RunConfig{
		Title:  "My Game",
		Width:  640,
		Height: 480,
	}); err != nil {
		log.Fatal(err)
	}
}

Examples

Runnable examples are included in the examples/ directory:

go run ./examples/basic        # Bouncing colored sprite
go run ./examples/shapes       # Rotating polygon hierarchy with parent/child transforms
go run ./examples/interaction  # Draggable, clickable rectangles with color toggle
go run ./examples/text         # Bitmap font text with colors, alignment, word wrap
go run ./examples/texttf       # TTF text with colors, per-line alignment, word wrap, and outline
go run ./examples/tweens       # Position, scale, rotation, alpha, and color tweens
go run ./examples/particles    # Fountain, campfire, and sparkler particle effects
go run ./examples/shaders      # 3x3 grid showcasing all built-in shader filters
go run ./examples/outline      # Outline and inline filters on a sprite
go run ./examples/masks        # Star polygon, cursor-following, and erase masking
go run ./examples/lighting     # Dark dungeon with colored torches and mouse-following lantern
go run ./examples/atlas        # TexturePacker atlas loading with named regions and magenta placeholders
go run ./examples/tilemap      # Tile map rendering with camera panning
go run ./examples/rope         # Draggable endpoints connected by a textured rope
go run ./examples/watermesh    # Water surface with per-vertex wave animation

Documentation


Features

  • Scene graph - Parent/child transform inheritance (position, rotation, scale, skew, pivot) with alpha propagation and Pixi-style ZIndex sibling reordering.
  • Sprite batching - TexturePacker JSON atlas loading with multi-page, trimmed, and rotated region support. Consecutive draws are grouped automatically into single DrawImage calls.
  • Camera system - Multiple independent viewports with smooth follow, scroll-to animation (45+ easings), bounds clamping, frustum culling, and world/screen coordinate conversion.
  • Input and interaction - Hierarchical hit testing with pluggable shapes (rect, circle, polygon). Pointer capture, drag dead zones, multi-touch, and two-finger pinch with rotation. Callbacks per-node or scene-wide.
  • Text rendering - Bitmap fonts (BMFont .fnt) for pixel-perfect rendering, TTF fallback via Ebitengine text/v2. Alignment, word wrapping, line height overrides, and outlines.
  • Particle system - CPU-simulated with preallocated pools. Configurable emit rate, lifetime, speed, gravity, and scale/alpha/color interpolation. Optional world-space emission.
  • Mesh support - DrawTriangles with preallocated vertex and index buffers. High-level helpers for rope meshes, filled polygons, and deformable grids.
  • Subtree command caching - SetCacheAsTree caches all render commands for a container's subtree and replays them with delta transform remapping. Camera panning, parent movement, and alpha changes never invalidate the cache. Animated tiles (same-page UV swaps) are handled automatically via a two-tier source pointer - no invalidation, no API overhead. Manual and auto-invalidation modes. Includes sort-skip optimization when the entire scene is cache hits.
  • Filters and effects - Composable filter chains via Kage shaders. Built-in: color matrix, blur, outline, pixel-perfect outline, pixel-perfect inline, palette swap. Render-target masking and CacheAsTexture.
  • Lighting - Dedicated lighting layer using erase-blend render targets with automatic compositing.
  • Animation - Tweening via gween with 45+ easing functions. Convenience wrappers for position, scale, rotation, alpha, and color. Auto-stops on node disposal.
  • ECS integration - Optional EntityStore interface to bridge interaction events into your ECS. Ships with a Donburi adapter.
  • Debug mode - Performance timers, draw call and batch counting, tree depth warnings, and disposed-node assertions via scene.SetDebugMode(true).

Performance

Willow is designed around a zero-allocation-per-frame contract on the hot path:

  • Preallocated command buffer reused each frame
  • Dirty flag propagation - static subtrees skip transform recomputation entirely
  • Custom merge sort with preallocated scratch buffer (no sort.SliceStable allocations)
  • Typed callback slices - no interface{} boxing in event dispatch
  • Render-texture pooling by power-of-two size buckets
  • Value-type DrawImageOptions declared once, reused per iteration

Subtree command caching (SetCacheAsTree) avoids re-traversing static subtrees entirely. Commands are stored at cache time and replayed with a single matrix multiply per command. Camera movement, parent transforms, and alpha changes are handled via delta remapping. Animated tiles (UV swaps within the same atlas page) are handled automatically via a two-tier source pointer indirection - no invalidation, no API overhead. This is designed to allow batch group of tilemaps with animated tiles (e.g. water) to be performance-optimized by avoiding full subtree invalidation on every frame.

Scenario (10K sprites) Time vs uncached
Manual cache, camera scrolling ~39 μs ~125x faster
Manual cache, 100 animated tile UV swaps ~1.97 ms ~2.5x faster
Auto cache, 1% of children moving ~4.0 ms ~1.2x faster
No cache (baseline) ~4.9 ms -

The cache is per-container, and will be invalidated if a child within the container moves. It is recommended to separate static content (tilemaps, UI panels) from dynamic content (players, projectiles) into different containers for best results.

Benchmark suite included: go test -bench . -benchmem


Roadmap

  • Dynamic atlas packing - Runtime Atlas.Add(name, img) for lazy asset loading. Shelf/guillotine packing into existing atlas pages to preserve batching.
  • UI widget layer (buttons, text input, layout, focus traversal) as a separate companion library (willowui)
  • Example projects and starter templates
  • Comprehensive API documentation and guides
  • Tutorials and integration walkthroughs
  • Performance profiling across mobile and WebAssembly targets
  • Community feedback and API stabilization

Built with

  • Go 1.24+
  • Ebitengine v2.9+
  • Currently tested on: macOS and WebAssembly

Contributing

Contributions are welcome. Please a pull request and ensure tests pass. For major changes, open an issue first to discuss the design and implementation.


License

MIT - see LICENSE for details.

Documentation

Overview

Package willow is a retained-mode 2D game framework for Ebitengine.

Willow provides the scene graph, transform hierarchy, sprite batching, input handling, camera viewports, text rendering, and particle systems that every non-trivial 2D game needs.

Game loop

There are two ways to integrate Willow into your game.

The simplest is Run, which creates a window and game loop for you:

scene := willow.NewScene()
// ... add nodes ...
willow.Run(scene, willow.RunConfig{
	Title: "My Game", Width: 640, Height: 480,
})

Register a callback with Scene.SetUpdateFunc to run your game logic each tick:

scene.SetUpdateFunc(func() error {
	dt := float32(1.0 / float64(ebiten.TPS()))
	myTween.Update(dt)
	hero.X += speed
	hero.Invalidate()
	return nil
})

For full control, implement ebiten.Game yourself and call Scene.Update and Scene.Draw directly:

type Game struct{ scene *willow.Scene }

func (g *Game) Update() error         { g.scene.Update(); return nil }
func (g *Game) Draw(s *ebiten.Image)  { g.scene.Draw(s) }
func (g *Game) Layout(w, h int) (int, int) { return w, h }

Both approaches are fully supported. Run is a thin wrapper around ebiten.RunGame — it does not add hidden behavior.

Nodes and the scene graph

Every visual element is a Node. Nodes form a tree rooted at Scene.Root. Children inherit their parent's transform (position, rotation, scale, skew) and alpha.

Create nodes with the typed constructors:

Add nodes to the tree with Node.AddChild:

container := willow.NewContainer("ui")
scene.Root().AddChild(container)

btn := willow.NewSprite("btn", willow.TextureRegion{})
btn.X, btn.Y = 100, 50
container.AddChild(btn)

Sprites and solid colors

NewSprite with a zero-value TextureRegion produces a 1x1 white pixel. Use [Node.ScaleX], [Node.ScaleY], and [Node.Color] to turn it into a solid-color rectangle of any size:

box := willow.NewSprite("box", willow.TextureRegion{})
box.ScaleX = 80
box.ScaleY = 40
box.Color = willow.Color{R: 0.3, G: 0.7, B: 1, A: 1}

For atlas-based sprites, load a TexturePacker JSON with LoadAtlas and look up regions by name:

atlas, _ := willow.LoadAtlas(jsonBytes, pages)
hero := willow.NewSprite("hero", atlas.Region("hero_idle"))

Transforms

Each node has local transform fields: X, Y, ScaleX, ScaleY, Rotation, SkewX, SkewY, PivotX, PivotY. World transforms are computed lazily via dirty-flag propagation — static subtrees skip recomputation entirely.

Call Node.Invalidate after changing a transform field at runtime (X, Y, Rotation, etc.) to ensure the change takes effect:

sprite.X += 5
sprite.Invalidate()

PivotX and PivotY set the transform origin as a fraction of the node's size (0.5, 0.5 = center). By default pivots are (0, 0) meaning the top-left corner.

Cameras and viewports

A Camera defines a viewport rectangle on screen and a position in world space. Create cameras with Scene.NewCamera:

cam := scene.NewCamera(willow.Rect{X: 0, Y: 0, Width: 640, Height: 480})
cam.X = worldCenterX
cam.Y = worldCenterY
cam.Invalidate()

Cameras support smooth follow, animated scroll-to with 45+ easing functions, zoom, rotation, bounds clamping, and frustum culling. Convert between coordinate spaces with Camera.WorldToScreen and Camera.ScreenToWorld.

When no cameras are created, the scene renders with a default 1:1 mapping from world to screen coordinates.

Interaction and input

Set [Node.Interactable] to true and assign a HitShape to make a node respond to pointer input. Assign callback functions directly to node fields:

btn.Interactable = true
btn.HitShape = willow.HitRect{Width: 80, Height: 40}

btn.OnClick = func(ctx willow.ClickContext) {
	fmt.Println("clicked at", ctx.WorldX, ctx.WorldY)
}
btn.OnDrag = func(ctx willow.DragContext) {
	btn.X += ctx.DeltaX
	btn.Y += ctx.DeltaY
	btn.Invalidate()
}

Available callbacks: OnPointerDown, OnPointerUp, OnPointerMove, OnClick, OnDragStart, OnDrag, OnDragEnd, OnPinch, OnPointerEnter, OnPointerLeave.

Hit testing runs in reverse painter order (front-to-back). Built-in hit shapes are HitRect, HitCircle, and HitPolygon.

Per-node update callbacks

Each node has an [Node.OnUpdate] callback that fires every frame during Scene.Update. Use it for self-contained per-node logic like spinning, bobbing, or AI:

sprite.OnUpdate = func(dt float64) {
	sprite.Rotation += 0.02
}

Filters and visual effects

Assign a slice of Filter implementations to [Node.Filters] to apply shader effects. Filters are applied in order during rendering:

sprite.Filters = []willow.Filter{
	willow.NewBlurFilter(3),
	willow.NewOutlineFilter(2, willow.Color{R: 1, G: 1, B: 0, A: 1}),
}

Built-in filters: ColorMatrixFilter (brightness, contrast, saturation, and arbitrary 4x5 matrix), BlurFilter, OutlineFilter, PixelPerfectOutlineFilter, PixelPerfectInlineFilter, PaletteFilter, and CustomShaderFilter for user-provided Kage shaders.

Nodes can also be cached as textures (Node.SetCacheAsTexture) or masked by another node (Node.SetMask).

Animation and tweens

Willow includes tween helpers built on gween:

tween := willow.TweenPosition(sprite, 500, 300, 1.5, ease.OutBounce)

Advance tweens manually each frame:

dt := float32(1.0 / float64(ebiten.TPS()))
tween.Update(dt)
if tween.Done { /* finished */ }

Convenience constructors: TweenPosition, TweenScale, TweenRotation, TweenAlpha, TweenColor. Tweens automatically stop if their target node is disposed.

There is no global tween manager — you own the TweenGroup values and call Update yourself, giving you full control over timing and lifetime.

Text rendering

Create text nodes with NewText and either a BitmapFont (pixel-perfect BMFont .fnt files) or a TTFFont (Ebitengine text/v2 wrapper):

font, _ := willow.LoadBitmapFont(fntData, pageImages)
label := willow.NewText("title", "Hello Willow", font)
label.TextBlock.Align = willow.TextAlignCenter
label.TextBlock.WrapWidth = 200

Particles

Create emitters with NewParticleEmitter and an EmitterConfig:

emitter := willow.NewParticleEmitter("sparks", willow.EmitterConfig{
	MaxParticles: 200,
	EmitRate:     60,
	Lifetime:     willow.Range{Min: 0.5, Max: 1.5},
	Speed:        willow.Range{Min: 50, Max: 150},
})
scene.Root().AddChild(emitter)
emitter.Emitter.Start()

Particles are CPU-simulated in Update (not Draw) with preallocated pools. Set WorldSpace to true in the config to emit particles in world coordinates instead of local to the emitter.

Meshes and polygons

For custom geometry, use NewMesh with vertex and index buffers, or the higher-level helpers:

Blend modes

Set [Node.BlendMode] to control compositing. Available modes: BlendNormal (default source-over), BlendAdd, BlendMultiply, BlendScreen, BlendErase, BlendMask, BlendBelow, BlendNone.

Draw ordering

Siblings are drawn in child-list order by default. Use Node.SetZIndex to reorder among siblings without changing the tree. For coarser control, [Node.RenderLayer] provides a primary sort key across the entire scene.

Lighting

A LightLayer creates a dark overlay with soft circles of light cut through it. Add the layer as the topmost scene node so it composites over all content:

ll := willow.NewLightLayer(640, 480, 0.9) // ambientAlpha: 1=black, 0=fully lit
scene.Root().AddChild(ll.Node())

Add point lights with LightLayer.AddLight:

torch := &willow.Light{
	X: 200, Y: 300,
	Radius: 120, Intensity: 1.0,
	Color:   willow.Color{R: 1, G: 0.7, B: 0.3, A: 1},
	Enabled: true,
}
ll.AddLight(torch)

To attach a light to a moving node, set [Light.Target] instead of fixed X/Y coordinates. Call LightLayer.Redraw each frame to rebuild the light texture before Scene.Draw.

ECS integration

Set a node's [Node.EntityID] and call Scene.SetEntityStore with an EntityStore implementation. Interaction events are forwarded as InteractionEvent values, bridging the scene graph into your entity-component system. A Donburi adapter ships in the willow/ecs submodule.

Subtree command caching

Node.SetCacheAsTree caches all render commands emitted by a container's subtree. On subsequent frames the cached commands are replayed with a delta transform remap instead of walking the tree — the CPU cost drops from O(N) traversal to O(N) memcpy with a matrix multiply per command.

Camera panning, parent movement, and parent alpha changes are handled automatically via delta remapping and never invalidate the cache.

Two modes are available:

  • CacheTreeAuto (default) — setters on descendant nodes auto-invalidate the cache. Always correct, small per-setter overhead.
  • CacheTreeManual — only Node.InvalidateCacheTree triggers a rebuild. Zero setter overhead. Best for large tilemaps where the developer knows exactly when tiles change.

Basic usage:

tilemap := willow.NewContainer("tilemap")
for _, tile := range tiles {
    tilemap.AddChild(tile)
}
tilemap.SetCacheAsTree(true) // auto mode (default)

For maximum performance with large tilemaps:

tilemap.SetCacheAsTree(true, willow.CacheTreeManual)
// ... later, when tiles change:
tilemap.InvalidateCacheTree()

Animated tiles (texture swaps)

Animated tiles (water, lava, torches) that swap UV coordinates on the same atlas page work without cache invalidation. The first Node.SetTextureRegion call with a same-page region automatically registers the node as "animated" — subsequent replays read the live TextureRegion from the node instead of the cached snapshot:

// waterTile is a child of a cached tilemap container.
// This does NOT invalidate the cache:
waterTile.SetTextureRegion(waterFrames[frame])

Changing to a different atlas page does invalidate the cache, since page changes affect batch keys. All animation frames for a tile should be on the same atlas page (TexturePacker typically does this automatically for related frames).

When to use SetCacheAsTree

The cache stores commands per-container. Any mutation to a child invalidates the entire container's cache (in auto mode) or produces stale output (in manual mode until you call InvalidateCacheTree).

This makes it ideal for large, mostly-static subtrees:

  • Tilemaps (thousands of tiles, camera scrolling every frame)
  • Static UI panels (dozens of widgets, occasionally updated)
  • Background decoration layers

It is NOT useful for containers where children move individually every frame — one child's Node.SetPosition dirties the whole container, so you pay rebuild cost plus cache management overhead.

The recommended pattern is to separate static and dynamic content:

// Cached: 10K tiles, camera scrolls for free
tilemap := willow.NewContainer("tilemap")
tilemap.SetCacheAsTree(true, willow.CacheTreeManual)

// NOT cached: entities move every frame
entities := willow.NewContainer("entities")

scene.Root().AddChild(tilemap)
scene.Root().AddChild(entities)

Mesh nodes and particle emitters inside a cached subtree block caching — the cache stays dirty and falls back to normal traversal. Move particles and meshes to a separate uncached container.

SetCacheAsTree vs SetCacheAsTexture

Willow has two user-facing caching mechanisms:

  • Node.SetCacheAsTree skips CPU traversal. Output is N render commands replayed from cache. Pixel-perfect at any zoom. Animated tiles work for free.
  • Node.SetCacheAsTexture skips GPU draw calls. Output is a single texture. Blurs if zoomed past cached resolution. Best for filtered/masked nodes and complex visual effects.

Performance

Willow targets zero heap allocations per frame on the hot path. Render commands, sort buffers, and vertex arrays are preallocated and reused. Dirty flags skip transform recomputation for static subtrees. Typed callback slices avoid interface boxing in event dispatch.

Caching strategy

Willow provides two caching mechanisms that solve different bottlenecks. Choosing the right one (or combining both) is the single biggest lever for frame-time optimization.

SetCacheAsTree — skip CPU traversal

Node.SetCacheAsTree stores the render commands emitted by a container's subtree and replays them on subsequent frames with a matrix multiply per command instead of re-walking the tree. This targets CPU time: the traversal, transform computation, and command emission for N children collapse to a memcpy + delta remap.

When to use it:

  • Large, mostly-static containers (tilemaps, background layers, UI panels)
  • Scenes where the camera scrolls every frame but the content does not move
  • Containers with animated tiles that swap UV coordinates on the same atlas page

When NOT to use it:

  • Containers where many children move individually every frame — one child's Node.SetPosition invalidates the entire container's cache (auto mode) or produces stale output (manual mode)
  • Containers that hold mesh nodes or particle emitters — these block caching entirely and fall back to normal traversal

Two modes are available:

  • CacheTreeAuto (default) — descendant setters auto-invalidate the cache. Always correct, small per-setter overhead. Good for UI panels that update occasionally.
  • CacheTreeManual — only Node.InvalidateCacheTree triggers a rebuild. Zero setter overhead. Best for large tilemaps where you control exactly when tiles change.

Setup example (tilemap with animated water):

tilemap := willow.NewContainer("tilemap")
tilemap.SetCacheAsTree(true, willow.CacheTreeManual)
for _, tile := range tiles {
    tilemap.AddChild(tile)
}
scene.Root().AddChild(tilemap)

// Animate water tiles every N frames — does NOT invalidate the cache
// because the UV swap stays on the same atlas page:
waterTile.SetTextureRegion(waterFrames[frame])

// If you add/remove tiles at runtime, invalidate manually:
tilemap.InvalidateCacheTree()

Caveats:

  • The cache is per-container. Separate static content (tilemaps) from dynamic content (players, projectiles) into different containers.
  • Mesh and particle emitter children block caching — move them to an uncached sibling container.
  • Changing a tile's texture to a different atlas page invalidates the cache because page changes affect batch keys. Keep all animation frames on the same atlas page.
  • In manual mode, forgetting to call Node.InvalidateCacheTree after structural changes (AddChild, RemoveChild, SetVisible) produces stale visual output. Prefer auto mode unless profiling shows the setter overhead matters.

Benchmark results (10K sprites):

Manual cache, camera scrolling       ~39 µs   (~125x faster than uncached)
Manual cache, 100 animated UV swaps  ~1.97 ms (~2.5x faster)
Auto cache, 1% children moving       ~4.0 ms  (~1.2x faster)
No cache (baseline)                  ~4.9 ms

SetCacheAsTexture — skip GPU draw calls

Node.SetCacheAsTexture renders a node's entire subtree to an offscreen image once, then draws that single texture on subsequent frames. This targets GPU draw-call count: N children become one textured quad.

When to use it:

  • Nodes with expensive filter chains (blur, outline, palette swap) — the filter is applied once and the result is reused
  • Complex visual effects that combine masking and filters
  • Small, visually-rich subtrees where draw-call reduction matters more than pixel-perfect scaling

When NOT to use it:

  • Large scrolling tilemaps — the texture is rasterized at a fixed resolution and will blur if the camera zooms in past that resolution
  • Subtrees that change frequently — every change requires a full re-render to the offscreen texture
  • Nodes that need pixel-perfect rendering at varying zoom levels

Setup example (filtered badge):

badge := willow.NewContainer("badge")
badge.AddChild(icon)
badge.AddChild(label)
badge.Filters = []willow.Filter{
    willow.NewOutlineFilter(2, willow.Color{R: 1, G: 1, B: 0, A: 1}),
}
badge.SetCacheAsTexture(true)
scene.Root().AddChild(badge)

// When the badge content changes, invalidate:
badge.InvalidateCache()

Caveats:

  • The offscreen texture is allocated at the subtree's bounding-box size (rounded up to the next power of two). Very large subtrees consume significant GPU memory.
  • Zooming the camera past the cached resolution produces blurry output. If the camera zooms dynamically, prefer Node.SetCacheAsTree instead.
  • The texture is re-rendered from scratch on invalidation — there is no partial update. Frequent invalidation can be worse than no caching.

Choosing between the two

Use Node.SetCacheAsTree when the bottleneck is CPU traversal of large static subtrees (tilemaps, backgrounds). The output is N render commands replayed from cache — still pixel-perfect at any zoom, animated tiles work for free, and camera movement never invalidates.

Use Node.SetCacheAsTexture when the bottleneck is GPU draw calls or filter cost. The output is a single textured quad — minimal GPU work, but locked to a fixed resolution.

They can be combined: a tilemap container uses SetCacheAsTree for fast traversal, while a filtered HUD badge inside it uses SetCacheAsTexture to avoid re-applying shaders every frame.

Organize your scene tree to maximize cache effectiveness:

scene.Root()
├── tilemap      (SetCacheAsTree, manual mode — thousands of tiles)
├── entities     (NO cache — players, enemies, projectiles move every frame)
├── particles    (NO cache — emitters block tree caching)
└── ui           (SetCacheAsTree, auto mode — panels that update occasionally)
    └── badge    (SetCacheAsTexture — filtered, rarely changes)

Index

Constants

This section is empty.

Variables

View Source
var ColorWhite = Color{1, 1, 1, 1}

ColorWhite is the default tint (no color modification).

View Source
var WhitePixel *ebiten.Image

WhitePixel is a 1x1 white image used by default for solid color sprites.

Functions

func NewDistortionGrid

func NewDistortionGrid(name string, img *ebiten.Image, cols, rows int) (*DistortionGrid, *Node)

NewDistortionGrid creates a grid mesh over the given image. cols and rows define the number of cells (vertices = (cols+1) * (rows+1)).

func NewRope

func NewRope(name string, img *ebiten.Image, points []Vec2, cfg RopeConfig) (*Rope, *Node)

NewRope creates a rope mesh node that renders a textured ribbon along the given points. The image is tiled along the path (SrcX) and spans the full image height (SrcY).

func Run

func Run(scene *Scene, cfg RunConfig) error

Run is a convenience entry point that creates an Ebitengine game loop around the given Scene. It configures the window and calls ebiten.RunGame.

For full control over the game loop, skip Run and implement ebiten.Game yourself, calling Scene.Update and Scene.Draw directly.

func SetPolygonPoints

func SetPolygonPoints(n *Node, points []Vec2)

SetPolygonPoints updates the polygon's vertices. Maintains fan triangulation. If textured is true and img is non-nil, UVs are mapped to the bounding box.

Types

type AnimFrame added in v0.1.3

type AnimFrame struct {
	GID      uint32 // tile GID for this frame (no flag bits)
	Duration int    // milliseconds
}

AnimFrame describes a single frame in a tile animation sequence.

type Atlas

type Atlas struct {
	// Pages contains the atlas page images indexed by page number.
	Pages []*ebiten.Image
	// contains filtered or unexported fields
}

Atlas holds one or more atlas page images and a map of named regions.

func LoadAtlas

func LoadAtlas(jsonData []byte, pages []*ebiten.Image) (*Atlas, error)

LoadAtlas parses TexturePacker JSON data and associates the given page images. Supports both the hash format (single "frames" object) and the array format ("textures" array with per-page frame lists).

func (*Atlas) Region

func (a *Atlas) Region(name string) TextureRegion

Region returns the TextureRegion for the given name. If the name doesn't exist, it logs a warning (debug stderr) and returns a 1×1 magenta placeholder region on page index magentaPlaceholderPage.

type BatchMode added in v0.1.2

type BatchMode uint8

BatchMode controls how the render pipeline submits draw calls.

const (
	// BatchModeCoalesced accumulates vertices and submits one DrawTriangles32 per batch key run.
	// This is the default mode.
	BatchModeCoalesced BatchMode = iota
	// BatchModeImmediate submits one DrawImage per sprite (legacy).
	BatchModeImmediate
)

type BitmapFont

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

BitmapFont renders text from pre-rasterized glyph atlases in BMFont format.

func LoadBitmapFont

func LoadBitmapFont(fntData []byte) (*BitmapFont, error)

LoadBitmapFont parses BMFont .fnt text-format data. The page index defaults to 0. Register the atlas page image on the Scene via Scene.RegisterPage.

func LoadBitmapFontPage

func LoadBitmapFontPage(fntData []byte, pageIndex uint16) (*BitmapFont, error)

LoadBitmapFontPage parses BMFont .fnt text-format data with an explicit page index.

func (*BitmapFont) LineHeight

func (f *BitmapFont) LineHeight() float64

LineHeight returns the vertical distance between baselines.

func (*BitmapFont) MeasureString

func (f *BitmapFont) MeasureString(s string) (width, height float64)

MeasureString returns the width and height of the rendered text.

type BlendMode

type BlendMode uint8

BlendMode selects a compositing operation. Each maps to a specific ebiten.Blend value.

const (
	BlendNormal   BlendMode = iota // source-over (standard alpha blending)
	BlendAdd                       // additive / lighter
	BlendMultiply                  // multiply (source * destination; only darkens)
	BlendScreen                    // screen (1 - (1-src)*(1-dst); only brightens)
	BlendErase                     // destination-out (punch transparent holes)
	BlendMask                      // clip destination to source alpha
	BlendBelow                     // destination-over (draw behind existing content)
	BlendNone                      // opaque copy (skip blending)
)

func (BlendMode) EbitenBlend

func (b BlendMode) EbitenBlend() ebiten.Blend

EbitenBlend returns the ebiten.Blend value corresponding to this BlendMode.

type BlurFilter

type BlurFilter struct {
	Radius int
	// contains filtered or unexported fields
}

BlurFilter applies a Kawase iterative blur using downscale/upscale passes. No Kage shader needed — bilinear filtering during DrawImage does the work.

func NewBlurFilter

func NewBlurFilter(radius int) *BlurFilter

NewBlurFilter creates a blur filter with the given radius (in pixels).

func (*BlurFilter) Apply

func (f *BlurFilter) Apply(src, dst *ebiten.Image)

Apply renders a Kawase blur from src into dst using iterative downscale/upscale.

func (*BlurFilter) Padding

func (f *BlurFilter) Padding() int

Padding returns the blur radius; the offscreen buffer is expanded to avoid clipping.

type CacheTreeMode added in v0.1.2

type CacheTreeMode uint8

CacheTreeMode controls how a SetCacheAsTree node detects stale caches.

const (
	// CacheTreeManual requires the user to call InvalidateCacheTree() when
	// the subtree changes. Zero overhead on setters. Best for large tilemaps.
	CacheTreeManual CacheTreeMode = iota + 1
	// CacheTreeAuto (the default) auto-invalidates the cache when setters on
	// descendant nodes are called. Small per-setter overhead. Always correct.
	CacheTreeAuto
)

type CallbackHandle

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

CallbackHandle allows removing a registered scene-level callback.

func (CallbackHandle) Remove

func (h CallbackHandle) Remove()

Remove unregisters this callback so it no longer fires. The entry is removed from the slice to avoid nil iteration waste.

type Camera

type Camera struct {
	// X and Y are the world-space position the camera centers on.
	X, Y float64
	// Zoom is the scale factor (1.0 = no zoom, >1 = zoom in, <1 = zoom out).
	Zoom float64
	// Rotation is the camera rotation in radians (clockwise).
	Rotation float64
	// Viewport is the screen-space rectangle this camera renders into.
	Viewport Rect

	// CullEnabled skips nodes whose world AABB doesn't intersect the
	// camera's visible bounds.
	CullEnabled bool

	// BoundsEnabled clamps the camera position so the visible area stays
	// within Bounds.
	BoundsEnabled bool
	// Bounds is the world-space rectangle the camera is clamped to when
	// BoundsEnabled is true.
	Bounds Rect
	// contains filtered or unexported fields
}

Camera controls the view into the scene: position, zoom, rotation, and viewport.

func (*Camera) ClampToBounds added in v0.1.3

func (c *Camera) ClampToBounds()

ClampToBounds immediately clamps the camera position so the visible area stays within Bounds. Call this after modifying X/Y directly (e.g. in a drag callback) to prevent a single frame where the camera sees outside the bounds. No-op if BoundsEnabled is false.

func (*Camera) ClearBounds

func (c *Camera) ClearBounds()

ClearBounds disables camera bounds clamping.

func (*Camera) Follow

func (c *Camera) Follow(node *Node, offsetX, offsetY, lerp float64)

Follow makes the camera track a target node with the given offset and lerp factor. A lerp of 1.0 snaps immediately; lower values give smoother following.

func (*Camera) Invalidate added in v0.1.3

func (c *Camera) Invalidate()

Invalidate forces a recomputation of the view matrix.

func (*Camera) ScreenToWorld

func (c *Camera) ScreenToWorld(sx, sy float64) (wx, wy float64)

ScreenToWorld converts screen coordinates to world coordinates.

func (*Camera) ScrollTo

func (c *Camera) ScrollTo(x, y float64, duration float32, easeFn ease.TweenFunc)

ScrollTo animates the camera to the given world position over duration seconds.

func (*Camera) ScrollToTile

func (c *Camera) ScrollToTile(tileX, tileY int, tileW, tileH float64, duration float32, easeFn ease.TweenFunc)

ScrollToTile scrolls to the center of the given tile in a tile-based layout.

func (*Camera) SetBounds

func (c *Camera) SetBounds(bounds Rect)

SetBounds enables camera bounds clamping.

func (*Camera) Unfollow

func (c *Camera) Unfollow()

Unfollow stops tracking the current target node.

func (*Camera) VisibleBounds

func (c *Camera) VisibleBounds() Rect

VisibleBounds returns the axis-aligned bounding rect of the camera's visible area in world space.

func (*Camera) WorldToScreen

func (c *Camera) WorldToScreen(wx, wy float64) (sx, sy float64)

WorldToScreen converts world coordinates to screen coordinates.

type ClickContext

type ClickContext struct {
	Node      *Node        // the clicked node
	EntityID  uint32       // the clicked node's EntityID (for ECS bridging)
	UserData  any          // the clicked node's UserData
	GlobalX   float64      // click X in world coordinates
	GlobalY   float64      // click Y in world coordinates
	LocalX    float64      // click X in the node's local coordinates
	LocalY    float64      // click Y in the node's local coordinates
	Button    MouseButton  // which mouse button was clicked
	PointerID int          // 0 = mouse, 1-9 = touch contacts
	Modifiers KeyModifiers // keyboard modifier keys held during the click
}

ClickContext carries click event data passed to click callbacks.

type Color

type Color struct {
	R, G, B, A float64
}

Color represents an RGBA color with components in [0, 1]. Not premultiplied. Premultiplication occurs at render submission time.

type ColorMatrixFilter

type ColorMatrixFilter struct {
	Matrix [20]float64
	// contains filtered or unexported fields
}

ColorMatrixFilter applies a 4x5 color matrix transformation using a Kage shader. The matrix is stored in row-major order: [R_r, R_g, R_b, R_a, R_offset, G_r, ...].

func NewColorMatrixFilter

func NewColorMatrixFilter() *ColorMatrixFilter

NewColorMatrixFilter creates a color matrix filter initialized to the identity.

func (*ColorMatrixFilter) Apply

func (f *ColorMatrixFilter) Apply(src, dst *ebiten.Image)

Apply renders the color matrix transformation from src into dst.

func (*ColorMatrixFilter) Padding

func (f *ColorMatrixFilter) Padding() int

Padding returns 0; color matrix transforms don't expand the image bounds.

func (*ColorMatrixFilter) SetBrightness

func (f *ColorMatrixFilter) SetBrightness(b float64)

SetBrightness sets the matrix to adjust brightness by the given offset [-1, 1].

func (*ColorMatrixFilter) SetContrast

func (f *ColorMatrixFilter) SetContrast(c float64)

SetContrast sets the matrix to adjust contrast. c=1 is normal, 0=gray, >1 is higher.

func (*ColorMatrixFilter) SetSaturation

func (f *ColorMatrixFilter) SetSaturation(s float64)

SetSaturation sets the matrix to adjust saturation. s=1 is normal, 0=grayscale.

type CommandType

type CommandType uint8

CommandType identifies the kind of render command.

const (
	CommandSprite   CommandType = iota // DrawImage
	CommandMesh                        // DrawTriangles
	CommandParticle                    // particle quads (batches as sprites)
	CommandTilemap                     // DrawTriangles for tilemap layers
)

type CustomShaderFilter

type CustomShaderFilter struct {
	Shader   *ebiten.Shader
	Uniforms map[string]any
	Images   [3]*ebiten.Image
	// contains filtered or unexported fields
}

CustomShaderFilter wraps a user-provided Kage shader, exposing Ebitengine's shader system directly. Images[0] is auto-filled with the source texture; the user may set Images[1] and Images[2] for additional textures.

func NewCustomShaderFilter

func NewCustomShaderFilter(shader *ebiten.Shader, padding int) *CustomShaderFilter

NewCustomShaderFilter creates a custom shader filter with the given shader and padding.

func (*CustomShaderFilter) Apply

func (f *CustomShaderFilter) Apply(src, dst *ebiten.Image)

Apply runs the user-provided Kage shader with src as Images[0].

func (*CustomShaderFilter) Padding

func (f *CustomShaderFilter) Padding() int

Padding returns the padding value set at construction time.

type DistortionGrid

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

DistortionGrid provides a grid mesh that can be deformed per-vertex.

func (*DistortionGrid) Cols

func (g *DistortionGrid) Cols() int

Cols returns the number of grid columns.

func (*DistortionGrid) Node

func (g *DistortionGrid) Node() *Node

Node returns the underlying mesh node.

func (*DistortionGrid) Reset

func (g *DistortionGrid) Reset()

Reset returns all vertices to their original positions.

func (*DistortionGrid) Rows

func (g *DistortionGrid) Rows() int

Rows returns the number of grid rows.

func (*DistortionGrid) SetAllVertices

func (g *DistortionGrid) SetAllVertices(fn func(col, row int, restX, restY float64) (dx, dy float64))

SetAllVertices calls fn for each vertex, passing (col, row, restX, restY). fn returns the (dx, dy) displacement from the rest position.

func (*DistortionGrid) SetVertex

func (g *DistortionGrid) SetVertex(col, row int, dx, dy float64)

SetVertex offsets a single grid vertex by (dx, dy) from its rest position.

type DragContext

type DragContext struct {
	Node         *Node        // the node being dragged
	EntityID     uint32       // the dragged node's EntityID (for ECS bridging)
	UserData     any          // the dragged node's UserData
	GlobalX      float64      // current pointer X in world coordinates
	GlobalY      float64      // current pointer Y in world coordinates
	LocalX       float64      // current pointer X in the node's local coordinates
	LocalY       float64      // current pointer Y in the node's local coordinates
	StartX       float64      // world X where the drag began
	StartY       float64      // world Y where the drag began
	DeltaX       float64      // X movement since the previous drag event
	DeltaY       float64      // Y movement since the previous drag event
	ScreenDeltaX float64      // X movement in screen pixels since the previous drag event
	ScreenDeltaY float64      // Y movement in screen pixels since the previous drag event
	Button       MouseButton  // which mouse button initiated the drag
	PointerID    int          // 0 = mouse, 1-9 = touch contacts
	Modifiers    KeyModifiers // keyboard modifier keys held during the drag
}

DragContext carries drag event data passed to drag callbacks.

type EmitterConfig

type EmitterConfig struct {
	// MaxParticles is the pool size. New particles are silently dropped when full.
	MaxParticles int
	// EmitRate is the number of particles spawned per second.
	EmitRate float64
	// Lifetime is the range of particle lifetimes in seconds.
	Lifetime Range
	// Speed is the range of initial particle speeds in pixels per second.
	Speed Range
	// Angle is the range of emission angles in radians.
	Angle Range
	// StartScale is the range of scale factors at birth, interpolated to EndScale over lifetime.
	StartScale Range
	// EndScale is the range of scale factors at death.
	EndScale Range
	// StartAlpha is the range of alpha values at birth, interpolated to EndAlpha over lifetime.
	StartAlpha Range
	// EndAlpha is the range of alpha values at death.
	EndAlpha Range
	// Gravity is the constant acceleration applied to all particles each frame.
	Gravity Vec2
	// StartColor is the tint at birth, interpolated to EndColor over lifetime.
	StartColor Color
	// EndColor is the tint at death.
	EndColor Color
	// Region is the TextureRegion used to render each particle.
	Region TextureRegion
	// BlendMode is the compositing operation for particle rendering.
	BlendMode BlendMode
	// WorldSpace, when true, causes particles to keep their world position
	// once emitted rather than following the emitter node.
	WorldSpace bool
}

EmitterConfig controls how particles are spawned and behave.

type EntityStore

type EntityStore interface {
	// EmitEvent delivers an interaction event to the ECS.
	EmitEvent(event InteractionEvent)
}

EntityStore is the interface for optional ECS integration. When set on a Scene via SetEntityStore, interaction events on nodes with a non-zero EntityID are forwarded to the ECS.

type EventType

type EventType uint8

EventType identifies a kind of interaction event.

const (
	EventPointerDown  EventType = iota // fires when a pointer button is pressed
	EventPointerUp                     // fires when a pointer button is released
	EventPointerMove                   // fires when the pointer moves (hover, no button)
	EventClick                         // fires on press then release over the same node
	EventDragStart                     // fires when movement exceeds the drag dead zone
	EventDrag                          // fires each frame while dragging
	EventDragEnd                       // fires when the pointer is released after dragging
	EventPinch                         // fires during a two-finger pinch/rotate gesture
	EventPointerEnter                  // fires when the pointer enters a node's bounds
	EventPointerLeave                  // fires when the pointer leaves a node's bounds
)

type Filter

type Filter interface {
	// Apply renders src into dst with the filter effect.
	Apply(src, dst *ebiten.Image)
	// Padding returns the extra pixels needed around the source to accommodate
	// the effect (e.g. blur radius, outline thickness). Zero means no padding.
	Padding() int
}

Filter is the interface for visual effects applied to a node's rendered output.

type Font

type Font interface {
	// MeasureString returns the pixel width and height of the rendered text,
	// accounting for newlines and the font's line height.
	MeasureString(text string) (width, height float64)
	// LineHeight returns the vertical distance between baselines in pixels.
	LineHeight() float64
}

Font is the interface for text measurement and layout. Implemented by BitmapFont and TTFFont.

type HitCircle

type HitCircle struct {
	CenterX, CenterY, Radius float64
}

HitCircle is a circular hit area in local coordinates.

func (HitCircle) Contains

func (c HitCircle) Contains(x, y float64) bool

Contains reports whether (x, y) lies inside or on the circle.

type HitPolygon

type HitPolygon struct {
	Points []Vec2
}

HitPolygon is a convex polygon hit area in local coordinates. Points must define a convex polygon in either winding order. Concave polygons will produce incorrect results.

func (HitPolygon) Contains

func (p HitPolygon) Contains(x, y float64) bool

Contains reports whether (x, y) lies inside a convex polygon using cross-product sign test.

type HitRect

type HitRect struct {
	X, Y, Width, Height float64
}

HitRect is an axis-aligned rectangular hit area in local coordinates.

func (HitRect) Contains

func (r HitRect) Contains(x, y float64) bool

Contains reports whether (x, y) lies inside the rectangle.

type HitShape

type HitShape interface {
	// Contains reports whether the local-space point (x, y) is inside the shape.
	Contains(x, y float64) bool
}

HitShape defines a custom hit testing region in local coordinates. Implement this interface and assign it to Node.HitShape to override the default axis-aligned bounding box test.

type InteractionEvent

type InteractionEvent struct {
	Type      EventType    // which kind of interaction occurred
	EntityID  uint32       // the ECS entity associated with the hit node
	GlobalX   float64      // pointer X in world coordinates
	GlobalY   float64      // pointer Y in world coordinates
	LocalX    float64      // pointer X in the hit node's local coordinates
	LocalY    float64      // pointer Y in the hit node's local coordinates
	Button    MouseButton  // which mouse button is involved
	Modifiers KeyModifiers // keyboard modifier keys held during the event
	// Drag fields (valid for EventDragStart, EventDrag, EventDragEnd)
	StartX       float64 // world X where the drag began
	StartY       float64 // world Y where the drag began
	DeltaX       float64 // X movement since the previous drag event
	DeltaY       float64 // Y movement since the previous drag event
	ScreenDeltaX float64 // X movement in screen pixels since the previous drag event
	ScreenDeltaY float64 // Y movement in screen pixels since the previous drag event
	// Pinch fields (valid for EventPinch)
	Scale      float64 // cumulative scale factor since pinch start
	ScaleDelta float64 // frame-to-frame scale change
	Rotation   float64 // cumulative rotation in radians since pinch start
	RotDelta   float64 // frame-to-frame rotation change in radians
}

InteractionEvent carries interaction data for the ECS bridge.

type KeyModifiers

type KeyModifiers uint8

KeyModifiers is a bitmask of keyboard modifier keys. Values can be combined with bitwise OR (e.g. ModShift | ModCtrl).

const (
	ModShift KeyModifiers = 1 << iota // Shift key
	ModCtrl                           // Control key
	ModAlt                            // Alt / Option key
	ModMeta                           // Meta / Command / Windows key
)

type Light

type Light struct {
	// X and Y are the light's position in the light layer's local coordinate space.
	X, Y float64
	// Radius controls the drawn size (diameter = Radius*2 pixels).
	Radius float64
	// Rotation is the light's rotation in radians; useful for directional shapes.
	Rotation float64
	// Intensity controls light brightness in the range [0, 1].
	Intensity float64
	// Enabled determines whether this light is drawn during Redraw.
	// Disabled lights are skipped entirely.
	Enabled bool
	// Color is the tint color. Zero value or white means neutral (no tint).
	Color Color
	// TextureRegion, if non-zero, uses this sprite sheet region instead of
	// the default feathered circle.
	TextureRegion TextureRegion
	// Target, if set, makes the light follow this node's pivot point each Redraw.
	Target *Node
	// OffsetX and OffsetY offset the light from the target's pivot in
	// light-layer space.
	OffsetX float64
	OffsetY float64
}

Light represents a light source in a LightLayer.

type LightLayer

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

LightLayer provides a convenient 2D lighting effect using erase blending. It manages a set of lights, renders them into an offscreen texture filled with an ambient darkness color, and erases feathered circles at each light position. The resulting texture is displayed as a sprite node with BlendMultiply so it darkens the scene everywhere except where lights shine.

func NewLightLayer

func NewLightLayer(w, h int, ambientAlpha float64) *LightLayer

NewLightLayer creates a light layer covering (w x h) pixels. ambientAlpha controls the base darkness (0 = fully transparent, 1 = fully opaque black).

func (*LightLayer) AddLight

func (ll *LightLayer) AddLight(l *Light)

AddLight adds a light to the layer.

func (*LightLayer) AmbientAlpha

func (ll *LightLayer) AmbientAlpha() float64

AmbientAlpha returns the current ambient darkness level.

func (*LightLayer) ClearLights

func (ll *LightLayer) ClearLights()

ClearLights removes all lights from the layer.

func (*LightLayer) Dispose

func (ll *LightLayer) Dispose()

Dispose releases all resources owned by the light layer.

func (*LightLayer) Lights

func (ll *LightLayer) Lights() []*Light

Lights returns the current light list. The returned slice MUST NOT be mutated.

func (*LightLayer) Node

func (ll *LightLayer) Node() *Node

Node returns the sprite node that displays the light layer. Add this to the scene graph to render the lighting effect.

func (*LightLayer) Redraw

func (ll *LightLayer) Redraw()

Redraw fills the texture with ambient darkness then erases light shapes at each enabled light position. Lights with a TextureRegion use that sprite; lights without fall back to a generated feathered circle. Call this every frame (or whenever lights change) before drawing the scene.

func (*LightLayer) RemoveLight

func (ll *LightLayer) RemoveLight(l *Light)

RemoveLight removes a light from the layer.

func (*LightLayer) RenderTexture

func (ll *LightLayer) RenderTexture() *RenderTexture

RenderTexture returns the underlying RenderTexture.

func (*LightLayer) SetAmbientAlpha

func (ll *LightLayer) SetAmbientAlpha(a float64)

SetAmbientAlpha sets the base darkness level.

func (*LightLayer) SetCircleRadius

func (ll *LightLayer) SetCircleRadius(radius float64)

SetCircleRadius pre-generates a feathered circle texture at the given radius and stores it in the cache.

func (*LightLayer) SetPages

func (ll *LightLayer) SetPages(pages []*ebiten.Image)

SetPages stores the atlas page images used to resolve Light.TextureRegion. Typically called with the Scene's pages after loading atlases.

type MouseButton

type MouseButton uint8

MouseButton identifies a mouse button.

const (
	MouseButtonLeft   MouseButton = iota // primary (left) mouse button
	MouseButtonRight                     // secondary (right) mouse button
	MouseButtonMiddle                    // middle mouse button (scroll wheel click)
)

type Node

type Node struct {

	// Visible controls whether this node and its subtree are drawn.
	// An invisible node is also excluded from hit testing.
	Visible bool
	// Renderable controls whether this node emits render commands. When false
	// the node is skipped during drawing but its children are still traversed.
	Renderable bool

	// Type determines how this node is rendered (container, sprite, mesh, etc.).
	Type NodeType
	// RenderLayer is the primary sort key for render commands.
	// All commands in a lower layer draw before any command in a higher layer.
	RenderLayer uint8
	// BlendMode selects the compositing operation used when drawing this node.
	BlendMode BlendMode

	// Alpha is the node's opacity in [0, 1]. Multiplied with the parent's
	// computed alpha, so children inherit transparency.
	Alpha float64

	// GlobalOrder is a secondary sort key within the same RenderLayer.
	// Set it to override the default tree-order sorting.
	GlobalOrder int
	// TextureRegion identifies the sub-image within an atlas page to draw.
	TextureRegion TextureRegion
	// Color is a multiplicative tint applied to the sprite. The default
	// {1,1,1,1} means no tint.
	Color Color

	// X and Y are the local-space position in pixels (origin at top-left, Y down).
	X, Y float64
	// ScaleX and ScaleY are the local scale factors (1.0 = no scaling).
	ScaleX float64
	ScaleY float64
	// Rotation is the local rotation in radians (clockwise).
	Rotation float64
	// SkewX and SkewY are shear angles in radians.
	SkewX, SkewY float64
	// PivotX and PivotY are the transform origin in local pixels. Scale,
	// rotation, and skew are applied around this point.
	PivotX float64
	PivotY float64

	// Parent points to this node's parent, or nil for the root.
	Parent *Node
	// ID is a unique auto-assigned identifier (never zero for live nodes).
	ID uint32
	// ZIndex controls draw order among siblings. Higher values draw on top.
	// Use SetZIndex to change this so the parent is notified to re-sort.
	ZIndex int
	// EntityID links this node to an ECS entity. When non-zero, interaction
	// events on this node are forwarded to the Scene's EntityStore.
	EntityID uint32
	// Name is a human-readable label for debugging; not used for lookups.
	Name string
	// UserData is an arbitrary value the application can attach to a node.
	UserData any

	// Vertices holds the mesh vertex data for DrawTriangles.
	Vertices []ebiten.Vertex
	// Indices holds the triangle index list for DrawTriangles.
	Indices []uint16
	// MeshImage is the texture sampled by DrawTriangles.
	MeshImage *ebiten.Image

	// Emitter manages the particle pool and simulation for this node.
	Emitter *ParticleEmitter
	// TextBlock holds the text content, font, and cached layout state.
	TextBlock *TextBlock

	// OnUpdate is called once per tick during Scene.Update if set.
	OnUpdate func(dt float64)

	// Interactable controls whether this node responds to pointer events.
	// When false the entire subtree is excluded from hit testing.
	Interactable bool
	// HitShape overrides the default AABB hit test with a custom shape.
	// Nil means use the node's bounding box.
	HitShape HitShape

	// Filters is the chain of visual effects applied to this node's rendered
	// output. Filters are applied in order; each reads from the previous
	// result and writes to a new buffer.
	Filters []Filter

	// OnPointerDown fires when a pointer button is pressed over this node.
	OnPointerDown func(PointerContext)
	// OnPointerUp fires when a pointer button is released over this node.
	OnPointerUp func(PointerContext)
	// OnPointerMove fires when the pointer moves over this node (hover).
	OnPointerMove func(PointerContext)
	// OnClick fires on press then release over this node.
	OnClick func(ClickContext)
	// OnDragStart fires when a drag gesture begins on this node.
	OnDragStart func(DragContext)
	// OnDrag fires each frame while this node is being dragged.
	OnDrag func(DragContext)
	// OnDragEnd fires when a drag gesture ends on this node.
	OnDragEnd func(DragContext)
	// OnPinch fires during a two-finger pinch gesture over this node.
	OnPinch func(PinchContext)
	// OnPointerEnter fires when the pointer enters this node's bounds.
	OnPointerEnter func(PointerContext)
	// OnPointerLeave fires when the pointer leaves this node's bounds.
	OnPointerLeave func(PointerContext)
	// contains filtered or unexported fields
}

Node is the fundamental scene graph element. A single flat struct is used for all node types to avoid interface dispatch on the hot path.

Field order is performance-critical: the gc compiler lays out fields in declaration order. Hot-path fields (traverse, updateWorldTransform) are packed first so they share cache lines. Cold fields (callbacks, mesh data, metadata) are pushed to the tail. Do not reorder without benchmarking.

func NewContainer

func NewContainer(name string) *Node

NewContainer creates a container node with no visual representation.

func NewFPSWidget

func NewFPSWidget() *Node

NewFPSWidget creates a new Node that displays the current FPS and TPS. The widget is transparently updated every ~0.5 seconds. It uses a custom internal image and ebitenutil.DebugPrint for rendering.

func NewMesh

func NewMesh(name string, img *ebiten.Image, vertices []ebiten.Vertex, indices []uint16) *Node

NewMesh creates a mesh node that uses DrawTriangles for rendering.

func NewParticleEmitter

func NewParticleEmitter(name string, cfg EmitterConfig) *Node

NewParticleEmitter creates a particle emitter node with a preallocated pool.

func NewPolygon

func NewPolygon(name string, points []Vec2) *Node

NewPolygon creates an untextured polygon mesh from the given vertices. Uses fan triangulation (convex polygons). The polygon is drawn with a shared 1x1 white pixel image; color comes from the node's Color field.

func NewPolygonTextured

func NewPolygonTextured(name string, img *ebiten.Image, points []Vec2) *Node

NewPolygonTextured creates a textured polygon mesh. UVs are mapped to the bounding box of the points, so (0,0)→top-left and (imgW,imgH)→bottom-right.

func NewSprite

func NewSprite(name string, region TextureRegion) *Node

NewSprite creates a sprite node that renders a texture region.

func NewText

func NewText(name string, content string, font Font) *Node

NewText creates a text node that renders the given string using font. The node's TextBlock is initialized with white color and dirty layout.

func (*Node) AddChild

func (n *Node) AddChild(child *Node)

AddChild appends child to this node's children. If child already has a parent, it is removed from that parent first. Panics if child is nil or child is an ancestor of this node (cycle).

func (*Node) AddChildAt

func (n *Node) AddChildAt(child *Node, index int)

AddChildAt inserts child at the given index. Same reparenting and cycle-check behavior as AddChild.

func (*Node) ChildAt

func (n *Node) ChildAt(index int) *Node

ChildAt returns the child at the given index. Panics if the index is out of range.

func (*Node) Children

func (n *Node) Children() []*Node

Children returns the child list. The returned slice MUST NOT be mutated by the caller.

func (*Node) ClearMask

func (n *Node) ClearMask()

ClearMask removes the mask from this node.

func (*Node) CustomImage

func (n *Node) CustomImage() *ebiten.Image

CustomImage returns the user-provided image, or nil if not set.

func (*Node) Dispose

func (n *Node) Dispose()

Dispose removes this node from its parent, marks it as disposed, and recursively disposes all descendants.

func (*Node) GetMask

func (n *Node) GetMask() *Node

GetMask returns the current mask node, or nil if no mask is set.

func (*Node) Invalidate added in v0.1.3

func (n *Node) Invalidate()

Invalidate marks the node's transform and alpha as dirty, forcing recomputation on the next frame. Useful after bulk-setting fields directly.

func (*Node) InvalidateCache

func (n *Node) InvalidateCache()

InvalidateCache marks the cached texture as dirty so it will be re-rendered on the next frame. No-op if caching is not enabled.

func (*Node) InvalidateCacheTree added in v0.1.2

func (n *Node) InvalidateCacheTree()

InvalidateCacheTree marks the cache as stale. Next Draw() re-traverses. Works with both Auto and Manual modes.

func (*Node) InvalidateMeshAABB

func (n *Node) InvalidateMeshAABB()

InvalidateMeshAABB marks the mesh's cached AABB as needing recomputation. Call this after modifying Vertices.

func (*Node) IsCacheAsTreeEnabled added in v0.1.2

func (n *Node) IsCacheAsTreeEnabled() bool

IsCacheAsTreeEnabled reports whether subtree command caching is enabled.

func (*Node) IsCacheEnabled

func (n *Node) IsCacheEnabled() bool

IsCacheEnabled reports whether subtree caching is enabled for this node.

func (*Node) IsDisposed

func (n *Node) IsDisposed() bool

IsDisposed returns true if this node has been disposed.

func (*Node) LocalToWorld

func (n *Node) LocalToWorld(lx, ly float64) (wx, wy float64)

LocalToWorld converts a local-space point to world-space.

func (*Node) NumChildren

func (n *Node) NumChildren() int

NumChildren returns the number of children.

func (*Node) RemoveChild

func (n *Node) RemoveChild(child *Node)

RemoveChild detaches child from this node. Panics if child.Parent != n.

func (*Node) RemoveChildAt

func (n *Node) RemoveChildAt(index int) *Node

RemoveChildAt removes and returns the child at the given index. Panics if the index is out of range.

func (*Node) RemoveChildren

func (n *Node) RemoveChildren()

RemoveChildren detaches all children from this node. Children are NOT disposed.

func (*Node) RemoveFromParent

func (n *Node) RemoveFromParent()

RemoveFromParent detaches this node from its parent. No-op if this node has no parent.

func (*Node) SetAlpha

func (n *Node) SetAlpha(a float64)

SetAlpha sets the node's alpha and marks it alpha-dirty. Unlike other transform setters, this only triggers a worldAlpha recomputation (a single multiply), skipping the full matrix recompute.

func (*Node) SetBlendMode added in v0.1.2

func (n *Node) SetBlendMode(b BlendMode)

SetBlendMode sets the node's blend mode and invalidates ancestor static caches.

func (*Node) SetCacheAsTexture

func (n *Node) SetCacheAsTexture(enabled bool)

SetCacheAsTexture enables or disables caching of this node's subtree as a single texture. When enabled, the subtree is rendered to an offscreen image and reused across frames until InvalidateCache is called.

func (*Node) SetCacheAsTree added in v0.1.2

func (n *Node) SetCacheAsTree(enabled bool, mode ...CacheTreeMode)

SetCacheAsTree enables or disables subtree command caching. When enabled, traverse skips this node's subtree and replays cached commands. Camera movement is handled automatically via delta remapping.

Mode is optional and defaults to CacheTreeAuto (safe, always correct).

Modes:

CacheTreeAuto   — (default) setters on descendant nodes auto-invalidate
                  the cache. Small per-setter overhead. Always correct.
CacheTreeManual — user calls InvalidateCacheTree() when subtree changes.
                  Zero overhead on setters. Best for large tilemaps where
                  the developer knows exactly when tiles change.

func (*Node) SetChildIndex

func (n *Node) SetChildIndex(child *Node, index int)

SetChildIndex moves child to a new index among its siblings. Panics if child is not a child of n or if index is out of range.

func (*Node) SetColor added in v0.1.2

func (n *Node) SetColor(c Color)

SetColor sets the node's tint color and invalidates ancestor static caches.

func (*Node) SetCustomImage

func (n *Node) SetCustomImage(img *ebiten.Image)

SetCustomImage sets a user-provided *ebiten.Image to display instead of TextureRegion. Used by RenderTexture to attach a persistent offscreen canvas to a sprite node.

func (*Node) SetGlobalOrder added in v0.1.2

func (n *Node) SetGlobalOrder(o int)

SetGlobalOrder sets the node's global order and invalidates ancestor static caches.

func (*Node) SetMask

func (n *Node) SetMask(maskNode *Node)

SetMask sets a mask node for this node. The mask node's alpha channel determines which parts of this node are visible. The mask node is NOT part of the scene tree — its transforms are relative to the masked node.

func (*Node) SetPivot

func (n *Node) SetPivot(px, py float64)

SetPivot sets the node's PivotX and PivotY and marks it dirty.

func (*Node) SetPosition

func (n *Node) SetPosition(x, y float64)

SetPosition sets the node's local X and Y and marks it dirty.

func (*Node) SetRenderLayer added in v0.1.2

func (n *Node) SetRenderLayer(l uint8)

SetRenderLayer sets the node's render layer and invalidates ancestor static caches.

func (*Node) SetRenderable added in v0.1.2

func (n *Node) SetRenderable(r bool)

SetRenderable sets whether the node emits render commands and invalidates ancestor static caches.

func (*Node) SetRotation

func (n *Node) SetRotation(r float64)

SetRotation sets the node's rotation (in radians) and marks it dirty.

func (*Node) SetScale

func (n *Node) SetScale(sx, sy float64)

SetScale sets the node's ScaleX and ScaleY and marks it dirty.

func (*Node) SetSkew

func (n *Node) SetSkew(sx, sy float64)

SetSkew sets the node's SkewX and SkewY (in radians) and marks it dirty.

func (*Node) SetTextureRegion added in v0.1.2

func (n *Node) SetTextureRegion(r TextureRegion)

SetTextureRegion sets the node's texture region and invalidates ancestor caches. If the atlas page is unchanged (e.g. animated tile UV swap), the CacheAsTree cache is NOT invalidated — instead the node is registered as animated so replay reads the live TextureRegion. Page changes always invalidate.

func (*Node) SetVisible added in v0.1.2

func (n *Node) SetVisible(v bool)

SetVisible sets the node's visibility and invalidates ancestor caches.

func (*Node) SetZIndex

func (n *Node) SetZIndex(z int)

SetZIndex sets the node's ZIndex and marks the parent's children as unsorted, so the next traversal will re-sort siblings by ZIndex.

func (*Node) ToTexture

func (n *Node) ToTexture(s *Scene) *ebiten.Image

ToTexture renders this node's subtree to a new offscreen image and returns it. The caller owns the returned image (it is NOT pooled). Requires a Scene reference to use the render pipeline.

func (*Node) WorldToLocal

func (n *Node) WorldToLocal(wx, wy float64) (lx, ly float64)

WorldToLocal converts a world-space point to this node's local coordinate space.

type NodeType

type NodeType uint8

NodeType distinguishes rendering behavior for a Node.

const (
	NodeTypeContainer       NodeType = iota // group node with no visual output
	NodeTypeSprite                          // renders a TextureRegion or custom image
	NodeTypeMesh                            // renders arbitrary triangles via DrawTriangles
	NodeTypeParticleEmitter                 // CPU-simulated particle system
	NodeTypeText                            // renders text via BitmapFont glyphs or TTF
)

type Outline

type Outline struct {
	Color     Color
	Thickness float64
}

Outline defines a text stroke rendered behind the fill.

type OutlineFilter

type OutlineFilter struct {
	Thickness int
	Color     Color
	// contains filtered or unexported fields
}

OutlineFilter draws the source in 8 cardinal/diagonal offsets with the outline color, then draws the original on top. Works at any thickness.

func NewOutlineFilter

func NewOutlineFilter(thickness int, c Color) *OutlineFilter

NewOutlineFilter creates an outline filter.

func (*OutlineFilter) Apply

func (f *OutlineFilter) Apply(src, dst *ebiten.Image)

Apply draws an 8-direction offset outline behind the source image.

func (*OutlineFilter) Padding

func (f *OutlineFilter) Padding() int

Padding returns the outline thickness; the offscreen buffer is expanded by this amount.

type PaletteFilter

type PaletteFilter struct {
	Palette     [256]Color
	CycleOffset float64
	// contains filtered or unexported fields
}

PaletteFilter remaps pixel colors through a 256-entry color palette based on luminance. Supports a cycle offset for palette animation.

func NewPaletteFilter

func NewPaletteFilter() *PaletteFilter

NewPaletteFilter creates a palette filter with a default grayscale palette.

func (*PaletteFilter) Apply

func (f *PaletteFilter) Apply(src, dst *ebiten.Image)

Apply remaps pixel colors through the palette based on luminance.

func (*PaletteFilter) Padding

func (f *PaletteFilter) Padding() int

Padding returns 0; palette remapping doesn't expand the image bounds.

func (*PaletteFilter) SetPalette

func (f *PaletteFilter) SetPalette(palette [256]Color)

SetPalette sets the palette colors and marks the texture for rebuild.

type ParticleEmitter

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

ParticleEmitter manages a pool of particles with CPU-based simulation.

func (*ParticleEmitter) AliveCount

func (e *ParticleEmitter) AliveCount() int

AliveCount returns the number of alive particles.

func (*ParticleEmitter) Config

func (e *ParticleEmitter) Config() *EmitterConfig

Config returns a pointer to the emitter's config for live tuning.

func (*ParticleEmitter) IsActive

func (e *ParticleEmitter) IsActive() bool

IsActive reports whether the emitter is currently emitting new particles.

func (*ParticleEmitter) Reset

func (e *ParticleEmitter) Reset()

Reset stops emitting and kills all alive particles.

func (*ParticleEmitter) Start

func (e *ParticleEmitter) Start()

Start begins emitting particles.

func (*ParticleEmitter) Stop

func (e *ParticleEmitter) Stop()

Stop stops emitting new particles. Existing particles continue to live out.

type PinchContext

type PinchContext struct {
	CenterX, CenterY   float64 // midpoint between the two touch points in world coordinates
	Scale, ScaleDelta  float64 // cumulative scale factor and frame-to-frame change
	Rotation, RotDelta float64 // cumulative rotation (radians) and frame-to-frame change
}

PinchContext carries two-finger pinch/rotate gesture data.

type PixelPerfectInlineFilter

type PixelPerfectInlineFilter struct {
	Color Color
	// contains filtered or unexported fields
}

PixelPerfectInlineFilter uses a Kage shader to recolor edge pixels that border transparent areas.

func NewPixelPerfectInlineFilter

func NewPixelPerfectInlineFilter(c Color) *PixelPerfectInlineFilter

NewPixelPerfectInlineFilter creates a pixel-perfect inline filter.

func (*PixelPerfectInlineFilter) Apply

func (f *PixelPerfectInlineFilter) Apply(src, dst *ebiten.Image)

Apply recolors edge pixels that border transparent areas via a Kage shader.

func (*PixelPerfectInlineFilter) Padding

func (f *PixelPerfectInlineFilter) Padding() int

Padding returns 0; inlines only affect existing opaque pixels.

type PixelPerfectOutlineFilter

type PixelPerfectOutlineFilter struct {
	Color Color
	// contains filtered or unexported fields
}

PixelPerfectOutlineFilter uses a Kage shader to draw a 1-pixel outline around non-transparent pixels by testing cardinal neighbors.

func NewPixelPerfectOutlineFilter

func NewPixelPerfectOutlineFilter(c Color) *PixelPerfectOutlineFilter

NewPixelPerfectOutlineFilter creates a pixel-perfect outline filter.

func (*PixelPerfectOutlineFilter) Apply

func (f *PixelPerfectOutlineFilter) Apply(src, dst *ebiten.Image)

Apply renders a 1-pixel outline via a Kage shader testing cardinal neighbors.

func (*PixelPerfectOutlineFilter) Padding

func (f *PixelPerfectOutlineFilter) Padding() int

Padding returns 1; the outline extends 1 pixel beyond the source bounds.

type PointerContext

type PointerContext struct {
	Node      *Node        // the node under the pointer, or nil if none
	EntityID  uint32       // the hit node's EntityID (for ECS bridging)
	UserData  any          // the hit node's UserData
	GlobalX   float64      // pointer X in world coordinates
	GlobalY   float64      // pointer Y in world coordinates
	LocalX    float64      // pointer X in the hit node's local coordinates
	LocalY    float64      // pointer Y in the hit node's local coordinates
	Button    MouseButton  // which mouse button is involved
	PointerID int          // 0 = mouse, 1-9 = touch contacts
	Modifiers KeyModifiers // keyboard modifier keys held during the event
}

PointerContext carries pointer event data passed to pointer callbacks.

type Range

type Range struct {
	Min, Max float64
}

Range is a general-purpose min/max range. Used by the particle system (EmitterConfig) and potentially other systems.

func (Range) Random

func (r Range) Random() float64

Random returns a random float64 in [Min, Max].

type Rect

type Rect struct {
	X, Y, Width, Height float64
}

Rect is an axis-aligned rectangle. The coordinate system has its origin at the top-left, with Y increasing downward.

func (Rect) Contains

func (r Rect) Contains(x, y float64) bool

Contains reports whether the point (x, y) lies inside the rectangle. Points on the edge are considered inside.

func (Rect) Intersects

func (r Rect) Intersects(other Rect) bool

Intersects reports whether r and other overlap. Adjacent rectangles (sharing only an edge) are considered intersecting.

type RenderCommand

type RenderCommand struct {
	Type          CommandType
	Transform     [6]float32
	TextureRegion TextureRegion
	Color         color32
	BlendMode     BlendMode
	ShaderID      uint16
	TargetID      uint16
	RenderLayer   uint8
	GlobalOrder   int
	// contains filtered or unexported fields
}

RenderCommand is a single draw instruction emitted during scene traversal.

type RenderTexture

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

RenderTexture is a persistent offscreen canvas that can be attached to a sprite node via SetCustomImage. Unlike pooled render targets used internally, a RenderTexture is owned by the caller and is NOT recycled between frames.

func NewRenderTexture

func NewRenderTexture(w, h int) *RenderTexture

NewRenderTexture creates a persistent offscreen canvas of the given size.

func (*RenderTexture) Clear

func (rt *RenderTexture) Clear()

Clear fills the texture with transparent black.

func (*RenderTexture) Dispose

func (rt *RenderTexture) Dispose()

Dispose deallocates the underlying image. The RenderTexture should not be used after calling Dispose.

func (*RenderTexture) DrawImage

func (rt *RenderTexture) DrawImage(src *ebiten.Image, op *ebiten.DrawImageOptions)

DrawImage draws src onto this texture using the provided options.

func (*RenderTexture) DrawImageAt

func (rt *RenderTexture) DrawImageAt(src *ebiten.Image, x, y float64, blend BlendMode)

DrawImageAt draws src at the given position with the specified blend mode.

func (*RenderTexture) DrawImageColored

func (rt *RenderTexture) DrawImageColored(img *ebiten.Image, opts RenderTextureDrawOpts)

DrawImageColored draws a raw *ebiten.Image with full transform, color, and alpha.

func (*RenderTexture) DrawSprite

func (rt *RenderTexture) DrawSprite(region TextureRegion, x, y float64, blend BlendMode, pages []*ebiten.Image)

DrawSprite draws a TextureRegion from the atlas onto this texture at (x, y).

func (*RenderTexture) DrawSpriteColored

func (rt *RenderTexture) DrawSpriteColored(region TextureRegion, opts RenderTextureDrawOpts, pages []*ebiten.Image)

DrawSpriteColored draws a TextureRegion with full transform, color, and alpha.

func (*RenderTexture) Fill

func (rt *RenderTexture) Fill(c Color)

Fill fills the entire texture with the given color.

func (*RenderTexture) Height

func (rt *RenderTexture) Height() int

Height returns the texture height in pixels.

func (*RenderTexture) Image

func (rt *RenderTexture) Image() *ebiten.Image

Image returns the underlying *ebiten.Image for direct manipulation.

func (*RenderTexture) NewSpriteNode

func (rt *RenderTexture) NewSpriteNode(name string) *Node

NewSpriteNode creates a NodeTypeSprite with customImage pre-set to this texture. The returned node will display the RenderTexture contents.

func (*RenderTexture) Resize

func (rt *RenderTexture) Resize(width, height int)

Resize deallocates the old image and creates a new one at the given dimensions.

func (*RenderTexture) Width

func (rt *RenderTexture) Width() int

Width returns the texture width in pixels.

type RenderTextureDrawOpts

type RenderTextureDrawOpts struct {
	// X and Y are the draw position in pixels.
	X, Y float64
	// ScaleX and ScaleY are scale factors. Zero defaults to 1.0.
	ScaleX, ScaleY float64
	// Rotation is the rotation in radians (clockwise).
	Rotation float64
	// PivotX and PivotY are the transform origin for scale and rotation.
	PivotX, PivotY float64
	// Color is a multiplicative tint. Zero value defaults to white (no tint).
	Color Color
	// Alpha is the opacity multiplier. Zero defaults to 1.0 (fully opaque).
	Alpha float64
	// BlendMode selects the compositing operation.
	BlendMode BlendMode
}

RenderTextureDrawOpts controls how an image or sprite is drawn onto a RenderTexture when using the "Colored" draw methods.

type Rope

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

Rope generates a ribbon/rope mesh that follows a polyline path.

func (*Rope) Config added in v0.1.1

func (r *Rope) Config() *RopeConfig

Config returns a pointer to the rope's configuration so callers can mutate fields directly before calling Update().

func (*Rope) Node

func (r *Rope) Node() *Node

Node returns the underlying mesh node.

func (*Rope) SetPoints

func (r *Rope) SetPoints(points []Vec2)

SetPoints updates the rope's path. For N points: 2N vertices, 6(N-1) indices.

func (*Rope) Update added in v0.1.1

func (r *Rope) Update()

Update recomputes the rope's point path from the current RopeConfig and rebuilds the mesh. Call this in your update loop after changing the bound Vec2 values. Start and End must be non-nil (except for RopeCurveCustom).

type RopeConfig

type RopeConfig struct {
	Width    float64
	JoinMode RopeJoinMode

	CurveMode RopeCurveMode
	Segments  int // number of subdivisions (default 20)

	// Endpoint positions (pointers). Update() dereferences these each call,
	// so you can bind them once and mutate the underlying Vec2 freely.
	Start *Vec2
	End   *Vec2

	// Catenary sag in pixels (downward droop).
	Sag float64

	// Bézier control points (pointers). Quadratic uses Controls[0]; cubic uses both.
	Controls [2]*Vec2

	// Wave parameters.
	Amplitude float64
	Frequency float64 // cycles along the rope length
	Phase     float64 // phase offset in radians

	// Custom callback. Receives a preallocated buffer; must return the slice to use.
	PointsFunc func(buf []Vec2) []Vec2
}

RopeConfig configures a Rope mesh.

type RopeCurveMode added in v0.1.1

type RopeCurveMode uint8

RopeCurveMode selects the curve algorithm used by Rope.Update().

const (
	// RopeCurveLine draws a straight line between Start and End.
	RopeCurveLine RopeCurveMode = iota
	// RopeCurveCatenary simulates a drooping rope with gravity sag.
	RopeCurveCatenary
	// RopeCurveQuadBezier uses a quadratic Bézier with one control point.
	RopeCurveQuadBezier
	// RopeCurveCubicBezier uses a cubic Bézier with two control points.
	RopeCurveCubicBezier
	// RopeCurveWave produces a sinusoidal wave along the line.
	RopeCurveWave
	// RopeCurveCustom calls a user-provided PointsFunc callback.
	RopeCurveCustom
)

type RopeJoinMode

type RopeJoinMode uint8

RopeJoinMode controls how segments join in a Rope mesh.

const (
	// RopeJoinMiter extends segment corners to a sharp point.
	RopeJoinMiter RopeJoinMode = iota
	// RopeJoinBevel flattens corners by inserting extra vertices, avoiding spikes.
	RopeJoinBevel
)

type RunConfig

type RunConfig struct {
	// Title sets the window title. Ignored on platforms without a title bar.
	Title string
	// Width and Height set the window size in device-independent pixels.
	// If zero, defaults to 640x480.
	Width, Height int

	// ShowFPS enables a small FPS/TPS widget in the top-left corner.
	ShowFPS bool
}

RunConfig holds optional configuration for Run.

type Scene

type Scene struct {

	// ClearColor is the background color used to fill the screen each frame
	// when the scene is run via [Run]. If left at the zero value (transparent
	// black), the screen is not filled, resulting in a black background.
	ClearColor Color

	ScreenshotDir string
	// contains filtered or unexported fields
}

Scene is the top-level object that owns the node tree, cameras, input state, and render buffers.

func NewScene

func NewScene() *Scene

NewScene creates a new scene with a pre-created root container.

func (*Scene) Cameras

func (s *Scene) Cameras() []*Camera

Cameras returns the scene's camera list. The returned slice MUST NOT be mutated.

func (*Scene) CapturePointer

func (s *Scene) CapturePointer(pointerID int, node *Node)

CapturePointer routes all events for pointerID to the given node.

func (*Scene) Draw

func (s *Scene) Draw(screen *ebiten.Image)

Draw traverses the scene tree, emits render commands, sorts them, and submits batches to the given screen image.

func (*Scene) GetBatchMode added in v0.1.2

func (s *Scene) GetBatchMode() BatchMode

BatchMode returns the current draw-call batching strategy.

func (*Scene) InjectClick

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

InjectClick is a convenience that queues a press followed by a release at the same screen coordinates. Consumes two frames.

func (*Scene) InjectDrag

func (s *Scene) InjectDrag(fromX, fromY, toX, toY float64, frames int)

InjectDrag queues a full drag sequence: press at (fromX, fromY), linearly interpolated moves over frames-2 intermediate frames, and release at (toX, toY). The total sequence consumes `frames` frames. Minimum frames is 2 (press + release).

func (*Scene) InjectMove

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

InjectMove queues a pointer move event at the given screen coordinates with the button held down. Use this between InjectPress and InjectRelease to simulate a drag.

func (*Scene) InjectPress

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

InjectPress queues a pointer press event at the given screen coordinates (left button). The event is consumed on the next frame's processInput call.

func (*Scene) InjectRelease

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

InjectRelease queues a pointer release event at the given screen coordinates.

func (*Scene) LoadAtlas

func (s *Scene) LoadAtlas(jsonData []byte, pages []*ebiten.Image) (*Atlas, error)

LoadAtlas parses TexturePacker JSON, registers atlas pages with the scene, and returns the Atlas for region lookups. Pages are registered starting at the next available page index.

func (*Scene) NewCamera

func (s *Scene) NewCamera(viewport Rect) *Camera

NewCamera creates a camera with the given viewport and adds it to the scene.

func (*Scene) OnClick

func (s *Scene) OnClick(fn func(ClickContext)) CallbackHandle

OnClick registers a scene-level callback for click events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) OnDrag

func (s *Scene) OnDrag(fn func(DragContext)) CallbackHandle

OnDrag registers a scene-level callback for drag events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) OnDragEnd

func (s *Scene) OnDragEnd(fn func(DragContext)) CallbackHandle

OnDragEnd registers a scene-level callback for drag end events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) OnDragStart

func (s *Scene) OnDragStart(fn func(DragContext)) CallbackHandle

OnDragStart registers a scene-level callback for drag start events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) OnPinch

func (s *Scene) OnPinch(fn func(PinchContext)) CallbackHandle

OnPinch registers a scene-level callback for pinch events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) OnPointerDown

func (s *Scene) OnPointerDown(fn func(PointerContext)) CallbackHandle

OnPointerDown registers a scene-level callback for pointer down events. Multiple registrations are additive; scene-level handlers fire before per-node callbacks. Use CallbackHandle.Remove to unregister.

func (*Scene) OnPointerEnter

func (s *Scene) OnPointerEnter(fn func(PointerContext)) CallbackHandle

OnPointerEnter registers a scene-level callback for pointer enter events. Fired when the pointer moves over a new node (or from nil to a node).

func (*Scene) OnPointerLeave

func (s *Scene) OnPointerLeave(fn func(PointerContext)) CallbackHandle

OnPointerLeave registers a scene-level callback for pointer leave events. Fired when the pointer leaves a node (moves to a different node or to empty space).

func (*Scene) OnPointerMove

func (s *Scene) OnPointerMove(fn func(PointerContext)) CallbackHandle

OnPointerMove registers a scene-level callback for pointer move events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) OnPointerUp

func (s *Scene) OnPointerUp(fn func(PointerContext)) CallbackHandle

OnPointerUp registers a scene-level callback for pointer up events. Multiple registrations are additive. Use CallbackHandle.Remove to unregister.

func (*Scene) RegisterPage

func (s *Scene) RegisterPage(index int, img *ebiten.Image)

RegisterPage stores an atlas page image at the given index. The render compiler uses these to SubImage sprite regions.

func (*Scene) ReleasePointer

func (s *Scene) ReleasePointer(pointerID int)

ReleasePointer stops routing events for pointerID to a captured node.

func (*Scene) RemoveCamera

func (s *Scene) RemoveCamera(cam *Camera)

RemoveCamera removes a camera from the scene.

func (*Scene) Root

func (s *Scene) Root() *Node

Root returns the scene's root container node. The root node cannot be removed or disposed; it always exists for the lifetime of the Scene.

func (*Scene) Screenshot

func (s *Scene) Screenshot(label string)

Screenshot queues a labeled screenshot to be captured at the end of the current frame's Draw call. The resulting PNG is written to ScreenshotDir with a timestamped filename. Safe to call from Update or Draw.

func (*Scene) SetBatchMode added in v0.1.2

func (s *Scene) SetBatchMode(mode BatchMode)

SetBatchMode sets the draw-call batching strategy.

func (*Scene) SetDebugMode

func (s *Scene) SetDebugMode(enabled bool)

SetDebugMode enables or disables debug mode. When enabled, disposed-node access panics, tree depth and child count warnings are printed, and per-frame timing stats are logged to stderr.

func (*Scene) SetDragDeadZone

func (s *Scene) SetDragDeadZone(pixels float64)

SetDragDeadZone sets the minimum movement in pixels before a drag starts.

func (*Scene) SetEntityStore

func (s *Scene) SetEntityStore(store EntityStore)

SetEntityStore sets the optional ECS bridge.

func (*Scene) SetPostDrawFunc added in v0.1.3

func (s *Scene) SetPostDrawFunc(fn func(screen *ebiten.Image))

SetPostDrawFunc registers a callback that is called after Scene.Draw (and after the FPS widget) when the scene is run via Run. Use it for screen-space debug overlays (e.g. ebitenutil.DebugPrintAt). Pass nil to clear.

func (*Scene) SetTestRunner

func (s *Scene) SetTestRunner(runner *TestRunner)

SetTestRunner attaches a TestRunner to the scene. The runner's step method is called from Scene.Update before processInput each frame.

func (*Scene) SetUpdateFunc

func (s *Scene) SetUpdateFunc(fn func() error)

SetUpdateFunc registers a callback that is called once per tick before Scene.Update when the scene is run via Run. Use it for game-specific logic (movement, spawning, etc.). Pass nil to clear.

func (*Scene) Update

func (s *Scene) Update()

Update processes input, advances animations, and simulates particles.

type TTFFont

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

TTFFont wraps Ebitengine's text/v2 for TrueType font rendering.

func LoadTTFFont

func LoadTTFFont(ttfData []byte, size float64) (*TTFFont, error)

LoadTTFFont loads a TrueType font from raw TTF/OTF data at the given size.

func (*TTFFont) Face

func (f *TTFFont) Face() *text.GoTextFace

Face returns the underlying GoTextFace for direct Ebitengine text/v2 rendering.

func (*TTFFont) LineHeight

func (f *TTFFont) LineHeight() float64

LineHeight returns the vertical distance between baselines.

func (*TTFFont) MeasureString

func (f *TTFFont) MeasureString(s string) (width, height float64)

MeasureString returns the width and height of the rendered text.

type TestRunner

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

TestRunner sequences injected input events and screenshots across frames for automated visual testing. Attach to a Scene via SetTestRunner.

func LoadTestScript

func LoadTestScript(jsonData []byte) (*TestRunner, error)

LoadTestScript parses a JSON test script and returns a TestRunner ready to be attached to a Scene via SetTestRunner.

func (*TestRunner) Done

func (r *TestRunner) Done() bool

Done reports whether all steps in the test script have been executed.

type TextAlign

type TextAlign uint8

TextAlign controls horizontal text alignment within a TextBlock.

const (
	TextAlignLeft   TextAlign = iota // align text to the left edge (default)
	TextAlignCenter                  // center text horizontally
	TextAlignRight                   // align text to the right edge
)

type TextBlock

type TextBlock struct {
	// Content is the text string to render. Supports embedded newlines.
	Content string
	// Font is the BitmapFont or TTFFont used for measurement and rendering.
	Font Font
	// Align controls horizontal alignment within the wrap width or measured bounds.
	Align TextAlign
	// WrapWidth is the maximum line width in pixels before word wrapping.
	// Zero means no wrapping.
	WrapWidth float64
	// Color is the fill color for the text glyphs.
	Color Color
	// Outline defines a text stroke rendered behind the fill. Nil means no outline.
	Outline *Outline
	// LineHeight overrides the font's default line height. Zero uses Font.LineHeight().
	LineHeight float64
	// contains filtered or unexported fields
}

TextBlock holds text content, formatting, and cached layout state.

func (*TextBlock) Invalidate added in v0.1.3

func (tb *TextBlock) Invalidate()

Invalidate invalidates the cached layout and TTF image, forcing recomputation on the next frame. Call this after changing Content, Font, WrapWidth, Align, LineHeight, Color, or Outline at runtime.

type TextureRegion

type TextureRegion struct {
	Page      uint16 // atlas page index (references Scene.pages)
	X, Y      uint16 // top-left corner of the sub-image rect within the atlas page
	Width     uint16 // width of the sub-image rect (may differ from OriginalW if trimmed)
	Height    uint16 // height of the sub-image rect (may differ from OriginalH if trimmed)
	OriginalW uint16 // untrimmed sprite width as authored
	OriginalH uint16 // untrimmed sprite height as authored
	OffsetX   int16  // horizontal trim offset from TexturePacker
	OffsetY   int16  // vertical trim offset from TexturePacker
	Rotated   bool   // true if the region is stored 90 degrees clockwise in the atlas
}

TextureRegion describes a sub-rectangle within an atlas page. Value type (32 bytes) — stored directly on Node, no pointer.

type TileMapLayer added in v0.1.3

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

TileMapLayer is a single layer of tile data. It stores the raw integer grid and owns a geometry buffer that is rebuilt on tile boundary crossings and transformed each frame.

func (*TileMapLayer) InvalidateBuffer added in v0.1.3

func (l *TileMapLayer) InvalidateBuffer()

InvalidateBuffer forces a full buffer rebuild on the next frame.

func (*TileMapLayer) Node added in v0.1.3

func (l *TileMapLayer) Node() *Node

Node returns the layer's scene graph node.

func (*TileMapLayer) SetAnimations added in v0.1.3

func (l *TileMapLayer) SetAnimations(anims map[uint32][]AnimFrame)

SetAnimations sets the animation definitions for this layer. The map is keyed by base GID (no flag bits).

func (*TileMapLayer) SetData added in v0.1.3

func (l *TileMapLayer) SetData(data []uint32, w, h int)

SetData replaces the entire tile data array and forces a full buffer rebuild.

func (*TileMapLayer) SetTile added in v0.1.3

func (l *TileMapLayer) SetTile(col, row int, newGID uint32)

SetTile updates a single tile in the given layer. If the tile is currently visible in the buffer, its vertex UVs are updated immediately.

type TileMapViewport added in v0.1.3

type TileMapViewport struct {

	// Tile dimensions in pixels.
	TileWidth  int
	TileHeight int

	// MaxZoomOut is the minimum zoom level the developer expects. Controls
	// buffer sizing. Default 1.0 (no zoom out). E.g. 0.5 means the
	// viewport may show 2x as many tiles.
	MaxZoomOut float64

	// MarginTiles is the number of extra tiles beyond the viewport edge to
	// keep buffered. Prevents pop-in during fast pans. Default 2.
	MarginTiles int
	// contains filtered or unexported fields
}

TileMapViewport is a scene graph node that manages a viewport into a tilemap. It owns an ordered list of TileMapLayer (tile data) and supports interleaved regular Node containers for entities (sandwich layers).

func NewTileMapViewport added in v0.1.3

func NewTileMapViewport(name string, tileWidth, tileHeight int) *TileMapViewport

NewTileMapViewport creates a new tilemap viewport node with the given tile dimensions. The viewport is a container node that should be added to the scene graph.

func (*TileMapViewport) AddChild added in v0.1.3

func (v *TileMapViewport) AddChild(child *Node)

AddChild adds a regular node container as a child of the viewport (for sandwich layers: NPCs, items, effects, etc.).

func (*TileMapViewport) AddTileLayer added in v0.1.3

func (v *TileMapViewport) AddTileLayer(name string, w, h int, data []uint32, regions []TextureRegion, atlasImage *ebiten.Image) *TileMapLayer

AddTileLayer creates a tile layer with the given map data and adds it as a child of the viewport. All regions must come from the provided atlas image. Returns the layer for further configuration (RenderLayer, animations, etc.).

func (*TileMapViewport) Node added in v0.1.3

func (v *TileMapViewport) Node() *Node

Node returns the underlying scene graph node for this viewport.

func (*TileMapViewport) SetCamera added in v0.1.3

func (v *TileMapViewport) SetCamera(cam *Camera)

SetCamera binds this viewport to a specific camera. If nil, defaults to scene.Cameras()[0] during update.

type TweenGroup

type TweenGroup struct {

	// Done is true when all tweens in the group have finished or the target
	// node has been disposed.
	Done bool
	// contains filtered or unexported fields
}

TweenGroup animates up to 4 float64 fields on a Node simultaneously. Create one via the convenience constructors (TweenPosition, TweenScale, TweenColor) and call Update(dt) each frame. The group auto-applies values and marks the node dirty. If the target node is disposed, the group stops immediately.

There is no global animation manager — users call Update themselves.

func TweenAlpha

func TweenAlpha(node *Node, to float64, duration float32, fn ease.TweenFunc) *TweenGroup

TweenAlpha creates a TweenGroup that animates node.Alpha to the target value over the specified duration using the easing function.

func TweenColor

func TweenColor(node *Node, to Color, duration float32, fn ease.TweenFunc) *TweenGroup

TweenColor creates a TweenGroup that animates all four components of node.Color (R, G, B, A) to the target color over the specified duration.

func TweenPosition

func TweenPosition(node *Node, toX, toY float64, duration float32, fn ease.TweenFunc) *TweenGroup

TweenPosition creates a TweenGroup that animates node.X and node.Y to the given target coordinates over the specified duration using the easing function.

func TweenRotation

func TweenRotation(node *Node, to float64, duration float32, fn ease.TweenFunc) *TweenGroup

TweenRotation creates a TweenGroup that animates node.Rotation to the target value over the specified duration using the easing function.

func TweenScale

func TweenScale(node *Node, toSX, toSY float64, duration float32, fn ease.TweenFunc) *TweenGroup

TweenScale creates a TweenGroup that animates node.ScaleX and node.ScaleY to the given target values over the specified duration using the easing function.

func (*TweenGroup) Update

func (g *TweenGroup) Update(dt float32)

Update advances all tweens by dt seconds, writes values to the target fields, and marks the node dirty. If the target node has been disposed, Done is set to true and no writes occur.

type Vec2

type Vec2 struct {
	X, Y float64
}

Vec2 is a 2D vector used for positions, offsets, sizes, and directions throughout the API.

Directories

Path Synopsis
examples
atlas command
Atlas demonstrates the TexturePacker atlas system and dynamic page registration.
Atlas demonstrates the TexturePacker atlas system and dynamic page registration.
basic command
Basic demonstrates a minimal willow scene with a colored sprite bouncing around the window.
Basic demonstrates a minimal willow scene with a colored sprite bouncing around the window.
interaction command
Interaction demonstrates draggable colored rectangles with click callbacks using willow.Run for a minimal game loop.
Interaction demonstrates draggable colored rectangles with click callbacks using willow.Run for a minimal game loop.
lighting command
Lighting demonstrates the LightLayer system: a dark dungeon scene lit by five colored torches and a warm lantern that follows the mouse cursor.
Lighting demonstrates the LightLayer system: a dark dungeon scene lit by five colored torches and a warm lantern that follows the mouse cursor.
masks command
Masks demonstrates three node-masking techniques in Willow across three equal full-height panels:
Masks demonstrates three node-masking techniques in Willow across three equal full-height panels:
outline command
Outline demonstrates outline and inline filters applied to a sprite with alpha transparency (whelp.png).
Outline demonstrates outline and inline filters applied to a sprite with alpha transparency (whelp.png).
particles command
Particles demonstrates the ParticleEmitter system with three distinct effects and two blend modes.
Particles demonstrates the ParticleEmitter system with three distinct effects and two blend modes.
rope command
Rope demonstrates the Rope mesh helper by connecting two draggable nodes with a textured rope that sags under gravity.
Rope demonstrates the Rope mesh helper by connecting two draggable nodes with a textured rope that sags under gravity.
shaders command
Shaders showcases Willow's built-in filter system by displaying all shader effects simultaneously in a 3x3 grid, each applied to a pre-rendered tilemap panel with animated parameters.
Shaders showcases Willow's built-in filter system by displaying all shader effects simultaneously in a 3x3 grid, each applied to a pre-rendered tilemap panel with animated parameters.
shapes command
Shapes demonstrates scene graph hierarchy with polygons and containers.
Shapes demonstrates scene graph hierarchy with polygons and containers.
text command
Text demonstrates bitmap font (BMFont) text rendering with willow.NewText.
Text demonstrates bitmap font (BMFont) text rendering with willow.NewText.
texttf command
Text demonstrates TTF text rendering with willow.NewText and willow.LoadTTFFont.
Text demonstrates TTF text rendering with willow.NewText and willow.LoadTTFFont.
tilemap command
Tilemap demonstrates a grid-based tilemap with camera panning.
Tilemap demonstrates a grid-based tilemap with camera panning.
tilemapviewport command
TileMapViewport demonstrates the geometry-buffer tilemap renderer with camera panning, multiple tile layers, and a sandwich entity layer.
TileMapViewport demonstrates the geometry-buffer tilemap renderer with camera panning, multiple tile layers, and a sandwich entity layer.
tweens command
Tweens demonstrates the tween animation system using tiles from the shared tileset.
Tweens demonstrates the tween animation system using tiles from the shared tileset.
watermesh command
Watermesh demonstrates per-vertex wave animation using a DistortionGrid textured with tile 13 (the water tile) from the bundled tileset.
Watermesh demonstrates per-vertex wave animation using a DistortionGrid textured with tile 13 (the water tile) from the bundled tileset.

Jump to

Keyboard shortcuts

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