The Web¶
An Acron game can be exported as a static web site: an HTML page, the game compiled to WebAssembly, and its files. It renders with WebGL 2 (WebGL 1 as a fallback) and plays sound through Web Audio, in any current desktop browser, and needs nothing but a web server to host it – your own, GitHub Pages, or a game portal such as itch.io. Saved games stay in the player’s browser.
Nothing has to be installed on the player’s side, and the same site runs on Windows, macOS and Linux. The export itself also works from any of the three: WebAssembly is the same everywhere, so the engine is built for it once.
Exporting a Python game¶
A Python game is frozen with build_apps for the
platform emscripten_wasm32. Nothing is compiled: the engine wheel for
that platform carries a prebuilt web runtime – CPython, the engine and its
Python modules as one WebAssembly program – and build_apps adds your
frozen code and your files to it.
With acronmanager, in the project directory:
acronmanager dist --web
builds the site into build/emscripten_wasm32/ and zips it into
dist/<name>-web.zip. The engine wheel of the version the project pins
comes from the package index the project names, like the desktop wheels.
acronmanager run --web builds the site and serves it locally (see
Serving the site); acronmanager build --web only builds it.
Without acronmanager, name the platform in setup.py (or pass
-p emscripten_wasm32):
from setuptools import setup
setup(
name="mygame",
options={
"build_apps": {
"gui_apps": {"mygame": "main.py"},
"include_patterns": ["models/**", "textures/**"],
"platforms": ["emscripten_wasm32"],
"web_title": "My Game",
},
},
)
The web adds these options to the List of Build Options:
web_main_appThe app the page runs, when
gui_appsnames more than one.web_titleThe page title (default: the app’s name).
web_shellYour own page (default: the engine’s).
@TITLE@and@MANIFEST@in it are filled in; see Your own page.web_persist_dirsThe directories the browser keeps between visits (default
/home/web_user, which is the home directory,~).
The site, in build/emscripten_wasm32/:
index.html the page
acron-web.js loads the program and its files, keeps the saves
deploy-stub.js the web runtime (CPython and the engine) ...
deploy-stub.wasm ... about 30 MB (7 MB with Brotli, 10 MB with gzip)
acron-web.json what acron-web.js loads
files/app/... the frozen code and the included files
packs/... the content packs, if any (see below)
The game itself needs no changes. base.run() hands the frames to the
browser rather than looping itself, and files the game writes under ~
are kept. What does not work is listed under What is different on the web.
Starting before everything has arrived¶
Everything a web game starts with is downloaded before it starts. A large game can start much sooner if the big assets – maps, skies, music – come later, as content packs: Multifiles that the page downloads while the game runs, and that the game mounts over its own directory when they have arrived, so their files keep their paths.
Name the packs in the build_apps options, as file patterns relative to
the app’s directory:
"web_packs": {
"arena": ["models/arena.bam", "textures/arena/*"],
"music": ["music/*"],
},
"web_background_packs": ["arena"], # fetched as soon as the game runs
Their files leave the startup download and go to packs/<name>.mf beside
the page. A model that build_apps converts is packed under its new name
(level.glb becomes level.glb.bam), so a pattern for it should end in
* (models/level.glb*).
In the game, ContentPack fetches a pack and tells when
its files can be loaded:
from acron.core import ContentPack
pack = ContentPack.request("arena") # starts the download
def wait(task):
if pack.is_failed():
print("could not download the arena:", pack.get_error())
return task.done
if not pack.is_ready(): # mounts it once it is here
loading_bar.set_value(pack.get_progress())
return task.cont
start_level(loader.load_model("models/arena.bam"))
return task.done
taskMgr.add(wait, "wait-for-arena")
On the desktop, where nothing is packed, is_ready() is true at once, so
the same code runs there unchanged.
The page fetches one pack at a time, the ones the game asks for first, and keeps them in memory (not between visits). Downloads are whole Multifiles: split the content where the game needs it in parts (a pack per map) rather than streaming parts of one pack.
What is different on the web¶
The browser is a sandbox, and a few things a desktop game takes for granted work differently or not at all:
Files. The game sees its files as ordinary files, read from memory: they are downloaded before it starts, except for content packs (see Starting before everything has arrived). Writes to a persistent directory reach the browser’s storage (IndexedDB) within two seconds and when the page is hidden; everything else is lost when the page closes. The player can clear that storage with the site’s data.
One thread. There are no threads and no blocking waits. Long work in one frame freezes the page for that long.
No native plugins. Everything the game uses is in the one program. For Python, that means pure-Python packages only: a package with compiled extensions needs a WebAssembly build of its own (there is none for numpy, so
acron.hud’sRaster, which uses it, is not available; the rest ofacron.hud,MapViewincluded, is).Shaders. GLSL 330 and later goes through the shader pipeline and reaches WebGL as the ESSL it accepts; older GLSL (
#version 120) is refused, as it is on the desktop. A shader that names#version 300 esitself goes to WebGL as written, and must declare its own precisions (precision highp float; precision highp sampler2DShadow;). Desktop-only OpenGL features – geometry and tessellation shaders,glPolygonMode, reading textures back withglGetTexImage– are not there, and code that calls OpenGL directly gets OpenGL ES 3.0. Offscreen buffers can be multisampled; one in a format WebGL cannot multisample renders without it (with a warning).Input. Keyboard, mouse and touch (as a mouse). The page focuses the game on the first click. Relative mouse mode (
M_relative) becomes the browser’s pointer lock, which only a click can start, and which the browser ends itself when the player presses Esc: a game with mouse look should check the window’s mouse mode, treat its return toM_absoluteas a pause, and lock again on the next click.Networking. A page can neither send UDP nor listen for connections.
acron.enetis in the runtime, but a game’s server in the same page needs an in-process transport (queues standing in for the ENet host and peers), and play over the network needs a server reachable over WebSockets or WebRTC.Sound starts with the player’s first click or key press; browsers allow no sound before it.
Quitting is closing the tab: hide the game’s Quit item on the web.
Size. The engine is a large program to download: a Python game’s runtime (CPython and the whole engine) is about 30 MB, 7 MB with Brotli; a C++ game links only what it uses (the starter is 11 MB). Serve
.wasm,.jsand data files compressed (see below).
Serving the site¶
The site is static files, but the server has to send .wasm files as
application/wasm: browsers only compile WebAssembly while it downloads
when it has that type. Most servers do; Python’s own does:
python -m http.server -d build/emscripten_wasm32 8000
then open http://localhost:8000/. acronmanager run --web does the same and
opens the browser. Opening index.html straight from disk does not work:
browsers do not let a page fetch file: URLs.
For a public site, compress: .wasm files shrink to about a quarter with
Brotli and a third with gzip, .js files more. Most hosts and portals
compress by themselves; on your own server, enable it for
application/wasm and text/javascript, or upload precompressed
.br/.gz files with the matching Content-Encoding.
On itch.io, upload the zip as an HTML game, leave SharedArrayBuffer support off (the engine does not use threads), and give the frame the game’s size or choose click to launch in fullscreen.
Your own page¶
The default page fills the browser window, shows a progress bar while the files
load, and a fullscreen button. To use your own (web_shell, or SHELL),
start from the engine’s (share/acron/web/shell.html in the web SDK). All
a page needs is a <canvas id="canvas" tabindex="-1">, acron-web.js,
and:
AcronWeb.start({
manifest: "@MANIFEST@",
onProgress(loaded, total) {}, // bytes of the files downloaded so far
onStatus(text) {},
onReady() {}, // the game is running
onError(message) {},
});
@ICONS@ in <head> becomes the tags of the site’s icons (the
favicon, the home screen icon and the web app manifest), or an empty icon
when it has none.
AcronWeb.module is then the game’s Emscripten module, and
AcronWeb.sync() writes the persistent directories out at once.