Some Sprites don't render when World contains entity with Sprite and Mesh2d
Introduction
First off, this is my first issue here, so I hope I'm not doing something obviously wrong and silly. I greatly appreciate the work done on bevy and love working with it.
This issue is a bit niche and hard to describe, so I'll do my best to make it reproducible and clear.
Bevy version
0.16.1 and 0.16.0
Relevant system information
Cargo Version
cargo --version
cargo 1.87.0 (99624be96 2025-05-06)
Operating System
MacOS Seq15.5 (24F74)
Adaptor Info
`AdapterInfo { name: "Apple M1", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }`
What you did
This is the smallest code sample I've come up with that reproduces the problem. The code Spawns a camera, and 3 entities. A Marker entity, containing a sprite that is used to mark the last hovered entity. An entity with a sprite that can be hovered (by the mouse). An entity with a sprite AND a Mesh2d that can be hovered.
use bevy::color::palettes::basic::RED;
use bevy::prelude::*;
/// Component added to the entity used to show
#[derive(Component)]
struct HoverIndicator;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest()));
app.add_systems(Startup, setup);
app.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
) {
commands.spawn((
Camera2d,
Projection::Orthographic(OrthographicProjection {
scale: 0.5,
..OrthographicProjection::default_2d()
}),
Transform::from_xyz(0.0, 0.0, 0.0),
));
// 1. just sprite
commands
.spawn((
Transform::from_xyz(64.0, 0.0, 0.),
Name::new("Sprite"),
Sprite {
image: asset_server.load("Button_Blue.png"),
..default()
},
Pickable::default(),
))
.observe(on_pointer_over)
.with_children(|parent| {
// This entity gets moved to the entity last hovered by the mouse
parent.spawn((
HoverIndicator,
Sprite {
image: asset_server.load("02.png"),
..Default::default()
},
Transform::from_xyz(0.0, 0.0, 1.0),
));
});
// 2. mesh and sprite
commands
.spawn((
Transform::from_xyz(128.0, 0.0, 0.),
Name::new("Mesh and Sprite"),
Sprite {
image: asset_server.load("Button_Blue.png"),
..default()
},
Mesh2d(meshes.add(Rectangle::new(64., 64.))),
MeshMaterial2d(materials.add(ColorMaterial::from_color(RED))),
Pickable::default(),
))
.observe(on_pointer_over);
}
fn on_pointer_over(
trigger: Trigger<Pointer<Over>>,
mut commands: Commands,
hover_entity: Single<Entity, With<HoverIndicator>>,
) {
trace!("Pointer over selectable");
commands.entity(trigger.target).add_child(*hover_entity);
}
What went wrong
When I hover the entity with a sprite, the HoverIndicator entity disappears and is no longer rendered. If I then hover the entity with both mesh and sprite, the HoverIndicator is rendered and the hover functionality works fully (I can hover over the entity with the sprite and it renders now).
If I don't spawn any entities with both Mesh2d and Sprite, the HoverIndicator works as intended.
Additional information
Gif showing the HoverIndicator disappearing.
Other notable effects
- If you set the
transform.translate.zof theHoverIndicatorentity to0, it will continue to render sometimes. - if you spawn more sprites, one will not render (like I describe in this issue) and all the others will render normally (this is a potential workaround).
- If the entities with both
Sprite+Mesh2dare spawned later (during anUpdateschedule for example), then the issue does not arise.
My Theory
The Mesh2d + Sprite + system is interfering with rendering of the HoverIndicator sprite. I'm guessing some kind of conflict in the rendering order is what is causing sprites not to render.
Asset folder
In case you are wondering, the assets come from https://pixelfrog-assets.itch.io/tiny-swords.
You're not supposed to add both components Sprite and Mesh2d to the same entity. You should spawn one as a child of the other. See #18006
This issue still arises if the Mesh doesn't have a color and is transparent (making it totally invisible).
For some context,
I noticed this when I was using mesh to make smaller sprites easier to click/hover without having to make the sprite bigger. The Mesh2D in this case is not rendered (at least visually).
This might not be the place to ask it, but is there a way to defining a "clickable area" in 2D without using a Mesh2d and without using the Sprite itself - which in my case is too small - ?
I believe your approach is correct to define that clickable area but meshes are not pickable by default. You need to choose a backend (e.g. MeshPickingPlugin). I have your code working with a transparent mesh. I just added the plugin
fn main() {
let mut app = App::new();
app.add_plugins((
DefaultPlugins.set(ImagePlugin::default_nearest()),
MeshPickingPlugin,
));
app.add_systems(Startup, setup);
app.run();
}
and made the sprite a child of the transparent mesh. Here I reduced the size of the sprite as well.
// 2. mesh and sprite
commands
.spawn((
Transform::from_xyz(128.0, 0.0, 0.),
Name::new("Mesh and Sprite"),
Mesh2d(meshes.add(Rectangle::new(64., 64.))),
// MeshMaterial2d(materials.add(ColorMaterial::from_color(RED))),
Pickable::default(),
Children::spawn_one(Sprite {
image: asset_server.load("Button_Blue.png"),
custom_size: Some(Vec2::new(20., 20.)),
..default()
}),
))
.observe(on_pointer_over);
@gwafotapa Hello, i encounter a problem:
when you add Mesh2d as children entity on the Sprite entity, the transform hasn't any effort when you add Transform for both.
@lishaoxia1985 Hey. I'm not sure what you mean. If you have a Sprite entity with a Mesh2d child, you only need to add the Transform to the parent (and let the child have a default Transform) to obtain the result as if the entity had both the Sprite and Mesh2d.
@gwafotapa look at this #21764 That means some times mesh covers sprite, some time not. The behavior is uncertain.
I forgot to mention you have to specify the z coordinate of the child to put it on top or below its parent. But I see you've done that.
I've reproduced your code and the behavior is consistent on my machine with bevy 17.2
use bevy::prelude::*;
fn main() {
let mut app = App::new();
app.add_plugins(DefaultPlugins);
app.add_systems(Startup, spawn);
app.run();
}
fn spawn(
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
mut commands: Commands,
) {
commands.spawn(Camera2d);
commands
.spawn(Sprite::from_image(asset_server.load("feature_graphic.png")))
.with_children(|parent| {
parent.spawn((
Mesh2d(meshes.add(Circle::new(50.0))),
MeshMaterial2d(materials.add(Color::WHITE)),
Transform::from_xyz(0., 0., 7.),
));
parent.spawn((
Mesh2d(meshes.add(Circle::new(100.0))),
MeshMaterial2d(materials.add(Color::BLACK)),
Transform::from_xyz(0., 0., 6.),
));
});
}
Is the code above inconsistent on your machine ?
@gwafotapa You should test more.
For example, spawn or despawn the entity every 5s.
In my PC, the result is uncertain by using with_children, sometimes sprite covers mesh, sometimes mesh covers sprite.
You can find the code in main.rs of Civilization-Remastered.
In this project, i update the map according to Camera positon.
If you edit the code by using with_children, and then move the map, you will find that every civilization unit icon displays different.
Some doesn't has icon but only a circle with edge. Some has unit icon with circle.
@gwafotapa You should test more. For example, spawn or despawn the entity every 5s. In my PC, the result is uncertain by using
with_children, sometimes sprite covers mesh, sometimes mesh covers sprite.
Tested some more respawning every frame. Still consistent. Are you having an inconsistent result with my example ?
You can find the code in
main.rsof Civilization-Remastered. In this project, i update the map according to Camera positon. If you edit the code by usingwith_children, and then move the map, you will find that every civilization unit icon displays different. Some doesn't has icon but only a circle with edge. Some has unit icon with circle.
That's so vague. Can you at least point to a specific line number in your code ? Or better yet give a minimal piece of code to reproduce the issue ?
@gwafotapa I'm sorry, The line 548-580, 583-618, 629-663 in main.rs. I will write some codes for testing tomorrow.
I think I got it. When adding the meshes as children, you corrected the translation x and y but not z which is why you're getting the discrepancy. More precisely you had:
- Sprite z = 8
- first mesh z = 7
- second mesh z = 6
Adding the meshes as children of the sprite you should now use
- first mesh z = 7 - 8 = -1
- second mesh z = 6 - 8 = -2
Maybe you can add more and more same entity with different position to the window, that can test whether every entity shows the same result.
Are you still experiencing the issue after fixing the z coordinate ?
Your method is very good. Thank you very much.