Skip to main content

ldtk/
ldtk.rs

1use quaso::{
2    GameLauncher,
3    assets::{
4        ldtk::{FilteredLdtkEntityExtractor, LdtkAsset},
5        make_directory_database,
6        shader::ShaderAsset,
7    },
8    config::Config,
9    context::GameContext,
10    game::{GameInstance, GameState, GameStateChange},
11    map::{LdtkMapBuilder, LdtkMapColliderResult, Map, ldtk::EntityInstance},
12    third_party::{
13        rand::{RngExt, rng},
14        spitfire_draw::{
15            sprite::{Sprite, SpriteTexture},
16            utils::{Drawable, ShaderRef, TextureRef},
17        },
18        spitfire_glow::{
19            graphics::{CameraScaling, Shader},
20            renderer::GlowTextureFiltering,
21        },
22        spitfire_input::{
23            CardinalInputCombinator, InputActionRef, InputConsume, InputMapping, VirtualAction,
24        },
25        vek::{Rect, Vec2},
26        windowing::event::VirtualKeyCode,
27    },
28};
29use std::error::Error;
30
31const SPEED: f32 = 200.0;
32
33fn main() -> Result<(), Box<dyn Error>> {
34    GameLauncher::new(GameInstance::new(Preloader).setup_assets(|assets| {
35        *assets = make_directory_database("./resources/").unwrap();
36    }))
37    .title("LDTK")
38    .config(Config::load_from_file("./resources/GameConfig.toml")?)
39    .run();
40    Ok(())
41}
42
43#[derive(Default)]
44struct Preloader;
45
46impl GameState for Preloader {
47    fn enter(&mut self, context: GameContext) {
48        context.graphics.state.color = [0.2, 0.2, 0.2, 1.0];
49        context.graphics.state.main_camera.screen_alignment = 0.5.into();
50        context.graphics.state.main_camera.scaling = CameraScaling::FitVertical(200.0);
51
52        context
53            .assets
54            .spawn(
55                "shader://color",
56                (ShaderAsset::new(
57                    Shader::COLORED_VERTEX_2D,
58                    Shader::PASS_FRAGMENT,
59                ),),
60            )
61            .unwrap();
62        context
63            .assets
64            .spawn(
65                "shader://image",
66                (ShaderAsset::new(
67                    Shader::TEXTURED_VERTEX_2D,
68                    Shader::TEXTURED_FRAGMENT,
69                ),),
70            )
71            .unwrap();
72        context
73            .assets
74            .spawn(
75                "shader://text",
76                (ShaderAsset::new(Shader::TEXT_VERTEX, Shader::TEXT_FRAGMENT),),
77            )
78            .unwrap();
79
80        context.assets.ensure("ldtk://world.zip").unwrap();
81
82        *context.state_change = GameStateChange::Swap(Box::new(State::default()));
83    }
84}
85
86#[derive(Default)]
87struct State {
88    movement: CardinalInputCombinator,
89    map: Option<Map>,
90    animals: Vec<Sprite>,
91}
92
93impl GameState for State {
94    fn enter(&mut self, context: GameContext) {
95        // Load LDTK world.
96        let asset = context
97            .assets
98            .find("ldtk://world.zip")
99            .unwrap()
100            .access::<&LdtkAsset>(context.assets);
101
102        // Create world map from LDTK world.
103        self.map = Some(
104            asset.build_map(
105                LdtkMapBuilder::default()
106                    .image_shader(ShaderRef::name("image"))
107                    .int_grid_collision_extractor(|name| match name {
108                        "Buildings" => LdtkMapColliderResult::AggregateMask(1 << 0),
109                        "Forest" => LdtkMapColliderResult::AggregateMask(1 << 0),
110                        "Walls" => LdtkMapColliderResult::AggregateMask(1 << 0),
111                        "Water" => LdtkMapColliderResult::AggregateMask(1 << 1),
112                        "Mountains" => LdtkMapColliderResult::AggregateMask(1 << 0),
113                        _ => LdtkMapColliderResult::Ignore,
114                    }),
115            ),
116        );
117
118        let extractor = FilteredLdtkEntityExtractor::default().by_identifier(
119            "Animal",
120            |entity: &EntityInstance| {
121                let index = rng().random_range(0..=6);
122                Some(
123                    Sprite::single(
124                        SpriteTexture::new(
125                            "u_image".into(),
126                            TextureRef::name("world.zip/characters.png"),
127                        )
128                        .filtering(GlowTextureFiltering::Nearest),
129                    )
130                    .shader(ShaderRef::name("image"))
131                    .region_page(
132                        Rect {
133                            x: index as f32 * 8.0 / 48.0,
134                            y: 32.0 / 40.0,
135                            w: 8.0 / 48.0,
136                            h: 8.0 / 40.0,
137                        },
138                        0.0,
139                    )
140                    .size(8.0.into())
141                    .position(Vec2::new(
142                        entity.world_x.unwrap_or_default() as f32,
143                        entity.world_y.unwrap_or_default() as f32,
144                    )),
145                )
146            },
147        );
148        self.animals = asset.extract_entities(None, None, &extractor).collect();
149
150        // Setup inputs for moving the map.
151        let move_left = InputActionRef::default();
152        let move_right = InputActionRef::default();
153        let move_up = InputActionRef::default();
154        let move_down = InputActionRef::default();
155        self.movement = CardinalInputCombinator::new(
156            move_left.clone(),
157            move_right.clone(),
158            move_up.clone(),
159            move_down.clone(),
160        );
161        context.input.push_mapping(
162            InputMapping::default()
163                .consume(InputConsume::Hit)
164                .action(
165                    VirtualAction::KeyButton(VirtualKeyCode::A),
166                    move_left.clone(),
167                )
168                .action(
169                    VirtualAction::KeyButton(VirtualKeyCode::D),
170                    move_right.clone(),
171                )
172                .action(VirtualAction::KeyButton(VirtualKeyCode::W), move_up.clone())
173                .action(
174                    VirtualAction::KeyButton(VirtualKeyCode::S),
175                    move_down.clone(),
176                )
177                .action(VirtualAction::KeyButton(VirtualKeyCode::Left), move_left)
178                .action(VirtualAction::KeyButton(VirtualKeyCode::Right), move_right)
179                .action(VirtualAction::KeyButton(VirtualKeyCode::Up), move_up)
180                .action(VirtualAction::KeyButton(VirtualKeyCode::Down), move_down),
181        );
182    }
183
184    fn exit(&mut self, context: GameContext) {
185        context.input.pop_mapping();
186    }
187
188    fn fixed_update(&mut self, context: GameContext, delta_time: f32) {
189        context.graphics.state.main_camera.transform.position +=
190            Vec2::from(self.movement.get()) * SPEED * delta_time;
191    }
192
193    fn draw(&mut self, context: GameContext) {
194        let Some(map) = &self.map else { return };
195
196        map.draw()
197            // .show_colliders(
198            //     ShaderRef::name("color"),
199            //     Rgba::new(1.0, 0.0, 1.0, 0.5),
200            //     GlowBlending::Alpha,
201            // )
202            .draw(context.draw, context.graphics);
203
204        for sprite in &self.animals {
205            sprite.draw(context.draw, context.graphics);
206        }
207    }
208}