2d_platformer/
2d_platformer.rs1use std::f32::consts::{FRAC_PI_2, PI};
2
3use sge::prelude::*;
4
5const PLAYER_RADIUS: f32 = 30.0;
6
7struct Player {
8 color: Color,
9 controller: PlayerController,
10}
11
12#[main("2D Platformer")]
13async fn main() -> anyhow::Result<()> {
14 let mut world = PhysicsWorld::new();
15
16 let mut player = Player {
17 color: Color::RED_500,
18 controller: world
19 .create_player_controller(Bounds::Rect(Vec2::splat(PLAYER_RADIUS * 2.0)))
20 .with_position(Vec2::new(0.0, -200.0)),
21 };
22
23 const PREV_LEN: usize = 100;
24 const PREV_REPEAT: usize = 10;
25 let mut prev_pos = RotatingArray::new([Vec2::ZERO; PREV_LEN]);
26
27 player
28 .controller
29 .set_binds()
30 .jump(KeyCode::Space)
31 .right(KeyCode::KeyF)
32 .left(KeyCode::KeyS);
33
34 player.controller.set_move_speed(5.0);
35 player.controller.set_double_jumps(1);
36 player.controller.set_jump_velocity(13.0);
37 world.set_gravity(50.0);
38
39 let platforms = vec![
40 platform(-500.0, 400.0, 1000.0),
41 platform(-400.0, 250.0, 200.0),
42 platform(200.0, 250.0, 200.0),
43 platform(-100.0, 100.0, 200.0),
44 ];
45
46 for p in &platforms {
47 world
48 .create_fixed(Bounds::Rect(p.size))
49 .with_position(p.center());
50 }
51
52 let mut ps = ParticleSystem::new();
53
54 let jump_particles = ParticleOneshot::builder()
55 .shape(&Rect::new_square(
56 Vec2::ZERO,
57 10.0,
58 Color::WHITE.with_alpha(0.3),
59 ))
60 .color_randomness(Color::BLACK.with_alpha(0.1))
61 .end_color(Color::WHITE.with_alpha(0.0))
62 .speed(5.0)
63 .direction(-PI)
64 .direction_randomness(FRAC_PI_2)
65 .speed_randomness(3.0)
66 .lifetime_randomness(0.5)
67 .rotation_speed_randomness(0.2)
68 .lifetime(1.0)
69 .build();
70
71 let land_particles = ParticleOneshot::builder()
72 .shape(&Rect::new_square(
73 Vec2::ZERO,
74 10.0,
75 Color::WHITE.with_alpha(0.1),
76 ))
77 .color_randomness(Color::BLACK.with_alpha(0.1))
78 .end_color(Color::WHITE.with_alpha(0.0))
79 .speed(5.0)
80 .direction_randomness(FRAC_PI_2)
81 .acceleration(vec2(0.0, 0.2))
82 .speed_randomness(3.0)
83 .rotation_speed_randomness(0.2)
84 .quantity(5)
85 .quantity_randomness(2.0)
86 .lifetime_randomness(0.5)
87 .lifetime(0.5)
88 .build();
89
90 let mut debug_mode = false;
91
92 let mut camera_controller = PanningCameraController {
93 allow_panning: false,
94 ..Default::default()
95 };
96
97 loop {
98 ps.update();
99 camera_controller.update();
100
101 if !debug_mode {
102 clear_screen(Color::NEUTRAL_900);
103 vignette_screen(Color::NEUTRAL_950, 0.5);
104 }
105
106 draw_simple_debug_info();
107
108 draw_multiline_text(
109 "Press D to toggle debug mode\nSpace to jump\nA to move right\nF to move left",
110 Vec2::splat(10.0),
111 1.0,
112 );
113
114 if !debug_mode {
115 draw_2d_grid_world(Color::NEUTRAL_800);
116 }
117
118 if key_pressed(KeyCode::KeyD) {
119 debug_mode = !debug_mode;
120 }
121
122 if key_pressed(KeyCode::KeyR) {
123 player.controller.set_position(Vec2::new(0.0, -200.0));
124 }
125
126 if debug_mode {
127 world.draw_colliders_world();
128 }
129
130 if player.controller.position().y > 1000.0
131 && player.controller.velocity().y.is_sign_negative()
132 {
133 player.controller.add_impulse(Vec2::Y * 30.0);
134 }
135
136 if player.controller.just_double_jumped() {
137 ps.spawn_oneshot(
138 &jump_particles,
139 player.controller.position() + vec2(0.0, PLAYER_RADIUS),
140 );
141 }
142
143 if player.controller.just_landed() {
144 ps.spawn_oneshot(
145 &land_particles,
146 player.controller.position() + vec2(0.0, PLAYER_RADIUS),
147 );
148 }
149
150 {
151 let ppos = player.controller.position();
152 let cpos = get_camera_2d().translation();
153 let (normal, len) = (ppos - cpos).normalize_and_length();
154
155 if len > 300.0 {
156 let pos = -normal * 300.0 + ppos;
157 get_camera_2d_mut().set_translation(pos);
158 }
159 }
160
161 for i in 0..PREV_REPEAT {
162 let ratio = (i + 1) as f32 / PREV_REPEAT as f32;
163 let pos = player
164 .controller
165 .position_last_frame()
166 .lerp(player.controller.position(), ratio);
167 prev_pos.push(pos);
168 }
169
170 if !debug_mode {
171 for (i, pos) in prev_pos.iter().enumerate() {
172 let age = i as f32 / PREV_LEN as f32;
173 draw_circle_world(
174 *pos,
175 PLAYER_RADIUS * age,
176 player.color.darken(0.2).desaturate(0.5),
177 );
178 }
179
180 draw_circle_world(player.controller.position(), PLAYER_RADIUS, player.color);
181
182 for p in &platforms {
183 draw_world(p);
184 }
185
186 ps.draw_world();
187 }
188
189 world.update();
190
191 if should_quit() {
192 break;
193 }
194
195 next_frame().await;
196 }
197
198 Ok(())
199}
200
201fn platform(x: f32, y: f32, w: f32) -> Rect {
202 Rect::new(Vec2::new(x, y), Vec2::new(w, 20.0), Color::NEUTRAL_800)
203}