Remove the Component trait implementation from Handle (#15796)

# Objective

- Closes #15716
- Closes #15718

## Solution

- Replace `Handle<MeshletMesh>` with a new `MeshletMesh3d` component
- As expected there were some random things that needed fixing:
- A couple tests were storing handles just to prevent them from being
dropped I believe, which seems to have been unnecessary in some.
- The `SpriteBundle` still had a `Handle<Image>` field. I've removed
this.
- Tests in `bevy_sprite` incorrectly added a `Handle<Image>` field
outside of the `Sprite` component.
- A few examples were still inserting `Handle`s, switched those to their
corresponding wrappers.
- 2 examples that were still querying for `Handle<Image>` were changed
to query `Sprite`

## Testing

- I've verified that the changed example work now

## Migration Guide

`Handle` can no longer be used as a `Component`. All existing Bevy types
using this pattern have been wrapped in their own semantically
meaningful type. You should do the same for any custom `Handle`
components your project needs.

The `Handle<MeshletMesh>` component is now `MeshletMesh3d`.

The `WithMeshletMesh` type alias has been removed. Use
`With<MeshletMesh3d>` instead.
This commit is contained in:
Tim 2024-10-09 21:10:01 +00:00 committed by GitHub
parent a6be9b4ccd
commit 3da0ef048e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 87 additions and 79 deletions

View File

@ -58,7 +58,7 @@ pub(crate) fn trigger_animation_event(
/// let mut player = AnimationPlayer::default(); /// let mut player = AnimationPlayer::default();
/// player.play(animation_index).repeat(); /// player.play(animation_index).repeat();
/// ///
/// commands.spawn((graphs.add(graph), player)); /// commands.spawn((AnimationGraphHandle(graphs.add(graph)), player));
/// } /// }
/// # /// #
/// # bevy_ecs::system::assert_is_system(setup_animation); /// # bevy_ecs::system::assert_is_system(setup_animation);

View File

@ -3,7 +3,6 @@ use crate::{
UntypedAssetId, UntypedAssetId,
}; };
use alloc::sync::Arc; use alloc::sync::Arc;
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use core::{ use core::{
any::TypeId, any::TypeId,
@ -122,8 +121,8 @@ impl core::fmt::Debug for StrongHandle {
/// of the [`Handle`] are dropped. /// of the [`Handle`] are dropped.
/// ///
/// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists). /// [`Handle::Strong`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
#[derive(Component, Reflect)] #[derive(Reflect)]
#[reflect(Default, Component, Debug, Hash, PartialEq)] #[reflect(Default, Debug, Hash, PartialEq)]
pub enum Handle<A: Asset> { pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept /// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata. /// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.

View File

@ -900,7 +900,6 @@ mod tests {
let asset_server = app.world().resource::<AssetServer>().clone(); let asset_server = app.world().resource::<AssetServer>().clone();
let handle: Handle<CoolText> = asset_server.load(a_path); let handle: Handle<CoolText> = asset_server.load(a_path);
let a_id = handle.id(); let a_id = handle.id();
let entity = app.world_mut().spawn(handle).id();
app.update(); app.update();
{ {
let a_text = get::<CoolText>(app.world(), a_id); let a_text = get::<CoolText>(app.world(), a_id);
@ -1090,7 +1089,8 @@ mod tests {
a.text = "Changed".to_string(); a.text = "Changed".to_string();
} }
app.world_mut().despawn(entity); drop(handle);
app.update(); app.update();
assert_eq!( assert_eq!(
app.world().resource::<Assets<CoolText>>().len(), app.world().resource::<Assets<CoolText>>().len(),
@ -1225,7 +1225,6 @@ mod tests {
); );
} }
app.world_mut().spawn(handle);
gate_opener.open(a_path); gate_opener.open(a_path);
gate_opener.open(b_path); gate_opener.open(b_path);
gate_opener.open(c_path); gate_opener.open(c_path);
@ -1345,7 +1344,6 @@ mod tests {
let asset_server = app.world().resource::<AssetServer>().clone(); let asset_server = app.world().resource::<AssetServer>().clone();
let handle: Handle<CoolText> = asset_server.load(a_path); let handle: Handle<CoolText> = asset_server.load(a_path);
let a_id = handle.id(); let a_id = handle.id();
app.world_mut().spawn(handle);
gate_opener.open(a_path); gate_opener.open(a_path);
run_app_until(&mut app, |world| { run_app_until(&mut app, |world| {
@ -1746,8 +1744,6 @@ mod tests {
let a_handle: Handle<CoolText> = asset_server.load(a_path); let a_handle: Handle<CoolText> = asset_server.load(a_path);
let a_id = a_handle.id(); let a_id = a_handle.id();
app.world_mut().spawn(a_handle);
run_app_until(&mut app, |world| { run_app_until(&mut app, |world| {
let tracker = world.resource::<ErrorTracker>(); let tracker = world.resource::<ErrorTracker>();
match tracker.finished_asset { match tracker.finished_asset {

View File

@ -2229,7 +2229,7 @@ mod test {
AssetApp, AssetPlugin, AssetServer, Assets, Handle, LoadState, AssetApp, AssetPlugin, AssetServer, Assets, Handle, LoadState,
}; };
use bevy_core::TaskPoolPlugin; use bevy_core::TaskPoolPlugin;
use bevy_ecs::world::World; use bevy_ecs::{system::Resource, world::World};
use bevy_log::LogPlugin; use bevy_log::LogPlugin;
use bevy_render::mesh::{skinning::SkinnedMeshInverseBindposes, MeshPlugin}; use bevy_render::mesh::{skinning::SkinnedMeshInverseBindposes, MeshPlugin};
use bevy_scene::ScenePlugin; use bevy_scene::ScenePlugin;
@ -2270,6 +2270,10 @@ mod test {
} }
fn load_gltf_into_app(gltf_path: &str, gltf: &str) -> App { fn load_gltf_into_app(gltf_path: &str, gltf: &str) -> App {
#[expect(unused)]
#[derive(Resource)]
struct GltfHandle(Handle<Gltf>);
let dir = Dir::default(); let dir = Dir::default();
dir.insert_asset_text(Path::new(gltf_path), gltf); dir.insert_asset_text(Path::new(gltf_path), gltf);
let mut app = test_app(dir); let mut app = test_app(dir);
@ -2277,7 +2281,7 @@ mod test {
let asset_server = app.world().resource::<AssetServer>().clone(); let asset_server = app.world().resource::<AssetServer>().clone();
let handle: Handle<Gltf> = asset_server.load(gltf_path.to_string()); let handle: Handle<Gltf> = asset_server.load(gltf_path.to_string());
let handle_id = handle.id(); let handle_id = handle.id();
app.world_mut().spawn(handle.clone()); app.insert_resource(GltfHandle(handle));
app.update(); app.update();
run_app_until(&mut app, |_world| { run_app_until(&mut app, |_world| {
let load_state = asset_server.get_load_state(handle_id).unwrap(); let load_state = asset_server.get_load_state(handle_id).unwrap();
@ -2509,7 +2513,6 @@ mod test {
let asset_server = app.world().resource::<AssetServer>().clone(); let asset_server = app.world().resource::<AssetServer>().clone();
let handle: Handle<Gltf> = asset_server.load(gltf_path); let handle: Handle<Gltf> = asset_server.load(gltf_path);
let handle_id = handle.id(); let handle_id = handle.id();
app.world_mut().spawn(handle.clone());
app.update(); app.update();
run_app_until(&mut app, |_world| { run_app_until(&mut app, |_world| {
let load_state = asset_server.get_load_state(handle_id).unwrap(); let load_state = asset_server.get_load_state(handle_id).unwrap();
@ -2552,7 +2555,6 @@ mod test {
let asset_server = app.world().resource::<AssetServer>().clone(); let asset_server = app.world().resource::<AssetServer>().clone();
let handle: Handle<Gltf> = asset_server.load(gltf_path); let handle: Handle<Gltf> = asset_server.load(gltf_path);
let handle_id = handle.id(); let handle_id = handle.id();
app.world_mut().spawn(handle.clone());
app.update(); app.update();
run_app_until(&mut app, |_world| { run_app_until(&mut app, |_world| {
let load_state = asset_server.get_load_state(handle_id).unwrap(); let load_state = asset_server.get_load_state(handle_id).unwrap();

View File

@ -36,7 +36,7 @@ pub const MESHLET_MESH_ASSET_VERSION: u64 = 1;
/// * Materials must use the [`crate::Material::meshlet_mesh_fragment_shader`] method (and similar variants for prepass/deferred shaders) /// * Materials must use the [`crate::Material::meshlet_mesh_fragment_shader`] method (and similar variants for prepass/deferred shaders)
/// which requires certain shader patterns that differ from the regular material shaders. /// which requires certain shader patterns that differ from the regular material shaders.
/// ///
/// See also [`super::MaterialMeshletMeshBundle`] and [`super::MeshletPlugin`]. /// See also [`super::MeshletMesh3d`] and [`super::MeshletPlugin`].
#[derive(Asset, TypePath, Clone)] #[derive(Asset, TypePath, Clone)]
pub struct MeshletMesh { pub struct MeshletMesh {
/// Quantized and bitstream-packed vertex positions for meshlet vertices. /// Quantized and bitstream-packed vertex positions for meshlet vertices.

View File

@ -1,9 +1,9 @@
use super::{meshlet_mesh_manager::MeshletMeshManager, MeshletMesh}; use super::{meshlet_mesh_manager::MeshletMeshManager, MeshletMesh, MeshletMesh3d};
use crate::{ use crate::{
Material, MeshFlags, MeshTransforms, MeshUniform, NotShadowCaster, NotShadowReceiver, Material, MeshFlags, MeshTransforms, MeshUniform, NotShadowCaster, NotShadowReceiver,
PreviousGlobalTransform, RenderMaterialInstances, PreviousGlobalTransform, RenderMaterialInstances,
}; };
use bevy_asset::{AssetEvent, AssetServer, Assets, Handle, UntypedAssetId}; use bevy_asset::{AssetEvent, AssetServer, Assets, UntypedAssetId};
use bevy_ecs::{ use bevy_ecs::{
entity::{Entities, Entity, EntityHashMap}, entity::{Entities, Entity, EntityHashMap},
event::EventReader, event::EventReader,
@ -168,7 +168,7 @@ pub fn extract_meshlet_mesh_entities(
SystemState<( SystemState<(
Query<( Query<(
Entity, Entity,
&Handle<MeshletMesh>, &MeshletMesh3d,
&GlobalTransform, &GlobalTransform,
Option<&PreviousGlobalTransform>, Option<&PreviousGlobalTransform>,
Option<&RenderLayers>, Option<&RenderLayers>,

View File

@ -1,3 +1,4 @@
#![expect(deprecated)]
//! Render high-poly 3d meshes using an efficient GPU-driven method. See [`MeshletPlugin`] and [`MeshletMesh`] for details. //! Render high-poly 3d meshes using an efficient GPU-driven method. See [`MeshletPlugin`] and [`MeshletMesh`] for details.
mod asset; mod asset;
@ -57,19 +58,23 @@ use self::{
}; };
use crate::{graph::NodePbr, Material, MeshMaterial3d}; use crate::{graph::NodePbr, Material, MeshMaterial3d};
use bevy_app::{App, Plugin, PostUpdate}; use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::{load_internal_asset, AssetApp, Handle}; use bevy_asset::{load_internal_asset, AssetApp, AssetId, Handle};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_3d::graph::{Core3d, Node3d}, core_3d::graph::{Core3d, Node3d},
prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass}, prepass::{DeferredPrepass, MotionVectorPrepass, NormalPrepass},
}; };
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{ use bevy_ecs::{
bundle::Bundle, bundle::Bundle,
component::Component,
entity::Entity, entity::Entity,
prelude::With, prelude::With,
query::Has, query::Has,
reflect::ReflectComponent,
schedule::IntoSystemConfigs, schedule::IntoSystemConfigs,
system::{Commands, Query}, system::{Commands, Query},
}; };
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{ use bevy_render::{
render_graph::{RenderGraphApp, ViewNodeRunner}, render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::Shader, render_resource::Shader,
@ -83,6 +88,7 @@ use bevy_render::{
}; };
use bevy_transform::components::{GlobalTransform, Transform}; use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::tracing::error; use bevy_utils::tracing::error;
use derive_more::From;
const MESHLET_BINDINGS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(1325134235233421); const MESHLET_BINDINGS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(1325134235233421);
const MESHLET_MESH_MATERIAL_SHADER_HANDLE: Handle<Shader> = const MESHLET_MESH_MATERIAL_SHADER_HANDLE: Handle<Shader> =
@ -206,7 +212,7 @@ impl Plugin for MeshletPlugin {
.register_asset_loader(MeshletMeshLoader) .register_asset_loader(MeshletMeshLoader)
.add_systems( .add_systems(
PostUpdate, PostUpdate,
check_visibility::<WithMeshletMesh>.in_set(VisibilitySystems::CheckVisibility), check_visibility::<With<MeshletMesh3d>>.in_set(VisibilitySystems::CheckVisibility),
); );
} }
@ -284,10 +290,31 @@ impl Plugin for MeshletPlugin {
} }
} }
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
#[reflect(Component, Default)]
#[require(Transform, Visibility)]
pub struct MeshletMesh3d(pub Handle<MeshletMesh>);
impl From<MeshletMesh3d> for AssetId<MeshletMesh> {
fn from(mesh: MeshletMesh3d) -> Self {
mesh.id()
}
}
impl From<&MeshletMesh3d> for AssetId<MeshletMesh> {
fn from(mesh: &MeshletMesh3d) -> Self {
mesh.id()
}
}
/// A component bundle for entities with a [`MeshletMesh`] and a [`Material`]. /// A component bundle for entities with a [`MeshletMesh`] and a [`Material`].
#[derive(Bundle, Clone)] #[derive(Bundle, Clone)]
#[deprecated(
since = "0.15.0",
note = "Use the `MeshletMesh3d` and `MeshMaterial3d` components instead. Inserting them will now also insert the other components required by them automatically."
)]
pub struct MaterialMeshletMeshBundle<M: Material> { pub struct MaterialMeshletMeshBundle<M: Material> {
pub meshlet_mesh: Handle<MeshletMesh>, pub meshlet_mesh: MeshletMesh3d,
pub material: MeshMaterial3d<M>, pub material: MeshMaterial3d<M>,
pub transform: Transform, pub transform: Transform,
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
@ -313,10 +340,6 @@ impl<M: Material> Default for MaterialMeshletMeshBundle<M> {
} }
} }
/// A convenient alias for `With<Handle<MeshletMesh>>`, for use with
/// [`bevy_render::view::VisibleEntities`].
pub type WithMeshletMesh = With<Handle<MeshletMesh>>;
fn configure_meshlet_views( fn configure_meshlet_views(
mut views_3d: Query<( mut views_3d: Query<(
Entity, Entity,

View File

@ -35,7 +35,7 @@ use bevy_utils::tracing::error;
#[cfg(feature = "meshlet")] #[cfg(feature = "meshlet")]
use crate::meshlet::{ use crate::meshlet::{
prepare_material_meshlet_meshes_prepass, queue_material_meshlet_meshes, InstanceManager, prepare_material_meshlet_meshes_prepass, queue_material_meshlet_meshes, InstanceManager,
MeshletMesh, MeshletMesh3d,
}; };
use crate::*; use crate::*;
@ -221,7 +221,7 @@ pub struct PreviousGlobalTransform(pub Affine3A);
#[cfg(not(feature = "meshlet"))] #[cfg(not(feature = "meshlet"))]
type PreviousMeshFilter = With<Mesh3d>; type PreviousMeshFilter = With<Mesh3d>;
#[cfg(feature = "meshlet")] #[cfg(feature = "meshlet")]
type PreviousMeshFilter = Or<(With<Mesh3d>, With<Handle<MeshletMesh>>)>; type PreviousMeshFilter = Or<(With<Mesh3d>, With<MeshletMesh3d>)>;
pub fn update_mesh_previous_global_transforms( pub fn update_mesh_previous_global_transforms(
mut commands: Commands, mut commands: Commands,

View File

@ -1,10 +1,8 @@
#![expect(deprecated)] #![expect(deprecated)]
use crate::Sprite; use crate::Sprite;
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle; use bevy_ecs::bundle::Bundle;
use bevy_render::{ use bevy_render::{
sync_world::SyncToRenderWorld, sync_world::SyncToRenderWorld,
texture::Image,
view::{InheritedVisibility, ViewVisibility, Visibility}, view::{InheritedVisibility, ViewVisibility, Visibility},
}; };
use bevy_transform::components::{GlobalTransform, Transform}; use bevy_transform::components::{GlobalTransform, Transform};
@ -28,8 +26,6 @@ pub struct SpriteBundle {
pub transform: Transform, pub transform: Transform,
/// The absolute transform of the sprite. This should generally not be written to directly. /// The absolute transform of the sprite. This should generally not be written to directly.
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
/// A reference-counted handle to the image asset to be drawn.
pub texture: Handle<Image>,
/// User indication of whether an entity is visible /// User indication of whether an entity is visible
pub visibility: Visibility, pub visibility: Visibility,
/// Inherited visibility of an entity. /// Inherited visibility of an entity.

View File

@ -299,13 +299,11 @@ mod test {
// Add entities // Add entities
let entity = app let entity = app
.world_mut() .world_mut()
.spawn(( .spawn(Sprite {
Sprite { custom_size: Some(Vec2::ZERO),
custom_size: Some(Vec2::ZERO), image: image_handle,
..default() ..default()
}, })
image_handle,
))
.id(); .id();
// Create initial AABB // Create initial AABB
@ -364,14 +362,12 @@ mod test {
// Add entities // Add entities
let entity = app let entity = app
.world_mut() .world_mut()
.spawn(( .spawn(Sprite {
Sprite { rect: Some(Rect::new(0., 0., 0.5, 1.)),
rect: Some(Rect::new(0., 0., 0.5, 1.)), anchor: Anchor::TopRight,
anchor: Anchor::TopRight, image: image_handle,
..default() ..default()
}, })
image_handle,
))
.id(); .id();
// Create AABB // Create AABB

View File

@ -98,7 +98,7 @@ fn spawn_car_paint_sphere(
commands commands
.spawn(( .spawn((
Mesh3d(sphere.clone()), Mesh3d(sphere.clone()),
materials.add(StandardMaterial { MeshMaterial3d(materials.add(StandardMaterial {
clearcoat: 1.0, clearcoat: 1.0,
clearcoat_perceptual_roughness: 0.1, clearcoat_perceptual_roughness: 0.1,
normal_map_texture: Some(asset_server.load_with_settings( normal_map_texture: Some(asset_server.load_with_settings(
@ -109,7 +109,7 @@ fn spawn_car_paint_sphere(
perceptual_roughness: 0.5, perceptual_roughness: 0.5,
base_color: BLUE.into(), base_color: BLUE.into(),
..default() ..default()
}), })),
Transform::from_xyz(-1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)), Transform::from_xyz(-1.0, 1.0, 0.0).with_scale(Vec3::splat(SPHERE_SCALE)),
)) ))
.insert(ExampleSphere); .insert(ExampleSphere);

View File

@ -7,7 +7,7 @@ mod camera_controller;
use bevy::{ use bevy::{
pbr::{ pbr::{
experimental::meshlet::{MaterialMeshletMeshBundle, MeshletPlugin}, experimental::meshlet::{MeshletMesh3d, MeshletPlugin},
CascadeShadowConfigBuilder, DirectionalLightShadowMap, CascadeShadowConfigBuilder, DirectionalLightShadowMap,
}, },
prelude::*, prelude::*,
@ -84,9 +84,9 @@ fn setup(
let debug_material = debug_materials.add(MeshletDebugMaterial::default()); let debug_material = debug_materials.add(MeshletDebugMaterial::default());
for x in -2..=2 { for x in -2..=2 {
commands.spawn(MaterialMeshletMeshBundle { commands.spawn((
meshlet_mesh: meshlet_mesh_handle.clone(), MeshletMesh3d(meshlet_mesh_handle.clone()),
material: MeshMaterial3d(standard_materials.add(StandardMaterial { MeshMaterial3d(standard_materials.add(StandardMaterial {
base_color: match x { base_color: match x {
-2 => Srgba::hex("#dc2626").unwrap().into(), -2 => Srgba::hex("#dc2626").unwrap().into(),
-1 => Srgba::hex("#ea580c").unwrap().into(), -1 => Srgba::hex("#ea580c").unwrap().into(),
@ -98,22 +98,20 @@ fn setup(
perceptual_roughness: (x + 2) as f32 / 4.0, perceptual_roughness: (x + 2) as f32 / 4.0,
..default() ..default()
})), })),
transform: Transform::default() Transform::default()
.with_scale(Vec3::splat(0.2)) .with_scale(Vec3::splat(0.2))
.with_translation(Vec3::new(x as f32 / 2.0, 0.0, -0.3)), .with_translation(Vec3::new(x as f32 / 2.0, 0.0, -0.3)),
..default() ));
});
} }
for x in -2..=2 { for x in -2..=2 {
commands.spawn(MaterialMeshletMeshBundle { commands.spawn((
meshlet_mesh: meshlet_mesh_handle.clone(), MeshletMesh3d(meshlet_mesh_handle.clone()),
material: debug_material.clone().into(), MeshMaterial3d(debug_material.clone()),
transform: Transform::default() Transform::default()
.with_scale(Vec3::splat(0.2)) .with_scale(Vec3::splat(0.2))
.with_rotation(Quat::from_rotation_y(PI)) .with_rotation(Quat::from_rotation_y(PI))
.with_translation(Vec3::new(x as f32 / 2.0, 0.0, 0.3)), .with_translation(Vec3::new(x as f32 / 2.0, 0.0, 0.3)),
..default() ));
});
} }
commands.spawn(( commands.spawn((

View File

@ -43,7 +43,7 @@ fn setup(
Transform::from_translation(vec3(-6., 2., 0.)), Transform::from_translation(vec3(-6., 2., 0.)),
animation_target_name, animation_target_name,
animation_player, animation_player,
animation_graph, AnimationGraphHandle(animation_graph),
)) ))
.id(); .id();

View File

@ -105,22 +105,20 @@ fn spawn_text(mut commands: Commands) {
}, },
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(Text::new("Space: swap the right sprite's image handle"));
parent.spawn(Text::new( parent.spawn(Text::new(
"Space: swap image texture paths by mutating a Handle<Image>", "Return: modify the image Asset of the left sprite, affecting all uses of it",
));
parent.spawn(Text::new(
"Return: mutate the image Asset itself, changing all copies of it",
)); ));
}); });
} }
fn alter_handle( fn alter_handle(
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut right_bird: Query<(&mut Bird, &mut Handle<Image>), Without<Left>>, mut right_bird: Query<(&mut Bird, &mut Sprite), Without<Left>>,
) { ) {
// Image handles, like other parts of the ECS, can be queried as mutable and modified at // Image handles, like other parts of the ECS, can be queried as mutable and modified at
// runtime. We only spawned one bird without the `Left` marker component. // runtime. We only spawned one bird without the `Left` marker component.
let Ok((mut bird, mut handle)) = right_bird.get_single_mut() else { let Ok((mut bird, mut sprite)) = right_bird.get_single_mut() else {
return; return;
}; };
@ -130,18 +128,18 @@ fn alter_handle(
// Modify the handle associated with the Bird on the right side. Note that we will only // Modify the handle associated with the Bird on the right side. Note that we will only
// have to load the same path from storage media once: repeated attempts will re-use the // have to load the same path from storage media once: repeated attempts will re-use the
// asset. // asset.
*handle = asset_server.load(bird.get_texture_path()); sprite.image = asset_server.load(bird.get_texture_path());
} }
fn alter_asset(mut images: ResMut<Assets<Image>>, left_bird: Query<&Handle<Image>, With<Left>>) { fn alter_asset(mut images: ResMut<Assets<Image>>, left_bird: Query<&Sprite, With<Left>>) {
// It's convenient to retrieve the asset handle stored with the bird on the left. However, // It's convenient to retrieve the asset handle stored with the bird on the left. However,
// we could just as easily have retained this in a resource or a dedicated component. // we could just as easily have retained this in a resource or a dedicated component.
let Ok(handle) = left_bird.get_single() else { let Ok(sprite) = left_bird.get_single() else {
return; return;
}; };
// Obtain a mutable reference to the Image asset. // Obtain a mutable reference to the Image asset.
let Some(image) = images.get_mut(handle) else { let Some(image) = images.get_mut(&sprite.image) else {
return; return;
}; };

View File

@ -84,12 +84,12 @@ fn setup(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
} }
// Switch texture to display every frame to show the one that was written to most recently. // Switch texture to display every frame to show the one that was written to most recently.
fn switch_textures(images: Res<GameOfLifeImages>, mut displayed: Query<&mut Handle<Image>>) { fn switch_textures(images: Res<GameOfLifeImages>, mut displayed: Query<&mut Sprite>) {
let mut displayed = displayed.single_mut(); let mut sprite = displayed.single_mut();
if *displayed == images.texture_a { if sprite.image == images.texture_a {
*displayed = images.texture_b.clone_weak(); sprite.image = images.texture_b.clone_weak();
} else { } else {
*displayed = images.texture_a.clone_weak(); sprite.image = images.texture_a.clone_weak();
} }
} }

View File

@ -47,7 +47,7 @@ fn main() {
fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) { fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
commands.spawn(( commands.spawn((
meshes.add(Cuboid::new(0.5, 0.5, 0.5)), Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),
SpatialBundle::INHERITED_IDENTITY, SpatialBundle::INHERITED_IDENTITY,
InstanceMaterialData( InstanceMaterialData(
(1..=10) (1..=10)

View File

@ -79,7 +79,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {
// with our specialized pipeline // with our specialized pipeline
CustomRenderedEntity, CustomRenderedEntity,
// We need to add the mesh handle to the entity // We need to add the mesh handle to the entity
meshes.add(mesh.clone()), Mesh3d(meshes.add(mesh.clone())),
// This bundle's components are needed for something to be rendered // This bundle's components are needed for something to be rendered
SpatialBundle { SpatialBundle {
transform: Transform::from_xyz(x, y, 0.0), transform: Transform::from_xyz(x, y, 0.0),