bubbles
Branching dialogue for Rust games. Write scripts, compile once, drive from any game loop.
No async. No global state. No engine lock-in. Pull events when your game is ready, pick an option, continue. That's the whole API.
Works with Bevy, Godot-rust, Macroquad, or any custom engine. Unity and other native hosts can use bubbles-ffi - a C ABI wrapper with JSON events for P/Invoke.
What a script looks like
title: Start
tags: scene docks outdoor
---
<<declare $gold = 25>>
=> Dockworker: Oi, watch yer step!
=> Dockworker: These crates won't unload themselves, ye know.
=> Dockworker: Smells like low tide and regret out here.
Stumpy: Name's McGee. Stumpy McGee, Harbormaster.
Stumpy: Ye can't sail from Barnacle Bay without a travel permit.
<<jump Options>>
===
The harbour demo continues in examples/harbour/ with an Options node (guarded choices, <<detour MapSeller>>, <<jump Depart>>). Run it with:
Quick start
[]
= "1.0.0"
On crates.io the package is bubbles-dialogue (the name bubbles was already taken). The Rust library is still bubbles, so you write use bubbles::{…} in code.
use ;
Features
| Feature | Description |
|---|---|
| Lines and options | Plain text, Speaker: attribution, -> branches with optional <<if>> guards |
| Variables | Number, Text, Bool; <<declare>>, <<set>>, full expression language |
| Inline interpolation | {$var}, {$gold / 2}, {plural($n, "gem", "gems")} - evaluated before the event arrives |
| Inline markup | [b]text[/b], [color value=red]...[/color], [pause /] - stripped from text, returned as byte-precise MarkupSpans |
| Jumps and detours | <<jump Node>> replaces the call stack; <<detour Node>> / <<return>> push/pop it |
| Conditionals | <<if>> / <<elseif>> / <<else>> / <<endif>> |
| Once blocks | <<once>> content that fires exactly once, with optional <<else>> for every subsequent visit |
| Line groups | => alternatives chosen by the active saliency strategy - no repeated barks |
| Node groups | Multiple nodes sharing a title with when: conditions - great for time-of-day, relationship state |
| Saliency | FirstAvailable, RandomAvailable, BestLeastRecentlyViewed, or your own |
| Host commands | <<play_sound bell>> surfaces as DialogueEvent::Command |
| Built-in functions | visited, visited_count, random, random_range, dice, round, floor, ceil, min, max, abs, clamp, string, int, plural, select |
| Custom functions | Register closures callable from any expression |
| Localisation | LineProvider for #line:id lookup; translated templates can contain {expr} and [markup] |
| Multi-file | compile_many(&[(name, src)]) with duplicate-title detection and cross-file jump validation |
| Bookmarks / save | Runner::snapshot / Runner::restore with RunnerSnapshot (always available); enable serde to persist snapshot and HashMapStorage to disk |
| Line modes | #narration and #debug on lines set DialogueEvent::Line::line_mode for filtering or routing |
| Variable inspection | Runner::all_variables, Runner::variable, Runner::variable_ref; VariableStorage::all_variables (override on custom stores) |
| Option groups | #group:<name> on options for UI constraints (radio buttons, mutually exclusive choices) |
Feature flags
| Flag | Default | |
|---|---|---|
rand |
on | random(), random_range(), dice(), RandomAvailable |
serde |
off | Serialize / Deserialize on Value, HashMapStorage, RunnerSnapshot |
full |
off | Both of the above |
Prior art
Bubbles sits in the same space as Yarn Spinner and Ink. The difference is integration model: Bubbles compiles to a plain Rust struct and runs wherever your binary runs - no binary format, no external runtime, no Unity package. If you want something that drops into a Bevy, Godot-rust, or Macroquad project in an afternoon, that's what this is for.
Learn more
The full guide - language reference, integration walkthrough, localisation, save/load, WebAssembly, and annotated examples - lives at https://viezevingertjes.github.io/bubbles/.
Contributing
All changes to main go through a pull request (no direct pushes). These status checks must pass before merge:
| Check | Source |
|---|---|
ci |
GitHub Actions (format, clippy, tests, docs, …) |
msrv |
GitHub Actions (MSRV compile) |
The branch must be up to date with main before merge. Conversation threads on the PR must be resolved. Force-push and branch deletion on main are blocked.
See CONTRIBUTING.md for development setup and code quality gates.
License
Licensed under either of Apache-2.0 or MIT at your option.