Aego Playground

Native extensions

A project extends the engine with Go code in native/. It imports only aego/sdk and implements sdk.Module:

type Module struct{}

func (Module) Info() sdk.ModuleInfo { return sdk.ModuleInfo{ID: "mygame", Requires: []string{"render2d"}} }
func (Module) Register(r *sdk.Registry) error { ... }

Engine subsystems (render2d, render3d, script) register through the same registry, so anything they can do a project can do.

Components

Everything about a component is declared in one chain and stored in its schema descriptor:

sdk.Component[Spinner](r, sdk.Name("game.Spinner")).
    Category("Game").                 // Inspector section and Add Component group
    Display("Spinner").               // card title, defaults to the humanized type name
    Icon("cat.game").                 // icon id from the engine atlas or from r.Icons
    Default(func() Spinner { ... }).  // values used by Add Component, Reset and templates
    Requires("engine.SpriteRenderer").// Add Component is disabled until the node has it; templates add it
    SceneUnique().                    // at most one per scene
    Script("Speed").                  // fields visible to scripts; all fields when omitted, none with ScriptNone()
    Proc0("Reset", fn, sdk.ScriptDoc("...")).
    Template("Spinner", setup).       // Create menu entry under the module's submenu; "" uses the type name
    Err()

Script methods are declared with typed adapters: Method0..Method2 for functions with a result (func(*sdk.ScriptCtx, *Spinner, args...) R) and Proc0..Proc2 without one. Scripts call them as functions of the module <module id>/<type>: import "mygame/spinner", then spinner.Reset(node). ScriptMethod(name, fn) with an untyped function still works in development builds through reflection, logs a warning and fails in runtime builds.

A component is added with one file in the module and one line in its Register, as registerSpinner(r) in examples/native-rotator.

Registration errors are collected and reported together when the project starts, with the module id in every message.

Systems

r.System(sdk.SystemDesc{ID: "mygame.spin", Phase: sdk.PhaseUpdate, System: spinSystem{}})

System ids must start with the module id. FrameBegin, PollEvents and FrameEnd are reserved for the engine.

Templates without a component

r.Template(sdk.TemplateDesc{ID: "mygame.enemy", Name: "Enemy", Category: "Actors", Setup: buildEnemy})

Space selects the node space (sdk.SpaceAny creates a node in the scene's default space, sdk.Kind3D a 3D node).

Script bindings

m := r.ScriptModule("mygame/combat")
sdk.Proc2(m, "Damage", func(c *sdk.ScriptCtx, target sdk.NodeHandle, amount int64) { ... })

Importers

sdk.RegisterImporter(r, imp) adds an asset importer. It exists only in development builds.

Editor tools

Editor code lives in files tagged //go:build aego_editor and is attached with a hook:

func registerEditor(r *sdk.Registry) {
    sdk.EditorHook(r, func(e *sdk.EditorRegistry) error {
        e.Inspector("mygame.Health", sdk.InspectorFunc(drawHealth))
        e.Panel(&questPanel{})
        return e.Command(sdk.CommandDesc{...})
    })
}

Provide an empty registerEditor in a //go:build !aego_editor file. EditorRegistry also offers Gizmo, Bounds (picking and framing shape of a component), Overlay (scene view drawing) and AssetDrop (what a dropped asset creates). A panic in any of them disables the extension and keeps the editor running.

Icons

r.Icons(fsys, "mygame.") adds <id>@<size>x<scale>.png files. Every id used by Icon must exist in the engine atlas or in a registered source.