Pyxen Script
Pyxen games are written in Pyxen Script, a Python dialect made for games. It has Python’s syntax and feel: indentation, def, lists, dictionaries, f-strings, comprehensions. What changes is underneath. Your code is checked as a whole and compiled before the game starts, and it runs on a runtime built for one job: running a game, the same way, on every device.
If you already know Python, most of what you write works as you expect. This page explains why Pyxen Script exists, what it shares with Python, and what it leaves out on purpose.
Why a dialect
A game asks different things of a language than a script does.
- It runs the same everywhere. The same compiled program runs in the app on iPad and Mac, in a game exported for Windows, and in a browser through the web export. Pyxen Script was designed for WebAssembly from the start, so the browser is not a second-class target.
- No garbage-collector pauses. Pyxen Script has no garbage collector. Temporary values created during a frame live in an area of memory that is cleared in one step at the end of that frame; values you keep, such as a list stored in a global, are released as soon as nothing refers to them. Nothing stops your game mid-frame to clean up.
- Errors before the game runs. The whole project is checked before the first frame. A misspelled name, a call to a module function that does not exist, or a construct Pyxen Script does not support is reported at its line, with the game not yet started.
- Errors at the right line. When something goes wrong while the game is running, the error is captured where it happens, with the chain of calls that led there still intact, so Pyxen can show you the exact line.
Earlier versions of Pyxen ran a MicroPython interpreter; Pyxen Script replaces it.
What is the same as Python
Pyxen Script is tested against hundreds of one-line Python idioms, each compared with the output of standard Python. Everything below behaves as it does in Python:
- Blocks and control flow: indentation,
if/elif/else,forandwhilewithbreak,continueandelse, conditional expressions (a if ok else b), chained comparisons (0 <= x < 320),pass,assert. - Functions:
defwith default and keyword arguments, several return values as a tuple,lambda, functions stored in lists and dictionaries, nested functions and closures, recursion,global. - Data: lists, dictionaries (which keep insertion order), sets, tuples, slicing, negative indices, unpacking (
x, y = pos,first, *rest = items), and list, dict and set comprehensions. - Strings: f-strings with format specs (
f"{score:05d}",f"{t:.2f}"),str.format,%formatting, and the usual methods (split,join,strip,replace,find,upper,startswith,zfill, …). Lengths and indices count characters, so text in any language works. - Built-in functions:
print,len,range,enumerate,zip,sortedandmin/maxwithkey=andreverse=,sum,any,all,map,filter,round,abs,int,float,str,isinstance, and more. - Numbers: integers are 64-bit and floats are double precision, so
2 ** 40and0.1 + 0.2give the answers Python gives.//and%round toward negative infinity, as in Python. math:import mathworks, withsqrt,floor,sin,atan2,hypot,piand the rest of the usual functions.
What is different
No classes
Pyxen Script has no class. This is by design.
In a Pyxen game, state lives on entities and their components, which the engine owns, stores compactly, and shows you in the frame inspector. A class would be a second place to keep the same state, invisible to the engine and to the inspector. So where Python code would define a class, Pyxen code spawns an entity, attaches a component, and writes functions that act on it.
A typical Python class:
class Enemy:
def __init__(self, x, y):
self.x = x
self.y = y
self.hp = 3
def hit(self):
self.hp -= 1
The same thing in Pyxen Script:
def spawn_enemy(x, y):
e = world.spawn(name="enemy", x=x, y=y, sprite=Sprite("slime"))
e.enemy = {"hp": 3, "speed": 1.5}
return e
def hit(e):
e.enemy.hp -= 1
if e.enemy.hp <= 0:
world.destroy(e)
def update():
for e in world.all("enemy"):
e.x -= e.enemy.speed
The entity already has a position, a sprite and a place in the scene. The enemy component holds what is specific to enemies, and world.all("enemy") finds every one of them, so there is no list of instances to keep in sync. For plain data that is not a game object, such as a high-score table or a level description, use dictionaries and lists. See Entities & Components for the full model.
No exceptions
Pyxen Script has no try, except, finally or raise. This is by design too.
An error in Pyxen Script stops the game and is reported at its line, with the calls that led to it. Nothing can catch it on the way, so an error is never swallowed by an except: pass and turned into a game that misbehaves quietly three levels later.
Most try blocks in game code ask a question that has a direct answer. Ask the question instead:
# does this entity have a component?
hp = e.health.value if e.has("health") else 0
# is this key in the dictionary?
best = scores.get(name, 0)
# is this text a number?
if text.isdigit():
level = int(text)
# is this index inside the list?
if i < len(items):
item = items[i]
assert is available, and stops the game at its line when its condition is false.
Also not in Pyxen Script
with,yield(generator functions),nonlocal, decorators,asyncandawait, and the walrus operator:=. Generator expressions such assum(x * x for x in xs)do work.*argsand**kwargsin a function’s parameters. Spreading a list or a dictionary into a call, as inprint(*items), does work.- Python’s standard library.
mathis available; Pyxen provides its ownrandom,time,inputandstoragemodules, documented in the scripting reference. Modules such asos,sys,jsonorreare reported before the game runs. - Integers beyond 64 bits. A result that does not fit stops the game with an
OverflowError; it never wraps around silently. Numbers stored in a component’s integer fields are 32-bit. - Tabs for indentation. Indent with spaces: a tab’s width depends on the editor, so a block that relies on it could mean different things to different people.
Each of these is reported by name, at its line, before the game starts.
Modules and imports
A project is compiled as one program. Every script in it is already visible to the others by its file name: a function spawn() in monsters.py is called as monsters.spawn() from any other script, with no import. The engine API (world, input, Sprite, …) is always available too.
Imports you already have, such as from pyxen import * or import monsters, still work. Pyxen Script treats them as unnecessary, and you can remove them.