use bevy::prelude::*;
use bevy::sprite::Anchor;
use bevy::window::WindowResolution;
const GAME_SCALE: f32 = 8.0;
const WALL_COLUMNS: i32 = 12;
const WALL_ROWS: i32 = 4;
const Z_BASE: f32 = 5_000.0;
const Z_ROW_SPACING: f32 = 100.0;
const Z_LAYER_SPACING: f32 = 10.0;
#[derive(States, Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
enum ReproState {
#[default]
Menu,
Gameplay,
}
#[derive(Resource)]
struct ReproAssets {
level_image: Handle<Image>,
tall_layout: Handle<TextureAtlasLayout>,
square_layout: Handle<TextureAtlasLayout>,
shadow_material: Handle<ColorMaterial>,
shadow_mesh: Handle<Mesh>,
}
fn main() {
App::new()
.add_plugins(
DefaultPlugins
.set(ImagePlugin::default_nearest())
.set(WindowPlugin {
primary_window: Some(Window {
title: "Bevy 0.19 state-transition flicker reproduction".into(),
resolution: WindowResolution::new(440, 800),
resizable: false,
..default()
}),
..default()
}),
)
.insert_resource(ClearColor(Color::srgb(0.025, 0.03, 0.045)))
.init_state::<ReproState>()
.add_systems(Startup, setup)
.add_systems(Update, transition_on_input)
.add_systems(OnEnter(ReproState::Gameplay), spawn_gameplay)
.add_systems(OnEnter(ReproState::Menu), announce_menu)
.run();
}
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
) {
commands.spawn((
Name::new("Persistent Camera"),
Camera2d,
Msaa::Off,
Projection::Orthographic(OrthographicProjection {
near: -100_000.0,
far: 100_000.0,
scale: 0.18,
..OrthographicProjection::default_2d()
}),
Transform::from_xyz(
(WALL_COLUMNS - 1) as f32 * GAME_SCALE * 0.5,
(WALL_ROWS - 1) as f32 * GAME_SCALE * 0.5,
0.0,
),
IsDefaultUiCamera,
));
commands.spawn((
Name::new("Instructions"),
Text::new("press space to toggle between gameplay and menu"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE),
TextLayout::justify(Justify::Center),
Node {
position_type: PositionType::Absolute,
top: Val::Px(20.0),
width: Val::Percent(100.0),
..default()
},
));
commands.insert_resource(ReproAssets {
level_image: asset_server.load("level_example.png"),
tall_layout: atlas_layouts.add(TextureAtlasLayout::from_grid(
UVec2::new(64, 128),
8,
3,
None,
None,
)),
square_layout: atlas_layouts.add(TextureAtlasLayout::from_grid(
UVec2::new(64, 64),
8,
6,
None,
None,
)),
shadow_material: materials.add(Color::srgba(0.0, 0.0, 0.0, 0.3)),
shadow_mesh: meshes.add(Rectangle::new(GAME_SCALE * 0.65, GAME_SCALE * 0.45)),
});
}
fn transition_on_input(
keys: Res<ButtonInput<KeyCode>>,
mouse: Res<ButtonInput<MouseButton>>,
state: Res<State<ReproState>>,
mut next_state: ResMut<NextState<ReproState>>,
) {
if !keys.just_pressed(KeyCode::Space) && !mouse.just_pressed(MouseButton::Left) {
return;
}
let next = match state.get() {
ReproState::Menu => ReproState::Gameplay,
ReproState::Gameplay => ReproState::Menu,
};
info!("Switching from {:?} to {next:?}", state.get());
next_state.set(next);
}
fn announce_menu() {
info!("Entered Menu; board contents should now be despawned");
}
fn spawn_gameplay(mut commands: Commands, assets: Res<ReproAssets>) {
let game_board = commands
.spawn((
Name::new("Game Board"),
DespawnOnExit(ReproState::Gameplay),
Transform::default(),
Visibility::Inherited,
))
.id();
let environment = commands
.spawn((
Name::new("Board Environment"),
Transform::default(),
Visibility::Inherited,
))
.id();
commands.entity(game_board).add_child(environment);
commands.entity(environment).with_children(|parent| {
for y in 0..WALL_ROWS {
for x in 0..WALL_COLUMNS {
let mut floor_transform = build_transform(x, y, 0.0);
floor_transform.translation.z = -2.0;
parent.spawn((
floor_transform,
Visibility::Inherited,
children![square_sprite(
assets.level_image.clone(),
assets.square_layout.clone(),
16,
)],
));
}
}
for y in 0..WALL_ROWS {
for x in 0..WALL_COLUMNS {
let wall_index = x.rem_euclid(2) as usize;
parent.spawn((
build_transform(x, y, -10.5),
Visibility::Inherited,
children![tall_sprite(
assets.level_image.clone(),
assets.tall_layout.clone(),
wall_index,
)],
));
if x % 3 == 0 {
parent.spawn((
build_transform(x, y, 1.0),
Visibility::Inherited,
children![tall_sprite(
assets.level_image.clone(),
assets.tall_layout.clone(),
2 + (x as usize % 6),
)],
));
}
}
}
for x in 0..WALL_COLUMNS {
let owner = build_transform(x, 1, 4.0);
parent.spawn((
owner,
Visibility::Inherited,
children![
(
Mesh2d(assets.shadow_mesh.clone()),
MeshMaterial2d(assets.shadow_material.clone()),
Transform::from_xyz(0.0, -GAME_SCALE * 0.05, -owner.translation.z - 1.0),
),
square_sprite(assets.level_image.clone(), assets.square_layout.clone(), 8,),
],
));
}
});
}
fn build_transform(x: i32, y: i32, layer: f32) -> Transform {
Transform::from_xyz(
GAME_SCALE * x as f32,
GAME_SCALE * y as f32,
Z_BASE - y as f32 * Z_ROW_SPACING + layer * Z_LAYER_SPACING,
)
}
fn tall_sprite(
image: Handle<Image>,
layout: Handle<TextureAtlasLayout>,
index: usize,
) -> (Anchor, Sprite) {
(
Anchor::from(Vec2::new(0.0, -0.25)),
Sprite {
image,
texture_atlas: Some(TextureAtlas { layout, index }),
custom_size: Some(Vec2::new(GAME_SCALE, GAME_SCALE * 2.0)),
..default()
},
)
}
fn square_sprite(image: Handle<Image>, layout: Handle<TextureAtlasLayout>, index: usize) -> Sprite {
Sprite {
image,
texture_atlas: Some(TextureAtlas { layout, index }),
custom_size: Some(Vec2::splat(GAME_SCALE)),
..default()
}
}
Bevy version and system info
If your bug is rendering-related, copy the adapter info that appears when you run Bevy.
What you did
Reproduction
put this asset in assets/
What went wrong
Sprites start flickering
Screen.Recording.2026-07-25.at.9.22.49.PM.mov