This repository contains a basic multiplayer game inspired by Achtung, die Kurve!, written in Rust using Bevy Engine (v0.18) and Avian Physics. The goal of the project was to explore network programming when real-time movement is involved.
⬆️ Play the local multiplayer build on itch.io. A signalling server for the online multiplayer build is not currently deployed.
- Implementation of Achtung, die Kurve! (also known as Curve Fever)
- Local multiplayer (up to 5 players)
- Online multiplayer (up to 8 players)
- Native and browser-friendly online multiplayer (plus a standalone signalling server)
- You can mix and match local and online players in the same game
- Touch controls for mobile devices
- Cross-platform (Linux, Windows, WebAssembly)
- Uses
bevy_matchboxwhen theonlineCargo feature flags is provided - Supports native and WASM/browser builds
- Both backends use a client-server topology: one instance acts as host, all others connect as clients
- Wire data is serialised with
postcardand sent over three channels:Unreliable,ReliableUnordered, andReliableOrdered - Browser/WASM Matchbox builds broker initial WebRTC connections through the standalone WebSocket signalling server
(
mooplas_signalling_server, see README); ICE uses Google's public STUN (stun.l.google.com:19302) - The standalone signalling server uses the WebSocket URL path as the room ID and supports multiple independent rooms on one process
- The host generates a 6-character room ID and shares only that ID with clients; internally the host connects to the
same room URL with
?role=host, while clients enter the bare room ID or paste a full room URL in the join menu SIGNALLING_SERVER_URLis baked in at build time (defaults tows://localhost:3536); for native development the signalling server starts embedded in the host process and remains a single-room local-dev helper
Online multiplayer:
Lobby with touch controls enabled:
In-game:
You can run this project in any way you like, but I have set things up to make it easy to develop using JetBrains RustRover. For this, you'll need:
direnv- Any Direnv integration plugin e.g. https://plugins.jetbrains.com/plugin/15285-direnv-integration
nix
This way, you'll just need to direnv allow in the project directory after which all prerequisites (incl. Rust, Cargo,
all Bevy dependencies, etc.) will be available to you. The JetBrains plugin will ensure that the environment is
available to your IDE and you can run the project from there (vs cargo build and cargo run in the terminal).
RustRover will always fail to sync the project when you open it because it doesn't wait for direnv. Just re-sync
immediately after the failure and it will work.
Did RustRover forget where the Rust standard library is again? Run the below and update the path in the settings:
find /nix/store -type d -name rust_lib_srccargo sweep is your friend and comes with the Flake. For example, the below will delete all build artefacts that
are older than 7 days:
cargo sweep -t 7To clean everything except for the latest build:
cargo sweep --stamp
<Insert any number of cargo build, cargo test, etc. commands>
cargo sweep --file
Upgrade the flake by running nix flake update --flake . in the repository's base directory.
Note
Browser builds intended for online multiplayer should use the Matchbox backend (--features online) and a
reachable signalling server URL.
- Run (already included in the Flake if using Nix):
rustup target add wasm32-unknown-unknown
- Set
RUSTFLAGSenvironment variable:- Linux:
export RUSTFLAGS="--cfg=web_sys_unstable_apis --cfg=getrandom_backend=\"wasm_js\""
- Windows:
$env:RUSTFLAGS="--cfg=web_sys_unstable_apis --cfg=getrandom_backend=`"wasm_js`""
- Linux:
- Set
SIGNALLING_SERVER_URLwhen building for production. If omitted, the build falls back tows://localhost:3536, which is useful for native/local development but not for deployed HTTPS browser builds.- Linux:
export SIGNALLING_SERVER_URL="wss://signal.example.com"
- Windows:
$env:SIGNALLING_SERVER_URL="wss://signal.example.com"
- Linux:
- Make sure you have Node.js with
serveinstalled
Then you can build the WASM file:
- Build the WASM file:
For local development builds you can omit
SIGNALLING_SERVER_URL='ws://localhost:3536' RUSTFLAGS='--cfg=web_sys_unstable_apis --cfg=getrandom_backend="wasm_js"' cargo build --target wasm32-unknown-unknown --release --manifest-path mooplas_game/Cargo.toml --package mooplas_game --bin mooplas_game --no-default-features --features online
SIGNALLING_SERVER_URLand the game will usews://localhost:3536. - Clean the
/www/publicdirectory and copy the game's assets over:- Linux:
./scripts/clean-mooplas-files.sh ./scripts/copy-assets.sh
- Windows:
./scripts/clean-wasm-files.ps1 ./scripts/copy-assets.ps1
- Linux:
- Run
wasm-bindgento generate the JS bindings and move all relevant files to the/www/publicdirectory:- Linux:
wasm-bindgen --out-dir ./www/public --target web ./target/wasm32-unknown-unknown/release/mooplas_game.wasm
- Windows:
wasm-bindgen.exe --out-dir ./www/public --target web ./target/wasm32-unknown-unknown/release/mooplas_game.wasm
- Linux:
- Zip the WASM file if needed (e.g. for itch.io):
zip -r mooplas.zip ./www/public
You can optimise the WASM file (from the Unofficial Bevy Cheat Book):
# Optimize for size (z profile).
wasm-opt -Oz -o output.wasm input.wasm
# Optimize for size (s profile).
wasm-opt -Os -o output.wasm input.wasm
# Optimize for speed.
wasm-opt -O3 -o output.wasm input.wasm
# Optimize for both size and speed.
wasm-opt -O -ol 100 -s 100 -o output.wasm input.wasmFinally, to run the game in your browser locally, run the below and paste the URL copied to your clipboard into your browser:
npx serve ./www/public



