Bevy 0.10.1中如何渲染3D文本?支持macOS与WASM
针对Bevy 0.10.1的3D动态文本渲染方案
以下是适配你的需求(兼容macOS/WASM、可动态更新、作为3D实体子节点)的几种可行方案,按推荐程度排序:
方案1:使用bevy_prototype_text_mesh第三方crate(推荐)
这个crate专门为Bevy提供3D文本网格生成能力,直接与PbrBundle兼容,支持动态更新文本内容,且完美适配Bevy 0.10.1和目标平台。
步骤1:添加依赖
在Cargo.toml中加入对应版本的依赖:
[dependencies] bevy = "0.10.1" bevy_prototype_text_mesh = "0.10.0" # 与Bevy 0.10.x版本匹配
步骤2:初始化插件与字体资源
use bevy::prelude::*; use bevy_prototype_text_mesh::*; #[derive(Resource)] struct GameFont(Handle<Font>); fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(TextMeshPlugin) .add_startup_system(load_game_font) .add_system(update_card_numbers) .run(); } // 预加载游戏字体 fn load_game_font(mut commands: Commands, asset_server: Res<AssetServer>) { commands.insert_resource(GameFont(asset_server.load("fonts/your_font.ttf"))); }
步骤3:实现render_text函数
该函数会生成带自定义组件的文本实体,并挂载为卡牌的子节点:
#[derive(Component)] struct CardNumberComponent { value: u32, } fn render_text( commands: &mut Commands, game_font: Res<GameFont>, parent_entity: Entity, initial_value: u32, transform: Transform, ) -> Entity { commands.spawn(( TextMeshBundle { text_mesh: TextMesh::new(game_font.0.clone(), initial_value.to_string()) .with_font_size(24.0) .with_color(Color::WHITE), transform, // 绑定PBR材质以支持3D光照 pbr: PbrBundle { material: commands.spawn(MaterialBundle { material: StandardMaterial { base_color: Color::WHITE, ..Default::default() }, ..Default::default() }).id(), ..Default::default() }, ..Default::default() }, CardNumberComponent { value: initial_value }, )) .set_parent(parent_entity) .id() }
步骤4:动态更新文本
通过Query监听自定义组件的变化,实时更新文本内容:
fn update_card_numbers(mut query: Query<(&mut TextMesh, &CardNumberComponent)>) { for (mut text_mesh, number_comp) in query.iter_mut() { let target_text = number_comp.value.to_string(); if text_mesh.text() != target_text { text_mesh.set_text(target_text); } } }
方案2:运行时生成纹理绑定到PbrBundle
如果你不想依赖第三方crate,可以手动用rusttype生成文本纹理,直接通过Bevy的Assets<Image>管理,无需文件系统操作,兼容WASM。
步骤1:添加依赖
[dependencies] bevy = "0.10.1" rusttype = "0.9.2"
步骤2:实现文本纹理生成函数
use bevy::prelude::*; use rusttype::{Font, Scale, point}; fn generate_text_image(text: &str, font_data: &[u8], font_size: f32, color: Color) -> Image { let font = Font::try_from_bytes(font_data).unwrap(); let scale = Scale::uniform(font_size); let v_metrics = font.v_metrics(scale); // 计算文本边界 let glyphs: Vec<_> = font.layout(text, scale, point(0.0, v_metrics.ascent)).collect(); let width = glyphs.iter() .map(|g| g.position().x + g.unpositioned().h_metrics().advance_width) .max_by(|a, b| a.partial_cmp(b).unwrap()) .unwrap_or(0.0) as u32; let height = (v_metrics.ascent.abs() + v_metrics.descent.abs()) as u32; // 生成RGBA纹理缓冲区 let mut buffer = vec![0; (width * height * 4) as usize]; for glyph in glyphs { if let Some(bb) = glyph.pixel_bounding_box() { glyph.draw(|x, y, alpha| { let x = x + bb.min.x as u32; let y = y + bb.min.y as u32; if x < width && y < height { let idx = (y * width + x) * 4; buffer[idx] = (color.r() * 255.0) as u8; buffer[idx + 1] = (color.g() * 255.0) as u8; buffer[idx + 2] = (color.b() * 255.0) as u8; buffer[idx + 3] = (color.a() * alpha * 255.0) as u8; } }); } } Image::new_fill( UVec2::new(width, height), ImagePixelFormat::Rgba8UnormSrgb, &buffer, ) }
步骤3:实现render_text与更新逻辑
#[derive(Component)] struct CardNumberComponent { value: u32, current_text: String, font_handle: Handle<Font>, } fn render_text( commands: &mut Commands, asset_server: Res<AssetServer>, mut images: ResMut<Assets<Image>>, mut materials: ResMut<Assets<StandardMaterial>>, fonts: Res<Assets<Font>>, parent_entity: Entity, initial_value: u32, transform: Transform, ) -> Entity { let font = fonts.get(&asset_server.load("fonts/your_font.ttf")).unwrap(); let initial_text = initial_value.to_string(); let text_image = generate_text_image(&initial_text, font.data(), 24.0, Color::WHITE); let image_handle = images.add(text_image); let material_handle = materials.add(StandardMaterial { base_color_texture: Some(image_handle), alpha_mode: AlphaMode::Blend, ..Default::default() }); commands.spawn(( PbrBundle { mesh: commands.spawn(MeshBundle { mesh: shape::Quad::new(Vec2::new(1.0, 0.2)).into(), // 根据文本尺寸调整Quad大小 transform: Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, -90.0, 0.0, 0.0)), ..Default::default() }).id(), material: material_handle, transform, ..Default::default() }, CardNumberComponent { value: initial_value, current_text: initial_text, font_handle: asset_server.load("fonts/your_font.ttf"), }, )) .set_parent(parent_entity) .id() } // 动态更新文本纹理 fn update_card_numbers( mut query: Query<(&mut Handle<StandardMaterial>, &mut CardNumberComponent)>, mut materials: ResMut<Assets<StandardMaterial>>, mut images: ResMut<Assets<Image>>, fonts: Res<Assets<Font>>, ) { for (mut mat_handle, mut number_comp) in query.iter_mut() { let target_text = number_comp.value.to_string(); if number_comp.current_text != target_text { let font = fonts.get(&number_comp.font_handle).unwrap(); let new_image = generate_text_image(&target_text, font.data(), 24.0, Color::WHITE); let new_image_handle = images.add(new_image); if let Some(material) = materials.get_mut(mat_handle.as_mut()) { material.base_color_texture = Some(new_image_handle); } number_comp.current_text = target_text; } } }
方案3:优化预生成图片方案
如果坚持使用预生成的数字图片,可通过缓存图片Handle减少重复加载,同时确保WASM下资源路径正确(trunk serve会自动打包assets目录下的资源):
#[derive(Resource)] struct NumberTextures(Vec<Handle<Image>>); // 预加载所有数字纹理 fn load_number_textures(mut commands: Commands, asset_server: Res<AssetServer>) { let mut textures = Vec::new(); for i in 0..10 { textures.push(asset_server.load(format!("cards/nums/number_{}.png", i))); } commands.insert_resource(NumberTextures(textures)); } // 更新卡牌数字 fn update_card_numbers( mut query: Query<(&mut Handle<StandardMaterial>, &CardNumberComponent)>, mut materials: ResMut<Assets<StandardMaterial>>, number_textures: Res<NumberTextures>, ) { for (mut mat_handle, number_comp) in query.iter_mut() { let texture_handle = &number_textures.0[number_comp.value as usize]; if let Some(material) = materials.get_mut(mat_handle.as_mut()) { material.base_color_texture = Some(texture_handle.clone()); } } }
内容的提问来源于stack exchange,提问作者Smartguy 88
相关产品推荐
相关产品推荐

