Big additions.
Updated speeds etc for smaller resolution. Added a squish animation on collision of paddles. Updated the paddles from here to indicate a hit. Make use of simple particle system for on hit effects.
This commit is contained in:
+131
-11
@@ -1,5 +1,6 @@
|
|||||||
package game
|
package game
|
||||||
|
|
||||||
|
import "core:math"
|
||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
@@ -8,12 +9,13 @@ init_ball :: proc() -> Entity {
|
|||||||
type = .Ball,
|
type = .Ball,
|
||||||
active = true,
|
active = true,
|
||||||
pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)},
|
pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)},
|
||||||
speed = 500,
|
speed = 100,
|
||||||
max_speed = 800,
|
max_speed = 550,
|
||||||
vel = rl.Vector2{0,0},
|
vel = rl.Vector2{0,0},
|
||||||
shape = .Circle,
|
shape = .Circle,
|
||||||
size = rl.Vector2{15, 15}
|
size = rl.Vector2{8, 0},
|
||||||
}
|
tween_duration = 0.65,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serve_ball :: proc(ball: ^Entity) {
|
serve_ball :: proc(ball: ^Entity) {
|
||||||
@@ -26,10 +28,15 @@ serve_ball :: proc(ball: ^Entity) {
|
|||||||
reset_ball :: proc(ball: ^Entity) {
|
reset_ball :: proc(ball: ^Entity) {
|
||||||
ball.pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)}
|
ball.pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)}
|
||||||
ball.vel = rl.Vector2{0,0}
|
ball.vel = rl.Vector2{0,0}
|
||||||
ball.speed = 500
|
ball.speed = 300
|
||||||
}
|
}
|
||||||
|
|
||||||
update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
|
update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
|
||||||
|
// squish animation check
|
||||||
|
if ball.tween_elapsed < ball.tween_duration {
|
||||||
|
ball.tween_elapsed += dt
|
||||||
|
}
|
||||||
|
|
||||||
// first check the level timer are we waiting to serve?
|
// first check the level timer are we waiting to serve?
|
||||||
if level.serve_timer > 0 {
|
if level.serve_timer > 0 {
|
||||||
level.serve_timer -= dt
|
level.serve_timer -= dt
|
||||||
@@ -41,14 +48,30 @@ update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update trail history buffer
|
||||||
|
update_ball_trail(ball, dt)
|
||||||
|
|
||||||
radius := ball.size.x
|
radius := ball.size.x
|
||||||
field_bounds := level.field_bounds
|
field_bounds := level.field_bounds
|
||||||
|
|
||||||
// paddle collisions detection (player then enemy)
|
// paddle collisions detection (player then enemy)
|
||||||
if check_ball_paddle_collision(ball, level.player) {
|
if check_ball_paddle_collision(ball, level.player) {
|
||||||
|
level.player.color = rl.Color{255,255,255,255}
|
||||||
|
ball.tween_elapsed = 0.0
|
||||||
resolve_collision(ball, level.player, true)
|
resolve_collision(ball, level.player, true)
|
||||||
|
spawn_max_speed_sparks(&level.particles, level.player, ball.pos.y, true)
|
||||||
|
|
||||||
|
// if ball.speed >= ball.max_speed - 100 {
|
||||||
|
// spawn_max_speed_sparks(&level.particles, ball.pos, true)
|
||||||
|
// }
|
||||||
} else if check_ball_paddle_collision(ball, level.opponent) {
|
} else if check_ball_paddle_collision(ball, level.opponent) {
|
||||||
|
level.opponent.color = rl.Color{255,255,255,255}
|
||||||
|
ball.tween_elapsed = 0.0
|
||||||
resolve_collision(ball, level.opponent, false)
|
resolve_collision(ball, level.opponent, false)
|
||||||
|
spawn_max_speed_sparks(&level.particles, level.opponent, ball.pos.y, false)
|
||||||
|
} else {
|
||||||
|
level.opponent.color = rl.Color{255,255,255,0}
|
||||||
|
level.player.color = rl.Color{255,255,255,0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bounds collision detection
|
// bounds collision detection
|
||||||
@@ -56,13 +79,13 @@ update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
|
|||||||
if ball.pos.y <= field_bounds.y + ball.size.x { ball.vel.y = -ball.vel.y }
|
if ball.pos.y <= field_bounds.y + ball.size.x { ball.vel.y = -ball.vel.y }
|
||||||
|
|
||||||
if ball.pos.x >= (field_bounds.x + field_bounds.width) - ball.size.x {
|
if ball.pos.x >= (field_bounds.x + field_bounds.width) - ball.size.x {
|
||||||
increment_score(&level.score_player, level.score_win, true)
|
increment_score(&level.score_player, level.score_win, true, ball)
|
||||||
reset_ball(ball)
|
reset_ball(ball)
|
||||||
level.serve_timer = level.wait_time
|
level.serve_timer = level.wait_time
|
||||||
}
|
}
|
||||||
|
|
||||||
if ball.pos.x <= field_bounds.x + ball.size.x {
|
if ball.pos.x <= field_bounds.x + ball.size.x {
|
||||||
increment_score(&level.score_enemy, level.score_win, false)
|
increment_score(&level.score_enemy, level.score_win, false, ball)
|
||||||
reset_ball(ball)
|
reset_ball(ball)
|
||||||
level.serve_timer = level.wait_time
|
level.serve_timer = level.wait_time
|
||||||
}
|
}
|
||||||
@@ -82,14 +105,17 @@ check_ball_paddle_collision :: proc(ball: ^Entity, paddle: Entity) -> bool {
|
|||||||
return collided
|
return collided
|
||||||
}
|
}
|
||||||
|
|
||||||
increment_score :: proc(score_side: ^int, max_score: int, player: bool) {
|
increment_score :: proc(score_side: ^int, max_score: int, player: bool, ball: ^Entity) {
|
||||||
score_side^ += 1
|
score_side^ += 1
|
||||||
if score_side^ == max_score do emit_event(Game_Over_Data{player})
|
if score_side^ == max_score {
|
||||||
|
emit_event(Game_Over_Data{player})
|
||||||
|
ball.trail_count = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
increment_speed :: proc(ball: ^Entity) {
|
increment_speed :: proc(ball: ^Entity) {
|
||||||
if ball.speed <= ball.max_speed {
|
if ball.speed <= ball.max_speed {
|
||||||
ball.speed += 100
|
ball.speed += 45
|
||||||
ball.vel = rl.Vector2Normalize({ball.vel.x, ball.vel.y}) * ball.speed
|
ball.vel = rl.Vector2Normalize({ball.vel.x, ball.vel.y}) * ball.speed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +147,100 @@ resolve_collision :: proc(ball: ^Entity, paddle: Entity, is_player: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update_ball_trail :: proc(ball: ^Entity, dt: f32) {
|
||||||
|
if ball.speed < 300 {
|
||||||
|
ball.trail_count = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ball.trail_timer += dt
|
||||||
|
// get pos every 20ms
|
||||||
|
sample_rate := f32(0.02)
|
||||||
|
|
||||||
|
if ball.trail_timer >= sample_rate {
|
||||||
|
ball.trail_timer = 0.0
|
||||||
|
|
||||||
|
// shift old pos back
|
||||||
|
for i := MAX_TRAILS - 1; i > 0; i -= 1 {
|
||||||
|
ball.trail_positions[i] = ball.trail_positions[i - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// store new pos
|
||||||
|
ball.trail_positions[0] = ball.pos
|
||||||
|
if ball.trail_count < MAX_TRAILS {
|
||||||
|
ball.trail_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spawn_max_speed_sparks :: proc(particles: ^[dynamic]Particle, paddle: Entity, ball_y: f32, is_player: bool) {
|
||||||
|
count := 12
|
||||||
|
|
||||||
|
impact_x := is_player ? (paddle.pos.x + paddle.size.x) : paddle.pos.x
|
||||||
|
impact_y := clamp(ball_y, paddle.pos.y, paddle.pos.y + paddle.size.y)
|
||||||
|
impact_pos := rl.Vector2{impact_x, impact_y}
|
||||||
|
|
||||||
|
for _ in 0 ..< count {
|
||||||
|
// Direct sparks outward from the paddle face
|
||||||
|
base_angle := is_player ? f32(0.0) : rl.PI // 0 rad = right, PI rad = left
|
||||||
|
spread := (f32(rand.float32()) - 0.5) * 1.2
|
||||||
|
angle := base_angle + spread
|
||||||
|
|
||||||
|
speed := f32(rand.float32() * 180.0 + 100.0)
|
||||||
|
|
||||||
|
append(particles, Particle{
|
||||||
|
pos = impact_pos,
|
||||||
|
vel = {math.cos(angle) * speed, math.sin(angle) * speed},
|
||||||
|
color = rl.SKYBLUE,
|
||||||
|
life = 0.25,
|
||||||
|
max_life = 0.25,
|
||||||
|
size = f32(rand.float32() * 2.0 + 1.5),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render_ball_trail :: proc(ball: Entity) {
|
||||||
|
if ball.trail_count == 0 do return
|
||||||
|
|
||||||
|
base_radius := ball.size.x
|
||||||
|
|
||||||
|
for i in 0 ..< ball.trail_count {
|
||||||
|
pos := ball.trail_positions[i]
|
||||||
|
|
||||||
|
// Farther back in history = smaller and fainter
|
||||||
|
progress := f32(i + 1) / f32(ball.trail_count) // 0.0 -> 1.0
|
||||||
|
|
||||||
|
radius := base_radius * (1.0 - (progress * 0.6)) // Shrinks slightly
|
||||||
|
alpha := u8(180.0 * (1.0 - progress)) // Fades out
|
||||||
|
|
||||||
|
trail_color := rl.Color{rl.MAROON.r, rl.MAROON.g, rl.MAROON.b, alpha}
|
||||||
|
|
||||||
|
rl.DrawCircleV(pos, radius, trail_color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render_ball :: proc(ball: Entity, field_bounds: rl.Rectangle) {
|
render_ball :: proc(ball: Entity, field_bounds: rl.Rectangle) {
|
||||||
rl.DrawCircleV(ball.pos, ball.size.x, rl.MAROON)
|
render_ball_trail(ball)
|
||||||
|
|
||||||
|
radius_x := ball.size.x
|
||||||
|
radius_y := ball.size.x
|
||||||
|
|
||||||
|
// if animation active
|
||||||
|
if ball.tween_elapsed < ball.tween_duration {
|
||||||
|
radius_x = rl.EaseBackOut(
|
||||||
|
ball.tween_elapsed,
|
||||||
|
5.0,
|
||||||
|
ball.size.x - 5.0,
|
||||||
|
ball.tween_duration
|
||||||
|
)
|
||||||
|
|
||||||
|
radius_y = rl.EaseBackOut(
|
||||||
|
ball.tween_elapsed,
|
||||||
|
12.0,
|
||||||
|
ball.size.x - 12.0,
|
||||||
|
ball.tween_duration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.DrawEllipse(i32(ball.pos.x), i32(ball.pos.y), radius_x, radius_y, rl.MAROON)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user