use bevy::prelude::*;
use bevy_simple_screenshot::prelude::*;
use clap::{Parser, ValueEnum};
#[derive(Parser, Debug)]
#[command(name = "moving_object")]
#[command(about = "Screenshot example with burn-in text overlay")]
struct Args {
#[arg(long, short, default_value = "upper-right")]
position: PositionArg,
#[arg(long, short = 's', default_value = "18")]
font_size: f32,
#[arg(long, short = 'k')]
show_key: bool,
#[arg(long, short = 'd')]
show_description: bool,
#[arg(long, short = 'e')]
entity_only: bool,
#[arg(long, default_value = "20")]
padding: u32,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
enum PositionArg {
UpperLeft,
UpperRight,
LowerLeft,
LowerRight,
}
impl From<PositionArg> for BurnInPosition {
fn from(p: PositionArg) -> Self {
match p {
PositionArg::UpperLeft => BurnInPosition::UpperLeft,
PositionArg::UpperRight => BurnInPosition::UpperRight,
PositionArg::LowerLeft => BurnInPosition::LowerLeft,
PositionArg::LowerRight => BurnInPosition::LowerRight,
}
}
}
#[derive(Resource)]
struct ScreenshotMode {
entity_only: bool,
padding: u32,
}
fn main() {
let args = Args::parse();
println!("Screenshot config:");
println!(" Mode: {}", if args.entity_only { "entity-only" } else { "full window" });
if args.entity_only {
println!(" Padding: {}px", args.padding);
}
println!("Burn-in config:");
println!(" Position: {:?}", args.position);
println!(" Font size: {}", args.font_size);
println!(" Show key: {}", args.show_key);
println!(" Show description: {}", args.show_description);
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Screenshot Example - Moving Object".into(),
resolution: (800u32, 600u32).into(),
..default()
}),
..default()
}))
.add_plugins(ScreenshotBufferPlugin::with_config(
ScreenshotConfig::default()
.with_output_dir(".screenshots")
.with_buffer_capacity(5)
.with_burn_in(
BurnInConfig::enabled()
.with_font("FiraMono-Medium.ttf")
.with_position(args.position.into())
.with_font_size(args.font_size)
.with_show_key(args.show_key)
.with_show_description(args.show_description),
),
))
.insert_resource(ScreenshotMode {
entity_only: args.entity_only,
padding: args.padding,
})
.add_systems(Startup, setup)
.add_systems(Update, (move_object, animate_color, screenshot_on_position))
.run();
}
#[derive(Component)]
struct MovingObject {
speed: f32,
screenshot_positions: Vec<f32>,
screenshots_taken: Vec<f32>,
}
#[derive(Component)]
struct ColorAnimation {
speed: f32,
phase: f32,
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d::default());
commands.spawn((
Sprite {
color: Color::srgb(1.0, 0.0, 0.0), custom_size: Some(Vec2::new(50.0, 50.0)),
..default()
},
Transform::from_translation(Vec3::new(-300.0, 0.0, 0.0)),
MovingObject {
speed: 150.0,
screenshot_positions: vec![-200.0, -100.0, 0.0, 100.0, 200.0],
screenshots_taken: vec![],
},
ColorAnimation {
speed: 0.381,
phase: 0.0,
},
));
}
fn move_object(time: Res<Time>, mut query: Query<(&mut Transform, &mut MovingObject)>) {
for (mut transform, mut obj) in &mut query {
transform.translation.x += obj.speed * time.delta_secs();
if transform.translation.x > 350.0 {
transform.translation.x = -350.0;
obj.screenshots_taken.clear();
}
}
}
fn animate_color(time: Res<Time>, mut query: Query<(&mut Sprite, &mut ColorAnimation)>) {
for (mut sprite, mut anim) in &mut query {
anim.phase += time.delta_secs() * anim.speed;
let t = (anim.phase * std::f32::consts::PI).sin().abs();
let r = 1.0 - t;
let g = t;
let b = 0.0;
sprite.color = Color::srgb(r, g, b);
}
}
fn screenshot_on_position(
trigger: ScreenshotTrigger,
entity_trigger: EntityScreenshotTrigger,
mode: Res<ScreenshotMode>,
mut query: Query<(Entity, &Transform, &mut MovingObject)>,
) {
for (entity, transform, mut obj) in &mut query {
let x = transform.translation.x;
for &pos in &obj.screenshot_positions.clone() {
if x >= pos && !obj.screenshots_taken.contains(&pos) {
let desc = format!("position_{}", pos as i32);
if mode.entity_only {
let settings = EntityScreenshotSettings::default().with_padding(mode.padding);
screenshot_entity!(&entity_trigger, entity, "movement", &desc, settings);
info!("Entity screenshot taken at x={} (padding={}px)", pos, mode.padding);
} else {
screenshot!(&trigger, "movement", &desc);
info!("Screenshot taken at x={}", pos);
}
obj.screenshots_taken.push(pos);
}
}
}
}