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 let asset = context
97 .assets
98 .find("ldtk://world.zip")
99 .unwrap()
100 .access::<&LdtkAsset>(context.assets);
101
102 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 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 .draw(context.draw, context.graphics);
203
204 for sprite in &self.animals {
205 sprite.draw(context.draw, context.graphics);
206 }
207 }
208}