bevy icon indicating copy to clipboard operation
bevy copied to clipboard

Some Sprites don't render when World contains entity with Sprite and Mesh2d

Open bruno-robert opened this issue 7 months ago • 3 comments

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.

Image

Other notable effects

  • If you set the transform.translate.z of the HoverIndicator entity to 0, 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 + Mesh2d are spawned later (during an Update schedule 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

assets.zip

In case you are wondering, the assets come from https://pixelfrog-assets.itch.io/tiny-swords.

bruno-robert avatar Jun 01 '25 20:06 bruno-robert

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

gwafotapa avatar Jun 04 '25 08:06 gwafotapa

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 - ?

bruno-robert avatar Jun 04 '25 14:06 bruno-robert

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 avatar Jun 04 '25 15:06 gwafotapa

@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 avatar Nov 06 '25 04:11 lishaoxia1985

@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 avatar Nov 06 '25 09:11 gwafotapa

@gwafotapa look at this #21764 That means some times mesh covers sprite, some time not. The behavior is uncertain.

lishaoxia1985 avatar Nov 06 '25 13:11 lishaoxia1985

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 avatar Nov 06 '25 15:11 gwafotapa

@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.

lishaoxia1985 avatar Nov 06 '25 16:11 lishaoxia1985

@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.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.

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 avatar Nov 06 '25 17:11 gwafotapa

@gwafotapa I'm sorry, The line 548-580, 583-618, 629-663 in main.rs. I will write some codes for testing tomorrow.

lishaoxia1985 avatar Nov 06 '25 17:11 lishaoxia1985

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

gwafotapa avatar Nov 06 '25 18:11 gwafotapa

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.

lishaoxia1985 avatar Nov 06 '25 19:11 lishaoxia1985

Are you still experiencing the issue after fixing the z coordinate ?

gwafotapa avatar Nov 06 '25 19:11 gwafotapa

Your method is very good. Thank you very much.

lishaoxia1985 avatar Nov 06 '25 19:11 lishaoxia1985