1use lotus_engine::*;
9use rand::{rngs::ThreadRng, Rng};
10use std::time::Duration;
11
12#[derive(Component)]
13struct Border();
14
15#[derive(Component)]
16struct Racket();
17
18#[derive(Component)]
19struct GrayRacket();
20
21#[derive(Component)]
22struct PinkRacket();
23
24#[derive(Component)]
25struct PongBall();
26
27#[derive(Clone, Resource)]
28pub struct PongBallRespawnTimer(pub Timer);
29
30impl Default for PongBallRespawnTimer {
31 fn default() -> Self {
32 return Self(Timer::new(TimerType::Repeat, Duration::new(2, 0)))
33 }
34}
35
36#[derive(Resource)]
37pub struct GameAudio(pub AudioSource);
38
39impl Default for GameAudio {
40 fn default() -> Self {
41 return Self(AudioSource::new().expect("Should create a audio source."));
42 }
43}
44
45your_game!(
46 WindowConfiguration {
47 icon_path: "textures/pong/pink_racket_256x256.png".to_string(),
48 title: "Pong Game :)".to_string(),
49 background_color: None,
50 background_image_path: Some("textures/pong/pong_background_960x600.png".to_string()),
51 width: 960.0,
52 height: 600.0,
53 position_x: 200.0,
54 position_y: 150.0,
55 resizable: false,
56 decorations: true,
57 transparent: false,
58 active: true,
59 enabled_buttons: WindowButtons::CLOSE | WindowButtons::MINIMIZE
60 },
61 setup,
62 update
63);
64
65fn setup(context: &mut Context) {
66 let gray_racket_sprite: Sprite = Sprite::new("textures/pong/gray_racket_256x256.png".to_string());
67 let pink_racket_sprite: Sprite = Sprite::new("textures/pong/pink_racket_256x256.png".to_string());
68 let pong_ball_sprite: Sprite = Sprite::new("textures/pong/pong_ball_left_256x256.png".to_string());
69
70 let mut game_audio: GameAudio = GameAudio::default();
71 game_audio.0.load_streaming_sound(
72 "game_music",
73 "audio/pong/soundtrack/arcade_music.ogg",
74 AudioSettings::default().loop_region(..).volume(Value::Fixed(Decibels(-10.0)))
75 ).ok();
76 game_audio.0.play_streaming_sound("game_music".to_string()).ok();
77
78 game_audio.0.load_static_sound(
79 "racket_hit",
80 "audio/pong/effect/pong_hit.wav",
81 AudioSettings::default()
82 ).ok();
83
84 context.commands.add_resources(vec![
85 Box::new(PongBallRespawnTimer::default()),
86 Box::new(game_audio)
87 ]);
88
89 spawn_border(context, Vector2::new(0.0, -1.0));
90 spawn_border(context, Vector2::new(0.0, 1.0));
91
92 context.commands.spawn(
93 vec![
94 Box::new(gray_racket_sprite),
95 Box::new(Transform::new(
96 Position::new(Vector2::new(-1.0, 0.23), Strategy::Normalized),
97 0.0,
98 Vector2::new(0.55, 0.55)
99 )),
100 Box::new(Racket()),
101 Box::new(GrayRacket()),
102 Box::new(Velocity::new(Vector2::new(1.5, 1.5))),
103 Box::new(Collision::new(Collider::new_simple(GeometryType::Square)))
104 ]
105 );
106
107 context.commands.spawn(
108 vec![
109 Box::new(pink_racket_sprite),
110 Box::new(Transform::new(
111 Position::new(Vector2::new(1.0, 0.25), Strategy::Normalized),
112 0.0,
113 Vector2::new(0.55, 0.55)
114 )),
115 Box::new(Racket()),
116 Box::new(PinkRacket()),
117 Box::new(Velocity::new(Vector2::new(1.5, 1.5))),
118 Box::new(Collision::new(Collider::new_simple(GeometryType::Square)))
119 ]
120 );
121
122 context.commands.spawn(
123 vec![
124 Box::new(pong_ball_sprite),
125 Box::new(Transform::new(
126 Position::new(Vector2::new(0.0, 0.0), Strategy::Normalized),
127 0.0,
128 Vector2::new(0.55, 0.55)
129 )),
130 Box::new(PongBall()),
131 Box::new(Velocity::new(Vector2::new(1.0, 1.0))),
132 Box::new(Collision::new(Collider::new_simple(GeometryType::Square)))
133 ]
134 );
135}
136
137fn update(context: &mut Context) {
138 let input: Input = {
139 let input_ref: ResourceRefMut<'_, Input> = context.world.get_resource_mut::<Input>().unwrap();
140 input_ref.clone()
141 };
142
143 let mut pong_ball_query: Query = Query::new(&context.world).with::<PongBall>();
144 let pong_ball_entities: Vec<Entity> = pong_ball_query.entities_with_components().unwrap();
145 let pong_ball: &Entity = pong_ball_entities.first().unwrap();
146 let mut thread_rng: ThreadRng = rand::rng();
147 let random_factor: f32 = thread_rng.random_range(-0.5..0.5);
148
149 move_gray_racket(context, input.clone());
150 move_pink_racket(context, input.clone());
151 move_pong_ball(context, pong_ball);
152 check_rackets_ball_collision(context, pong_ball, random_factor);
153 check_borders_ball_collision(context, pong_ball, random_factor);
154 respawn_pong_ball_after_outbounds(context, pong_ball);
155}
156
157fn spawn_border(context: &mut Context, position: Vector2<f32>) {
158 let border: Shape = Shape::new(Orientation::Horizontal, GeometryType::Rectangle, Color::BLACK);
159
160 context.commands.spawn(
161 vec![
162 Box::new(border),
163 Box::new(Border()),
164 Box::new(Transform::new(
165 Position::new(position, Strategy::Normalized),
166 0.0,
167 Vector2::new(context.window_configuration.width as f32, 0.01)
168 )),
169 Box::new(Collision::new(Collider::new_simple(GeometryType::Rectangle)))
170 ]
171 );
172}
173
174fn move_gray_racket(context: &mut Context, input: Input) {
175 let mut query: Query = Query::new(&context.world).with::<GrayRacket>();
176 let entities: Vec<Entity> = query.entities_with_components().unwrap();
177 let gray_racket_entity: &Entity = entities.first().unwrap();
178
179 let mut transform: ComponentRefMut<'_, Transform> = context.world.get_entity_component_mut::<Transform>(gray_racket_entity).unwrap();
180 let velocity: ComponentRef<'_, Velocity> = context.world.get_entity_component::<Velocity>(gray_racket_entity).unwrap();
181
182 if input.is_key_pressed(PhysicalKey::Code(KeyCode::KeyW)) {
183 transform.position.y += velocity.y * context.delta;
184 let new_position: Vector2<f32> = Vector2::new(transform.position.x, transform.position.y);
185 transform.set_position(&context.render_state, new_position);
186 } else if input.is_key_pressed(PhysicalKey::Code(KeyCode::KeyS)) {
187 transform.position.y -= velocity.y * context.delta;
188 let new_position: Vector2<f32> = Vector2::new(transform.position.x, transform.position.y);
189 transform.set_position(&context.render_state, new_position);
190 }
191}
192
193fn move_pink_racket(context: &mut Context, input: Input) {
194 let mut query: Query = Query::new(&context.world).with::<PinkRacket>();
195 let entities: Vec<Entity> = query.entities_with_components().unwrap();
196 let pink_racket_entity: &Entity = entities.first().unwrap();
197
198 let mut transform: ComponentRefMut<'_, Transform> = context.world.get_entity_component_mut::<Transform>(pink_racket_entity).unwrap();
199 let velocity: ComponentRef<'_, Velocity> = context.world.get_entity_component::<Velocity>(pink_racket_entity).unwrap();
200
201 if input.is_key_pressed(PhysicalKey::Code(KeyCode::ArrowUp)) {
202 transform.position.y += velocity.y * context.delta;
203 let new_position: Vector2<f32> = Vector2::new(transform.position.x, transform.position.y);
204 transform.set_position(&context.render_state, new_position);
205 } else if input.is_key_pressed(PhysicalKey::Code(KeyCode::ArrowDown)) {
206 transform.position.y -= velocity.y * context.delta;
207 let new_position: Vector2<f32> = Vector2::new(transform.position.x, transform.position.y);
208 transform.set_position(&context.render_state, new_position);
209 }
210}
211
212fn move_pong_ball(context: &mut Context, pong_ball: &Entity) {
213 let mut transform: ComponentRefMut<'_, Transform> = context.world.get_entity_component_mut::<Transform>(&pong_ball).unwrap();
214 let velocity: ComponentRef<'_, Velocity> = context.world.get_entity_component::<Velocity>(&pong_ball).unwrap();
215
216 let new_position: Vector2<f32> = transform.position.to_vec() + velocity.to_vec() * context.delta;
217 transform.set_position(&context.render_state, new_position);
218}
219
220fn check_rackets_ball_collision(context: &mut Context, pong_ball: &Entity, random_factor: f32) {
221 let mut racket_query: Query = Query::new(&context.world).with::<Racket>();
222 let rackets: Vec<Entity> = racket_query.entities_with_components().unwrap();
223 let mut game_audio: ResourceRefMut<'_, GameAudio> = context.world.get_resource_mut::<GameAudio>().unwrap();
224
225 for racket in &rackets {
226 let racket_collision: ComponentRef<'_, Collision> = context.world.get_entity_component::<Collision>(racket).unwrap();
227 let racket_transform: ComponentRef<'_, Transform> = context.world.get_entity_component::<Transform>(racket).unwrap();
228
229 let pong_ball_collision: ComponentRef<'_, Collision> = context.world.get_entity_component::<Collision>(&pong_ball).unwrap();
230 let mut pong_ball_transform: ComponentRefMut<'_, Transform> = context.world.get_entity_component_mut::<Transform>(&pong_ball).unwrap();
231 let mut pong_ball_velocity: ComponentRefMut<'_, Velocity> = context.world.get_entity_component_mut::<Velocity>(&pong_ball).unwrap();
232
233 if Collision::check(CollisionAlgorithm::Aabb, &racket_collision, &pong_ball_collision) {
234 game_audio.0.play_static_sound("racket_hit".to_string()).ok();
235
236 let relative_collision_point: f32 = pong_ball_transform.position.y - racket_transform.position.y;
237 let rebound_angle: f32 = relative_collision_point * 1.0 + random_factor;
238
239 let pong_ball_new_velocity: Vector2<f32>;
240
241 if racket_transform.position.x > 0.0 {
242 pong_ball_new_velocity = Vector2::new(-1.0, rebound_angle).normalize() * pong_ball_velocity.to_vec().magnitude();
243 pong_ball_velocity.x = pong_ball_new_velocity.x; pong_ball_velocity.y = pong_ball_new_velocity.y;
244 pong_ball_transform.position.x -= 0.1;
245 } else if racket_transform.position.x < 0.0 {
246 pong_ball_new_velocity = Vector2::new(1.0, rebound_angle).normalize() * pong_ball_velocity.to_vec().magnitude();
247 pong_ball_velocity.x = pong_ball_new_velocity.x; pong_ball_velocity.y = pong_ball_new_velocity.y;
248 pong_ball_transform.position.x += 0.1;
249 }
250 let new_position: Vector2<f32> = Vector2::new(pong_ball_transform.position.x, pong_ball_transform.position.y);
251 pong_ball_transform.set_position(&context.render_state, new_position);
252 }
253 }
254}
255
256fn check_borders_ball_collision(context: &mut Context, pong_ball: &Entity, random_factor: f32) {
257 let mut border_query: Query = Query::new(&context.world).with::<Border>();
258 let borders: Vec<Entity> = border_query.entities_with_components().unwrap();
259
260 for border in &borders {
261 let border_collision: ComponentRef<'_, Collision> = context.world.get_entity_component::<Collision>(border).unwrap();
262 let border_transform: ComponentRef<'_, Transform> = context.world.get_entity_component::<Transform>(border).unwrap();
263
264 let pong_ball_collision: ComponentRef<'_, Collision> = context.world.get_entity_component::<Collision>(&pong_ball).unwrap();
265 let mut pong_ball_velocity: ComponentRefMut<'_, Velocity> = context.world.get_entity_component_mut::<Velocity>(&pong_ball).unwrap();
266 let mut pong_ball_transform: ComponentRefMut<'_, Transform> = context.world.get_entity_component_mut::<Transform>(&pong_ball).unwrap();
267
268 let pong_ball_new_velocity: Vector2<f32>;
269
270 if Collision::check(CollisionAlgorithm::Aabb, &border_collision, &pong_ball_collision) {
271 if border_transform.position.y > 0.0 {
272 pong_ball_new_velocity = Vector2::new(pong_ball_velocity.x.signum(), -1.0 + random_factor).normalize() * pong_ball_velocity.to_vec().magnitude();
273 pong_ball_velocity.x = pong_ball_new_velocity.x; pong_ball_velocity.y = pong_ball_new_velocity.y;
274 pong_ball_transform.position.y -= 0.1;
275 } else if border_transform.position.y < 0.0 {
276 pong_ball_new_velocity = Vector2::new(pong_ball_velocity.x.signum(), 1.0 + random_factor).normalize() * pong_ball_velocity.to_vec().magnitude();
277 pong_ball_velocity.x = pong_ball_new_velocity.x; pong_ball_velocity.y = pong_ball_new_velocity.y;
278 pong_ball_transform.position.y += 0.1;
279 }
280 let new_position: Vector2<f32> = Vector2::new(pong_ball_transform.position.x, pong_ball_transform.position.y);
281 pong_ball_transform.set_position(&context.render_state, new_position);
282 }
283 }
284}
285
286fn respawn_pong_ball_after_outbounds(context: &mut Context, pong_ball: &Entity) {
287 let mut pong_ball_transform: ComponentRefMut<'_, Transform> = context.world.get_entity_component_mut::<Transform>(pong_ball).unwrap();
288 let position_default: Vector2<f32> = Vector2::new(0.0, 0.0);
289
290 if pong_ball_transform.position.x > 2.0 || pong_ball_transform.position.x < -2.0 {
291 let mut pong_ball_respawn_timer: ResourceRefMut<'_, PongBallRespawnTimer> = context.world.get_resource_mut::<PongBallRespawnTimer>().unwrap();
292 pong_ball_respawn_timer.0.tick(context.delta);
293
294 if pong_ball_respawn_timer.0.is_finished() {
295 pong_ball_transform.set_position(&context.render_state, position_default);
296 }
297 }
298}