text
Dynamic TTF text, multiline wrapping and live MQTT updates. Send a new string through update/<id>.
Define your scene and send your first command. No ERGameStudio installation required.
You'll need the Player, an MQTT broker reachable from your device, and an MQTT client or game controller. The examples below use mosquitto_pub to send commands.
C:/ergs/player/ on Windows or /home/pi/ergsplayer/ on Linux. Add a media directory inside it.config.json in your project folder. Change base_path to that folder and mqtt_broker_uri to your broker.{
"base_path": "C:/ergs/player/",
"mqtt_broker_uri": "tcp://192.168.1.10:1883",
"mqtt_base_topic": "ergs",
"device_id": "room01",
"mqtt_client_id": "ergsplayer-room01",
"resolution": { "width": 1280, "height": 720 },
"fullscreen": false,
"cursor_visible": true,
"exit_by_keyboard": true
}For Linux, change base_path to /home/pi/ergsplayer/, replacing pi with your actual username. Use a unique device ID and MQTT client ID for every Player.
Save this scene as media/screen.json. It uses the built-in font and needs no external assets.
{
"default_scene": "main",
"scenes": [
{
"scene_id": "main",
"elements": [
{
"type": "text",
"id": "message",
"pos": [100, 280],
"size": 48,
"color": "#C6F46B",
"value": "Your story starts here."
}
]
}
]
}Launch with an explicit path to your configuration:
.\ergsplayer.exe C:/ergs/player/config.jsonergsplayer /home/pi/ergsplayer/config.jsonFrom a machine with an MQTT client installed, send your first update. Replace the broker address with your own.
mosquitto_pub -h 192.168.1.10 -t ergs/room01/update/message -m "The door is now unlocked."This updates the on-screen message. Connecting the command to a physical lock or puzzle is the responsibility of your game controller.
Download Windows config · Download Linux config · Download the sample scene
Pass a configuration file explicitly when launching the Player. See the installation guide for platform-specific launch commands and installed directories. The base_path setting determines where the Player loads your scenes and assets.
| Setting | Purpose |
|---|---|
base_path | Project folder containing media/screen.json and media/assets/. Set it explicitly to keep asset paths predictable. |
mqtt_broker_uri | Broker URI, for example tcp://192.168.1.10:1883. |
mqtt_base_topic / device_id | Build the device namespace: ergs/room01. |
mqtt_client_id | Unique broker client identifier. Give each running Player its own value. |
resolution | Startup dimensions: {"width":1920,"height":1080}. |
rotation | Clockwise virtual rotation: 0, 90, 180 or 270. Quarter turns swap the default logical scene axes. |
fullscreen | Use fullscreen or a normal window. |
cursor_visible | Show or hide the system pointer. |
always_on_top | Request a topmost window where the platform supports it. |
exit_by_keyboard | Set to false to prevent ESC from exiting the Player. |
timer_topic / timer_element_id | Forward an external timer topic to the specified timer element's time command. |
game_events_topic | Forward external game events to elements in the active scene. |
terminate_on_endgame | Exit on the onEndPlay event when enabled. |
post_shader_fragment | Optional fullscreen fragment shader. Accepts an absolute path or a path relative to the project or its assets directory. |
shader_time_uniform | Time uniform passed to the postprocess shader each frame; default time. |
ambient_file | Looping background audio filename in media/assets/. |
ambient_audio_device / audio_channels | Linux ALSA routing. Map channel names such as sfx or voice to devices; an empty string uses the system default. |
The Player loads media/screen.json under your configured base_path. Put video, audio, images, fonts and shader files in media/assets/.
project/
├── config.json
└── media/
├── screen.json
├── dialog-example.json
└── assets/
├── intro.mp4
├── ambient.ogg
└── terminal.ttfOnly one scene is active at a time. Set default_scene to its scene_id. For transitions, use top-level "transition": "fade" and "transition_time": 0.5, with optional per-scene transition_in and transition_out durations.
Elements use pos: [x, y], visible and layer (lower layers render behind higher ones). Most visual elements accept size: [width, height]. Text-based elements use a scalar size for font size; do not duplicate the JSON key. Use globally unique element IDs to make routing and discovery unambiguous.
Common framing options include frame_color, frame_thickness, frame_patch9 and frame_padding: [x, y]. Colors accept hex values, including alpha where supported.
Publish ON to root/reload to reload the current scene JSON. Or send a JSON filename located in the project's media/ folder. A successful reload refreshes subscriptions and autodiscovery. If the reload fails, the current runtime scene remains active. OFF is ignored.
All examples use root as shorthand for <mqtt_base_topic>/<device_id>. With the quick-start configuration, that is ergs/room01.
| Topic under root | Payload / effect |
|---|---|
scene/select | Scene ID. Activates that scene. |
reload | ON or filename.json. Reloads the scene tree. |
update/<id> | String value. Updates the element. |
visible/<id> | ON / OFF. Shows or hides the element. |
cmd/<id>/<suffix> | Element-specific command, such as play, clear or file. |
audio/ambient | ON / OFF. Controls the configured ambient loop. |
audio/<channel>/play | Audio filename from assets. OFF / STOP stops tracked Windows channel sounds; Linux one-shots are fire-and-forget. |
audio/<channel>/volume | 0..100. Linux volume control uses amixer. |
shader/uniform/<name> | Numeric value or {"type":"float","value":0.65} for the fullscreen shader. |
For scene-scoped updates, insert the scene ID: root/update/main/message, root/visible/main/message or root/cmd/main/intro_video/play. Unscoped commands reach all matching element IDs across all scenes. A scoped update targets the named scene, even when it is inactive.
Scene selection also accepts root/cmd/scene/select and root/cmd/__scene__/activate. Shader uniform aliases are root/cmd/shader/uniform/<name> and root/cmd/__shader__/uniform/<name>.
| Topic under root | Payload |
|---|---|
status/player | online / offline, retained. Offline is also the Last Will. |
status/scene/current | Active scene ID, retained. |
status/scene/event | {"event":"changed","from":"old","to":"new"} |
status/reload | JSON result with state, file and scene. |
input/<id> | Submitted text. Also available at status/<id>/input. |
status/<id>/click | Button: ON on press, OFF on release. Button grid: clicked label. |
status/<id> | Keypress and GPIO input changes: ON / OFF. |
status/<id>/event | Video / audio visualizer lifecycle: on_play, on_stop, on_finish. |
status/<id>/state | Media state: playing, stopped, finished, retained. |
status/audio/<channel>/event | Channel playback lifecycle events. |
status/audio/<channel>/file | Last started filename, retained. |
status/audio/<channel>/volume | Last requested volume, retained. |
status/audio/ambient/state | Ambient playback state, retained. |
ping | ON every 20 seconds. |
Lifecycle events are non-retained pulses; current-state topics are retained. Avoid retaining transient control commands that should not replay when a device reconnects.
At startup and after successful reloads, the Player publishes a retained description to <mqtt_base_topic>/autodiscovery/player_<device_id>. It includes the root topic, scenes, elements and typed MQTT input/output fields. Duplicate element IDs are listed in warnings.duplicate_element_ids.
mosquitto_sub -h 192.168.1.10 -t 'ergs/room01/status/#' -vMix visual elements with input and output. Each element has a type and a unique id.
Dynamic TTF text, multiline wrapping and live MQTT updates. Send a new string through update/<id>.
Mouse and touchscreen buttons publish ON/OFF on status/<id>/click. Use on_press and on_release for local action chains.
Static PNG/JPG textures. Combine position, size and layers to build backgrounds, clues and interface graphics.
VLC media playback, loops and Linux V4L2 camera feeds. Commands: play (ON/OFF) and file (filename). Use audio: false for silent video.
A text-based game timer that parses game-timer JSON. Connect an external timer_topic to the chosen timer_element_id.
Horizontal fill bar driven by a percentage value. Update the element value over MQTT to reflect game progress.
Circular HUD-style progress ring with center text and subtext. Drive the percentage through element value updates.
A matrix of buttons. Grid labels use pipe-separated strings such as 1|2|3|4. The clicked label is published to status/<id>/click.
Single-line keyboard input. Submitted text is published through input/<id> and status/<id>/input.
Non-visual keyboard controls on Windows and Linux. Key names accept F12, KEY_SPACE or A. Publishes ON/OFF to status/<id>.
Rolling message log. Prefix a message with {#FF4444} for a custom color. Commands include clear, maxlines and color.
Decorative borders and textured frames. Style with frame_color, frame_thickness, frame_patch9 and frame_padding.
Procedural fragment-shader surfaces. Send a JSON uniform map through update/<id>, or use cmd/<id>/uniform/<name>.
Audio playback with waveform, circle or particles modes. Supports MP3, WAV and OGG through the raylib audio backend.
Raspberry Pi physical input and output using BCM numbering. Modes: input, input_pullup and output. See the GPIO examples below.
Branching interviews using an existing video or audio visualizer and button, keypress or GPIO inputs. Stays hidden while idle. See the action and dialog guide below.
{
"type": "video",
"id": "hint_video",
"file": "hint01.mp4",
"loop": false,
"audio": true,
"layer": 0,
"on_finish": "hint_video.hide;message.show"
}Place hint01.mp4 in the assets directory. The message element must exist in the project. For a Linux camera stream, use "file": "/dev/video0".
Active audio visualizers can feed shader uniforms audioAmplitude, audioProgress and audioActive. Visualizer commands include play, file, volume and mode. Analysis availability depends on the platform and media backend.
Use semicolon-separated action chains in event hooks. They run from left to right. Buttons, keypress elements and input-mode GPIO support on_press and on_release. Video supports on_play, on_stop and on_finish.
main.switchto;message.set("Access granted");message.show;intro_video.stopSupported actions include .play, .stop, .switchto, .show, .hide, .toggle, .set("text") and .open('filename'). The open action loads and starts a dialog, video or audio visualizer.
A dialog references an existing media element using media_element_id and input elements using choice_input_ids. Input types can be mixed. Provide cancel_input_id and timeout_seconds as needed.
{
"type": "interactive_dialog",
"id": "interview",
"pos": [80, 400],
"size": [1120, 280],
"media_element_id": "dialog_audio",
"choice_input_ids": ["choice_a", "choice_b"],
"timeout_seconds": 60,
"content_font_size": 28,
"choice_font_size": 30
}Publish a metadata filename to root/cmd/interview/file to start or replace the dialog. Use root/cmd/interview/play with ON/OFF, or root/cmd/interview/close to close it.
Metadata uses intro_text, intro_audio and options. Options support id, label, question, answer, question_audio, answer_audio, important, repeatable, show_after and hide_after. Audio asset paths are relative to media/assets/.
The dialog ends when all important options have been asked, no options remain, cancel is pressed or the choice timeout expires. Set a file and autoplay: true to open the dialog whenever its scene becomes active.
For package installation, file locations and launching without a desktop, see the Raspberry Pi installation guide.
GPIO is available in Raspberry Pi builds and uses BCM numbering. The Debian 13 build uses the libgpiod v2 API. Inputs publish state over MQTT; outputs respond to commands from your game controller.
{
"type": "gpio",
"id": "door_sensor",
"pin": 17,
"mode": "input_pullup",
"invert": true
}
{
"type": "gpio",
"id": "relay",
"pin": 27,
"mode": "output",
"initial": "OFF"
}Add these as separate objects to the scene's elements array. The sensor publishes ON/OFF to root/status/door_sensor. Control the output with root/update/relay. invert reverses logical and physical levels.
The download targets arm64, not every historical Pi model. Check architecture, drivers and your scene's performance on the actual device. No universal boot-time or frame-rate guarantee is implied.
ERGS Player is not an open-source release. It follows a Freemium / OpenCore model. The license included with each distribution defines usage rights and the features available in that release. This guide does not establish pricing or a feature entitlement matrix.
ERGameStudio's public release is planned for late 2026. It will bring a WYSIWYG scene editor, node-based game logic, autodiscovery and remote upload/update workflows. Until then, use the standalone Player with JSON scenes and your MQTT controller.
Get ERGS Player ↓