ui_examples/
f_sprites.rs

1use std::time::Duration;
2
3use mooeye::{scene_manager, sprite, ui, ui::UiContent};
4
5use ggez::{
6    context::Context,
7    glam::Vec2,
8    graphics::{Color, DrawParam, Rect},
9    *,
10};
11
12// # Sprites
13// In this example, we have a look at the sprite class that can be used for drawing animated images.
14
15/// A struct of 'game data' for scene F.
16/// In addition to our GUI, this also contains sprite and position data for a game entity.
17pub struct FScene {
18    /// The root element of FScene's GUI.
19    gui: ui::UiElement<()>,
20    /// A sprite for a wizard.
21    /// A sprite can be used as an UI element, but also simply as part of your game state separated from the UI
22    sprite: sprite::Sprite,
23    /// Another sprite displaying a cow.
24    /// It will graze next to the wizard.
25    sprite2: sprite::Sprite,
26    /// The position of the wizard sprite on the screen.
27    pos: Vec2,
28    /// The speed of the wizard sprite on the screen
29    v: Vec2,
30}
31
32impl FScene {
33    /// Creates a new FScene in the mooeye-idiomatic way.
34    pub fn new(ctx: &Context) -> Result<Self, GameError> {
35        // Reusing the visuals from E.
36
37        let vis = ui::Visuals::new(
38            Color::from_rgb(180, 120, 60),
39            Color::from_rgb(18, 12, 6),
40            1.,
41            0.,
42        );
43
44        let hover_vis = ui::Visuals::new(
45            Color::from_rgb(160, 100, 40),
46            Color::from_rgb(18, 12, 6),
47            3.,
48            0.,
49        );
50
51        let cont_vis = ui::Visuals::new(
52            Color::from_rgb(60, 120, 180),
53            Color::from_rgb(180, 180, 190),
54            1.,
55            0.,
56        );
57
58        // A sprite can be loaded by specifying a path, just like an Image.
59        // Additionaly, you need to inform the sprite of the grid size of its sheet and the duration each frame is displayed.
60        let ui_sprite = sprite::Sprite::from_path(
61            "/moo-sheet_16_16.png",
62            ctx,
63            16,
64            24,
65            Duration::from_secs_f32(0.25),
66        )?
67        // Just like any UI element, a sprite can have visuals, tooltip, ect.
68        .to_element_builder(1, ctx)
69        .scaled(5., 5.)
70        .with_visuals(vis)
71        .with_hover_visuals(hover_vis)
72        .with_tooltip(
73            graphics::Text::new("This is a sprite! Click it to end the scene.")
74                .set_scale(28.)
75                .set_font("Bahnschrift")
76                .to_owned()
77                .to_element_builder(0, ctx)
78                .with_visuals(cont_vis)
79                .build(),
80        )
81        .as_shrink()
82        .build();
83
84        // Sprites can also be initiated from a sprite pool, to make repeated file system access unneccessary
85        // and streamline loading of multiple sprites. This requires sprites in the folder to be formatted appropriately.
86
87        let sprite_pool = sprite::SpritePool::new()
88            // with_folder loads all .png/.bmp/.jpg/.jpeg files from the passed folder and optionally its subfolders
89            .with_folder(ctx, "/", true);
90
91        // We can now init a sprite from the pool. Sprites are saved in the pool with a key corresponding to their relative path
92        // (from the resource folder) with the format information and file ending removed.
93        let non_ui_sprite = sprite_pool.init_sprite("/mage-sheet", Duration::from_secs_f32(0.2))?;
94
95        // you can also initialize a sprite pool without any folder at all
96        let mut sprite_pool2 = sprite::SpritePool::new();
97
98        // in this case, you can use lazy initialisation of sprites to fill the sprite pool only with those sprites currently needed.
99        // Lazy initilisation draws from the pool if possible, from the file system if needed (and loads into the pool in this case) and panics if it can't find anything in the fs.
100        // Requires a mutable sprite pool!
101        // For testing purposes, we are loading a sprite we have already loaded - this should be drawn from the pool.
102        let lazy_sprite =
103            sprite_pool2.init_sprite_lazy(ctx, "/mage-sheet", Duration::from_secs_f32(0.5))?;
104        // now load the correct sprite
105        let lazy_sprite =
106            sprite_pool2.init_sprite_lazy(ctx, "/moo-sheet", lazy_sprite.get_frame_time())?;
107
108        Ok(Self {
109            gui: ui_sprite,
110            sprite: non_ui_sprite,
111            sprite2: lazy_sprite,
112            pos: Vec2::new(50., 200.),
113            v: Vec2::new(4., 4.),
114        })
115    }
116}
117
118impl scene_manager::Scene for FScene {
119    fn update(&mut self, ctx: &mut Context) -> Result<scene_manager::SceneSwitch, GameError> {
120        // Actually implementing some game state logic.
121
122        // Pressing space changes the variant of the sprite.
123        if ctx
124            .keyboard
125            .is_key_just_pressed(winit::event::VirtualKeyCode::Space)
126        {
127            self.sprite.set_variant(self.sprite.get_variant() + 1);
128        }
129
130        // Move the sprite.
131        self.pos += self.v;
132
133        // Make the sprite bounce off the screen edges.
134        let scaling = 5.;
135
136        if self.pos.x - scaling * 4. < 0. || self.pos.x + scaling * 4. >= ctx.gfx.drawable_size().0
137        {
138            self.v.x *= -1.;
139        }
140
141        if self.pos.y - scaling * 8. < 0. || self.pos.y + scaling * 8. >= ctx.gfx.drawable_size().1
142        {
143            self.v.y *= -1.;
144        }
145
146        // And handle messages as usual
147
148        let messages = self.gui.manage_messages(ctx, None);
149
150        if messages.contains(&ui::UiMessage::Triggered(1)) {
151            return Ok(scene_manager::SceneSwitch::pop(1));
152        }
153
154        Ok(scene_manager::SceneSwitch::None)
155    }
156
157    fn draw(&mut self, ctx: &mut Context, mouse_listen: bool) -> Result<(), GameError> {
158        // Once again, we first create a canvas and set a pixel sampler. Note that this time, we dont clear the background.
159
160        let mut canvas = ggez::graphics::Canvas::from_frame(ctx, None);
161        canvas.set_sampler(ggez::graphics::Sampler::nearest_clamp());
162
163        // Drawing of our (limited) game state.
164
165        // see if we need to mirror our sprite if it moves left
166        let mirroring = if self.v.x > 0. { 1. } else { -1. };
167        let scaling = 5.;
168
169        self.sprite.draw_sprite(
170            ctx,
171            &mut canvas,
172            DrawParam::new().dest_rect(Rect::new(
173                self.pos.x - scaling * 4. * mirroring,
174                self.pos.y - scaling * 8.,
175                scaling * mirroring,
176                scaling,
177            )),
178        );
179
180        let scaling = 2.;
181
182        self.sprite2.draw_sprite(
183            ctx,
184            &mut canvas,
185            DrawParam::new().dest_rect(Rect::new(
186                self.pos.x - scaling * 4. + 32.,
187                self.pos.y - scaling * 8. + 32.,
188                scaling,
189                scaling,
190            )),
191        );
192
193        // And once again drawing the GUI.
194
195        self.gui.draw_to_screen(ctx, &mut canvas, mouse_listen);
196
197        canvas.finish(ctx)?;
198
199        Ok(())
200    }
201}