About bevy-capture
bevy-capture is a skill for recording Bevy 0.19 camera output, aimed at developers producing gameplay, cinematic, or render-test footage. It explains headless capture setup, encoder selection, and common recording pitfalls.
Key features
- 01Headless Camera2d and Camera3d capture
- 02MP4, GIF, and PNG sequence output
- 03OpenH264 and ffmpeg encoder choices
- 04Guidance on deterministic batch capture
- 05Troubleshooting encoder and output issues
Use cases
- 01Record gameplay or cinematic footage
- 02Capture evidence for render tests
- 03Choose an encoder for offline or streaming capture
Install
Copy the command and run it in a terminal.
$
npx skills add https:/ / github.com/ chrisgliddon/ bevy-skills --skill bevy-captureAfter installing, type/bevy-capturein Claude Code to confirm it loaded.
Bundled filesreferences/compatibility.mdreferences/encoders.md
Details
From the project READMEbevy-capture — recording Bevy 0.19 cameras
When to use this skill
- Record deterministic gameplay, cinematics, or render-test evidence.
- Capture a headless
Camera2d/Camera3dto MP4, GIF, or numbered PNGs. - Choose between in-process OpenH264, ffmpeg, and lossless frame output.
- Diagnose a missing encoder module, an empty output, or an unflushed recording.
bevy_capture = "0.6" natively targets Bevy 0.19. Do not carry forward the
0.4.x Bevy patch or its old CameraTargetHeadless API.
Canonical pattern
[dependencies]
bevy = "0.19"
bevy_capture = { version = "0.6", features = ["mp4_openh264"] }
use bevy::{
app::{RunMode, ScheduleRunnerPlugin},
camera::RenderTarget,
prelude::*,
render::RenderPlugin,
winit::WinitPlugin,
};
use bevy_capture::{
encoder::mp4_openh264::Mp4Openh264Encoder,
Capture, CaptureBundle, CapturePlugin, RenderTargetHeadless,
};
use std::fs::{self, File};
const WIDTH: u16 = 1280;
const HEIGHT: u16 = 720;
fn main() -> AppExit {
App::new()
.add_plugins((
DefaultPlugins.build().disable::<WinitPlugin>().set(RenderPlugin {
synchronous_pipeline_compilation: true,
..default()
}),
ScheduleRunnerPlugin {
run_mode: RunMode::Loop { wait: None },
},
CapturePlugin,
))
.add_systems(Startup, setup)
.add_systems(Update, record)
.run()
}
fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
commands.spawn((
Camera2d,
RenderTarget::target_headless(WIDTH.into(), HEIGHT.into(), &mut images),
CaptureBundle::default(),
));
}
fn record(
mut captures: Query<&mut Capture>,
mut waited_for_pipeline: Local<bool>,
mut frame: Local<u32>,
) {
// Allow the render pipeline one frame to become ready.
if !*waited_for_pipeline {
*waited_for_pipeline = true;
return;
}
let Ok(mut capture) = captures.single_mut() else { return };
if !capture.is_capturing() && *frame == 0 {
fs::create_dir_all("captures").expect("create captures directory");
let encoder = Mp4Openh264Encoder::new(
File::create("captures/run.mp4").expect("create output"),
WIDTH,
HEIGHT,
)
.expect("initialize OpenH264");
capture.start(encoder);
}
*frame += 1;
if *frame == 300 {
capture.stop(); // drops the encoder and finalizes the MP4
}
}
Encoder choice
| Encoder | Feature | External program | Best fit |
|---|---|---|---|
Mp4Openh264Encoder | mp4_openh264 | No | In-process H.264 |
Mp4FfmpegCliEncoder | mp4_ffmpeg_cli | ffmpeg | Short/offline clips |
Mp4FfmpegCliPipeEncoder | mp4_ffmpeg_cli_pipe | ffmpeg | Long streaming captures |
GifEncoder | gif | No | Small previews |
FramesEncoder | none | No | Lossless PNG sequence |
See encoder details before choosing a production codec or distribution model.
Gotchas
RenderTargetHeadlessis implemented forRenderTarget; callRenderTarget::target_headless(...). The old helper onCamerais stale.- Attach
Camera2d/Camera3d, the headlessRenderTarget, andCaptureBundleto the same entity. - Wait at least one frame before starting. Synchronous pipeline compilation improves deterministic batch capture but can increase startup time.
- Encoder modules are feature-gated. Enable exactly the features you import.
Capture::stop()or droppingCapturefinalizes encoders. Abrupt process termination can leave an invalid MP4 or GIF.- The ffmpeg encoders require
ffmpegonPATH; constructors returnResult. - Match the encoder dimensions to the headless target. OpenH264 takes
u16. - Browser WASM has no ordinary filesystem or child processes. Design an explicit JS download/streaming path instead of assuming these encoders work.
See also
bevy-cameras— cameras and render targets.bevy-cargo-features— feature selection.bevy-wasm-webgpu— browser constraints.- Encoder comparison.
- 0.19 compatibility.
Comments
Markdown supported