├── examples ├── resources │ ├── moose.png │ ├── water_normal.png │ ├── dig_escape_snap.png │ ├── transition_slide.png │ ├── transition_split.png │ └── transition_spiral.png ├── transition_simple.rs ├── animation.rs ├── water.rs └── states.rs ├── src ├── lib.rs ├── progress_bar.rs ├── transition.rs ├── resources.rs ├── animation.rs ├── water.rs └── states.rs ├── .gitignore ├── Cargo.toml └── LICENSE /examples/resources/moose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/macroquad_tantan_toolbox/HEAD/examples/resources/moose.png -------------------------------------------------------------------------------- /examples/resources/water_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/macroquad_tantan_toolbox/HEAD/examples/resources/water_normal.png -------------------------------------------------------------------------------- /examples/resources/dig_escape_snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/macroquad_tantan_toolbox/HEAD/examples/resources/dig_escape_snap.png -------------------------------------------------------------------------------- /examples/resources/transition_slide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/macroquad_tantan_toolbox/HEAD/examples/resources/transition_slide.png -------------------------------------------------------------------------------- /examples/resources/transition_split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/macroquad_tantan_toolbox/HEAD/examples/resources/transition_split.png -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod animation; 2 | pub mod progress_bar; 3 | pub mod resources; 4 | pub mod states; 5 | pub mod transition; 6 | pub mod water; 7 | -------------------------------------------------------------------------------- /examples/resources/transition_spiral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TanTanDev/macroquad_tantan_toolbox/HEAD/examples/resources/transition_spiral.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macroquad_tantan_toolbox" 3 | version = "0.1.0" 4 | authors = ["TanTanDev "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = """ 8 | general gamedev tools for macroquad 9 | """ 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | name = "macroquad_tantan_toolbox" 15 | path = "src/lib.rs" 16 | 17 | [dependencies] 18 | macroquad = "0.3.3" 19 | futures = "0.3.14" 20 | async-trait = "0.1.48" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TanTanDev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/transition_simple.rs: -------------------------------------------------------------------------------- 1 | use macroquad::prelude::*; 2 | use macroquad_tantan_toolbox::transition; 3 | use macroquad_tantan_toolbox::transition::Transition; 4 | 5 | const GAME_SIZE: Vec2 = const_vec2!([512f32, 512f32]); 6 | 7 | #[macroquad::main("transition simple")] 8 | async fn main() { 9 | let render_target_view1 = render_target(GAME_SIZE.x as u32, GAME_SIZE.y as u32); 10 | render_target_view1.texture.set_filter(FilterMode::Nearest); 11 | 12 | let render_target_view2 = render_target(GAME_SIZE.x as u32, GAME_SIZE.y as u32); 13 | render_target_view2.texture.set_filter(FilterMode::Nearest); 14 | 15 | let transition_tex: Texture2D = load_texture("examples/resources/transition_slide.png") 16 | .await 17 | .unwrap(); 18 | 19 | let mut camera2d = Camera2D { 20 | zoom: vec2(0.01, 0.01), 21 | ..Default::default() 22 | }; 23 | 24 | let fade = 0.1f32; 25 | let mut transition = Transition::new(transition_tex, fade); 26 | loop { 27 | // draw first view to render texture 28 | camera2d.render_target = Some(render_target_view1); 29 | set_camera(&camera2d); 30 | clear_background(GREEN); 31 | draw_circle(0f32, 0f32, 10.0f32, BLACK); 32 | draw_text("VIEW 1", -50f32, 0f32, 40f32, BLACK); 33 | 34 | // draw second screen to render texture 35 | camera2d.render_target = Some(render_target_view2); 36 | set_camera(&camera2d); 37 | clear_background(BLUE); 38 | draw_text("VIEW 2", -50f32, 0f32, 40f32, WHITE); 39 | 40 | // draw the transition 41 | set_default_camera(); 42 | // we wont see yellow because transition is drawn over, but we need to clear anyway 43 | clear_background(YELLOW); 44 | let progress = (get_time() as f32 * 4.0f32).sin() * 0.5f32 + 0.5f32; 45 | 46 | // flip_y because rendertexture are flipped... 47 | transition.draw_ex( 48 | render_target_view1.texture, 49 | render_target_view2.texture, 50 | progress, 51 | transition::DrawParam { flip_y: true }, 52 | ); 53 | 54 | next_frame().await 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/animation.rs: -------------------------------------------------------------------------------- 1 | use macroquad::prelude::*; 2 | use macroquad_tantan_toolbox::animation::*; 3 | 4 | #[derive(std::hash::Hash, Eq, PartialEq)] 5 | enum MooseAnimationIdentifier { 6 | Run, 7 | Sleep, 8 | } 9 | 10 | const GAME_SIZE: Vec2 = const_vec2!([64f32, 64f32]); 11 | 12 | #[macroquad::main("animation")] 13 | async fn main() { 14 | let texture: Texture2D = load_texture("examples/resources/moose.png").await.unwrap(); 15 | texture.set_filter(FilterMode::Nearest); 16 | 17 | let game_render_target = render_target(GAME_SIZE.x as u32, GAME_SIZE.y as u32); 18 | game_render_target.texture.set_filter(FilterMode::Nearest); 19 | 20 | let mut animation = AnimationInstance::::new( 21 | 10f32, 22 | 1f32, 23 | texture, 24 | MooseAnimationIdentifier::Run, 25 | ); 26 | animation.add_animation(0, 3, None, 15f32, MooseAnimationIdentifier::Run); 27 | animation.add_animation(4, 9, Some(7), 13f32, MooseAnimationIdentifier::Sleep); 28 | let camera = Camera2D { 29 | zoom: vec2(1. / GAME_SIZE.x * 2., 1. / GAME_SIZE.y * 2.), 30 | target: vec2(0.0, 0.0), 31 | render_target: Some(game_render_target), 32 | ..Default::default() 33 | }; 34 | loop { 35 | set_camera(&camera); 36 | clear_background(BLUE); 37 | 38 | // change animation 39 | if is_key_pressed(KeyCode::Space) { 40 | let next_state = match animation.current_animation { 41 | MooseAnimationIdentifier::Run => MooseAnimationIdentifier::Sleep, 42 | MooseAnimationIdentifier::Sleep => MooseAnimationIdentifier::Run, 43 | }; 44 | animation.play_animation(next_state); 45 | } 46 | 47 | animation.update(get_frame_time()); 48 | animation.draw(&vec2(0f32, 0f32), false); 49 | //animation.draw(&vec2(-GAME_SIZE.x * 0.5f32, 0f32), false); 50 | 51 | set_default_camera(); 52 | clear_background(BLUE); 53 | // draw game 54 | draw_texture_ex( 55 | game_render_target.texture, 56 | 0., 57 | 0., 58 | WHITE, 59 | DrawTextureParams { 60 | dest_size: Some(vec2(screen_width(), screen_height())), 61 | ..Default::default() 62 | }, 63 | ); 64 | draw_text( 65 | "tap space to change animation", 66 | screen_width() * 0.5f32 - 100f32, 67 | 40f32, 68 | 30f32, 69 | BLACK, 70 | ); 71 | 72 | next_frame().await 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/progress_bar.rs: -------------------------------------------------------------------------------- 1 | use macroquad::prelude::*; 2 | 3 | pub struct DrawParam {} 4 | 5 | impl Default for DrawParam { 6 | fn default() -> Self { 7 | DrawParam {} 8 | } 9 | } 10 | 11 | pub struct ProgressBar { 12 | pub material: Material, 13 | pub texture: Texture2D, 14 | pub texture_border: Texture2D, 15 | pub scroll_speed: f32, 16 | pub inner_offset: Vec2, 17 | } 18 | 19 | impl ProgressBar { 20 | pub fn draw(&mut self, pos: Vec2, progress: f32) { 21 | self.draw_ex(pos, progress, DrawParam::default()); 22 | } 23 | 24 | pub fn draw_ex(&mut self, pos: Vec2, progress: f32, _draw_param: DrawParam) { 25 | self.material.set_uniform("progress", progress); 26 | self.material 27 | .set_uniform("uv_offset", get_time() as f32 * self.scroll_speed); 28 | draw_texture(self.texture_border, pos.x, pos.y, WHITE); 29 | gl_use_material(self.material); 30 | draw_texture_ex( 31 | self.texture, 32 | pos.x + self.inner_offset.x, 33 | pos.y + self.inner_offset.y, 34 | WHITE, 35 | DrawTextureParams { 36 | dest_size: Some(vec2(233., 41.)), 37 | ..Default::default() 38 | }, 39 | ); 40 | gl_use_default_material(); 41 | } 42 | 43 | pub fn new(texture: Texture2D, texture_border: Texture2D, inner_offset: Vec2) -> Self { 44 | let fragment_shader = FRAGMENT_SHADER.to_string(); 45 | let vertex_shader = VERTEX_SHADER.to_string(); 46 | 47 | let pipeline_params = PipelineParams { 48 | depth_write: true, 49 | depth_test: Comparison::LessOrEqual, 50 | ..Default::default() 51 | }; 52 | 53 | let material = load_material( 54 | &vertex_shader, 55 | &fragment_shader, 56 | MaterialParams { 57 | //textures: vec!["tex_bar".to_string()], 58 | textures: vec![], 59 | uniforms: vec![ 60 | ("progress".to_string(), UniformType::Float1), 61 | ("uv_offset".to_string(), UniformType::Float1), 62 | ], 63 | pipeline_params, 64 | }, 65 | ) 66 | .unwrap(); 67 | 68 | ProgressBar { 69 | material, 70 | texture, 71 | texture_border, 72 | scroll_speed: 0.5f32, 73 | inner_offset, 74 | } 75 | } 76 | } 77 | 78 | const FRAGMENT_SHADER: &str = "#version 100 79 | precision lowp float; 80 | varying vec2 uv; 81 | 82 | uniform float progress; 83 | uniform float uv_offset; 84 | uniform sampler2D Texture; 85 | 86 | varying vec4 color; 87 | 88 | void main() { 89 | if (uv.x > progress) { 90 | discard; 91 | } 92 | vec4 base_color = texture2D(Texture, uv+vec2(uv_offset, 0)); 93 | 94 | gl_FragColor = base_color; 95 | } 96 | "; 97 | 98 | const VERTEX_SHADER: &str = "#version 100 99 | attribute vec3 position; 100 | attribute vec2 texcoord; 101 | attribute vec4 color0; 102 | 103 | varying lowp vec2 uv; 104 | varying lowp vec4 color; 105 | uniform mat4 Model; 106 | uniform mat4 Projection; 107 | 108 | void main() { 109 | gl_Position = Projection * Model * vec4(position, 1); 110 | color = color0 / 255.0; 111 | uv = texcoord; 112 | } 113 | "; 114 | -------------------------------------------------------------------------------- /src/transition.rs: -------------------------------------------------------------------------------- 1 | use macroquad::{miniquad::gl::GL_TEXTURE_CUBE_MAP_NEGATIVE_X, prelude::*}; 2 | 3 | pub struct DrawParam { 4 | pub flip_y: bool, 5 | } 6 | 7 | impl Default for DrawParam { 8 | fn default() -> Self { 9 | DrawParam { flip_y: false } 10 | } 11 | } 12 | 13 | pub struct Transition { 14 | pub material: Material, 15 | pub fade: f32, 16 | } 17 | 18 | impl Transition { 19 | pub fn draw(&mut self, base_texture: Texture2D, into_texture: Texture2D, progress: f32) { 20 | self.draw_ex(base_texture, into_texture, progress, DrawParam::default()); 21 | } 22 | 23 | pub fn draw_ex( 24 | &mut self, 25 | base_texture: Texture2D, 26 | into_texture: Texture2D, 27 | progress: f32, 28 | draw_param: DrawParam, 29 | ) { 30 | self.material.set_uniform("cutoff", progress); 31 | self.material.set_uniform("fade", self.fade); 32 | self.material.set_texture("tex_into", into_texture); 33 | gl_use_material(self.material); 34 | clear_background(WHITE); 35 | draw_texture_ex( 36 | base_texture, 37 | -1., 38 | -1., 39 | WHITE, 40 | DrawTextureParams { 41 | dest_size: Some(vec2(2., 2.)), 42 | flip_y: draw_param.flip_y, 43 | ..Default::default() 44 | }, 45 | ); 46 | gl_use_default_material(); 47 | } 48 | 49 | pub fn change_transition_tex(&mut self, texture: Texture2D) { 50 | self.material.set_texture("tex_transition", texture); 51 | } 52 | 53 | pub fn new(transition_tex: Texture2D, fade: f32) -> Self { 54 | let fragment_shader = DEFAULT_FRAGMENT_SHADER.to_string(); 55 | let vertex_shader = DEFAULT_VERTEX_SHADER.to_string(); 56 | 57 | let pipeline_params = PipelineParams { 58 | depth_write: true, 59 | depth_test: Comparison::LessOrEqual, 60 | ..Default::default() 61 | }; 62 | 63 | let material = load_material( 64 | &vertex_shader, 65 | &fragment_shader, 66 | MaterialParams { 67 | textures: vec!["tex_transition".to_string(), "tex_into".to_string()], 68 | uniforms: vec![ 69 | ("cutoff".to_string(), UniformType::Float1), 70 | ("fade".to_string(), UniformType::Float1), 71 | ], 72 | pipeline_params, 73 | }, 74 | ) 75 | .unwrap(); 76 | 77 | material.set_texture("tex_transition", transition_tex); 78 | Transition { material, fade } 79 | } 80 | } 81 | 82 | const DEFAULT_FRAGMENT_SHADER: &str = "#version 100 83 | precision lowp float; 84 | varying vec2 uv; 85 | 86 | uniform float cutoff; 87 | uniform float fade; 88 | // base texture 89 | uniform sampler2D Texture; 90 | uniform sampler2D tex_into; 91 | uniform sampler2D tex_transition; 92 | 93 | varying vec4 color; 94 | 95 | void main() { 96 | float transition = texture2D(tex_transition, uv).r; 97 | vec4 base_color = texture2D(Texture, uv); 98 | vec4 into_color = texture2D(tex_into, uv); 99 | 100 | // remap transition from 0-1 to fade -> 1.0-fade 101 | transition = transition * (1.0 - fade) + fade; 102 | float f = smoothstep(cutoff, cutoff + fade, transition); 103 | gl_FragColor = mix(base_color, into_color, f); 104 | } 105 | "; 106 | 107 | const DEFAULT_VERTEX_SHADER: &str = "#version 100 108 | attribute vec3 position; 109 | attribute vec2 texcoord; 110 | varying vec2 uv; 111 | 112 | void main() { 113 | gl_Position = vec4(position, 1); 114 | uv = texcoord; 115 | } 116 | "; 117 | -------------------------------------------------------------------------------- /examples/water.rs: -------------------------------------------------------------------------------- 1 | use macroquad::prelude::*; 2 | use macroquad_tantan_toolbox::water::*; 3 | 4 | const GAME_SIZE: Vec2 = const_vec2!([1344f32, 768f32]); 5 | 6 | #[macroquad::main("water")] 7 | async fn main() { 8 | let render_target_game = render_target(GAME_SIZE.x as u32, GAME_SIZE.y as u32); 9 | render_target_game.texture.set_filter(FilterMode::Nearest); 10 | 11 | let camera2d = Camera2D { 12 | zoom: vec2(1. / GAME_SIZE.x * 2., 1. / GAME_SIZE.y * 2.), 13 | target: vec2( 14 | (GAME_SIZE.x * 0.5f32).floor() + (get_time().sin() as f32 * 3.0f32), 15 | (GAME_SIZE.y * 0.5f32).floor(), 16 | ), 17 | render_target: Some(render_target_game), 18 | ..Default::default() 19 | }; 20 | 21 | let tex_water_raw = load_image("examples/resources/water_normal.png") 22 | .await 23 | .unwrap(); 24 | let tex_water_normal = { 25 | // All of this is so we can sample the texture repeatedly 26 | use miniquad; 27 | use miniquad::{FilterMode, TextureFormat, TextureParams, TextureWrap}; 28 | let ctx = unsafe { get_internal_gl().quad_context }; 29 | let texture_miniquad = miniquad::graphics::Texture::from_data_and_format( 30 | ctx, 31 | &tex_water_raw.bytes, 32 | TextureParams { 33 | format: TextureFormat::RGBA8, 34 | wrap: TextureWrap::Repeat, 35 | filter: FilterMode::Linear, 36 | width: tex_water_raw.width as u32, 37 | height: tex_water_raw.height as u32, 38 | }, 39 | ); 40 | Texture2D::from_miniquad_texture(texture_miniquad) 41 | }; 42 | 43 | let water_size = vec2(GAME_SIZE.x, GAME_SIZE.y * 0.5f32 + 10f32); 44 | let water_pos = vec2(water_size.x * 0.0f32, GAME_SIZE.y * 0.5f32 + 100f32); 45 | let water_dir = vec2(1.0f32, 0.0f32); 46 | let water_speed = 0.05f32; 47 | let water_strength = 0.02f32; 48 | let mut water = Water::new( 49 | water_pos, 50 | water_size, 51 | render_target_game.texture, 52 | tex_water_normal, 53 | water_dir, 54 | water_speed, 55 | water_strength, 56 | 0f32, 57 | ); 58 | 59 | let beautiful = load_texture("examples/resources/dig_escape_snap.png") 60 | .await 61 | .unwrap(); 62 | beautiful.set_filter(FilterMode::Nearest); 63 | loop { 64 | // draw first view to render texture 65 | set_camera(&camera2d); 66 | clear_background(BLUE); 67 | draw_texture_ex( 68 | beautiful, 69 | 0f32, 70 | -200f32, 71 | WHITE, 72 | DrawTextureParams { 73 | dest_size: Some(vec2(GAME_SIZE.x, GAME_SIZE.y)), 74 | ..Default::default() 75 | }, 76 | ); 77 | 78 | water.update(get_frame_time()); 79 | water.draw(&camera2d); 80 | 81 | set_default_camera(); 82 | // we wont see yellow because transition is drawn over, but we need to clear anyway 83 | clear_background(WHITE); 84 | 85 | draw_texture_ex( 86 | render_target_game.texture, 87 | 0., 88 | 0., 89 | WHITE, 90 | DrawTextureParams { 91 | dest_size: Some(vec2(screen_width(), screen_height())), 92 | ..Default::default() 93 | }, 94 | ); 95 | draw_ui(&mut water); 96 | 97 | next_frame().await 98 | } 99 | } 100 | 101 | use macroquad::ui::{ 102 | hash, root_ui, 103 | widgets::{self}, 104 | }; 105 | 106 | fn draw_ui(water: &mut Water) { 107 | widgets::Window::new(hash!(), vec2(400., 200.), vec2(320., 400.)) 108 | .label("Settings") 109 | .ui(&mut *root_ui(), |ui| { 110 | ui.label(Vec2::new(10., 10.), "HAAH"); 111 | ui.slider(hash!(), "strength", 0f32..0.4f32, &mut water.strength); 112 | ui.slider(hash!(), "speed", 0f32..1f32, &mut water.speed); 113 | }); 114 | } 115 | -------------------------------------------------------------------------------- /src/resources.rs: -------------------------------------------------------------------------------- 1 | use macroquad::audio::*; 2 | use macroquad::prelude::*; 3 | use std::collections::HashMap; 4 | use std::fmt::Debug; 5 | use std::hash::Hash; 6 | use std::marker::PhantomData; 7 | 8 | // THIS doesn't work on wasm builds atm due to futures::executor::block_on not being allowed in wasm 9 | 10 | pub struct DefaultFactory; 11 | impl ResourceFactory for DefaultFactory { 12 | fn load_resource(path: &str) -> Texture2D { 13 | let texture = futures::executor::block_on(load_texture(path)).unwrap(); 14 | texture.set_filter(FilterMode::Nearest); 15 | texture 16 | } 17 | } 18 | 19 | impl ResourceFactory for DefaultFactory { 20 | fn load_resource(path: &str) -> Image { 21 | let image = futures::executor::block_on(load_image(path)).unwrap(); 22 | image 23 | } 24 | } 25 | 26 | impl ResourceFactory for DefaultFactory { 27 | fn load_resource(path: &str) -> Sound { 28 | let file = futures::executor::block_on(load_sound(path)); 29 | file.unwrap() 30 | } 31 | } 32 | 33 | pub trait ResourceFactory { 34 | fn load_resource(path: &str) -> ResourceType; 35 | } 36 | 37 | // TextureIdentifier: used as a key to acces the resource 38 | pub trait Resources: Sized 39 | where 40 | ResourceIdentifier: Eq + Hash + Clone + Debug, 41 | F: ResourceFactory, 42 | { 43 | fn build(builder: &mut ResourceBuilder) -> Self; 44 | } 45 | 46 | // R: resources 47 | pub struct ResourceBuilder 48 | where 49 | ResourceIdentifier: Eq + Hash + Clone + Debug, 50 | R: Resources + Sized, 51 | F: ResourceFactory, 52 | { 53 | // path to file 54 | queued_resources: Vec<(ResourceIdentifier, &'static str)>, 55 | loaded_resources: HashMap, 56 | total_resources_to_load: i32, 57 | phantom_resource_r: PhantomData, 58 | phantom_resource_f: PhantomData, 59 | } 60 | 61 | pub async fn test(path: &str) -> Texture2D { 62 | load_texture(path).await.unwrap() 63 | } 64 | 65 | impl ResourceBuilder 66 | where 67 | TextureIdentifier: Eq + Hash + Copy + Clone + Debug, 68 | R: Resources, 69 | F: ResourceFactory, 70 | { 71 | pub fn new(queued_resources: Vec<(TextureIdentifier, &'static str)>) -> Self { 72 | let total_resources_to_load = queued_resources.len() as i32; 73 | Self { 74 | queued_resources, 75 | loaded_resources: HashMap::new(), 76 | total_resources_to_load, 77 | phantom_resource_r: PhantomData, 78 | phantom_resource_f: PhantomData, 79 | } 80 | } 81 | 82 | pub async fn load_next(&mut self) -> bool { 83 | let is_done = match self.queued_resources.get(0) { 84 | Some(identifier_name_pair) => { 85 | let resource = F::load_resource(identifier_name_pair.1); //load_texture(identifier_name_pair.1).await; 86 | println!("loaded resource: {:?}", identifier_name_pair); 87 | self.loaded_resources 88 | .insert(identifier_name_pair.0, resource); 89 | false 90 | } 91 | None => true, 92 | }; 93 | match is_done { 94 | false => { 95 | let _ = self.queued_resources.remove(0); 96 | } 97 | true => {} 98 | } 99 | is_done 100 | } 101 | 102 | pub fn progress(&mut self) -> f32 { 103 | if self.queued_resources.is_empty() { 104 | 1f32 105 | } else { 106 | 1. - self.queued_resources.len() as f32 / self.total_resources_to_load as f32 107 | } 108 | } 109 | 110 | pub fn get_or_panic(&mut self, key: TextureIdentifier) -> ResourceType { 111 | self.loaded_resources 112 | .remove(&key) 113 | .unwrap_or_else(|| panic!("can't find resource: {:?}", key)) 114 | } 115 | 116 | pub fn build(&mut self) -> R { 117 | R::build(self) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/animation.rs: -------------------------------------------------------------------------------- 1 | use macroquad::prelude::*; 2 | use std::collections::HashMap; 3 | 4 | pub struct AnimationSpriteData { 5 | pub columns: f32, 6 | pub rows: f32, 7 | pub true_size: Vec2, 8 | pub texture: Texture2D, 9 | } 10 | 11 | pub struct AnimationData { 12 | pub start_frame: i32, 13 | pub end_frame: i32, 14 | pub loop_frame: Option, 15 | pub fps: f32, 16 | } 17 | 18 | impl AnimationData { 19 | pub fn total_time(&self) -> f32 { 20 | let start = if let Some(loop_frame) = self.loop_frame { 21 | loop_frame 22 | } else { 23 | self.start_frame 24 | }; 25 | (self.end_frame - start) as f32 26 | } 27 | } 28 | 29 | pub struct AnimationInstance 30 | where 31 | A: Sized, 32 | { 33 | pub timer: f32, 34 | pub sprite_data: AnimationSpriteData, 35 | pub animations: HashMap, 36 | pub current_animation: A, 37 | // auto player this animation after current finishes 38 | pub into_animation_optional: Option, 39 | pub scale: Vec2, 40 | } 41 | 42 | impl AnimationInstance 43 | where 44 | A: std::cmp::Eq + std::hash::Hash, 45 | { 46 | pub fn new(columns: f32, rows: f32, texture: Texture2D, start_animation: A) -> Self { 47 | let true_size = vec2(texture.width() / columns, texture.height() / rows); 48 | Self { 49 | timer: 0f32, 50 | sprite_data: AnimationSpriteData { 51 | columns, 52 | rows, 53 | true_size, 54 | texture, 55 | }, 56 | animations: HashMap::new(), 57 | current_animation: start_animation, 58 | into_animation_optional: None, 59 | scale: vec2(1., 1.), 60 | } 61 | } 62 | pub fn add_animation( 63 | &mut self, 64 | start_frame: i32, 65 | end_frame: i32, 66 | loop_frame: Option, 67 | fps: f32, 68 | identifier: A, 69 | ) { 70 | self.animations.insert( 71 | identifier, 72 | AnimationData { 73 | start_frame, 74 | end_frame, 75 | loop_frame, 76 | fps, 77 | }, 78 | ); 79 | } 80 | 81 | pub fn update(&mut self, dt: f32) { 82 | let animation_data = self 83 | .animations 84 | .get(&self.current_animation) 85 | .expect("NO ANIMATION"); 86 | self.timer += dt * animation_data.fps; 87 | 88 | let start_frame = if let Some(loop_frame) = animation_data.loop_frame { 89 | loop_frame 90 | } else { 91 | animation_data.start_frame 92 | }; 93 | if self.timer > start_frame as f32 + animation_data.total_time() + 1f32 { 94 | if let Some(loop_frame) = animation_data.loop_frame { 95 | self.timer = loop_frame as f32; 96 | } else { 97 | self.timer = animation_data.start_frame as f32; 98 | } 99 | if let Some(into_animation) = self.into_animation_optional.take() { 100 | self.play_animation(into_animation); 101 | } 102 | } 103 | } 104 | 105 | pub fn play_animation(&mut self, identifier: A) { 106 | self.current_animation = identifier; 107 | let animation_data = self 108 | .animations 109 | .get(&self.current_animation) 110 | .expect("NO ANIMATION"); 111 | self.timer = animation_data.start_frame as f32; 112 | } 113 | 114 | // playe animation then the second one right after 115 | pub fn play_animation_then(&mut self, identifier: A, after: A) { 116 | self.current_animation = identifier; 117 | self.into_animation_optional = Some(after); 118 | let animation_data = self 119 | .animations 120 | .get(&self.current_animation) 121 | .expect("NO ANIMATION"); 122 | self.timer = animation_data.start_frame as f32; 123 | } 124 | 125 | pub fn draw(&self, pos: &Vec2, flip_x: bool) { 126 | let x_index = self.timer as i32 % (self.sprite_data.columns) as i32; 127 | let y_index = (self.timer as f32 / self.sprite_data.columns).floor(); 128 | 129 | draw_texture_ex( 130 | self.sprite_data.texture, 131 | pos.x - self.sprite_data.true_size.x * self.scale.x * 0.5f32, 132 | pos.y - self.sprite_data.true_size.y * self.scale.y * 0.5f32, 133 | WHITE, 134 | DrawTextureParams { 135 | flip_x, 136 | dest_size: Some(vec2( 137 | self.sprite_data.true_size.x * self.scale.x, 138 | self.sprite_data.true_size.y * self.scale.y, 139 | )), 140 | source: Some(Rect { 141 | x: x_index as f32 * self.sprite_data.true_size.x, 142 | y: y_index as f32 * self.sprite_data.true_size.y, 143 | w: self.sprite_data.true_size.x, 144 | h: self.sprite_data.true_size.y, 145 | }), 146 | ..Default::default() 147 | }, 148 | ); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/water.rs: -------------------------------------------------------------------------------- 1 | use macroquad::prelude::*; 2 | use miniquad::*; 3 | 4 | pub struct Water { 5 | pub pos: Vec2, 6 | size: Vec2, 7 | pub rotation: f32, 8 | 9 | pub direction: Vec2, 10 | pub speed: f32, 11 | pub strength: f32, 12 | 13 | offset: Vec2, 14 | stage: Stage, 15 | } 16 | 17 | #[repr(C)] 18 | struct Vertex { 19 | pos: Vec2, 20 | uv: Vec2, 21 | sample_uv: Vec2, 22 | } 23 | 24 | struct Stage { 25 | pipeline: Pipeline, 26 | bindings: Bindings, 27 | } 28 | 29 | #[repr(C)] 30 | pub struct Uniforms { 31 | strength: f32, 32 | offset: Vec2, 33 | pub view: glam::Mat4, 34 | pub model: glam::Mat4, 35 | } 36 | 37 | impl Stage { 38 | pub fn new( 39 | ctx: &mut Context, 40 | tex_water_normal: Texture2D, 41 | tex_target: Texture2D, 42 | flip_y: bool, 43 | ) -> Stage { 44 | #[rustfmt::skip] 45 | let vertices = match flip_y { 46 | true => { 47 | [ 48 | Vertex { pos : Vec2::new(-1.0, -1.0), uv: Vec2::new(0., 0.), sample_uv: Vec2::new(-1., -1.) }, 49 | Vertex { pos : Vec2::new( 1.0, -1.0), uv: Vec2::new(1., 0.), sample_uv: Vec2::new(1., -1.)}, 50 | Vertex { pos : Vec2::new( 1.0, 1.0), uv: Vec2::new(1., 1.), sample_uv: Vec2::new(1., -3.) }, 51 | Vertex { pos : Vec2::new(-1.0, 1.0), uv: Vec2::new(0., 1.), sample_uv: Vec2::new(-1., -3.) }, 52 | ] 53 | }, 54 | false => { 55 | [ 56 | Vertex { pos : Vec2::new(-1.0,-1.0 ), uv: Vec2::new(0., 0. ), sample_uv: Vec2::new( -1., -3.) }, 57 | Vertex { pos : Vec2::new( 1.0,-1.0 ), uv: Vec2::new(1., 0. ), sample_uv: Vec2::new( 1., -3.) }, 58 | Vertex { pos : Vec2::new( 1.0, 1.0 ), uv: Vec2::new(1., 1. ), sample_uv: Vec2::new( 1., -1.) }, 59 | Vertex { pos : Vec2::new(-1.0, 1.0 ), uv: Vec2::new(0., 1. ), sample_uv: Vec2::new( -1., -1.) }, 60 | ] 61 | } 62 | }; 63 | 64 | let vertex_buffer = Buffer::immutable(ctx, BufferType::VertexBuffer, &vertices); 65 | 66 | let indices: [u16; 6] = [0, 1, 2, 0, 2, 3]; 67 | let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices); 68 | 69 | let bindings = Bindings { 70 | vertex_buffers: vec![vertex_buffer], 71 | index_buffer, 72 | images: vec![ 73 | tex_target.raw_miniquad_texture_handle(), 74 | tex_water_normal.raw_miniquad_texture_handle(), 75 | ], 76 | }; 77 | 78 | let shader_meta = ShaderMeta { 79 | images: vec!["tex_water_normal".to_string(), "texture".to_string()], 80 | uniforms: UniformBlockLayout { 81 | uniforms: vec![ 82 | UniformDesc::new("strength", UniformType::Float1), 83 | UniformDesc::new("offset", UniformType::Float2), 84 | UniformDesc::new("view", UniformType::Mat4), 85 | UniformDesc::new("model", UniformType::Mat4), 86 | ], 87 | }, 88 | }; 89 | 90 | let shader = 91 | Shader::new(ctx, WATER_VERTEX_SHADER, WATER_FRAGMENT_SHADER, shader_meta).unwrap(); 92 | 93 | let pipeline = Pipeline::new( 94 | ctx, 95 | &[BufferLayout::default()], 96 | &[ 97 | VertexAttribute::new("pos", VertexFormat::Float2), 98 | VertexAttribute::new("uv", VertexFormat::Float2), 99 | VertexAttribute::new("sample_uv", VertexFormat::Float2), 100 | ], 101 | shader, 102 | ); 103 | 104 | Stage { pipeline, bindings } 105 | } 106 | } 107 | 108 | impl Water { 109 | pub fn new( 110 | pos: Vec2, 111 | size: Vec2, 112 | tex_water_normal: Texture2D, 113 | tex_target: Texture2D, 114 | direction: Vec2, 115 | speed: f32, 116 | strength: f32, 117 | rotation: f32, 118 | ) -> Self { 119 | let ctx = unsafe { get_internal_gl().quad_context }; 120 | let stage = Stage::new(ctx, tex_water_normal, tex_target, true); 121 | Water { 122 | pos, 123 | size, 124 | speed, 125 | rotation, 126 | strength, 127 | direction: direction.normalize(), 128 | offset: vec2(0., 0.), 129 | stage, 130 | } 131 | } 132 | 133 | pub fn update(&mut self, dt: f32) { 134 | self.offset += self.direction * dt * self.speed; 135 | } 136 | 137 | pub fn draw(&self, current_camera: &dyn Camera) { 138 | let mut gl = unsafe { get_internal_gl() }; 139 | 140 | // Ensure that macroquad's shapes are not going to be lost 141 | gl.flush(); 142 | gl.quad_context.apply_pipeline(&self.stage.pipeline); 143 | let current_pass = current_camera.render_pass(); 144 | gl.quad_context 145 | .begin_pass(current_pass, miniquad::PassAction::Nothing); 146 | gl.quad_context.apply_bindings(&self.stage.bindings); 147 | 148 | let view = current_camera.matrix(); 149 | 150 | let half_size = self.size * 0.5f32; 151 | let model = glam::f32::Mat4::from_scale_rotation_translation( 152 | vec3(half_size.x, half_size.y, 1.0f32), 153 | glam::Quat::from_rotation_z(self.rotation), 154 | vec3(self.pos.x + half_size.x, self.pos.y + half_size.y, 0f32), 155 | ); 156 | 157 | gl.quad_context.apply_uniforms(&Uniforms { 158 | strength: self.strength, 159 | offset: self.offset, 160 | view, 161 | model, 162 | }); 163 | gl.quad_context.draw(0, 6, 1); 164 | 165 | gl.quad_context.end_render_pass(); 166 | } 167 | } 168 | 169 | const WATER_FRAGMENT_SHADER: &str = "#version 140 170 | in vec2 v_uv; 171 | uniform float strength; 172 | uniform sampler2D texture; 173 | uniform sampler2D tex_water_normal; 174 | uniform vec2 offset; 175 | 176 | out vec4 color; 177 | 178 | void main() { 179 | vec4 water_color = texture2D(tex_water_normal, v_uv+offset); 180 | vec4 base_color_offset = texture2D(texture, v_uv+(water_color.rg*strength)); 181 | color = base_color_offset; 182 | } 183 | "; 184 | 185 | const WATER_VERTEX_SHADER: &str = "#version 140 186 | in vec2 pos; 187 | in vec2 uv; 188 | in vec2 sample_uv; 189 | out vec2 v_uv; 190 | 191 | uniform mat4 model; 192 | uniform mat4 view; 193 | uniform vec2 sample_offset; 194 | 195 | vec2 screen_to_uv(vec2 screen) { 196 | return screen * 0.5 + vec2(0.5, 0.5); 197 | } 198 | 199 | void main() { 200 | mat4 modelview = view * model; 201 | gl_Position = modelview * vec4(pos, 0.0, 1); 202 | vec4 uv_sample_offset = modelview * vec4(sample_uv.xy, 0, 1); 203 | v_uv = screen_to_uv(uv_sample_offset.xy); 204 | } 205 | "; 206 | -------------------------------------------------------------------------------- /examples/states.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use macroquad::prelude::*; 3 | use macroquad_tantan_toolbox::resources::*; 4 | use macroquad_tantan_toolbox::states::*; 5 | use std::collections::HashMap; 6 | 7 | const GAME_SIZE: Vec2 = const_vec2!([1024f32, 604f32]); 8 | 9 | pub struct MenuState; 10 | #[async_trait] 11 | impl State for MenuState { 12 | async fn on_update( 13 | &mut self, 14 | _delta_time: f32, 15 | _payload: &mut StateManagerPayload, 16 | ) -> Option> { 17 | if is_key_pressed(KeyCode::Space) { 18 | return Some(StateManagerCommand::ChangeStateEx( 19 | Box::new(LoadingState::new(Box::new(GameState))), 20 | TransitionTime(0.8), 21 | TransitionData::Spiral, 22 | )); 23 | } 24 | None 25 | } 26 | fn on_draw(&mut self, _payload: StateManagerPayload) { 27 | clear_background(WHITE); 28 | draw_text( 29 | "MENU STATE", 30 | GAME_SIZE.x * 0.5f32 - 70f32, 31 | GAME_SIZE.y * 0.5f32, 32 | 40f32, 33 | BLACK, 34 | ); 35 | } 36 | } 37 | 38 | pub struct GameState; 39 | #[async_trait] 40 | impl State for GameState { 41 | async fn on_update( 42 | &mut self, 43 | _delta_time: f32, 44 | _shared_data: &mut StateManagerPayload, 45 | ) -> Option> { 46 | if is_key_pressed(KeyCode::Space) { 47 | return Some(StateManagerCommand::ChangeStateEx( 48 | Box::new(LoadingState::new(Box::new(MenuState))), 49 | TransitionTime(0.3), 50 | TransitionData::Split, 51 | )); 52 | } 53 | None 54 | } 55 | fn on_draw(&mut self, _payload: StateManagerPayload) { 56 | clear_background(YELLOW); 57 | draw_text( 58 | "GAME STATE", 59 | GAME_SIZE.x * 0.5f32 - 70f32, 60 | GAME_SIZE.y * 0.5f32, 61 | 40f32, 62 | BLACK, 63 | ); 64 | } 65 | } 66 | 67 | pub struct LoadingState { 68 | // optional because we need to consume the internal value when calling changeState 69 | into_state: Option>>, 70 | } 71 | 72 | impl LoadingState { 73 | pub fn new(into_state: Box>) -> Self { 74 | Self { 75 | into_state: Some(into_state), 76 | } 77 | } 78 | } 79 | 80 | #[derive(Hash, Eq, Clone, Debug, Copy, PartialEq)] 81 | pub enum TextureIdentifier { 82 | Player, 83 | Moose, 84 | } 85 | 86 | pub struct TextureResources { 87 | _player: Texture2D, 88 | _moose: Texture2D, 89 | } 90 | 91 | impl Resources for TextureResources { 92 | fn build( 93 | builder: &mut ResourceBuilder, 94 | ) -> Self { 95 | Self { 96 | _player: builder.get_or_panic(TextureIdentifier::Player), 97 | _moose: builder.get_or_panic(TextureIdentifier::Moose), 98 | } 99 | } 100 | } 101 | 102 | // BootState will load textures asyncronously whilst drawing the procentage process 103 | // when every texture resource is loaded, transition to into_state 104 | pub struct BootState { 105 | into_state: Option>>, 106 | texture_resource_builder: 107 | ResourceBuilder, 108 | } 109 | 110 | impl BootState { 111 | pub fn new(into_state: Box>) -> Self { 112 | Self { 113 | into_state: Some(into_state), 114 | texture_resource_builder: ResourceBuilder::< 115 | TextureIdentifier, 116 | TextureResources, 117 | Texture2D, 118 | DefaultFactory, 119 | >::new( 120 | [ 121 | (TextureIdentifier::Player, "examples/resources/moose.png"), 122 | (TextureIdentifier::Moose, "examples/resources/moose.png"), 123 | ] 124 | .into(), 125 | ), 126 | } 127 | } 128 | } 129 | 130 | #[async_trait] 131 | impl State for BootState { 132 | fn on_enter(&mut self, _payload: StateManagerPayload) {} 133 | 134 | async fn on_update( 135 | &mut self, 136 | _delta_time: f32, 137 | payload: &mut StateManagerPayload, 138 | ) -> Option> { 139 | // load all textures 140 | let is_done_loading = self.texture_resource_builder.load_next().await; 141 | if !is_done_loading { 142 | return None; 143 | } 144 | payload.shared_data.texture_resources_optional = 145 | Some(self.texture_resource_builder.build()); 146 | // unwrap should be safe 147 | let into_state = self.into_state.take().unwrap(); 148 | return Some(StateManagerCommand::ChangeStateEx( 149 | into_state, 150 | TransitionTime(0.3), 151 | TransitionData::Slide, 152 | )); 153 | } 154 | fn on_draw(&mut self, _shared_data: StateManagerPayload) { 155 | clear_background(BLACK); 156 | draw_text( 157 | format!( 158 | "BOOTING UP... {:.0}%", 159 | self.texture_resource_builder.progress() * 100f32 160 | ) 161 | .as_str(), 162 | GAME_SIZE.x * 0.5f32 - 140f32, 163 | GAME_SIZE.y * 0.5f32, 164 | 40f32, 165 | WHITE, 166 | ); 167 | } 168 | } 169 | 170 | // loading state basically is just a scene inbetween the actual scene we want to load 171 | #[async_trait] 172 | impl State for LoadingState { 173 | async fn on_update( 174 | &mut self, 175 | _delta_time: f32, 176 | _payload: &mut StateManagerPayload, 177 | ) -> Option> { 178 | // unwrap should be safe 179 | let into_state = self.into_state.take().unwrap(); 180 | return Some(StateManagerCommand::ChangeStateEx( 181 | into_state, 182 | TransitionTime(0.3), 183 | TransitionData::Slide, 184 | )); 185 | } 186 | fn on_draw(&mut self, _shared_data: StateManagerPayload) { 187 | clear_background(BLACK); 188 | draw_text( 189 | format!( 190 | //"loading level... {:.0}%", 191 | "fancy transition huh?" 192 | //(self.download_progress as f32 / self.total_download as f32) * 100f32 193 | ) 194 | .as_str(), 195 | GAME_SIZE.x * 0.5f32 - 140f32, 196 | GAME_SIZE.y * 0.5f32, 197 | 40f32, 198 | WHITE, 199 | ); 200 | } 201 | } 202 | 203 | pub struct SharedData { 204 | texture_resources_optional: Option, 205 | } 206 | 207 | #[derive(Eq, PartialEq, Hash, Clone, Copy)] 208 | pub enum TransitionData { 209 | Slide, 210 | Split, 211 | Spiral, 212 | } 213 | 214 | impl Default for TransitionData { 215 | fn default() -> Self { 216 | TransitionData::Slide 217 | } 218 | } 219 | 220 | #[macroquad::main("states")] 221 | async fn main() { 222 | let render_target_game = render_target(GAME_SIZE.x as u32, GAME_SIZE.y as u32); 223 | render_target_game.texture.set_filter(FilterMode::Nearest); 224 | 225 | let camera2d = Camera2D { 226 | zoom: vec2(1. / GAME_SIZE.x * 2., 1. / GAME_SIZE.y * 2.), 227 | target: vec2( 228 | (GAME_SIZE.x * 0.5f32).floor(), 229 | (GAME_SIZE.y * 0.5f32).floor(), 230 | ), 231 | render_target: Some(render_target_game), 232 | ..Default::default() 233 | }; 234 | 235 | let loadingstate_menu = Box::new(LoadingState::new(Box::new(MenuState))); 236 | let boot_state = Box::new(BootState::new(loadingstate_menu)); 237 | let size = RenderTargetSize { 238 | width: GAME_SIZE.x as u32, 239 | height: GAME_SIZE.y as u32, 240 | }; 241 | let transition_tex_split: Texture2D = load_texture("examples/resources/transition_split.png") 242 | .await 243 | .unwrap(); 244 | let transition_tex_slide: Texture2D = load_texture("examples/resources/transition_slide.png") 245 | .await 246 | .unwrap(); 247 | let transition_tex_spiral: Texture2D = load_texture("examples/resources/transition_spiral.png") 248 | .await 249 | .unwrap(); 250 | let shared_data = SharedData { 251 | texture_resources_optional: None, 252 | }; 253 | 254 | let mut transition_texture_map = HashMap::new(); 255 | transition_texture_map.insert(TransitionData::Split, transition_tex_split); 256 | transition_texture_map.insert(TransitionData::Slide, transition_tex_slide); 257 | transition_texture_map.insert(TransitionData::Spiral, transition_tex_spiral); 258 | let mut state_manager: StateManager = StateManager::new( 259 | boot_state, 260 | size, 261 | camera2d, 262 | shared_data, 263 | transition_texture_map, 264 | ); 265 | 266 | loop { 267 | state_manager.update(get_frame_time()).await; 268 | state_manager.draw(); 269 | next_frame().await 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/states.rs: -------------------------------------------------------------------------------- 1 | use crate::transition; 2 | use crate::transition::*; 3 | use async_trait::async_trait; 4 | use macroquad::prelude::*; 5 | use std::collections::HashMap; 6 | use std::hash::Hash; 7 | 8 | // T: user defined transition data 9 | // S: shared data 10 | #[async_trait] 11 | pub trait State 12 | where 13 | S: Send, 14 | Self: Send, 15 | { 16 | fn on_enter(&mut self, _payload: StateManagerPayload) {} 17 | fn on_exit(&mut self, _payload: StateManagerPayload) {} 18 | async fn on_update( 19 | &mut self, 20 | _delta_time: f32, 21 | _payload: &mut StateManagerPayload, 22 | ) -> Option>; 23 | fn on_draw(&mut self, _payload: StateManagerPayload) {} 24 | } 25 | 26 | pub struct TransitioningData 27 | where 28 | S: Send + Sized, 29 | T: Send + Sized, 30 | Self: Sized + Send, 31 | { 32 | pub time_left: f32, 33 | start_time: f32, 34 | into_state: Box>, 35 | } 36 | 37 | impl TransitioningData 38 | where 39 | S: Send + Sized, 40 | T: Send + Sized, 41 | Self: Sized + Send, 42 | { 43 | pub fn progress(&self) -> f32 { 44 | self.time_left / self.start_time 45 | } 46 | } 47 | 48 | pub enum TransitionState 49 | where 50 | S: Send + Sized, 51 | T: Send + Sized, 52 | Self: Sized + Send, 53 | { 54 | None, 55 | Transitioning(TransitioningData), 56 | } 57 | 58 | impl Default for TransitionState 59 | where 60 | S: Send + Sized, 61 | T: Send + Sized, 62 | Self: Sized + Send, 63 | { 64 | fn default() -> Self { 65 | TransitionState::None 66 | } 67 | } 68 | 69 | // Used to take ownership of the TransitioningData 70 | // whilst resetting back to TransitionState::default(), which is TransitionSate::None 71 | impl TransitionState 72 | where 73 | S: Send + Sized, 74 | T: Send + Sized, 75 | Self: Sized + Send, 76 | { 77 | pub fn take(&mut self) -> Self { 78 | std::mem::take(self) 79 | } 80 | } 81 | 82 | pub struct TransitionTime(pub f32); 83 | 84 | pub struct RenderTargetSize { 85 | pub width: u32, 86 | pub height: u32, 87 | } 88 | 89 | pub enum StateManagerCommand { 90 | ChangeState(Box>), 91 | ChangeStateEx(Box>, TransitionTime, T), 92 | } 93 | 94 | // passed to states to reference members of statemanager 95 | pub struct StateManagerPayload<'a, S> 96 | where 97 | S: Send + Sized, 98 | { 99 | pub shared_data: &'a mut S, 100 | pub camera: &'a mut Camera2D, 101 | pub current_rendertarget: &'a mut RenderTarget, 102 | } 103 | 104 | // this statemanager handles transitioning animation built in 105 | // the rendering part is heavily tailored around macroquad 106 | // T: transition data 107 | // S: shared data owned by statemanager 108 | pub struct StateManager 109 | where 110 | S: Send + Sized, 111 | T: Send + Sized, 112 | Self: Sized + Send, 113 | { 114 | current_state: Box>, 115 | transition_state: TransitionState, 116 | last_transition_data: T, 117 | pub shared_data: S, 118 | transition: Transition, 119 | current_rendertarget: RenderTarget, 120 | into_rendertarget: RenderTarget, 121 | // to draw the transtition this will be the target texture to draw to 122 | transition_rendertarget: RenderTarget, 123 | camera: Camera2D, 124 | transition_texture_map: HashMap, 125 | 126 | // callbacks that always run 127 | pub on_update_optional: Option, 128 | pub on_draw_optional: Option, 129 | } 130 | 131 | // T: transition data 132 | impl StateManager 133 | where 134 | T: Default + Send + Sized + Copy + Eq + PartialEq + Hash, 135 | S: Send + Sized, 136 | Self: Sized + Send, 137 | { 138 | pub fn new( 139 | initial_state: Box>, 140 | rendertarget_size: RenderTargetSize, 141 | camera: Camera2D, 142 | shared_data: S, 143 | transition_texture_map: HashMap, 144 | ) -> Self { 145 | let first_transition_tex = transition_texture_map.iter().next().unwrap().1; 146 | let mut state_manager = Self { 147 | current_state: initial_state, 148 | transition_state: TransitionState::None, 149 | last_transition_data: T::default(), 150 | transition: Transition::new(*first_transition_tex, 0.3f32), 151 | current_rendertarget: render_target(rendertarget_size.width, rendertarget_size.height), 152 | into_rendertarget: render_target(rendertarget_size.width, rendertarget_size.height), 153 | transition_rendertarget: render_target( 154 | rendertarget_size.width, 155 | rendertarget_size.height, 156 | ), 157 | shared_data, 158 | camera, 159 | transition_texture_map, 160 | on_update_optional: None, 161 | on_draw_optional: None, 162 | }; 163 | //state_manager.into_rendertarget.texture.set_filter(FilterMode::Nearest); 164 | state_manager 165 | .into_rendertarget 166 | .texture 167 | .set_filter(FilterMode::Nearest); 168 | //state_manager.current_rendertarget.texture.set_filter( FilterMode::Nearest); 169 | state_manager 170 | .current_rendertarget 171 | .texture 172 | .set_filter(FilterMode::Nearest); 173 | state_manager.current_state.on_enter(StateManagerPayload { 174 | shared_data: &mut state_manager.shared_data, 175 | camera: &mut state_manager.camera, 176 | current_rendertarget: &mut state_manager.current_rendertarget, 177 | }); 178 | state_manager 179 | } 180 | 181 | // change state instantly without transition 182 | pub fn change_state(&mut self, state: Box>) { 183 | self.current_state.on_exit(StateManagerPayload { 184 | shared_data: &mut self.shared_data, 185 | camera: &mut self.camera, 186 | current_rendertarget: &mut self.current_rendertarget, 187 | }); 188 | self.current_state = state; 189 | self.current_state.on_enter(StateManagerPayload { 190 | shared_data: &mut self.shared_data, 191 | camera: &mut self.camera, 192 | current_rendertarget: &mut self.current_rendertarget, 193 | }); 194 | } 195 | 196 | // change state with transition 197 | pub fn change_state_ex( 198 | &mut self, 199 | mut state: Box>, 200 | time: TransitionTime, 201 | transition_data: T, 202 | ) { 203 | // update transition texture 204 | if self.last_transition_data != transition_data { 205 | let transition_tex = self.transition_texture_map.get(&transition_data).unwrap(); 206 | self.transition.change_transition_tex(*transition_tex); 207 | } 208 | self.last_transition_data = transition_data; 209 | // called right as we start transitioning... 210 | state.on_enter(StateManagerPayload { 211 | shared_data: &mut self.shared_data, 212 | camera: &mut self.camera, 213 | current_rendertarget: &mut self.current_rendertarget, 214 | }); 215 | self.transition_state = TransitionState::Transitioning(TransitioningData { 216 | time_left: time.0, 217 | start_time: time.0, 218 | into_state: state, 219 | }); 220 | } 221 | 222 | // updates the current state, might handle transitioning 223 | pub async fn update(&mut self, delta_time: f32) { 224 | if let TransitionState::Transitioning(transitioning_data) = &mut self.transition_state { 225 | transitioning_data.time_left -= delta_time; 226 | if transitioning_data.time_left < 0f32 { 227 | if let TransitionState::Transitioning(transitioning_data) = 228 | self.transition_state.take() 229 | { 230 | self.current_state.on_exit(StateManagerPayload { 231 | shared_data: &mut self.shared_data, 232 | camera: &mut self.camera, 233 | current_rendertarget: &mut self.current_rendertarget, 234 | }); 235 | self.current_state = transitioning_data.into_state; 236 | } 237 | } 238 | return; 239 | } else { 240 | let command_optional = self 241 | .current_state 242 | .on_update( 243 | delta_time, 244 | &mut StateManagerPayload { 245 | shared_data: &mut self.shared_data, 246 | camera: &mut self.camera, 247 | current_rendertarget: &mut self.current_rendertarget, 248 | }, 249 | ) 250 | .await; 251 | if let Some(command) = command_optional { 252 | match command { 253 | StateManagerCommand::ChangeState(state) => { 254 | self.change_state(state); 255 | } 256 | StateManagerCommand::ChangeStateEx(state, transition_time, transition_data) => { 257 | self.change_state_ex(state, transition_time, transition_data); 258 | } 259 | } 260 | } 261 | } 262 | 263 | if let Some(update_fn) = self.on_update_optional { 264 | update_fn(self); 265 | } 266 | } 267 | 268 | fn change_rendertarget(mut camera: &mut Camera2D, target: RenderTarget) { 269 | camera.render_target = Some(target); 270 | set_camera(camera); 271 | } 272 | 273 | // call the current states, draw funciton 274 | pub fn draw(&mut self) { 275 | Self::change_rendertarget(&mut self.camera, self.current_rendertarget); 276 | self.current_state.on_draw(StateManagerPayload { 277 | shared_data: &mut self.shared_data, 278 | camera: &mut self.camera, 279 | current_rendertarget: &mut self.current_rendertarget, 280 | }); 281 | 282 | if let TransitionState::Transitioning(transitioning_data) = &mut self.transition_state { 283 | // draw into state 284 | Self::change_rendertarget(&mut self.camera, self.into_rendertarget); 285 | transitioning_data.into_state.on_draw(StateManagerPayload { 286 | shared_data: &mut self.shared_data, 287 | camera: &mut self.camera, 288 | current_rendertarget: &mut self.current_rendertarget, 289 | }); 290 | 291 | // combine and draw transition 292 | // transition is drawn to the temp target 293 | // and we then draw temp targget to current rendertarget 294 | Self::change_rendertarget(&mut self.camera, self.transition_rendertarget); 295 | self.transition.draw_ex( 296 | self.current_rendertarget.texture, 297 | self.into_rendertarget.texture, 298 | transitioning_data.progress(), 299 | transition::DrawParam { flip_y: false }, 300 | ); 301 | Self::change_rendertarget(&mut self.camera, self.current_rendertarget); 302 | draw_texture_ex( 303 | self.transition_rendertarget.texture, 304 | 0f32, 305 | 0f32, 306 | WHITE, 307 | DrawTextureParams { 308 | ..Default::default() 309 | }, 310 | ); 311 | } 312 | let game_size = vec2( 313 | self.current_rendertarget.texture.width(), 314 | self.current_rendertarget.texture.height(), 315 | ); 316 | let game_diff_w = screen_width() / game_size.x; 317 | let game_diff_h = screen_height() / game_size.y; 318 | let aspect_diff = game_diff_w.min(game_diff_h); 319 | 320 | let scaled_game_size_w = game_size.x * aspect_diff; 321 | let scaled_game_size_h = game_size.y * aspect_diff; 322 | 323 | let width_padding = (screen_width() - scaled_game_size_w) * 0.5f32; 324 | let height_padding = (screen_height() - scaled_game_size_h) * 0.5f32; 325 | let dest_size = Some(Vec2::new(scaled_game_size_w, scaled_game_size_h)); 326 | // DRAW CURRENT STATE ONLY 327 | set_default_camera(); 328 | clear_background(BLACK); 329 | draw_texture_ex( 330 | self.current_rendertarget.texture, 331 | width_padding, 332 | height_padding, 333 | WHITE, 334 | DrawTextureParams { 335 | dest_size, 336 | ..Default::default() 337 | }, 338 | ); 339 | } 340 | } 341 | --------------------------------------------------------------------------------