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}