Bevy 0.10.1 ECS延迟生成子实体致间歇性渲染异常求助
Bevy 0.10.1 2048游戏棋盘间歇性渲染问题解决
问题核心
在使用Bevy 0.10.1开发2048时,棋盘tile的文本子实体出现间歇性不渲染/显示异常的情况。原代码通过commands.spawn创建tile后用with_children生成文本,尝试用废弃的ShouldRun延迟生成无效,推测问题源于实体重复创建、层级遮挡或ECS系统执行时机冲突。
关键解决方案
1. 避免实体堆积,批量清理旧实体
原代码每次调用display_game_board都会生成新实体,旧实体未销毁会导致渲染层级混乱、资源占用。给所有棋盘tile添加标签组件,每次生成前批量销毁旧实体:
#[derive(Component)] struct BoardTile; // 标记棋盘tile实体
2. 调整渲染层级,避免文本被遮挡
文本子实体与tile的Z轴相同,可能被tile sprite遮挡,将文本Z轴设为更高值(如1.0),确保显示在tile上方。
3. 规范ECS资源获取,确保数据有效性
使用Res而非直接传参获取GameBoard和GameFonts,保证获取的是最新的资源状态,避免数据不一致。
4. 控制系统执行时机
将棋盘更新逻辑绑定到棋盘状态变化事件(而非每一帧运行),比如定义BoardUpdated事件,仅在棋盘数据改变时触发实体生成,减少不必要的ECS操作。
修改后的完整代码
use bevy::prelude::*; // 自定义组件:标记棋盘tile实体 #[derive(Component)] struct BoardTile; // 自定义组件:存储tile的数值 #[derive(Component)] struct TileValue(u32); // 假设你的GameBoard和GameFonts定义如下 #[derive(Resource)] struct GameBoard { size: usize, board: Vec<Vec<Option<u32>>>, } #[derive(Resource)] struct GameFonts { main_font: Handle<Font>, } fn display_game_board( mut commands: Commands, game_board: Res<GameBoard>, fonts: Res<GameFonts>, mut tile_query: Query<Entity, With<BoardTile>>, ) { // 销毁所有旧的棋盘tile及其子实体 for entity in tile_query.iter_mut() { commands.entity(entity).despawn_recursive(); } let tile_size = 100.0; let gap_size = 5.0; let board_width = (tile_size + gap_size) * game_board.size as f32 - gap_size; let board_height = (tile_size + gap_size) * game_board.size as f32 - gap_size; // 棋盘居中偏移 let translation = Vec3::new( (-board_width / 2.0) + (tile_size / 2.0), (-board_height / 2.0) + (tile_size / 2.0), 0.0, ); for row in 0..game_board.size { for col in 0..game_board.size { let tile_translation = translation + Vec3::new( col as f32 * (tile_size + gap_size), row as f32 * (tile_size + gap_size), 0.0, ); // 生成空tile背景(添加BoardTile标签) commands.spawn(( SpriteBundle { sprite: Sprite { color: Color::rgba_u8(200, 200, 200, 255), // 修正alpha值:原6几乎透明,改为255正常显示 custom_size: Some(Vec2::new(tile_size, tile_size)), ..default() }, transform: Transform::from_translation(tile_translation), ..default() }, BoardTile, )); if let Some(value) = game_board.board[row][col] { let text = value.to_string(); let brightness = (value as f32 / 2048.0).min(1.0); // 生成有值的tile,Z轴设为0.5(在背景上方) let tile_entity = commands.spawn(( SpriteBundle { sprite: Sprite { color: Color::rgba(brightness, brightness, brightness, 1.0), custom_size: Some(Vec2::new(tile_size, tile_size)), ..default() }, transform: Transform::from_translation(tile_translation + Vec3::Z * 0.5), ..default() }, BoardTile, TileValue(value), )).id(); // 文本子实体Z轴设为1.0(在tile上方) commands.entity(tile_entity).with_children(|parent| { parent.spawn(Text2dBundle { text: Text::from_section( text, TextStyle { font: fonts.main_font.clone(), font_size: 40.0, color: Color::WHITE, }, ), transform: Transform::from_translation(Vec3::Z * 1.0), ..default() }); }); } } } }
额外注意事项
- 字体加载确认:确保
GameFonts中的main_font已完全加载,可通过AssetServer的load_state或AssetEvent监听加载完成事件,再初始化GameFonts资源。 - 事件触发逻辑:添加
BoardUpdated事件,仅在玩家移动、合并tile等棋盘数据变化时发送事件,绑定display_game_board系统响应事件,避免无意义的实体重建。
内容的提问来源于stack exchange,提问作者Ruben Villalvazo Manríquez
相关产品推荐
相关产品推荐

