Modules
Share functions, classes and constants between a game's scripts with modules, in JavaScript or Python.
On this page
A module is a script file that holds shared code instead of a world class. Your scripts (and other modules) import names from it. Modules live in assets/scripts with your scripts and use the game's one language.
A JavaScript module#
A module exports names. It has no export default:
// A point on a square of side 8 around the origin, `t` laps per second.
export function square(t) {
const u = (t % 1) * 4; // 0..4 along the four sides
const side = Math.floor(u);
const f = u - side;
const a = -4 + 8 * f;
if (side === 0) return { x: a, y: 1, z: -4 };
if (side === 1) return { x: 4, y: 1, z: a };
if (side === 2) return { x: -a, y: 1, z: 4 };
return { x: -4, y: 1, z: -a };
}A script imports it by name, with ./ in front:
import { World, on } from "neo";
import { square } from "./paths";
export default class PlatformMover extends World {
state = { lap: 0 };
@on("tick")
move(dt) {
const platform = this.query("Platform")[0];
if (!platform) return;
this.state.lap += (dt / 1000) * platform.speed;
platform.position = square(this.state.lap);
}
}What a JavaScript module can export#
| Form | Example |
|---|---|
| A function | export function lerp(a, b, t) { ... } (also async function, function*) |
| A class | export class Board { ... } |
| A constant or variable | export const HALF = 0.5; (also let, var) |
| A list of names | export { clamp01, hidden as one }; |
Destructuring exports (export const { a, b } = ...) and export default aren't supported in modules.
Importing#
| Form | Example |
|---|---|
| Named imports | import { lerp, HALF } from "./maths"; |
| Renamed | import { lerp as mix } from "./maths"; |
| The whole module | import * as maths from "./maths"; |
| With the extension | import { clamp01 } from "./util.js"; (the same module as "./util") |
Write each import on one line, at the top level. Modules can import neo and other modules, and can use @on and other decorators in classes they export.
A Python module#
In Python, a module's public names are its top-level functions, classes and variables whose names don't start with _:
def clamp(x, lo, hi):
return min(hi, max(lo, x))
BONUS = 7from .util import clamp, BONUS
def points(streak):
return clamp(streak * 3, 0, 20) + BONUS
class Board:
def __init__(self):
self.rows = []
def add(self, name, pts):
self.rows.append((name, pts))
self.rows.sort(key=lambda r: (-r[1], r[0]))
return len(self.rows)
def top(self):
return self.rows[0][0] if self.rows else NoneA script imports names or the whole module, with a leading .:
from neo import World, on, sleep
from .scoring import points, Board
from . import util
board = Board()
class Game(World):
state = {"scores": {}, "top": None, "n": 0}
@on("scored")
async def scored(self, name, streak):
p = points(streak)
self.state.scores = {**self.state.scores, name: self.state.scores.get(name, 0) + p}
self.state.n = board.add(name, p)
await sleep(0.032)
self.state.top = board.top()
self.emit("leader", {"name": board.top(), "bonus": util.BONUS})from .name import a, bandfrom . import nameare the two forms. Modules are flat:from ..x import yandfrom .pkg.mod import yare compile errors.from .name import *isn't available.- Importing a name the module doesn't export raises
ImportError. - Imports go at the top of the file.
Rules for both languages#
- Only
neoand your game's modules. Anything else, such asfs, a URL or../x, is a compile error. Python can also import NEO's standard modules (see Python scripts). - One language. A JavaScript script can't import a Python module, or the other way round.
- Frozen exports. You can't reassign or add names on an imported module:
util.clamp01 = nullthrows. - One copy per script. Each script evaluates its own copy of every module it imports, in dependency order, before the script itself. Module-level variables (like
boardabove) belong to that one script and are not shared with other scripts. - No cycles. If two modules import each other, loading fails with a message naming the cycle.
- Not state. Module-level variables are not part of
this.state: they start fresh when a script reloads and aren't in the state hash. Keep anything the game must remember in state.
Updating a module while the game runs#
When you save a module, scripts loaded or reloaded after that import the new version. Scripts already running keep the version they started with, so reload the scripts that import it. The Scripts panel tells you when a module was updated. See The Scripts panel.
If your game adds scripting through Rust, pass modules to the plugin as (name, source) pairs:
NeoScriptPlugin { modules: vec![("paths".into(), PATHS_JS.into())], .. }