Your first script
Write a small script, list it in neo.toml, run it and watch what it does.
On this page
This page walks you through a first script: it counts ticks, reports once a second, and needs no exposed entities, so it runs in any game that has scripting turned on.
Before you start#
You need a NEO project whose game runs scripts. Projects that start from the NEO engine's GASP template support scripts: the template's gameplay kit exposes its character and course blocks to scripts and loads the scripts your neo.toml lists. If your game doesn't run scripts yet, see Plugins and neo.toml and Exposure and permissions.
1. Create the script file#
Create assets/scripts/hello.js (or hello.py if your game's language is Python):
import { World, on } from "neo";
export default class Hello extends World {
state = { ticks: 0, reports: 0 };
@on("tick")
count(dt) {
this.state.ticks += 1;
}
@on("tick", { every: 1 })
report() {
this.state.reports += 1;
this.emit("hello", { ticks: this.state.ticks, at: this.time });
}
}from neo import World, on
class Hello(World):
state = {"ticks": 0, "reports": 0}
@on("tick")
def count(self, dt):
self.state.ticks += 1
@on("tick", every=1)
def report(self):
self.state.reports += 1
self.emit("hello", {"ticks": self.state.ticks, "at": self.time})
default = HelloWhat each part does:
| Part | Meaning |
|---|---|
import { World, on } from "neo" | neo is the only built-in module. It exports World, on, sleep and all (Python also has gather). |
export default class Hello extends World | Every script has exactly one world class. In Python, default = Hello names it (without that line, the last class that derives from World is used). |
state = { ... } | Initial values for the script's state. The world keeps them between ticks and across reloads. |
@on("tick") | Runs count every simulation tick. dt is the time since the last tick, in milliseconds. |
@on("tick", { every: 1 }) | Runs report at most once per simulation second. |
this.emit("hello", {...}) | Sends an event to the game's Rust systems with a payload. |
this.time | Simulation time in seconds. |
2. List it in neo.toml#
Add the script to the [script] section of your project's neo.toml. The language must match the file:
[script]
language = "js" # or "py"
scripts = ["assets/scripts/hello.js"]Paths are relative to the project. If you already have a [script] section, add the path to its scripts list instead of adding a second section.
3. Run the game#
When the game starts, it loads each script listed in scripts as the game's own script. The script's name is its file name without the extension: this one runs as hello.
To see it working, open the Scripts panel in the Studio. It lists hello with the fuel it used last tick, its fault count and its state hash, and the hash changes every tick because ticks keeps growing. See The Scripts panel. Tools and agents can read the same information with the script.list command (Script commands).
4. Change it while it runs#
Edit the script and save it. The running game hot-reloads it on the next fixed tick and keeps its state, so ticks carries on from where it was instead of starting at 0. Try changing every: 1 to every: 0.5 and save.
If the script doesn't load#
A script that fails to compile doesn't run, and the load log says why, with a line number. Common causes:
- No world class. JavaScript needs
export default class Name extends World; Python needs a class that derives fromWorld. - Another import. Scripts can import only
neoand the game's own modules (./name).import fs from "fs"is a compile error. In Python, the standard modules NEO provides are listed in Python scripts. - A second
export. A script exports only its world class. Share code through modules. - The wrong language. A
.pyscript in a game whoselanguageisjs(or the other way round) is compiled as the game's language, so it fails to load.
A script that loads but throws an error in a handler records a fault and keeps running on later ticks. See Events and handlers.