1use quaso::{
2 GameLauncher,
3 animation::spine::{
4 BudgetedSpineSkeleton, BudgetedSpineSkeletonLodSwitchStrategy, LodSpineSkeleton,
5 SpineSkeleton,
6 },
7 assets::{make_directory_database, shader::ShaderAsset, spine::SpineAsset},
8 config::Config,
9 context::GameContext,
10 game::{GameInstance, GameState, GameStateChange},
11 third_party::{
12 spitfire_draw::utils::Drawable,
13 spitfire_glow::graphics::{CameraScaling, Shader},
14 spitfire_input::{
15 CardinalInputCombinator, InputActionRef, InputConsume, InputMapping, VirtualAction,
16 },
17 vek::Vec2,
18 windowing::event::VirtualKeyCode,
19 },
20};
21use std::error::Error;
22
23const SPEED: f32 = 200.0;
24
25fn main() -> Result<(), Box<dyn Error>> {
26 GameLauncher::new(GameInstance::new(Preloader).setup_assets(|assets| {
27 *assets = make_directory_database("./resources/").unwrap();
28 }))
29 .title("Spine 2D")
30 .config(Config::load_from_file("./resources/GameConfig.toml")?)
31 .run();
32 Ok(())
33}
34
35#[derive(Default)]
36struct Preloader;
37
38impl GameState for Preloader {
39 fn enter(&mut self, context: GameContext) {
40 context.graphics.state.color = [0.2, 0.2, 0.2, 1.0];
41 context.graphics.state.main_camera.screen_alignment = 0.5.into();
42 context.graphics.state.main_camera.scaling = CameraScaling::FitVertical(500.0);
43
44 context
45 .assets
46 .spawn(
47 "shader://color",
48 (ShaderAsset::new(
49 Shader::COLORED_VERTEX_2D,
50 Shader::PASS_FRAGMENT,
51 ),),
52 )
53 .unwrap();
54 context
55 .assets
56 .spawn(
57 "shader://image",
58 (ShaderAsset::new(
59 Shader::TEXTURED_VERTEX_2D,
60 Shader::TEXTURED_FRAGMENT,
61 ),),
62 )
63 .unwrap();
64 context
65 .assets
66 .spawn(
67 "shader://text",
68 (ShaderAsset::new(Shader::TEXT_VERTEX, Shader::TEXT_FRAGMENT),),
69 )
70 .unwrap();
71
72 context.assets.ensure("spine://robot-lod0.zip").unwrap();
73 context.assets.ensure("spine://robot-lod1.zip").unwrap();
74
75 *context.state_change = GameStateChange::Swap(Box::new(State::default()));
76 }
77}
78
79#[derive(Default)]
80struct State {
81 skeleton: Option<BudgetedSpineSkeleton>,
82 movement: CardinalInputCombinator,
83 lod0: InputActionRef,
84 lod1: InputActionRef,
85}
86
87impl GameState for State {
88 fn enter(&mut self, context: GameContext) {
89 let asset_lod0 = context
91 .assets
92 .find("spine://robot-lod0.zip")
93 .unwrap()
94 .access::<&SpineAsset>(context.assets);
95 let asset_lod1 = context
96 .assets
97 .find("spine://robot-lod1.zip")
98 .unwrap()
99 .access::<&SpineAsset>(context.assets);
100
101 let lod0 = SpineSkeleton::new(asset_lod0);
103 lod0.play_animation("idle", 0, 0.75, true).unwrap();
105 let lod1 = SpineSkeleton::new(asset_lod1);
106
107 self.skeleton = Some(
109 BudgetedSpineSkeleton::default()
110 .lod_switch_strategy(BudgetedSpineSkeletonLodSwitchStrategy {
111 transfer_root_bone_transform: true,
115 synchronize_animations: true,
118 ..Default::default()
119 })
120 .with_lod(LodSpineSkeleton {
122 skeleton: lod0,
123 refresh_delay: 0.0,
124 })
125 .with_lod(LodSpineSkeleton {
128 skeleton: lod1,
129 refresh_delay: 0.05,
131 }),
132 );
133
134 let move_left = InputActionRef::default();
136 let move_right = InputActionRef::default();
137 let move_up = InputActionRef::default();
138 let move_down = InputActionRef::default();
139 self.lod0 = InputActionRef::default();
140 self.lod1 = InputActionRef::default();
141 self.movement = CardinalInputCombinator::new(
142 move_left.clone(),
143 move_right.clone(),
144 move_up.clone(),
145 move_down.clone(),
146 );
147 context.input.push_mapping(
148 InputMapping::default()
149 .consume(InputConsume::Hit)
150 .action(
151 VirtualAction::KeyButton(VirtualKeyCode::A),
152 move_left.clone(),
153 )
154 .action(
155 VirtualAction::KeyButton(VirtualKeyCode::D),
156 move_right.clone(),
157 )
158 .action(VirtualAction::KeyButton(VirtualKeyCode::W), move_up.clone())
159 .action(
160 VirtualAction::KeyButton(VirtualKeyCode::S),
161 move_down.clone(),
162 )
163 .action(VirtualAction::KeyButton(VirtualKeyCode::Left), move_left)
164 .action(VirtualAction::KeyButton(VirtualKeyCode::Right), move_right)
165 .action(VirtualAction::KeyButton(VirtualKeyCode::Up), move_up)
166 .action(VirtualAction::KeyButton(VirtualKeyCode::Down), move_down)
167 .action(
168 VirtualAction::KeyButton(VirtualKeyCode::Key1),
169 self.lod0.clone(),
170 )
171 .action(
172 VirtualAction::KeyButton(VirtualKeyCode::Key2),
173 self.lod1.clone(),
174 ),
175 );
176 }
177
178 fn exit(&mut self, context: GameContext) {
179 context.input.pop_mapping();
180 }
181
182 fn fixed_update(&mut self, _: GameContext, delta_time: f32) {
183 let Some(budgeted_skeleton) = self.skeleton.as_mut() else {
184 return;
185 };
186
187 if self.lod0.get().is_pressed() {
189 budgeted_skeleton.set_lod(0);
190 } else if self.lod1.get().is_pressed() {
191 budgeted_skeleton.set_lod(1);
192 }
193
194 if let Some(skeleton) = budgeted_skeleton.lod_skeleton_mut() {
196 let movement = Vec2::<f32>::from(self.movement.get());
197 skeleton
198 .skeleton
199 .update_local_transform(None, false, |position, _, _| {
200 position.x += movement.x * SPEED * delta_time;
201 position.y += movement.y * SPEED * delta_time;
202 });
203 };
204 budgeted_skeleton.try_refresh(delta_time);
206 }
207
208 fn draw(&mut self, context: GameContext) {
209 let Some(skeleton) = self.skeleton.as_ref() else {
210 return;
211 };
212 skeleton.draw(context.draw, context.graphics);
213 }
214}