bevy_2dviewangle/
component.rs

1// Copyright 2024,2025 Trung Do <dothanhtrung@pm.me>
2
3use std::collections::HashMap;
4
5use bevy::asset::Handle;
6use bevy::prelude::{
7    Component,
8    Deref,
9    DerefMut,
10    Entity,
11    EntityEvent,
12    Image,
13    Message,
14    Reflect,
15    ReflectComponent,
16    Resource,
17    TextureAtlasLayout,
18    Timer,
19};
20pub use bevy_2dviewangle_macro::View2dCollection;
21use xxhash_rust::xxh3::xxh3_64;
22
23/// The trait to use in derive macro. You won't need to implement this trait.
24///
25/// Example:
26/// ```rust
27/// use bevy::prelude::*;
28/// use bevy_2dviewangle::View2dCollection;
29///
30/// #[derive(View2dCollection)]
31/// pub struct MyAssets {
32///     #[textureview(actor = "frog", action = "idle", angle = "front")]
33///     pub idle_front: Handle<Image>,
34///
35///     // If not specify actor/action, the previous value will be used
36///     #[textureview(angle = "back")]
37///     pub idle_back: Handle<Image>,
38///
39///     // If the angle "right" is not defined, it will be flipped (2d) or rotate (3d) base on the angle "left" image
40///     #[textureview(angle = "left")]
41///     pub idle_left: Handle<Image>,
42///
43///     // If angle is any, other angle which has not been defined will use this value
44///     #[textureview(angle = "any")]
45///     pub layout: Handle<TextureAtlasLayout>,
46/// }
47/// ```
48///
49/// Two enums will be generated base on declared actor and action:
50/// ```rust
51/// #[derive(Eq, PartialEq, Clone)]
52/// pub enum ActorMyAssets {
53///     Frog,
54/// }
55///
56/// #[derive(Eq, PartialEq, Clone)]
57/// pub enum ActionMyAssets {
58///     Idle,
59/// }
60/// ```
61pub trait View2dCollection {
62    fn get_all(
63        &self,
64    ) -> Vec<(
65        Option<u64>,
66        Option<u64>,
67        Option<Angle>,
68        Option<&Handle<Image>>,
69        Option<&Handle<TextureAtlasLayout>>,
70    )>;
71}
72
73/// All supported angles.
74#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
75#[derive(Reflect, Default, Clone, Copy, Eq, PartialEq, Hash, Debug)]
76pub enum Angle {
77    Any,
78    #[default]
79    Front,
80    Back,
81    Left,
82    Right,
83    FrontLeft,
84    FrontRight,
85    BackLeft,
86    BackRight,
87}
88
89/// Sprite sheet for one angle, store image and atlas layout
90#[derive(Default, Clone)]
91pub struct SpriteSheet {
92    pub layout: Option<Handle<TextureAtlasLayout>>,
93    pub image: Option<Handle<Image>>,
94}
95
96/// Map of Angle and its SpriteSheet  
97#[derive(Default, Deref, DerefMut)]
98pub struct AngleSpriteSheets(HashMap<Angle, SpriteSheet>);
99
100#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
101#[derive(Reflect, Clone)]
102pub enum Notification {
103    LastFrame,
104}
105
106#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
107#[derive(Component, Reflect, Default, Clone)]
108#[reflect(Component)]
109pub struct View2dActor {
110    pub angle: Angle,
111    pub action: u64,
112    /// Next action when the last frame of the current action is done
113    pub next_action: Vec<u64>,
114    pub actor: u64,
115    pub flipped: bool,
116    pub animation_timer: Option<Timer>,
117    pub notify: Vec<Notification>,
118}
119
120/// The resource that stores every spritesheets. Organized by actor id and action id.
121#[derive(Resource, Deref, DerefMut, Default)]
122pub struct ActorSpriteSheets(HashMap<u64, HashMap<u64, AngleSpriteSheets>>);
123
124/// Notify the view is changed.
125///
126/// Example:
127/// ```rust
128/// use bevy::prelude::*;
129/// use bevy_2dviewangle::{Angle, View2dActor, ViewChanged};
130///
131/// pub fn input(
132///     mut actors: Query<(&mut View2dActor, Entity)>,
133///     mut action_event: MessageWriter<ViewChanged>,
134/// ) {
135///     for (mut act, e) in actors.iter_mut() {
136///             act.action = ActionMyAssets::Idle.into();
137///             act.angle = Angle::Left;
138///             // Send event to change to sprite sheet to another view
139///             action_event.write(ViewChanged { entity: e });
140///         }
141///     }
142/// }
143/// ```
144#[derive(Message)]
145pub struct ViewChanged {
146    pub entity: Entity,
147}
148
149/// Sent when animation went to the last frame
150#[derive(EntityEvent)]
151pub struct LastFrame {
152    pub entity: Entity,
153}
154
155impl AngleSpriteSheets {
156    /// Store spritesheets from list of Angle and SpriteSheet in case you don't want to use derive `View2dCollection`.
157    pub fn from(items: Vec<(Angle, SpriteSheet)>) -> Self {
158        let mut map = HashMap::new();
159        for (key, value) in items {
160            map.insert(key, value);
161        }
162        Self(map)
163    }
164}
165
166impl ActorSpriteSheets {
167    /// Store spiresheets from an instance of struct that uses derive `View2dCollection`.
168    ///
169    /// Example:
170    /// ```rust
171    /// use bevy::prelude::*;
172    /// use bevy_2dviewangle::{ActorSpriteSheets, View2dCollection};
173    ///
174    /// #[derive(View2dCollection)]
175    /// pub struct MyAssets {
176    ///     #[textureview(actor = "frog", action = "idle", angle = "front")]
177    ///     pub idle_front: Handle<Image>,
178    ///
179    ///     #[textureview(angle = "back")]
180    ///     pub idle_back: Handle<Image>,
181    ///
182    ///     #[textureview(angle = "left")]
183    ///     pub idle_left: Handle<Image>,
184    ///
185    ///     #[textureview(angle = "any")]
186    ///     pub layout: Handle<TextureAtlasLayout>,
187    /// }
188    ///
189    /// fn setup(
190    ///     mut commands: Commands,
191    ///     asset_server: Res<AssetServer>,
192    ///     mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
193    ///     mut animation2d: ResMut<ActorSpriteSheets>,
194    /// ) {
195    ///     let layout = TextureAtlasLayout::from_grid(UVec2::new(16, 16), 1, 3, None, None);
196    ///     let my_assets = MyAssets {
197    ///         idle_front: asset_server.load("frog_idle_front.png"),
198    ///         idle_back: asset_server.load("frog_idle_back.png"),
199    ///         idle_left: asset_server.load("frog_idle_left.png"),
200    ///         layout: texture_atlases.add(layout),
201    ///     };
202    ///
203    ///     // Load into collection
204    ///     animation2d.load_asset_loader(&my_assets);
205    /// }
206    /// ```
207    pub fn load_asset_loader<T: View2dCollection>(&mut self, loader: &T) {
208        let mut actor_id = 0;
209        let mut action_id = 0;
210
211        for (actor, action, angle, image, atlas_layout) in loader.get_all() {
212            actor_id = actor.unwrap_or(actor_id);
213            action_id = action.unwrap_or(action_id);
214            let field_angle = angle.unwrap_or_default();
215            let actor;
216            if let Some(_actor) = self.get_mut(&actor_id) {
217                actor = _actor;
218            } else {
219                self.insert(actor_id, HashMap::default());
220                actor = self.get_mut(&actor_id).unwrap();
221            }
222
223            let action;
224            if let Some(_action) = actor.get_mut(&action_id) {
225                action = _action;
226            } else {
227                actor.insert(action_id, AngleSpriteSheets::default());
228                action = actor.get_mut(&action_id).unwrap();
229            }
230
231            let any = action.get(&Angle::Any).cloned();
232            let sprite;
233            if let Some(_sprite) = action.get_mut(&field_angle) {
234                sprite = _sprite;
235            } else {
236                action.insert(field_angle, SpriteSheet::default());
237                sprite = action.get_mut(&field_angle).unwrap();
238            }
239
240            if let Some(image_handle) = image {
241                sprite.image = Some(image_handle.clone());
242            } else if let Some(any) = any.as_ref() {
243                sprite.image.clone_from(&any.image);
244            }
245
246            if let Some(atlas_layout_handle) = atlas_layout {
247                sprite.layout = Some(atlas_layout_handle.clone());
248            } else if let Some(any) = any.as_ref() {
249                sprite.layout.clone_from(&any.layout);
250            }
251
252            if field_angle == Angle::Any {
253                let any = sprite.clone();
254                for s in action.values_mut() {
255                    if s.image.is_none() {
256                        s.image.clone_from(&any.image);
257                    }
258                    if s.layout.is_none() {
259                        s.layout.clone_from(&any.layout);
260                    }
261                }
262            }
263        }
264    }
265}
266
267/// Convert actor/action to number id using xxh3_64
268pub fn get_act_id(act: &str) -> u64 {
269    xxh3_64(act.as_bytes())
270}