79 lines
3.8 KiB
Markdown
79 lines
3.8 KiB
Markdown
---
|
|
id: extensions
|
|
icon: cat.scripts
|
|
group: extending
|
|
title: Native extensions
|
|
order: 1
|
|
---
|
|
# 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.
|