Skip to main content

2d_platformer/
2d_platformer.rs

1use 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}