I am developing a card game for which I need to render some 3d text. The game itself is designed to be 3d, however the card graphics are .png
files (so effectively 2d) which I am rendering using the shape::Quad
+ StandardMaterial.base_color_texture
to asset_server.load("cards/example.png")
on a PbrBundle
.
I don't know any other way of rendering effectively 2d content (e.g. icons) in a 3d context, this technique was even used in the official bevy examples to render text here.
My problem comes when I want to render text, e.g. numbers, as part of the game. Ideally, I would like to spawn an entity with a Transform
and a custom component which I can Query<&mut CardNumberComponent>
to update, which is rendered by my Camera3dBundle
as a 'normal mesh' alongside other entities (mostly PbrBundle
s), but is rendered as text. For example, I want to create a 'card' entities which aesthetically totals to a 2d image (normal PbrBundle
+ custom material as described above) and a health total, the health total being specifically a number that is part of the 3d 'card' entities (probably a child), so when I move the card the number moves with the card (like a child PbrBundle
entity moves with its parent entity as shown in this official bevy example).
My current attempts to achieve such "Text3dBundle
" like behaviour have partially succeeded, if I know the range of possible numbers (or more generally text) that I need I can pre-generate images and call asset_server.load(format!("cards/nums/number_{}.png), x))
.
This works, but I wonder if I have simply missed some simple API provided by bevy or a third party crate.
Stated formally:
My problem requires this function, using bevy 0.10.1 (from crates.io
) and compatible with macOS + WASM (trunk serve
should 'just work'), spawns text into the bevy world:
pub fn render_text(
at: Transform,
content: &str,
font_file: &str,
commands: &mut Commands,
asset_server: ResMut<AssetServer>,
materials: ResMut<Assets<Mesh>>,
meshs: ResMut<Assets<Mesh>>,
// other bevy system parameters
) {
// load font
let font = asset_server.load(font_file);
// create text
let text_something_idk = todo!();
// spawn in
commands.spawn(text_something_idk);
}
That is my problem, how do I render 3d text in bevy?
Almost solution, WASM not supported
One way of solving this is to render the text you want + font file into a .png
file at runtime as required. Then, theoretically you could save the data as a file locally and then load the same file as an asset in bevy. One such (working) implementation is this: (cargo add text-to-png@0.1.1
)
fn text_to_image_runtime(text: &str, asset_server: Res<AssetServer>) -> Handle<Image> {
use text_to_png::TextRenderer;
let renderer = TextRenderer::try_new_with_ttf_font_data(include_bytes!("font.ttf"))
.expect("Could not load file");
let text_png = renderer
.render_text_to_png_data(text, 42, "white")
.expect("Couldn't custom render text");
// NOTE: This line will break on WASM
std::fs::write(format!("assets/text-file-{}.png", text), text_png.data).expect("File works + not on WASM");
asset_server.load(format!("text-file-{}.png", text))
}
pub fn render_text(...) {
// using PbrMesh with StandardMaterial.base_color_texture = text_to_image_runtime(text)
}
The issue is, I need my game to support WASM
targets and std::fs
is basically unavailable in the browser, so I have no way of passing the png
data to the bevy asset_server
without using asset_server.load
so that I can render the custom png
as a texture as a text representation in my game.
If only there was an let img: Handle<image> = asset_server.load_from_bytes(input_bytes)
API, this solution could work.