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