Working pad, ball and blocks mechanics

This commit is contained in:
Nefrace 2024-09-08 03:39:03 +03:00
parent 1b19607bef
commit 91f72163c3
4 changed files with 265 additions and 21 deletions

93
ball.odin Normal file
View File

@ -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
}

120
game.odin
View File

@ -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)
}
}

View File

@ -54,3 +54,20 @@ main :: proc() {
}
}
/*
TODO:
[ ] Уничтожение биты от попадания кирпича
[ ] Проверка завершения уровня
[ ] Генерация уровней
[ ] (Опционально) редактор уровней с сохранением в файл
[ ] Меню
[ ] Гейм овер
[ ] Бонусы
[ ] Визуальное оформление
[ ] Интерфейс игры
[ ] Звуки
*/

56
pad.odin Normal file
View File

@ -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
}
}