ycraft/
obj.rs

1//! Generic "GameObject" system that defines sets of behaviors using enums for resources/obj data
2
3use std::{
4    collections::HashMap,
5    hash::Hash
6};
7use sdl2::{
8    event::Event,
9    rect::Rect,
10    render::{
11        Canvas, TextureCreator
12    }, video::{
13        Window, WindowContext
14    }
15};
16use crate::{
17    collision::CollisionShape,
18    res::{
19        Font, Image, Sound
20    }, IndexRestriction
21};
22
23/// Every game object should have these parameters to return them via state()
24///
25/// - name: String,
26/// - pos: (f64, f64)
27/// - collider: CollisionShape
28/// - cur_spr: SprId (a custom enum defined by you to distinguish between sprites)
29/// - sprs: HashMap<SprId, Sprite<ImgId>> (a mapping of sprite ids to sprites)
30/// - custom: Data (a custom enum containing data for all your objects)
31#[derive(Clone)]
32pub struct GameObjectState<Img, Spr, Data> where
33        Spr: IndexRestriction,
34        Img: IndexRestriction,
35        Data: Clone {
36    pub name: String,
37    pub pos: (f64, f64),
38    pub collider: CollisionShape,
39    pub cur_spr: Spr,
40    pub sprs: HashMap<Spr, Sprite<Img>>,
41    pub custom: Data
42}
43
44/// All game objects should implement these
45///
46/// Note: the generic types refer to custom enums for indexing items:
47///
48/// - Img -> Id for going between the various Images loaded in
49/// - Snd -> Same but for sounds
50/// - Fnt -> Font
51/// - Spr -> Sprites
52/// - Rm -> Rooms
53/// - Data -> Custom data for each object
54pub trait GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>:
55    GameObjectBehaviorClone<Img, Snd, Fnt, Spr, Rm, Data> where
56        Spr: IndexRestriction,
57        Img: IndexRestriction,
58        Snd: IndexRestriction,
59        Fnt: IndexRestriction,
60        Rm: IndexRestriction,
61        Data: Clone {
62    fn state(&self) -> GameObjectState<Img, Spr, Data>;
63
64    fn set_state(&mut self, new_state: &GameObjectState<Img, Spr, Data>);
65
66    /// Let game objects reset their data on room load. If you return true, the object is removed
67    /// from the room
68    fn on_reset(&mut self) -> bool;
69
70    /// Let game objects modify their state every loop. Return a room to change to and objects to
71    /// add to the room.
72    fn update(
73            &mut self, _delta: f64,
74            _ctl_objs: &Vec<Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>,
75            _others: &Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>) -> (
76                Option<Rm>, Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>
77            ) {
78        (None, vec![])
79    }
80
81    fn handle_sdl_event(&mut self, _event: &Event) {}
82
83    fn on_collision(
84        &mut self,
85        _other: &Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>) {}
86
87    fn render(
88            &mut self, cnv: &mut Canvas<Window>,
89            imgs: &HashMap<Img, Image>, _snds: &HashMap<Snd, Sound>,
90            _fonts: &HashMap<Fnt, Font>, _creator: &TextureCreator<WindowContext>,
91            elapsed: f64) -> Result<(), String> {
92        let mut state = self.state().clone();
93        let GameObjectState { ref mut sprs, ref mut cur_spr, pos, .. } = state;
94        if let Some(spr) = sprs.get_mut(cur_spr) {
95            spr.update(elapsed);
96            spr.render(cnv, imgs, (pos.0 as i32, pos.1 as i32))?;
97        }
98        self.set_state(&state);
99        Ok(())
100    }
101
102    fn should_remove(&self) -> bool {
103        false
104    }
105}
106
107/// A special trait to implement cloning for our dynamic GameObjects
108pub trait GameObjectBehaviorClone<Img, Snd, Fnt, Spr, Rm, Data> where
109        Spr: IndexRestriction,
110        Img: IndexRestriction,
111        Snd: IndexRestriction,
112        Fnt: IndexRestriction,
113        Rm: IndexRestriction,
114        Data: Clone {
115    fn clone_box(&self) -> Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>;
116}
117
118impl<Img, Snd, Fnt, Spr, Rm, Data, T>
119    GameObjectBehaviorClone<Img, Snd, Fnt, Spr, Rm, Data> for T where
120        T: 'static + GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> + Clone,
121        Spr: IndexRestriction,
122        Img: IndexRestriction,
123        Snd: IndexRestriction,
124        Fnt: IndexRestriction,
125        Rm: IndexRestriction,
126        Data: Clone {
127    fn clone_box(&self) -> Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>> {
128        Box::new(self.clone())
129    }
130}
131
132impl<Img, Snd, Fnt, Spr, Rm, Data> Clone
133    for Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>> where
134        Spr: IndexRestriction,
135        Img: IndexRestriction,
136        Snd: IndexRestriction,
137        Fnt: IndexRestriction,
138        Rm: IndexRestriction,
139        Data: Clone {
140    fn clone(&self) -> Self {
141        self.clone_box()
142    }
143}
144
145/// Control objects are basically game objects, but they are aware of the current room and do not
146/// possess colliders. They are the way for doing dynamic memory
147pub trait ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>:
148    ControlObjectBehaviorClone<Img, Snd, Fnt, Spr, Rm, Data> where
149        Spr: IndexRestriction,
150        Img: IndexRestriction,
151        Snd: IndexRestriction,
152        Fnt: IndexRestriction,
153        Rm: IndexRestriction,
154        Data: Clone {
155    fn data(&self) -> Data;
156    
157    fn handle_sdl_event(&mut self, _event: &Event) {}
158
159    fn update(
160            &mut self, _delta: f64, _cur_room: &Rm,
161            _others: &Vec<Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>,
162            _room_objs: &Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>) -> (
163                Option<Rm>, Vec<Box<dyn GameObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>>
164            ) {
165        (None, vec![])
166    }
167
168    fn render(
169            &mut self, _cnv: &mut Canvas<Window>, _cur_room: &Rm,
170            _imgs: &HashMap<Img, Image>, _snds: &HashMap<Snd, Sound>,
171            _fonts: &HashMap<Fnt, Font>, _creator: &TextureCreator<WindowContext>,
172            _elapsed: f64) -> Result<(), String> {
173        Ok(())
174    }
175}
176
177pub trait ControlObjectBehaviorClone<Img, Snd, Fnt, Spr, Rm, Data> where
178        Spr: IndexRestriction,
179        Img: IndexRestriction,
180        Snd: IndexRestriction,
181        Fnt: IndexRestriction,
182        Rm: IndexRestriction,
183        Data: Clone {
184    fn clone_box(&self) -> Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>>;
185}
186
187impl<Img, Snd, Fnt, Spr, Rm, Data, T>
188    ControlObjectBehaviorClone<Img, Snd, Fnt, Spr, Rm, Data> for T where
189        T: 'static + ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data> + Clone,
190        Spr: IndexRestriction,
191        Img: IndexRestriction,
192        Snd: IndexRestriction,
193        Fnt: IndexRestriction,
194        Rm: IndexRestriction,
195        Data: Clone {
196    fn clone_box(&self) -> Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>> {
197        Box::new(self.clone())
198    }
199}
200
201impl<Img, Snd, Fnt, Spr, Rm, Data> Clone
202    for Box<dyn ControlObjectBehavior<Img, Snd, Fnt, Spr, Rm, Data>> where
203        Spr: IndexRestriction,
204        Img: IndexRestriction,
205        Snd: IndexRestriction,
206        Fnt: IndexRestriction,
207        Rm: IndexRestriction,
208        Data: Clone {
209    fn clone(&self) -> Self {
210        self.clone_box()
211    }
212}
213
214/// A single frame of animation - where to clip an image and how to draw
215///
216/// ImgId refers to an enum that distinguishes between all the image resources in your game
217#[derive(Clone, Copy)]
218pub struct Frame<ImgId> where ImgId: Hash + Eq + Clone + Copy {
219    src: ImgId,
220    clip: Rect,
221    size: (i32, i32)
222}
223
224impl<ImgId> Frame<ImgId> where ImgId: Hash + Eq + Clone + Copy {
225    pub fn new(src: ImgId, clip: Rect, size: (i32, i32)) -> Self {
226        Self {
227            src,
228            clip,
229            size
230        }
231    }
232
233    pub fn render(
234            &self, cnv: &mut Canvas<Window>, imgs: &HashMap<ImgId, Image>,
235            pos: (i32, i32), origin: (i32, i32), scale: (f64, f64),
236            angle: f64, flip: (bool, bool)) -> Result<(), String> {
237        let base_scale = (
238            self.size.0 as f64 / self.clip.w as f64,
239            self.size.1 as f64 / self.clip.h as f64
240        );
241        let dest = Rect::new(
242            (pos.0 as f64 - origin.0 as f64 * base_scale.0 * scale.0) as i32,
243            (pos.1 as f64 - origin.1 as f64 * base_scale.1 * scale.1) as i32,
244            (self.size.0 as f64 * scale.0) as u32,
245            (self.size.1 as f64 * scale.1) as u32
246        );
247        imgs[&self.src].render(cnv, &self.clip, &dest, angle, flip)
248    }
249}
250
251/// A collection of different animation frames that can be moved around a screen
252#[derive(Clone)]
253pub struct Sprite<Img> where Img: IndexRestriction {
254    pub frames: Vec<Frame<Img>>,
255    pub anim_spd: f64,
256    pub origin: (i32, i32),
257    pub anim_idx: usize,
258    pub anim_idx_smooth: f64,
259    pub scale: (f64, f64),
260    pub angle: f64,
261    pub flip: (bool, bool)
262}
263
264impl<Img> Sprite<Img> where Img: IndexRestriction {
265    pub fn new(frames: Vec<Frame<Img>>, anim_spd: f64, origin: (i32, i32)) -> Self {
266        Self {
267            frames: frames.clone(),
268            anim_spd,
269            origin,
270            anim_idx: 0,
271            anim_idx_smooth: 0.0,
272            scale: (1.0, 1.0),
273            angle: 0.0,
274            flip: (false, false)
275        }
276    }
277
278    pub fn update(&mut self, elapsed: f64) {
279        self.anim_idx_smooth += elapsed * self.anim_spd;
280        if self.anim_idx_smooth > 1.0 {
281            if self.anim_idx + 1 >= self.frames.len() {
282                self.anim_idx = 0;
283            } else {
284                self.anim_idx += 1;
285            }
286            self.anim_idx_smooth = 0.0;
287        }
288    }
289
290    pub fn render(
291            &self, cnv: &mut Canvas<Window>, imgs: &HashMap<Img, Image>,
292            pos: (i32, i32)) -> Result<(), String> {
293        self.frames[self.anim_idx].render(
294            cnv, imgs, pos, self.origin, self.scale, self.angle, self.flip
295        )
296    }
297}
298