Wikipedia: Godot game engine
GDScript attaches a script to each Godot node; the engine calls _ready once on startup and _process(delta) every frame. Tengo maps to this naturally: script-level variables hold node state that persists between compiled.Call invocations, because they live in shared globals — mutations from one call are visible in the next, just like a GDScript node's exported properties.
fmt := import("fmt")
position := {x: 0.0, y: 0.0}
speed := 150.0
health := 100
coins := 0
_ready := func() {
fmt.printf("_ready pos=(%.0f,%.0f) hp=%d\n",
position.x, position.y, health)
}
_process := func(delta) {
position.x += speed * delta
fmt.printf("_process pos=(%.1f, %.1f)\n", position.x, position.y)
}
_on_coin_picked_up := func() {
coins += 1
fmt.printf("coin! total=%d\n", coins)
}
_on_hit := func(damage) {
health -= damage
fmt.printf("hit! hp=%d%s\n", health, health <= 0 ? " [dead]" : "")
}
_ready()
_process(0.016)
_process(0.016)
_process(0.016)
_on_coin_picked_up()
_on_hit(35)
_on_hit(80)
Go host
package main
import (
"log"
tengo "github.com/tengolang/tengo/v3"
)
// playerScript contains only the node definitions — no standalone demo.
// State (position, health, coins) persists across compiled.Call calls
// because script-level variables live in shared globals.
const playerScript = `
fmt := import("fmt")
position := {x: 0.0, y: 0.0}
speed := 150.0
health := 100
coins := 0
_ready := func() {
fmt.printf("_ready pos=(%.0f,%.0f) hp=%d\n",
position.x, position.y, health)
}
_process := func(delta) {
position.x += speed * delta
fmt.printf("_process pos=(%.1f, %.1f)\n", position.x, position.y)
}
_on_coin_picked_up := func() {
coins += 1
fmt.printf("coin! total=%d\n", coins)
}
_on_hit := func(damage) {
health -= damage
fmt.printf("hit! hp=%d%s\n", health, health <= 0 ? " [dead]" : "")
}
`
func main() {
compiled, err := tengo.NewScript([]byte(playerScript)).Run()
if err != nil {
log.Fatal(err)
}
compiled.Call("_ready")
// Game loop — three frames at ~60 fps.
for i := 0; i < 3; i++ {
compiled.Call("_process", 0.016)
}
// Dispatch signal handlers if the script defines them.
if compiled.CanCall("_on_coin_picked_up") {
compiled.Call("_on_coin_picked_up")
}
compiled.Call("_on_hit", 35)
compiled.Call("_on_hit", 80)
}
try it
_ready pos=(0,0) hp=100 _process pos=(2.4, 0.0) _process pos=(4.8, 0.0) _process pos=(7.2, 0.0) coin! total=1 hit! hp=65 hit! hp=-15 [dead]