Working pad, ball and blocks mechanics
This commit is contained in:
parent
1b19607bef
commit
91f72163c3
|
@ -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
120
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
17
main.odin
17
main.odin
|
@ -54,3 +54,20 @@ main :: proc() {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
TODO:
|
||||
[ ] Уничтожение биты от попадания кирпича
|
||||
[ ] Проверка завершения уровня
|
||||
[ ] Генерация уровней
|
||||
[ ] (Опционально) редактор уровней с сохранением в файл
|
||||
[ ] Меню
|
||||
[ ] Гейм овер
|
||||
[ ] Бонусы
|
||||
[ ] Визуальное оформление
|
||||
[ ] Интерфейс игры
|
||||
[ ] Звуки
|
||||
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue