- Rust 98.7%
- Nix 1.3%
Adds a View section to the editor sidebar that exposes ViewConfig fields directly. The auto-fit checkbox enables or disables automatic camera scaling, and the logarithmic zoom slider adjusts the zoom multiplier. Changes take effect immediately without triggering an entity respawn. |
||
|---|---|---|
| .github | ||
| docs/plans | ||
| src | ||
| .envrc | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| COPYING | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| README.md | ||
muni-tuber
A customizable vtubing app built with Bevy, made for municorn (me), but also for you!
Characters are defined as layered sprite hierarchies with spring physics, volume-reactive mouths, blinking eyes, and switchable expressions — all configurable through a built-in editor without touching code.
Features
- Layered sprite characters — compose your character from any number of image layers arranged in a parent-child hierarchy
- Volume-reactive mouths — up to four speaking phases (quiet, half-speak, speak, yell) driven by your microphone
- Spring physics — child layers wobble and lag behind parent movement for a natural, bouncy feel
- Blink animation — layers can blink on a random timer or on expression change
- Breathing idle animation — subtle sinusoidal scale oscillation on the root layer
- Expressions — named sets of per-layer image overrides switchable by hotkey
- Built-in editor — live property editing, layer tree, threshold meters, and file management
- Chroma key — configurable background color (yellow by default) for OBS or similar capture
- RON character files — human-readable save format, stored in your platform data directory
Requirements
- Rust (nightly toolchain; see
devenv.nixor install via rustup) - Bevy 0.18 and its platform graphics dependencies (see Dependencies below)
Running
Clone the repository:
git clone https://codeberg.org/municorn/muni-tuber
cd muni-tuber
With Nix / devenv (recommended)
If you have devenv installed, all system dependencies and the Rust toolchain are provided automatically:
devenv shell cargo run
Without Nix
Install the system libraries listed under Linux dependencies and then run:
cargo run
Linux dependencies
Bevy requires several system libraries on Linux. Install the packages for your distribution, or use
the devenv shell (see above) which provides them automatically via Nix.
| Library | Purpose |
|---|---|
alsa-lib |
Audio capture (microphone input) |
udev |
Device enumeration |
vulkan-loader |
GPU rendering |
libX11 |
X11 windowing |
libXcursor |
X11 cursor support |
libXi |
X11 input |
libXrandr |
X11 display configuration |
libxkbcommon |
Keyboard handling (Wayland) |
wayland |
Wayland windowing |
libGL / libGLU |
OpenGL (fallback renderer) |
Creating a character
Character file format
Characters are saved as .ron files in your platform data directory:
| Platform | Path |
|---|---|
| Linux | ~/.local/share/muni-tuber/characters/ |
| Windows | %APPDATA%\muni-tuber\characters\ |
| macOS | ~/Library/Application Support/muni-tuber/characters/ |
You can also open and save files from any location using the file dialog buttons in the editor.
Layer hierarchy
A character is built from a tree of layers. Each layer is a sprite image that can be attached to a parent at configurable anchor points.
Body (root layer)
├── Head
│ ├── Eyes (can_blink = true)
│ ├── Hair strand 1
│ └── Hair strand 2
└── Accessory
Root layers have no parent. All other layers specify a parent_id matching their parent layer's
id.
Pin points
Each layer has two anchor points, both expressed as normalized coordinates where (0.0, 0.0) is the
top-left of the sprite and (1.0, 1.0) is the bottom-right:
| Field | Meaning |
|---|---|
parent_anchor |
The point on the parent sprite where this layer attaches |
self_anchor |
The point on this layer's sprite that aligns with the parent anchor |
For example, to attach a head layer to the top-center of a body:
parent_anchor:(0.5, 0.0)— top-center of the body spriteself_anchor:(0.5, 1.0)— bottom-center of the head sprite
Volume images
Each layer holds a set of images for each speaking phase:
| Field | Phase | Fallback |
|---|---|---|
quiet |
Silent | (required) |
half_speak |
Murmuring | quiet |
speak |
Talking | half_speak |
yell |
Very loud | speak |
blink |
Blinking | current phase image |
Only quiet is required. Any omitted variant falls back through the chain shown above.
Volume thresholds
The four speaking phases are separated by dBFS thresholds (negative numbers, where 0 is maximum loudness):
| Threshold | Default | Meaning |
|---|---|---|
half_speak |
−46 dBFS | Minimum volume to enter HalfSpeak |
speak |
−30 dBFS | Minimum volume to enter FullSpeak |
yell |
−5 dBFS | Minimum volume to enter Yell |
Adjust these in the editor's Volume thresholds panel while watching the live meter.
Spring physics
Layers with spring physics enabled lag behind parent motion, producing a natural wobble. Each layer has its own spring configuration:
| Field | Default | Meaning |
|---|---|---|
enabled |
true |
Whether spring physics run on this layer |
stiffness |
150 | Restoring force (higher = snappier) |
damping |
12 | Energy dissipation (higher = less bounce) |
mass |
1.0 | Simulated mass (higher = more sluggish) |
rotation_stiffness |
150 | Same as stiffness, applied to rotation |
rotation_damping |
12 | Same as damping, applied to rotation |
Set enabled = false for layers that should snap rigidly to their parent without delay (e.g. a
rigid accessory glued to the head).
The underlying spring formula is a standard spring-damper:
force = -stiffness × displacement − damping × velocity
acceleration = force / mass
velocity += acceleration × dt
position += velocity × dt
Rotation uses the same formula with rotation_stiffness and rotation_damping. Velocity is clamped
each frame to prevent blow-up on large timesteps.
Expressions
An expression is a named set of per-layer image overrides. When an expression is active its overrides replace each referenced layer's default images. Layers not mentioned in an expression continue showing their defaults.
Expressions are activated by hotkeys or by using the preview button in the editor's expression panel.
Hotkeys
Hotkey bindings associate keyboard keys with actions:
| Action | Behaviour |
|---|---|
SwitchExpression |
Toggle the named expression on/off (press again to deactivate) |
HoldExpression |
Activate expression while key is held; deactivate on release |
ForceBlink |
Immediately trigger a blink on all blinkable layers |
Keys are specified by their Bevy KeyCode variant name, e.g. "F1", "KeyA", "Space". The
editor's Hotkeys panel provides a key-capture mode — click Record, press the desired key,
and the binding is set automatically.
Animations
| Animation | Description |
|---|---|
| Breathing | Sinusoidal scale oscillation on the root layer (idle) |
| Pop | Short scale impulse on the root layer when speaking starts |
| Blink | Random blink (1–5 s interval, 0.2 s duration) per layer with can_blink = true |
Blink is also triggered on every expression change.
Using the editor
Press Tab to toggle between editor mode and streaming mode.
Editor mode
The left panel contains all editing tools:
| Section | Purpose |
|---|---|
| Character picker | Save, load, create, and name characters; set background color |
| Layer tree | Hierarchical layer list; add, delete, duplicate, and reorder layers |
| Layer properties | Edit the selected layer's name, parent, anchors, spring, and images |
| Volume thresholds | Adjust dBFS thresholds with a live microphone meter |
| Expression editor | Create, rename, and delete expressions; set per-layer image overrides |
| Hotkey configuration | Add, remove, and record hotkey bindings |
Streaming mode
Press Tab to hide the editor. The character fills the entire window, ready for stream capture. The background color (configurable in the character picker) defaults to yellow for chroma keying.
Keyboard shortcuts
| Key | Action |
|---|---|
Tab |
Toggle editor panel on/off |
All other shortcuts are user-defined hotkeys configured in the Hotkeys panel.
Dependencies
| Crate | Version | Purpose |
|---|---|---|
bevy |
0.18 | Game engine and ECS |
bevy_egui |
0.39 | Editor UI overlay |
cpal |
0.16 | Microphone input |
serde |
1 | Serialization derives |
ron |
0.10 | Character config format |
dirs |
6 | Platform data directories |
rfd |
0.15 | Native file dialogs |
rand |
0.9 | Random blink intervals |
Reporting issues
If you run into problems, don't hesitate to open an issue!