spine/
spine.rs

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
80            .assets
81            .find("spine://robot-lod0.zip")
82            .unwrap()
83            .access::<&SpineAsset>(context.assets);
84        let asset_lod1 = context
85            .assets
86            .find("spine://robot-lod1.zip")
87            .unwrap()
88            .access::<&SpineAsset>(context.assets);
89
90        let lod0 = SpineSkeleton::new(asset_lod0);
91        lod0.play_animation("idle", 0, 0.75, true).unwrap();
92        let lod1 = SpineSkeleton::new(asset_lod1);
93        lod1.play_animation("idle", 0, 0.75, true).unwrap();
94        self.skeleton = Some(
95            BudgetedSpineSkeleton::default()
96                .lod_switch_strategy(
97                    BudgetedSpineSkeletonLodSwitchStrategy::TransferRootBoneTransform,
98                )
99                .with_lod(LodSpineSkeleton {
100                    skeleton: lod0,
101                    refresh_delay: 0.0,
102                })
103                .with_lod(LodSpineSkeleton {
104                    skeleton: lod1,
105                    refresh_delay: 0.075,
106                }),
107        );
108
109        let move_left = InputActionRef::default();
110        let move_right = InputActionRef::default();
111        let move_up = InputActionRef::default();
112        let move_down = InputActionRef::default();
113        self.lod0 = InputActionRef::default();
114        self.lod1 = InputActionRef::default();
115        self.movement = CardinalInputCombinator::new(
116            move_left.clone(),
117            move_right.clone(),
118            move_up.clone(),
119            move_down.clone(),
120        );
121        context.input.push_mapping(
122            InputMapping::default()
123                .consume(InputConsume::Hit)
124                .action(
125                    VirtualAction::KeyButton(VirtualKeyCode::A),
126                    move_left.clone(),
127                )
128                .action(
129                    VirtualAction::KeyButton(VirtualKeyCode::D),
130                    move_right.clone(),
131                )
132                .action(VirtualAction::KeyButton(VirtualKeyCode::W), move_up.clone())
133                .action(
134                    VirtualAction::KeyButton(VirtualKeyCode::S),
135                    move_down.clone(),
136                )
137                .action(VirtualAction::KeyButton(VirtualKeyCode::Left), move_left)
138                .action(VirtualAction::KeyButton(VirtualKeyCode::Right), move_right)
139                .action(VirtualAction::KeyButton(VirtualKeyCode::Up), move_up)
140                .action(VirtualAction::KeyButton(VirtualKeyCode::Down), move_down)
141                .action(
142                    VirtualAction::KeyButton(VirtualKeyCode::Key1),
143                    self.lod0.clone(),
144                )
145                .action(
146                    VirtualAction::KeyButton(VirtualKeyCode::Key2),
147                    self.lod1.clone(),
148                ),
149        );
150    }
151
152    fn exit(&mut self, context: GameContext) {
153        context.input.pop_mapping();
154    }
155
156    fn fixed_update(&mut self, _: GameContext, delta_time: f32) {
157        let Some(budgeted_skeleton) = self.skeleton.as_mut() else {
158            return;
159        };
160        if self.lod0.get().is_pressed() {
161            budgeted_skeleton.set_lod(0);
162        } else if self.lod1.get().is_pressed() {
163            budgeted_skeleton.set_lod(1);
164        }
165
166        if let Some(skeleton) = budgeted_skeleton.lod_skeleton_mut() {
167            let movement = Vec2::<f32>::from(self.movement.get());
168            skeleton
169                .skeleton
170                .update_transform(None, false, |transform| {
171                    transform.position.x += movement.x * SPEED * delta_time;
172                    transform.position.y -= movement.y * SPEED * delta_time;
173                });
174        };
175        budgeted_skeleton.try_refresh(delta_time);
176    }
177
178    fn draw(&mut self, context: GameContext) {
179        let Some(skeleton) = self.skeleton.as_ref() else {
180            return;
181        };
182        skeleton.draw(context.draw, context.graphics);
183    }
184}
185
186fn main() -> Result<(), Box<dyn Error>> {
187    GameLauncher::new(GameInstance::new(Preloader).setup_assets(|assets| {
188        *assets = make_directory_database("./resources/").unwrap();
189    }))
190    .title("Spine 2D")
191    .config(Config::load_from_file("./resources/GameConfig.toml")?)
192    .run();
193    Ok(())
194}