11//! Basic usage example for the Bevy game template
22//!
33//! This example demonstrates how to use the template's
4- //! components, resources, and systems.
4+ //! components, resources, and systems for a 2D platformer .
55
66use bevy:: prelude:: * ;
7- use template_bevy:: components:: { Health , Player , Speed , Velocity } ;
7+ use template_bevy:: components:: {
8+ BoxCollider , Gravity , Grounded , Health , JumpConfig , Platform , Player , Speed , Velocity ,
9+ } ;
810use template_bevy:: resources:: { GameSettings , Score } ;
911use template_bevy:: states:: GameState ;
1012
13+ /// Threshold for detecting landing on platforms (in pixels)
14+ const LANDING_THRESHOLD : f32 = 10.0 ;
15+
1116fn main ( ) {
1217 App :: new ( )
1318 . add_plugins ( DefaultPlugins )
@@ -17,7 +22,16 @@ fn main() {
1722 . add_systems ( Startup , setup)
1823 . add_systems (
1924 Update ,
20- ( update_player, print_score) . run_if ( in_state ( GameState :: Playing ) ) ,
25+ (
26+ player_horizontal_movement,
27+ player_jump,
28+ apply_gravity,
29+ apply_velocity,
30+ check_collisions,
31+ print_score,
32+ )
33+ . chain ( )
34+ . run_if ( in_state ( GameState :: Playing ) ) ,
2135 )
2236 . add_systems ( OnEnter ( GameState :: Loading ) , start_game)
2337 . run ( ) ;
@@ -31,55 +45,147 @@ fn setup(mut commands: Commands) {
3145 // Spawn camera
3246 commands. spawn ( Camera2d ) ;
3347
34- // Spawn player with components
48+ // Spawn player with platformer components
49+ let player_size = Vec2 :: new ( 40.0 , 50.0 ) ;
3550 commands. spawn ( (
3651 Player ,
37- Speed ( 300 .0) ,
52+ Speed ( 250 .0) ,
3853 Health :: new ( 100.0 ) ,
3954 Velocity :: default ( ) ,
55+ Gravity :: default ( ) ,
56+ Grounded ( false ) ,
57+ JumpConfig :: default ( ) ,
58+ BoxCollider :: new ( player_size. x , player_size. y ) ,
4059 Sprite {
41- color : Color :: srgb ( 0.25 , 0.75 , 0.25 ) ,
42- custom_size : Some ( Vec2 :: new ( 50.0 , 50.0 ) ) ,
60+ color : Color :: srgb ( 0.2 , 0.6 , 1.0 ) ,
61+ custom_size : Some ( player_size ) ,
4362 ..default ( )
4463 } ,
45- Transform :: default ( ) ,
64+ Transform :: from_xyz ( 0.0 , 100.0 , 0.0 ) ,
4665 ) ) ;
4766
48- println ! ( "Game initialized!" ) ;
49- println ! ( "Use WASD or Arrow keys to move" ) ;
50- println ! ( "Press ESC to quit" ) ;
67+ // Spawn ground
68+ let ground_size = Vec2 :: new ( 800.0 , 40.0 ) ;
69+ commands. spawn ( (
70+ Platform ,
71+ BoxCollider :: new ( ground_size. x , ground_size. y ) ,
72+ Sprite {
73+ color : Color :: srgb ( 0.4 , 0.3 , 0.2 ) ,
74+ custom_size : Some ( ground_size) ,
75+ ..default ( )
76+ } ,
77+ Transform :: from_xyz ( 0.0 , -250.0 , 0.0 ) ,
78+ ) ) ;
79+
80+ // Spawn floating platforms
81+ for ( x, y, width) in [
82+ ( -200.0 , -100.0 , 150.0 ) ,
83+ ( 150.0 , 0.0 , 120.0 ) ,
84+ ( -50.0 , 100.0 , 180.0 ) ,
85+ ] {
86+ let size = Vec2 :: new ( width, 20.0 ) ;
87+ commands. spawn ( (
88+ Platform ,
89+ BoxCollider :: new ( size. x , size. y ) ,
90+ Sprite {
91+ color : Color :: srgb ( 0.3 , 0.5 , 0.3 ) ,
92+ custom_size : Some ( size) ,
93+ ..default ( )
94+ } ,
95+ Transform :: from_xyz ( x, y, 0.0 ) ,
96+ ) ) ;
97+ }
98+
99+ println ! ( "2D Platformer initialized!" ) ;
100+ println ! ( "Use A/D or Arrow keys to move left/right" ) ;
101+ println ! ( "Press SPACE to jump (only when on ground)" ) ;
51102}
52103
53- fn update_player (
104+ fn player_horizontal_movement (
54105 keyboard_input : Res < ButtonInput < KeyCode > > ,
55- time : Res < Time > ,
56- mut query : Query < ( & Speed , & mut Transform ) , With < Player > > ,
57- mut score : ResMut < Score > ,
106+ mut query : Query < ( & Speed , & mut Velocity ) , With < Player > > ,
58107) {
59- for ( speed, mut transform) in query. iter_mut ( ) {
60- let mut direction = Vec2 :: ZERO ;
61-
62- if keyboard_input. pressed ( KeyCode :: KeyW ) || keyboard_input. pressed ( KeyCode :: ArrowUp ) {
63- direction. y += 1.0 ;
64- }
65- if keyboard_input. pressed ( KeyCode :: KeyS ) || keyboard_input. pressed ( KeyCode :: ArrowDown ) {
66- direction. y -= 1.0 ;
67- }
108+ for ( speed, mut velocity) in query. iter_mut ( ) {
109+ let mut direction = 0.0 ;
68110 if keyboard_input. pressed ( KeyCode :: KeyA ) || keyboard_input. pressed ( KeyCode :: ArrowLeft ) {
69- direction. x -= 1.0 ;
111+ direction -= 1.0 ;
70112 }
71113 if keyboard_input. pressed ( KeyCode :: KeyD ) || keyboard_input. pressed ( KeyCode :: ArrowRight ) {
72- direction. x += 1.0 ;
114+ direction += 1.0 ;
73115 }
116+ velocity. 0 . x = direction * speed. 0 ;
117+ }
118+ }
119+
120+ fn player_jump (
121+ keyboard_input : Res < ButtonInput < KeyCode > > ,
122+ mut query : Query < ( & mut Velocity , & Grounded , & JumpConfig ) , With < Player > > ,
123+ ) {
124+ for ( mut velocity, grounded, jump_config) in query. iter_mut ( ) {
125+ if keyboard_input. just_pressed ( KeyCode :: Space ) && grounded. 0 {
126+ velocity. 0 . y = jump_config. jump_velocity ;
127+ }
128+ if keyboard_input. just_released ( KeyCode :: Space ) && velocity. 0 . y > 0.0 {
129+ velocity. 0 . y *= jump_config. jump_cut_multiplier ;
130+ }
131+ }
132+ }
133+
134+ fn apply_gravity ( time : Res < Time > , mut query : Query < ( & Gravity , & mut Velocity , & Grounded ) > ) {
135+ for ( gravity, mut velocity, grounded) in query. iter_mut ( ) {
136+ if !grounded. 0 {
137+ velocity. 0 . y -= gravity. 0 * time. delta_seconds ( ) ;
138+ }
139+ }
140+ }
141+
142+ fn apply_velocity ( time : Res < Time > , mut query : Query < ( & Velocity , & mut Transform ) > ) {
143+ for ( velocity, mut transform) in query. iter_mut ( ) {
144+ transform. translation . x += velocity. 0 . x * time. delta_seconds ( ) ;
145+ transform. translation . y += velocity. 0 . y * time. delta_seconds ( ) ;
146+ }
147+ }
148+
149+ #[ allow( clippy:: type_complexity) ]
150+ fn check_collisions (
151+ mut player_query : Query <
152+ ( & mut Transform , & mut Velocity , & BoxCollider , & mut Grounded ) ,
153+ With < Player > ,
154+ > ,
155+ platform_query : Query < ( & Transform , & BoxCollider ) , ( With < Platform > , Without < Player > ) > ,
156+ ) {
157+ for ( mut player_tf, mut velocity, player_col, mut grounded) in player_query. iter_mut ( ) {
158+ let mut is_grounded = false ;
159+ let ph_w = player_col. width / 2.0 ;
160+ let ph_h = player_col. height / 2.0 ;
161+
162+ for ( platform_tf, platform_col) in platform_query. iter ( ) {
163+ let plat_h_w = platform_col. width / 2.0 ;
164+ let plat_h_h = platform_col. height / 2.0 ;
165+
166+ let p_left = player_tf. translation . x - ph_w;
167+ let p_right = player_tf. translation . x + ph_w;
168+ let p_bottom = player_tf. translation . y - ph_h;
169+ let p_top = player_tf. translation . y + ph_h;
170+
171+ let plat_left = platform_tf. translation . x - plat_h_w;
172+ let plat_right = platform_tf. translation . x + plat_h_w;
173+ let plat_top = platform_tf. translation . y + plat_h_h;
74174
75- let movement = direction. normalize_or_zero ( ) * speed. 0 * time. delta_seconds ( ) ;
76- transform. translation . x += movement. x ;
77- transform. translation . y += movement. y ;
175+ let h_overlap = p_left < plat_right && p_right > plat_left;
78176
79- // Add score for moving
80- if direction != Vec2 :: ZERO {
81- score. add ( 1 ) ;
177+ if h_overlap && velocity. 0 . y <= 0.0 {
178+ if p_bottom <= plat_top
179+ && p_bottom >= plat_top - LANDING_THRESHOLD
180+ && p_top > plat_top
181+ {
182+ player_tf. translation . y = plat_top + ph_h;
183+ velocity. 0 . y = 0.0 ;
184+ is_grounded = true ;
185+ }
186+ }
82187 }
188+ grounded. 0 = is_grounded;
83189 }
84190}
85191
0 commit comments