**quadplay✜** Fantasy Console by [Casual Effects](https://casual-effects.com)
Download Windows Installer |
*macOS and Linux:*
1. Install [Python 3.7](https://www.python.org/ target="_blank") or newer
2. Unzip [ |
 | [Starter](../console/quadplay.html?game=quad://examples/starter&IDE=1) is a project with a basic setup from which you can build your own. |
 | [Hello, World](../console/quadplay.html?game=quad://examples/helloworld&IDE=1) is a minimal example of a program with one mode, one asset, and no sections or optional elements, and that simply puts text on the screen. |
 | [RPG Demo](../console/quadplay.html?game=quad://examples/rpg&IDE=1) is a simple example of a single-player RPG game design: - Multiple [modes](#modes): Play, Inventory, and Shop - Multi-layer map - Player movement with obstructions - NPC interaction |
 | [Dual-Stick Example](../console/quadplay.html?game=quad://examples/dual-stick&IDE=1) shows how to use dual-stick controls. The body of the tank is controlled by player 1 and the turret is controlled by player 2. When using a dual-stick game controller or keyboard, a single player can also use the right stick or right side of the keyboard to control the turret. - Dual-stick controls - Working with angles - Entity parenting - 320x180 resolution |
 | [Maze](../console/quadplay.html?game=quad://examples/maze&IDE=1) is a demonstration of `map_generate_maze()`. |
 | [Robot Example](../console/quadplay.html?game=quad://examples/robot&IDE=1) shows how to create and animate a deep entity hierarchy. - Entity parenting - Pivots that are not at the center of sprites - Using scale and rotation together - Graphics tricks for reflections |
 | [Animation Example](../console/quadplay.html?game=quad://examples/animation&IDE=1) shows how to handle complicated sprite animations. - Sprite animations - Basic jumping physics - Changing direction - Modular items |
 | [Animation Example](../console/quadplay.html?game=quad://examples/coordinate_system&IDE=1) shows the coordinate systems produced by changing the default axes. |
 | [Vehicles Example](../console/quadplay.html?game=quad://examples/vehicles&IDE=1) contains examples of several vehicle controls from a free-direction top-down perspective. - 2.5D camera perspective - Using the ⓔ and ⓕ buttons - Simple vehicle simulation for control feel - Entities with moving child parts - 2.5D sprite stacking - Dynamic shadows - Minimap |
 | [Fluid Example](../console/quadplay.html?game=quad://examples/fluid&IDE=1) is a cellular automata fluid flow simulation with gravity and pressure. |
 | [Roguelike Example](../console/quadplay.html?game=quad://examples/roguelike&IDE=1) uses the roguelike sprite set for tilemap rendering with a simple text-heavy UI section. |
 | [Dynamic Acceleration Example](../console/quadplay.html?game=quad://examples/dynamic_accel&IDE=1) is a simple demonstration of how various acceleration curves feel, changing only three constants: top speed, acceleration time and deceleration time. Up and down change the constants to fit different games. |
 | [Boids Example](../console/quadplay.html?game=quad://examples/boids&IDE=1) implements the famous ["boids" flocking algorithm](https://en.wikipedia.org/wiki/Boids). |
 | [Clouds Example](../console/quadplay.html?game=quad://examples/clouds&IDE=1) demonstrates per-pixel graphics and multi-octave `noise()` functions. The virtual GPU is fast enough to process one point per pixel, but this technique is not recommended in general because the computations to produce the points tend to be too slow, even if this simple. |
 | [A Dark Drive](../console/quadplay.html?game=quad://examples/dark_drive&IDE=1) A simple horror game jam project using polygon drawing to create darkness around car headlights. |
 | [Entity Example](../console/quadplay.html?game=quad://examples/entity&IDE=1) Shows different ways of constructing entities with text and sprites. |
 | [Physics Example](../console/quadplay.html?game=quad://examples/physics&IDE=1) shows all of the features of the physics engine, including the debugging visualization. |
 | [Physics Example](../console/quadplay.html?game=quad://examples/physics_arrow&IDE=1) shows how to use dynamic attachments in the physics engine to make objects connect at runtime, and how to simulate aerodynamics so that arrows will fly straight. |
 | [Text Example](../console/quadplay.html?game=quad://examples/text&IDE=1) shows how to use `replace()`, `draw_text()`, `join()`, `format_number()`, and `draw_sprite_corner_rect()` to build text-heavy interfaces. |
 | [Font Preview](../console/quadplay.html?game=quad://examples/fontpreview&IDE=1) shows how to render text in various styles and acts as a tool for previewing fonts when creating new ones. Use the arrow keys to scroll down and see more examples in the program, and edit the `game.json` file to see a different font. |
 | [High Score Example](../console/quadplay.html?game=quad://examples/highscore&IDE=1) demonstrates `load_local()` and `save_local()` to maintain a high score list, with `push_mode()` for creating a popup dialog. |
 | [Vaporwave Example](../console/quadplay.html?game=quad://examples/vaporwave&IDE=1) uses pseudo-3D techniques of pre-rendered sprites and polygon meshes. |
 | [Sproing Example](../console/quadplay.html?game=quad://examples/sproing&IDE=1) shows a squash and stretch effect for transforming sprites. |
 | [Perceptual Color](../console/quadplay.html?game=quad://examples/perceptual_color&IDE=1) shows the difference between `perceptual_lerp_color()` and `lerp()`. |
 | [Z-Car Example](../console/quadplay.html?game=quad://examples/zcar&IDE=1) draws a 3D wireframe car using 2.5D graphics and CRT orange-screen retro phosophor effects. |
 | [Lift Team](../console/quadplay.html?game=quad://examples/lift_team&IDE=1) combines physics and dynamic constraints for an asymmetric platformer setup, where one player flies a helicopter that can lift the other player's mech. |
 | [Animated Entity Example](../console/quadplay.html?game=quad://examples/anim_entity_example&IDE=1) Entity animation example simplified from _Beat The Gobblins_. |
 | [Bezier Eye Creature](../console/quadplay.html?game=quad://examples/bezier_eye_creature&IDE=1) uses splines to create natural curves for a soft bodied creature. |
 | [Sequence Demo (transition effect)](../console/quadplay.html?game=quad://examples/sequence_demo&IDE=1) Shows how to use the sequence function to create a transition effect, which is factored out into an easy to drop in library. The sequence function lets choreograph series of frame hooks one after the other. |
 | [Grid Movement](../console/quadplay.html?game=quad://examples/gridmove&IDE=1) Atari style grid movement with smooth interpolation and wrapping, similar to PAC-MAN, Centipede, etc. |
 | [Islands](../console/quadplay.html?game=quad://examples/islands&IDE=1) contains optimized implementations of per-pixel rendering and plausible water and sailboat simulation. |
 | [Cards](../console/quadplay.html?game=quad://examples/cards&IDE=1) is an example of creating and manipulating a deck of cards, including `add_frame_hook()` for flipping animations. |
 | [Dice](../console/quadplay.html?game=quad://examples/dice&IDE=1) uses `scripts/dice.pyxl` to show how to make and roll customizable 3D dice for virtual board games. |
 | [Piano](../console/quadplay.html?game=quad://examples/piano&IDE=1) Using `pitch` with `play_sound()` to adjust frequency, and data-driven input testing. |
 | [Spritestack](../console/quadplay.html?game=quad://examples/spritestack&IDE=1) is a little engine for Grand Theft Auto 2.5D rendering and physics of a 2D game using sprite stacking and camera perspective. |
 | [Speed Street](../console/quadplay.html?game=quad://examples/speedstreet&IDE=1) is the setup for a four-player competitive racing game inspired by [_Excitebike_](https://en.wikipedia.org/wiki/Excitebike) and Tony Hawk games, using: - Entity hierarchy - Simple custom physics - Orthographic 2.5D graphics - Coordinates with +Y pointing up - `override_color` |
 | [Planet Generator](../console/quadplay.html?game=quad://examples/planetgen&IDE=1) uses built-in assets to create random 3D planets with moons, stars, atmosphere, and rings. |
 | [Change Resolution](../console/quadplay.html?game=quad://examples/change_res&IDE=1) is an example of using `set_screen_size()` to change resolution at runtime. |
 | [Input Example](../console/quadplay.html?game=quad://examples/input&IDE=1) is a demonstration and test of the full input API. It uses the cross-platform controllers and touch as well as the extended analog stick and mouse APIs. Shows how to completely hide the OS mouse cursor with `device_control("set_mouse_cursor", "none")`. |
 | [Touch Example](../console/quadplay.html?game=quad://examples/touch&IDE=1) uses the mouse/touch API. |
 | [Countdown Example](../console/quadplay.html?game=quad://examples/countdown&IDE=1) shows how to used `local_time()` and perform time zone math. |
 | [RPG Demo](../console/quadplay.html?game=quad://examples/hex&IDE=1) is an example of hex grids with coordinate system conversion, rendering, and click support. |
 | [Tic Tac Toe](../console/quadplay.html?game=quad://examples/tic_tac_toe&IDE=1) shows how to take turns, and mix `gamepad_array`, `touch`, and `device_control("get_mouse_state")` to support mouseover/hover, touch screen, and gamepads in a single game user interface. |
 | [Platformer](../console/quadplay.html?game=quad://examples/platformer&IDE=1) is an example of classic 8-bit platforming physics, with jump, wall jump, wall slide, short jump, ledge forgiveness, hazards, and variable friction. |
 | [Twin Analog Example](../console/quadplay.html?game=quad://examples/twin_analog&IDE=1) shows how to use `device_control()` to access twin analog sticks and game controller triggers that are not standard on quadplay consoles. |
 | [Kart Example](../console/quadplay.html?game=quad://examples/kart&IDE=1) is a perspective camera view with a textured ground plane similar to Space Harrier, Mario Kart, and Pilotwings. |
 | [Warlock Example](../console/quadplay.html?game=quad://examples/warlock3D&IDE=1) is 3D first person rendering similar to DOOM and Heretic, with shading, textured floors and walls, billboarded characters, pitch and yaw, and jumping. It demonstrates retro D-pad + shoulder button strafe, quadplay dual D-pad, conventional dual analog, and mouselook controls. |
 | [Word Game](../console/quadplay.html?game=quad://examples/word_game&IDE=1) Playful use of text and animated level transitions. |
 | [Zoom 2D](../console/quadplay.html?game=quad://examples/zoom2D&IDE=1) Simplified example of 2D zoom using `set_camera()`. |
 | [Zoom 3D](../console/quadplay.html?game=quad://examples/zoom&IDE=1) Simplified example of 3D perspective zoom using `set_camera()`. See also the Vehicle example. |
 | [Private Views](../console/quadplay.html?game=quad://examples/private_view&IDE=1) Private view example for online multiplayer using `set_screen_size()` and `VIEW_ARRAY`. Also shows how to streamline online game configuration using `start_guesting()`, `start_hosting()`, and `HOST_CODE`. |
 | [Text Spheres](../console/quadplay.html?game=quad://examples/textspheres&IDE=1) Sample title screen animation converting text (from a pre-drawn PNG) into 3D shapes for animation. |
 |
[MIDI Starrypad](../console/quadplay.html?game=quad://examples/midi_starrypad&IDE=1)
Example of using the Donner Starrypad physical MIDI controller with |
 |
[MIDI Launchpad](../console/quadplay.html?game=quad://examples/midi_launchpad&IDE=1)
Example of using the Novation Launchpad physical MIDI controller with |
 |
[MIDI FCB1010](../console/quadplay.html?game=quad://examples/midi_fcb1010&IDE=1)
Example of using the Behringer FCB1010 physical MIDI controller with |
 | [MIDI 8x8 Touch Jam](../console/quadplay.html?game=quad://examples/midi_8x8&IDE=1) Sample program for a MIDI controller jam, demonstrating the `midi_8x8.pyxl` helper script. |
 | [Multitouch](../console/quadplay.html?game=quad://examples/multitouch&IDE=1) Example of `device_control()` for reading multitouch input on touch screens. |
 | [Bezier Eye Creature](../console/quadplay.html?game=quad://examples/bezier_eye_creature&IDE=1) Example of a library that uses a simple quadratic bezier + spring system to simulate bouncy cable/rope/arms. |
 | [Animation Entity Example](../console/quadplay.html?game=quad://examples/anim_entity_example&IDE=1) By way of an anim entity library, demonstrates how to read sprite frame timing from a sprite sheet authored in aseprite. |
 | [Color Wheel](../console/quadplay.html?game=quad://examples/color_wheel&IDE=1) shows the difference between `hsv()` and `artist_hsv_to_rgb()` hues and brightnesses. |
 [8BitDo SN30 Pro](https://amzn.to/35XGEl9) (SN or Classic) |
 [8BitDo SN30](https://amzn.to/38ay83F) |
 [8BitDo Zero 2](https://amzn.to/2Ny5nWN) |
| ||
 [Microsoft Xbox One Wired Controller](https://amzn.to/36Y7CdR) |
 [Microsoft Xbox One Wireless Controller for Windows 10](https://amzn.to/35U36vz) |
 [Microsoft Xbox 360 Wired Controller](https://www.microsoft.com/accessories/en-ww/products/gaming/xbox-360-controller-for-windows/52a-00004) |
| ||
 [Nintendo Joy-Con](https://amzn.to/30recqP) |
 [Nintendo Switch Pro Controller](https://www.amazon.com/Nintendo-Switch-Pro-Controller/dp/B01NAWKYZ0) |
 [Thrustmaster T-Flight HOTAS X Flight Stick](https://amzn.to/2Tvf8J4) |
| ||
 [X-Arcade Dual](https://shop.xgaming.com/collections/arcade-joysticks/products/x-arcade-dual-joystick-usb-included) |
 [retro bit Sega Genesis](http://retro-bit.com/sega-collaboration) |
 [8bitdo M30 2.4G](https://www.8bitdo.com/m30/) |
| ||
 [PlayStation DualShock 4 Wireless Controller](https://www.playstation.com/en-us/explore/accessories/gaming-controllers/dualshock-4/) |
{"type":"raw", "url": "
your url"}
instead of an inline value.
The URL must be for a [JSON](https://www.json.org/) or [YAML](https://yaml.org/) file.
Note that the `raw` format is restricted to values that are expressible in those interchange
formats and does not support the full GUI editors and other format features.
`raw` type constants with URLs must be top-level constants. They
cannot be embedded within other constants. This is required in order
to separate the asynchronous loading from constant evaluation in the
implementation and to ensure that GUI editors can be built effectively
for objects and arrays.
Debug JSON
---------------------------------------------------------------------------------------------
When working with multiple developers on a game, it is often useful to
have slightly different settings for each developer. For example,
one developer may want a `DEBUG_GRAPHICS` constant set to `true`
while the usual value is false. Or the gameplay programmer may experiment
with different player velocity than what is in the main game branch.
One way to accomplish this is to explicitly use the built-in
`IDE_USER` constant described in the Debugging section. Another solution
is to use a `.debug.json` file.
A debug JSON file must be in the same directory as the `.game.json`
file and use the same basename. For example, if your game is
`space.game.json`, then the debug file is `space.debug.json`.
Typically you will _not_ add the debug file to version control, so
that each developer may have their own. No `.debug.json` file is
required, and if one is present it will be ignored unless running
on a quadplay server using the IDE.
The `.debug.json` format is:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ json
{
"start_mode": "Init",
"start_mode_enabled": true,
"constants" : {
// Constants in the same format as in the .game.json.
"player_speed": {
"type": "number",
"value": "3",
// If false, then this debug value is
// disabled in the IDE but remembered for later
// enabling.
"enabled": true
}
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The `start_mode` overrides the `.game.json` start mode, if it is present.
The constants override the equivalent constants from the `.game.json`
file (unless they are not enabled). Extra constants specified only
in the `.debug.json` file are ignored; they are not mapped at runtime.
Constant types that require external data are not supported in the
`debug.json` file. These are `raw` constants loaded from a JSON or
YAML url, `string` constants loaded from a text file url, and `table`
constants loaded from a CSV url.
Sprite JSON
---------------------------------------------------------------------------------------------
The sprite sheet JSON descriptor looks like:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JavaScript
{
"url": "hero.png",
// Optional. Defaults to the size of the png.
"sprite_size": {"x": 8, "y": 8},
// Optional blank pixels between sprites on the bottom and right. Default is zero.
"gutter": 0,
// Optional. Default is false. If true, indexing is transposed
// in game.
"transpose": false,
// Optional name of the file from which this PNG was generated.
// This is the file that will be loaded with an external editor from the IDE
// instead of the PNG when editing. This file will also be managed by version control
// alongside the PNG nad JOSN.
//
// Useul if you export from another tool such as Aseprite, Krita, or Photoshop.
"source_url": "hero.psd",
// Default value for sprite.frames, the number of 1/60-second frames
// to play a sprite during animation.
"default_frames": 1,
// Optional table mapping color value strings to strings.
// The source values (keys) must be exactly (4-bit) #RGB values; they cannot have
// alpha or use the 8-bit form #RRGGBB. They are applied to the source
// image after loading to recolor it.
//
// The destination values can be either 4-bit #RGB or #RGBA values.
// When remapping a color, the source alpha will be *bit masked*
// (not multiplied) by the destination alpha. So, only values of
// A = 1, 3, 7, and F are generally useful.
"palette_swap": {
"#F00": "#0F0", // Convert red to green
"#DD0": "#C0D0" // Make #DD0 transparent
},
// Optional. Applies to pre-transpose coordinates. Each becomes
// an array on the spritesheet with the `extrapolate` property set.
// A name cannot be `sprite_size` or any other spritesheet property.
//
// If an object with `x` and `y` properties is specified, creates a single sprite.
//
// If an object with `start` is specified, creates an array of sprites.
//
// The contents between `start` and `end` are treated as a 1D array. By default
// they are row major. To switch to column-major set "major_axis": "y".
//
// To strip frames from the end of the array, set `ignore` to the number of frames to
// ignore.
//
// If `extrapolate` is unspecified, defaults to looping.
// `extrapolate` options are "loop", "oscillate", and "clamp"
//
// Each object may have its own `default_frames` that overrides the
// spritesheet's default_frames, as well as a `frames` array with
// one element per sprite in the animation.
//
// `end` defaults to `start`, and each property of `end` defaults
// to the corresponding part of `start`.
"names": {
"idle": {"start": {"x": 0, "y": 0}, "end": {"x": 0, "y": 3}, "extrapolate": "oscillate",
"frames": [1, 2, 2, 1]},
"run": {"start": {"x": 1, "y": 0}, "end": {"y": 3}, "frames": 2},
"jumpUp": {"start": {"x": 2, "y": 0}},
"jumpDown": {"start": {"x": 2, "y": 1}},
"dead": {"x": 3, "y": 0},
"parachute":{"start": {"x": 3, "y": 1},
"pivot": {"x": 4, "y": 4},
"is_item": true // Arbitrary properties can be attached here, using the
// same syntax as game constants
},
},
// Optional offset and extent. Using this conserves memory if the sprites
// do not fill the entire PNG.
"region": {
"corner": {"x": 0, "y": 0}, // Optional, defaults to (0, 0)
"size": {"x": 128, "y": 32} // Optional, defaults to the remainder of the sheet
},
// Optional location on the sprites that aligns with (0, 0) in an entity's reference
// frame. Defaults to the sprite size / 2.
"pivot": {"x": 4, "y": 7},
// Optional license and copyright information.
"license": "GPL 3.0 (c) 2025 Morgan McGuire"
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This will appear as the global variable `heroSpriteSheet` in memory,
which is a 2D array of individual sprites.
!!!
I recommend a two-step process for compressing sprites to minimize loading time
and game size. First, use the \
character
and `"\""` is the `"` character.
The `+` operator performs string concatenation, coercing the right-hand argument if it is not a
string. String characters are read-only substrings accessed using zero-based array syntax.
Arrays
--------------------------------------------------------------------
Arrays are ordered sets of values. They are zero-based and have
dynamically typed elements. Array literals are surrounded in square
brackets and separated by commas. Square brackets are also used for
l-value and r-value dereference.
Array examples:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
a = [8, 4, 1]
a[0] = "hello"
debug_print(a[0])
debug_print(size(a))
a₁ += 2
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Negative indices are permitted on arrays but will not be reported in the length. Reading an
out-of-bounds value from an Array returns `∅`.
Objects
---------------------------------------------------------------------------
Objects are unordered sets of keys. Objects (also known as
dictionaries and tables in other languages) map these keys to values.
Literals are created using key-value pairs separated by a colon,
surrounded by curly braces, and delimited by commas. Object elements
can be accessed using the string name of a property in square brackets
or by a period. This allows them to act as objects/structs.
Object examples:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
t = {x:3, y:10}
t.x += 1
debug_print(t["x"])
debug_print(size(t))
debug_print(keyArray(t))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Functions
---------------------------------------------------------------------------
Functions are first-class values. They can be passed to and returned
from other functions, stored in data structures, and bound to
variables. Declare functions with `def` and return values from them
with `return`. Separate the argument list from the body with `:`.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
// Multi-line function block
def solve(a, b, c):
return ½(sqrt(b² - 4 a * c) - b) / a
// Inline function
def foo(x): x += 1; return 2 x
// Call a user function
text(foo(3))
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Functions create a local scope for their variables and are lexically
scoped (as in JavaScript) for free variables.
Local functions can be declared anywhere a statement can appear, and
local function definitions are efficient. It is common practice to
declare little callbacks right before they are used, as in:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
def destroy(entity):
play_sound(explosion_sound)
spawn_particles(entity.pos)
def cleanup(): remove_values(entity_array, entity)
delay(cleanup, 20)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Some API functions that use callbacks are `iterate()`, `delay()`,
`sequence()`, `add_frame_hook()`, `set_pause_menu()`, `ray_intersect_map()`,
and `physics_add_contact_callback()`.
There is no way to declare an anonymous function or use a function
definition as an expression. Simply declare your functions globally
or locally with a name as as statement. This improves readability.
In PyxlScript syntax, a parameter shown with `default` after it, for
example: `i default 0` indicates that this is an optional parameter and
the value after `default` be used if none is supplied. Explicitly
specifying `nil` for any parameter forces its default value.
If a function body takes too long to execute, then the interpreter
may terminate the program to prevent the game from becoming
unresponsive.
To call a function returned from an expression that ends in
parentheses, use the `call` function.
Variables
---------------------------------------------------------------------------
Variable name identifiers consist of an optional leading underscore `_`,
optional upper case delta `Δ`,
a series of Roman letters or one of the unambiguous-looking Greek letters `αβγδζηθιλμρσϕχτψωΩ` by itself, and
optionally trailing a series of digits and underscores.
The identifier also must not be a reserved word. No
other unicode characters are allowed in identifiers.
Examples of legal identifiers:
~~~~~~~~~~~~~~~~~~~ none
col
screen
long_variable
longVariable
L
i
damage
Y2
Δx
θ
θ_in
_health
~~~~~~~~~~~~~~~~~~~
Variables are declared to the scope of the containing block or
mode. Scope blocks are denoted by a statement ending in a colon and zero or
more indented lines.
Use `let` to declare variable names that can later be re-bound. Use
`const` to declare constants that cannot be rebound. Note that `const`
marks the *binding* as constant, but the bound value itself may be
mutated if it is an object or array.
Note that only a single variable is permitted per `let` or `const`
statement. Multiple statements may be chained using semi-colons on a
single line, however.
~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
const x = 7 // x may not be rebound
let y // Declare y
let z = 9 // Declare and initialize z
// Declare and initialize two variables on the same line:
let a = [1, 2]; let b = "hello"
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The variables captured by `for` and `with` and the arguments to a
`def` are bound to the scope of the body.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
const scale = 2
let y = 100
for i < 3:
y += 1
sprite(hero[anim][0], xy(scale * i, y))
def applyScale(x):
return x * scale
// i is out of scope here
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Variables declared within a mode are local to that mode and
reinitialized every frame. Variables declared in a global file are
global to the entire program.
It is an error to assign to a variable before it is declared.
### Reserved Words
These are reserved tokens that cannot be used as variable names. Some
of them are keywords or built-in syntax, and some of them do nothing
now but are reserved for future use.
`and`, `arguments`, `args`, `as`, `async`, `at`, `auto`, `await`, `assert`,
`because`, `begin`, `bitand`, `bitnot`, `bitor`, `bitashl`, `bitshl`, `bitshr`, `break`,
`catch`, `class`, `const`, `cont`, `coroutine`, `continue`, `constructor`,
`deg`, `do`, `delete`, `def`, `default`, `debug_watch`, `debug_print`,
`elif`, `end`, `extern`, `enum`,
`final`, `finally`, `for`, `from`, `freeze`, `function`,
`get`, `global`, `go`, `goto`,
`has`, `HOST_CODE`,
`if`, `in`, `implements`, `inherits`, `include`, `import`, `interface`,
`launch_game`, `let`, `local`,
`nan`, `NaN`, `new`, `nil`, `nonlocal`, `not`, `null`,
`main`, `mod`, `module`, `mode`,
`or`, `otherwise`,
`public`, `private`, `protected`, `prototype`, `package`, `preserve`, `preserving_transform`,
`quit_game`,
`ret`, `return`, `reset_game`, `require`, `requires`,
`seal`, `set`, `show`, `static`, `strict`, `SUB`, `swap`, `SOURCE_LOCATION`, `SCREEN_SIZE`,
`to`, `todo`, `to_string`, `then`, `try`, `throw`, `template`, `touch`,
`until`, `use`, `using`, `unless`,
`var`, `VIEW_ARRAY`,
`while`, `with`, `when`,
`xor`,
`yield`.
Built-in functions such as `draw_rect` that are legal identifiers can be
rebound. They are not reserved. `cos`, `sin`, and `tan` by themselves
are bound to first-class functions, but the special syntax that allows
them to be invoked without parentheses on single variables is tied to
their exact names. If you rebind them, then the new functions that you
have bound will be invoked instead.
### `with` Statement
The `with` statement creates a local scope in which explicitly named
keys from an object are variables aliased to the properties on the
object.
This allows compact syntax for repeated use of those keys. It fills
the role that methods and member variables do within object-oriented
languages. The local variables shadow any global (or enclosing `with`
statement) variables.
Example:
~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
G = {x:1, y:2, z:0}
with x,y in G:
x = 100
y = 0
z = 4
// G.x is now 100, G.y is now 0, G.z is still 0,
// and global z is now 4
~~~~~~~~~~~~~~~~~~~~~~
Within the body, the local variables and the properties are true
aliases. So, `G.x` and `x` may be used interchangably within the body
in the above example.
The single-line version of the `with` statement is comparable to other
single-line flow control:
~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
G = {x:1, y:2, z:0}
with x, y in G: x = 100; y = 0; z = 4
~~~~~~~~~~~~~~~~~~~~~~
`with` statements can make code clean but are expensive relative to
the overhead of other statements, so avoid them for performance-sensitive
inner loops.
### `local` Statement
The `local` statement allows you to create a block without any other
bindings or flow control. It is occasionally useful for creating
variables with a tight scope inside a long function or mode.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
let x = 4
local:
// This is a local scope and variables
// can shadow those in the outer scopes
let x = 7
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Comments
-------------------------------------------------------------------
Comments follow C/C++/Java/JavaScript syntax of `//` for single line comments and `/*`...`*/`
for multi-line comments.
`because "reason"`
: The special `because` statement allows a compile-time string comment
to follow specific statements such as `set_mode()`. It acts as a
machine-readable comment that the IDE will use to automatically
annotate the program in the debug output at runtime and the mode
diagram.
Operators
-------------------------------------------------------------------
Built-in arithmetic operators and functions are overloaded for numbers
and objects.
Some operators support a compact Unicode version and an ASCII version
that is easier to type on a conventional keyboard.
Unlike other languages, all mutating operators (for example, `++`,
`+=`, and `=`) return `nil`.
ASCII | Unicode | Meaning
---------|---------|--
`++` | | increment
`--` | | decrement
`=` | | assignment
`==` |`≟` | equality test
`!=` |`≠` | inequality test
`+` | | addition and string concatenation
`-` | | negation
`+=` | | mutating addition, string concatenation
`-=` | | mutating subtraction
`<` | | less-than
`<=` |`≤` | less-than or equal to
`>` | | greater-than
`>=` |`≥` | greater-than or equal to
`in` |`∈` | `for` loop container element-of and `with` statement object
`^` | | exponentiation (see also unicode exponents)
`*` | | multiplication (see also implicit multiplication)
`*=` | | mutating multiplication
`/` | | floating-point division
`/=` | | mutating division
`mod` | | remainder (modulo)
|`⌊⌋` | floor function
|`⌈⌉` | ceiling function
`||` | | absolute value
|`‖‖` | magnitude (of a vector or array)
`[]` | | object/array element reference, or array constructor
`{}` | | object constructor (follows JavaScript syntax)
`.` | | object property
`()` | | grouping or function call
`...` | `…` | spread operator for arrays and objects
`bitand` |`∩` | bitwise AND (bit set intersection)
|`∩=` | mutating bitwise AND
`bitor` |`∪` | bitwise OR (bit set union)
|`∪=` | mutating bitwise OR
`or` | | logical OR
`not` | | logical NOT
`and` | | logical AND
`bitxor` |`⊕` | bitwise XOR
|`⊕=` | mutating bitwise XOR
`bitnot` |`~` | bitwise NOT
`~=` | | mutating bitwise NOT
`<<` or `bitshl` |`◀` | arithmetic bit shift left
`<<=` |`◀=` | mutating arithmetic bit shift left
`>>` or `bitshr` |`▶` | arithmetic bit shift right
`>>=` |`▶=` | mutating arithmetic bit shift right
`if`...`then`...`else`| | conditional (C/Java `?:` a.k.a. "ternary" operator)
`default`| | default value operator
Assignment and mutating operators all return `nil`; they cannot be
chained or used in expressions.
There is no logical XOR or logical shift right.
The `default` operator has the form: _expr1_ `default` _expr2_. If
_expr1_ is not `nil`, then the value of the operation is the value of
_expr1_. If it is `nil`, then _expr2_ is evaluated and the value of
the expression is the value of _expr2_. Note that the second expression
is not evaluated unless it is needed, as with `and`, `or`, and `if` operators.
The `default` operator allows compact
statements handling default arguments or out of bounds array accesses,
for example:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
// Returns a transparent gray. Opacity is optional
// and defaults to 1
def transparent_gray(g, opacity):
return rgba(g, g, g, opacity default 1)
// Print the first element of array matching target, or
// the first element of the entire array if not found.
debug_print(array[find(array, target) default 0])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The conditional operator (distinct from the `if` block statement) has
the form: `if testexpr then truevalue else falsevalue`. Note that
there are no colons (:), because it does not create blocks. The
conditional operator is convenient when initializing constants, or
data structures. For example:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
const y = if x > 3 then "cat" else "dog"
spawnMonster(if pos then pos else xy(0,0))
let data = {name: if i > 2 then "Peter" else "Amit",
height: 7}
setTarget(if ‖pos - P.pos‖ > 10 then ∅ else P)
// Because "and" and "or" operate on non-booleans, the examples
// above could have been written less clearly with alternative
// logic:
const y = (x > 3) and "cat" or "dog"
spawnMonster(pos or xy(0,0))
let data = {name: (i > 2) and "Peter" or "Amit",
height: 7}
// Note that this one had to be restructured
// because ∅ acts as false for "and"
setTarget(‖pos - P.pos‖ ≤ 10 and P or ∅)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To nest conditionals, you must put parentheses around at least the
`falsevalue`, for example `y = if x > 3 then "cat" else (if x > 2 then
"dog" else "llama")`.
`a default b` is equal to `a` if `a` is not `nil` and `b`
otherwise. It does not evaluate `b` if `a` was not `nil`. This is
equivalent to the JavaScript and C#
[nullish](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator)
`??` operator. Some common uses are:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
// Default value on load
num_players = load_local("num_players") default 2
// Default value on missing object property
height = character_height[name] default 170
// Default value on going out of array bounds
vel = array[i + 2] default xy(2, 3)
// Fallback value on find() failing
debug_print(array[find(array, target) default 0])
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Spaces are not required around operators in most cases, so long as the text parses
unambiguously. For example, `1or3` is equivalent to `1 or 3`. There is no ASCII operator for
floor or ceiling; use the `floor` and `ceil` functions. There is no way to express mutating bitwise
operators using ASCII.
Behavior of these operators is consistent with the JavaScript definition of the operation
(even though JavaScript uses different operators). For example, the logical-AND expression
`4 and 1` evaluates to `1` because `4` is non-false and the result of non-false AND another
value is the other value.
The trigonometric functions `cos`, `sin`, and `tan` may be invoked as if they were operators
when the order of operations is unambiguous. For example, `cos a` ==> `cos(a)`, `cosβ` ==>
`cos(β)`, and `cosθ*sinφ` ==> `cos(θ) * sin(φ)`. To avoid confusion and reserve future syntax,
they may not be invoked in this form when a high-precedence operator follows the argument. Those
operators are `.`, `[`, and exponentiation.
Note that the `call()` function can in most cases be replaced simply with the function
expression followed by arguments:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
x = (if a then abs else cos)(θ)
y = tbl["myfunc"](3, 4)
z = f(a)(b)
x = call(if a then abs else cos, θ)
y = call(tbl["myfunc"], 3, 4)
z = call(f(a), b)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Assignment and Equality
As in Python and JavaScript, all assignment operators perform pointer
assignment and all equality operators check for pointer equality. All
function call parameters are passed by pointer. Strings, `nil`,
numbers, and booleans are immutable and unique.
This means that variables will be aliased after assignment:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
let v = xy(1, 2)
let k = v
k.x = 3
// v.x is 3 now
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use `clone()`, `deep_clone()`, or one of the vector-specific helper
functions for copying assignment:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
let v = xy(4, 5)
let k = xy(v) // copy
k = clone(v) // generic version
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A global game constant that is cloned becomes mutable. There is no way
to declare immutable objects or arrays at runtime in code. `const`
makes the _binding_ constant, but has no effect on the value (as in
JavaScript).
### Exponents
The characters `⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹ᵃᵝⁱʲᵏⁿᵘˣʸᶻ⁽⁾` can form exponent expressions that operate as if
the `^` operator or `pow` function was invoked using those expressions as a second argument.
The entire exponent expression is implicitly surrounded in parentheses.
The variables `a`, `β`, `i`, `j`, `x`, `y`, `z`, `n`, `k`, and `u` may appear only as single-character
variable names in exponents.
Exponentiation, including via exponent characters, may not be performed on a negative number
without parentheses. This follows the rules from JavaScript's `**` operator. For example,
`x=y-1²` is legal, but `x=y+-1²` is illegal and must be rewritten as `x=y+(-1)²` or (the
confusing and redundant expression, if that's really what was intended) `x=y+-(1²)`.
### Implicit Multiplication
Multiplication is implicit wherever a number is immediately followed by a parenthesized
expression or variable (including a function call). This also ocurrs when any exponent
appears immediately preceeding a parenthesized expression or variable, and where two
parenthesized expressions are back to back. For example,
`2x`, `x²y`, `3(x+y)`, `(2 + x) y`.
Implicit multiplication will not occur if the expression on the left of the pair
ends in any bracket: `| ) ] ⌋ ⌉ ‖` and the expression on the right also begins with a bracket.
For example, `(x + 1) (x + 2)` and `|y| x` are syntax errors, not implicit products.
Implicit multiplication can be used within exponents.
### Spread Operator
The spread operator (`…` or `...`) allows defining and calling
functions with a variable number of arguments and creating an extended
clone of an object or array. It is the same as `*rest` in Python and
`...rest` in JavaScript, and similar to varargs in C++ and `...` in
Java.
In a function declaration it designates the final argument as an array
of all further arguments. For example, `def foo(a, b, …rest):` takes
any number of arguments and maps them to `a`, `b`, and then an array
`rest` of the remainder. For example, `def bar(…args):` takes any
number of arguments and maps them all into array `args`.
In a function call or array literal, the spread operator expands an array as if its elements had
been entered inline. So, `bar(…a)` calls a function with all of the arguments from array `a`.
`[1, 2, …a, 10]` creates an array that begins with 1 and 2, then has all of the elements of `a`,
followed by the number 10.
For objects, `{a:1, b:2, …rest}` creates the object `{a:1, b:2}` and then
creates a new extended version in which the properties of `rest` are added,
overriding any with the same name in the original object (as with the `extended()` function).
This is helpful for the case of calling a function with an args object:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
const text_args = {color: #FFF, x_align: "center", font: roman_font}
draw_text({text: "Hello", pos: xy(10, 100), …text_args})
draw_text({text: "World", pos: xy(10, 120), …text_args})
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### Built-in Objects
The following are built-in constants and special zero-argument
operators. They are not variable names and thus cannot be
redefined. Several have both unicode and ASCII names.
Literals (replaced by the parser at compile tile):
Symbol | ASCII | Value
---------------|-----------------|----------------------------------------
`ε` | `epsilon` | `0.000001`
`π` | `pi` | `3.141592653589793115997963468544185161590576171875`
`∞` | `infinity` | infinity
| `nan` | floating-point "not-a-number" value
`∅` | `nil` | ("undefined")
The following are lowercase because they contain values that can change
without game code specifically triggering that:
Symbol | ASCII | Value
---------------|-----------------|----------------------------------------
`ξ` | `random()` | random number on [0, 1), resampled every time the expression is used
| `gamepad_array` | array gamepad of objects
| `touch` | the object for tracking touch and mouse input
| `joy` | shorthand for `gamepad_array[0]` (deprecated)
These have values that will not change unless game code specifically
invokes a routine to change them, such as `set_screen_size()` (or if
the player uses the system GUI for `HOST_CODE`):
Symbol | ASCII | Value
---------------|-----------------|----------------------------------------
| `CREDITS` | object of credit and license statements from assets and code
| `HOST_CODE` | string that is the code guests can use to connect when hosting
| `SCREEN_SIZE` | the size of the full screen in pixels, defaults to `xy(384, 224)`. See also `VIEW_ARRAY[0].size`
| `VIEW_ARRAY` | array of corner rects for host and each guest's view in private view online multiplayer. See `set_screen_size()`
| `CONSTANTS` | an object mapping all game-specific constants to their values
| `ASSETS` | an object mapping all game-specific assets to their values
| `SOURCE_LOCATION` | an object that has properties `{url:, filename:, line_number:}` from its location in the source code
Loops and Flow Control
--------------------------------------------------------------------------
Whitespace is significant, as in Python. A block is a line ending with a `:`
whose body is indented. All blocks create a new scope line (unlike Python).
The flow control statements are:
- Blocks:
- `if` conditional with optional `else` and `else if` clauses
- `while` loop
- `until` loop
- `for` loop
- `for-in` loop. _See also the `iterate()` function_.
- `for-with` loop
- `break` and `continue` within loops
- `preserving_transform` and `local`, which do not have branching flow
but do create local scopes
- `def`, which creates a local scope for a function but does not execute
immediately
- Modes:
- `set_mode(ModeName)` to switch game modes
- `push_mode(ModeName)` and `pop_mode()` to temporarily
enter a mode game mode
- Game:
- `reset_game()` to reset all state and restart the game.
- `quit_game()` to end the current game and return to the launcher.
- `launch_game(url)` to launch a different game.
`set_mode()`, `push_mode()`, `pop_mode()`, `reset_game()`, `quit_game()`, and `launch_game()` may be
followed by the keyword `because` and a reason that is a compile-time string. For example, `quit_game() because "lives = 0"`.
The reason allows the IDE to label the transitions on the mode diagram for the game.
There is no switch, do loop, goto, or try/catch.
For flow control purposes, the empty string, 0, and `∅` are false. All other objects (including empty data structures) are true.
Multi-line `if` statements may be followed by `else:` and `else if ...:` clauses. These must always be
multi-line.
Single-line `for`, `if`, `while`, and `until` statements are
permitted. There can be multiple single-line expressions on the same
line. No `else` is permitted with single-line `if` statement.
In any loop, `break` immediately terminates the tightest enclosing loop
and `continue` immediately begins the next iteration of the tightest
enclosing loop.
`while` and `until` loops evaluate the condition at the top of the loop for every
iteration.
The `until` loop is a `while` loop with an inverted test:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
until x == "end":
x = find_next(x, data)
// Is the same as:
while x != "end":
x = find_next(x, data)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### `&` Elision
Trivial blocks can increase indentation so much that code is hard to
read because it extends too far to the right. All blocks except for
`else` can be elided with an immediately-previous block to avoid
excess indentation.
To elide blocks, leave the trailing `:` off the outer block and begin
the inner block with `&` at the same indentation level:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for x < 3
& for y < 3:
& for z < 3:
debug_print(x, y, z)
def player_update(player)
& preserving_transform
& with angle, pos, vel, gamepad in player:
vel = lerp(vel, gamepad.xy, 10%)
if ‖vel‖ > 0: angle = xy_to_angle(vel)
pos = loop(pos + vel, xy(0, 0), SCREEN_SIZE)
set_transform(pos)
draw_sprite(arrow_sprite)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
### `for` Loop
Numeric `for` loops increment by 1 each iteration and always count up. The loop variable must
be on the left-side of a single comparison loop expression or the center of a multi-comparison
expression.
The iteration variable in any `for` loop is freshly bound for each iteration. This means that
if it is captured by a function as in:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PyxlScript
let array = []
for i < 3
def capture(): return i
push(array, capture)
for f in array: debug_print(f())
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
that the value will reflect the time of the capture. This avoids many
common bugs when using first class functions with loops.
`for` loops evaluate the bounds once, before the first iteration.
The Java/C++ equivalents of `for`-loop conditions are:
SCREEN | |||
*P3* #fd3 |
*P1* #f5a |
*P2* #0af |
*P4* #4e4 |