From 91f72163c31a27458e356cdc233635987134a1f0 Mon Sep 17 00:00:00 2001 From: Nefrace Date: Sun, 8 Sep 2024 03:39:03 +0300 Subject: [PATCH] Working pad, ball and blocks mechanics --- ball.odin | 93 ++++++++++++++++++++++++++++++++++++++++++ game.odin | 120 ++++++++++++++++++++++++++++++++++++++++++++---------- main.odin | 17 ++++++++ pad.odin | 56 +++++++++++++++++++++++++ 4 files changed, 265 insertions(+), 21 deletions(-) create mode 100644 ball.odin create mode 100644 pad.odin diff --git a/ball.odin b/ball.odin new file mode 100644 index 0000000..ccfc951 --- /dev/null +++ b/ball.odin @@ -0,0 +1,93 @@ +package main + +import "core:math" +import "core:math/linalg" +import rl "vendor:raylib" + + +Ball :: struct { + position: Vec2, + velocity: Vec2, + pad_offset: Vec2, + angular_velocity: f32, + radius: f32, + is_on_pad: bool, +} + + +ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool { + using game + if ball.is_on_pad { + ball.position = pad.position + ball.pad_offset + } else { + ball_position_prev := ball.position + ball.velocity = rl.Vector2Rotate(ball.velocity, ball.angular_velocity * delta) + if abs(ball.velocity.y) < 50 { + ball.velocity.y += math.sign(ball.velocity.y) * 100 * delta + } + ball.position += ball.velocity * delta + if ball.position.y < GameField.y { + if ball.position.x < ball.radius { + ball.position.x = ball.radius + ball.velocity.x = -ball.velocity.x + ball.angular_velocity = 0 + } + if ball.position.x > GameField.x - ball.radius { + ball.position.x = GameField.x - ball.radius + ball.velocity.x = -ball.velocity.x + ball.angular_velocity = 0 + } + } + + if ball.position.y < ball.radius - 20 { + ball.position.y = ball.radius + ball.velocity.y = -ball.velocity.y + ball.angular_velocity = 0 + return false + } + + if ball.position.y + ball.radius > pad.position.y - pad.size.y / 2 && + ball_position_prev.y + ball.radius < pad.position.y - pad.size.y / 2 && + ball.position.x + ball.radius > pad.position.x - pad.size.x / 2 && + ball.position.x - ball.radius < pad.position.x + pad.size.x / 2 { + dir := Vec2{0, -1} + offset_x := ball.position.x - pad.position.x + dir.x = offset_x / (pad.size.x / 2) + ball.velocity = rl.Vector2Normalize(dir) * rl.Vector2Length(ball.velocity) + ball.angular_velocity = -pad.velocity.x / PAD_MAX_SPEED * math.PI / 2 + } + + for &brick, i in bricks { + if brick.is_flying { + continue + } + clamped := Vec2 { + clamp( + ball.position.x, + brick.position.x - brick.size.x / 2, + brick.position.x + brick.size.x / 2, + ), + clamp( + ball.position.y, + brick.position.y - brick.size.y / 2, + brick.position.y + brick.size.y / 2, + ), + } + diff := clamped - ball.position + normal := rl.Vector2Normalize(-diff) + if rl.Vector2LengthSqr(diff) < ball.radius * ball.radius { + brick.hits -= 1 + if brick.hits <= 0 && !brick.is_flying { + brick.is_flying = true + brick.velocity = ball.velocity / 2 + brick.angular_velocity = brick.velocity.x + } + ball.position = clamped + normal * ball.radius + ball.velocity = linalg.reflect(ball.velocity, normal) + ball.angular_velocity = 0 + } + } + } + return true +} + diff --git a/game.odin b/game.odin index fe21263..e1bc7e5 100644 --- a/game.odin +++ b/game.odin @@ -3,29 +3,37 @@ package main import rl "vendor:raylib" import "vendor:raylib/rlgl" import "core:fmt" +import "core:math" // Virtual game field dimensions GameField := Vec2{800, 600} -Pad :: struct { - position: Vec2, - size: Vec2, -} - -Ball :: struct { - position: Vec2, - velocity: Vec2, - radius: f32, -} - Brick :: struct { position: Vec2, + velocity: Vec2, size: Vec2, + angle: f32, + angular_velocity: f32, + is_flying: bool, + is_fixed: bool, color: rl.Color, hits: u8, } +spawn_brick :: proc(position: Vec2, size: Vec2, is_fixed: bool = true, color: rl.Color = rl.RED, hits : u8 = 1) -> Brick { + return Brick { + position = position, + velocity = Vec2{}, + size = Vec2{30,20}, + is_flying = false, + is_fixed = is_fixed, + color = color, + hits = hits, + angle = 0 + } +} + Game :: struct { using state: GameState, @@ -51,6 +59,21 @@ game_init :: proc() -> ^GameState { position = Vec2{GameField.x / 2, GameField.y - 40}, size = {80, 10}, } + append(&state.balls, Ball{ + radius = 8, + position = Vec2{GameField.x / 2, GameField.y - 40 - 8}, + is_on_pad = true, + pad_offset = Vec2{0, -16}, + }) + + + offset : f32 = 0 + for y : f32 = 40; y < GameField.y / 2; y += 35 { + for x : f32 = 40 + offset; x < GameField.x - 40; x += 40 { + append(&state.bricks, spawn_brick(Vec2{x, y}, Vec2{30, 20}, color = rl.YELLOW)) + } + if offset == 0 { offset = 20 } else { offset = 0 } + } return state } @@ -59,21 +82,58 @@ game_update :: proc(state: ^GameState, delta: f32) { using game - if rl.IsKeyDown(rl.KeyboardKey.LEFT) { - pad.position.x -= 100 * delta - fmt.println(pad.position) - } - if rl.IsKeyDown(rl.KeyboardKey.RIGHT) { - pad.position.x += 100 * delta + pad_update(&game.pad, delta) + if rl.IsKeyPressed(rl.KeyboardKey.SPACE) { + for &ball, i in balls { + if !ball.is_on_pad { continue } + + direction := Vec2{pad.velocity.x / PAD_MAX_SPEED, -1} + ball.is_on_pad = false + ball.velocity = rl.Vector2Normalize(direction) * 400 + } } - game.camera.offset = WINDOWF / 2 + all_balls_outside := true + #reverse for &ball, i in balls { + is_inside := ball_update(&ball, game, delta) + if is_inside { all_balls_outside = false } + + if ball.position.y > GameField.y + 200 { + unordered_remove(&balls, i) + } + } + + if all_balls_outside { + lives -= 1 + append(&balls, Ball{ + radius = 8, + position = Vec2{GameField.x / 2, GameField.y - 40 - 8}, + is_on_pad = true, + pad_offset = Vec2{0, -16}, + }) + } + + #reverse for &brick, i in bricks { + if !brick.is_flying { continue } + brick.velocity.y += 500 * delta + brick.position += brick.velocity * delta + brick.angle += brick.angular_velocity * delta + screen_pos := rl.GetWorldToScreen2D(brick.position, camera) + fmt.println(screen_pos) + if !rl.CheckCollisionRecs(rl.Rectangle{screen_pos.x - brick.size.x / 2, screen_pos.y - brick.size.y / 2, brick.size.x, brick.size.y}, + rl.Rectangle{0, 0, WINDOWF.x, WINDOWF.y} + ) { + unordered_remove(&bricks, i) + } + } + + camera.offset = WINDOWF / 2 + camera.zoom = (WINDOWF.y / GameField.y) / 1.1 } game_draw :: proc(state: ^GameState) { game := transmute(^Game)state - using game rl.BeginMode2D(camera) @@ -81,7 +141,25 @@ game_draw :: proc(state: ^GameState) { rl.ClearBackground(rl.RAYWHITE) - rl.DrawRectangleV({0, 0}, GameField, rl.RED) - rl.DrawRectangleV(pad.position, pad.size, rl.GREEN) + rl.DrawRectangleGradientV(0, 0, i32(GameField.x), i32(GameField.y), rl.RED, rl.RAYWHITE) + rl.DrawRectanglePro(rl.Rectangle{pad.position.x, pad.position.y, pad.size.x, pad.size.y}, pad.size / 2, 0, rl.GREEN) + + for ball, i in balls { + rl.DrawCircleV(ball.position, ball.radius, rl.BLUE) + } + + + for brick, i in bricks { + rl.DrawRectanglePro(rl.Rectangle{brick.position.x, brick.position.y, brick.size.x, brick.size.y}, brick.size / 2, brick.angle, brick.color) + } + + //rl.DrawText(rl.TextFormat("%f\n%f\n%f\n%f", pad.position.x, pad.position.y, pad.velocity.x, pad.velocity.y), 0, 0, 20, rl.BLACK) + rl.DrawText(rl.TextFormat("%d", len(bricks)), 0, 0, 20, rl.BLACK) + rl.EndMode2D() + + for i : u8 = 0; i < lives; i += 1 { + rl.DrawCircleV(Vec2{20 + 20 * f32(i), 20}, 10, rl.BLUE) + } + } diff --git a/main.odin b/main.odin index ca801d7..1184396 100644 --- a/main.odin +++ b/main.odin @@ -54,3 +54,20 @@ main :: proc() { } } + + +/* + + TODO: +[ ] Уничтожение биты от попадания кирпича +[ ] Проверка завершения уровня +[ ] Генерация уровней +[ ] (Опционально) редактор уровней с сохранением в файл +[ ] Меню +[ ] Гейм овер +[ ] Бонусы +[ ] Визуальное оформление +[ ] Интерфейс игры +[ ] Звуки + +*/ diff --git a/pad.odin b/pad.odin new file mode 100644 index 0000000..6d36c3c --- /dev/null +++ b/pad.odin @@ -0,0 +1,56 @@ +package main + +import rl "vendor:raylib" +import "core:math" + + +PAD_MAX_SPEED : f32 = 600 +PAD_ACCELERATION : f32 = 2000 +PAD_ACCELERATION_REV : f32 = 2900 +PAD_DECCELERATION : f32 = 2000 + +Pad :: struct { + position: Vec2, + velocity: Vec2, + size: Vec2, +} + + +pad_update :: proc(pad: ^Pad, delta: f32) { + target : f32 = 0 + if rl.IsKeyDown(rl.KeyboardKey.LEFT) { + target = -PAD_MAX_SPEED + } + if rl.IsKeyDown(rl.KeyboardKey.RIGHT) { + target = PAD_MAX_SPEED + } + + target_sign := math.sign(target) + if target != 0 { + accel : f32 = PAD_ACCELERATION + if pad.velocity.x * target < 0 { + accel = PAD_ACCELERATION_REV + } + pad.velocity.x = clamp(pad.velocity.x + accel * target_sign * delta, -PAD_MAX_SPEED, PAD_MAX_SPEED) + } else { + vel_sign := math.sign(pad.velocity.x) + deccel := PAD_DECCELERATION * vel_sign * delta + if math.abs(pad.velocity.x) > math.abs(deccel) { + pad.velocity.x -= deccel + } + else { + pad.velocity.x = 0 + } + } + pad.position += pad.velocity * delta + + pad_half_width := pad.size.x / 2 + if pad.position.x < pad_half_width { + pad.position.x = pad_half_width + pad.velocity.x = 0 + } + if pad.position.x > GameField.x - pad_half_width { + pad.position.x = GameField.x - pad_half_width + pad.velocity.x = 0 + } +}