Arkadodge/game.odin

246 lines
5.4 KiB
Odin

package main
import rl "vendor:raylib"
import "vendor:raylib/rlgl"
import "core:fmt"
import "core:math"
import "core:math/ease"
import "core:math/rand"
import "core:strings"
import "core:strconv"
// Virtual game field dimensions
GameField := Vec2{800, 600}
Game :: struct {
using state: GameState,
lives: u8,
pad: Pad,
balls: [dynamic]Ball,
bricks: [dynamic]Brick,
camera: rl.Camera2D,
score: u32,
levels_done: u32,
levelsize: Vec2i,
background: rl.Texture
}
game_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(Game)
state.previous = prev
state.variant = state
state.draw = game_draw
state.update = game_update
state.free = game_free
state.levelsize = Vec2i{6, 4}
backimage := rl.GenImageChecked(800, 600, 80, 60, rl.Color{70, 20, 120, 255}, rl.Color{80, 30, 130, 255})
defer rl.UnloadImage(backimage)
state.background = rl.LoadTextureFromImage(backimage)
game_setup(state)
return state
}
game_setup :: proc(game: ^Game) {
#reverse for ball, i in game.balls {
unordered_remove(&game.balls, i)
}
#reverse for brick, i in game.bricks {
unordered_remove(&game.bricks, i)
}
game.lives = 3
game.camera = rl.Camera2D{
zoom = 1,
target = GameField / 2,
}
game.pad = Pad{
position = Vec2{GameField.x / 2, GameField.y - 40},
size = {80, 20},
health = 100,
}
game_gen_level(game)
}
game_init_ball :: proc(game: ^Game) {
game_clear_balls(game)
append(&game.balls, Ball{
radius = 8,
position = Vec2{GameField.x / 2, -100},
is_on_pad = true,
pad_offset = Vec2{0, -18},
})
}
game_clear_balls :: proc(game: ^Game) {
#reverse for &ball, i in game.balls {
unordered_remove(&game.balls, i)
}
}
game_clear_bricks :: proc(game: ^Game) {
#reverse for &brick, i in game.bricks {
unordered_remove(&game.bricks, i)
}
}
game_gen_level :: proc(game: ^Game) {
explosions_init()
game_init_ball(game)
game_clear_bricks(game)
brick_size := Vec2{40, 30}
brick_margin := brick_size + 10
center := Vec2{GameField.x / 2, 100}
offset : f32 = 0
for y : i32 = 0; y < game.levelsize.y; y += 1 {
for x : i32 = 0; x < game.levelsize.x / 2; x += 1{
choice := rand.choice([]Brick_Type{.NORMAL, .NORMAL, .NORMAL, .HARD, .HARD, .SOLID, .EXPLOSIVE, .BONUS})
append(&game.bricks, spawn_brick(center + {-brick_margin.x / 2 - f32(x) * brick_margin.x, f32(y) * brick_margin.y}, brick_size, choice))
append(&game.bricks, spawn_brick(center + {+brick_margin.x / 2 + f32(x) * brick_margin.x, f32(y) * brick_margin.y}, brick_size, choice))
}
}
}
game_update :: proc(state: ^GameState, delta: f32) {
game := transmute(^Game)state
using game
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
pause := pause_init(game)
stack_push(pause)
}
if rl.IsKeyPressed(rl.KeyboardKey.P) {
for &brick in bricks {
brick.is_flying = true
}
}
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) * 300
}
}
all_balls_outside := true
#reverse for &ball, i in balls {
is_inside := ball_update(&ball, game, delta)
if is_inside { all_balls_outside = false }
screen_pos := rl.GetWorldToScreen2D(ball.position, camera)
if screen_pos.y > WINDOWF.y{
unordered_remove(&balls, i)
}
}
if pad.health <= 0 {
go := gameover_init(game)
stack_push(go)
return
}
if all_balls_outside {
if lives == 0 {
go := gameover_init(game)
stack_push(go)
return
}
lives -= 1
append(&balls, Ball{
radius = 8,
position = Vec2{GameField.x / 2, GameField.y - 40 - 8},
is_on_pad = true,
pad_offset = Vec2{0, -18},
})
}
only_solid := true
#reverse for &brick, i in bricks {
brick_update(&brick, game, delta)
if brick.type != .SOLID {
only_solid = false
}
if brick.is_to_free {
unordered_remove(&bricks, i)
}
}
if only_solid {
leveldone := leveldone_init(game)
stack_push(leveldone)
}
explosions_process(delta)
camera.offset = WINDOWF / 2
camera.zoom = (WINDOWF.y / GameField.y) / 1.1
}
game_draw :: proc(state: ^GameState) {
game := transmute(^Game)state
rl.BeginMode2D(game.camera)
rl.DrawTextureV(game.background, {}, rl.WHITE)
// rl.DrawRectangleGradientV(0, 0, i32(GameField.x), i32(GameField.y), rl.RED, rl.RAYWHITE)
for &ball, i in game.balls {
ball_draw(&ball)
}
pad_draw(&game.pad)
for &brick, i in game.bricks {
brick_draw(&brick)
}
explosions_draw()
rl.EndMode2D()
game_field_zoomed := GameField * game.camera.zoom
buf : [12]byte
score_string := strconv.itoa(buf[:], int(game.score))
score_ui := strings.right_justify(score_string, 8, "0")
score_ui_c := strings.unsafe_string_to_cstring(score_ui)
size := rl.MeasureTextEx(FontUI, score_ui_c, 48, 2)
rl.DrawTextPro(FontUI, score_ui_c, {WINDOWF.x / 2, WINDOWF.y - 28}, size / 2, 0, 48, 2, rl.WHITE)
level_string := rl.TextFormat("Уровень %d", game.levels_done + 1)
size = rl.MeasureTextEx(FontUI, level_string, 48, 2)
rl.DrawTextPro(FontUI, level_string, {WINDOWF.x / 2 + game_field_zoomed.x / 2, WINDOWF.y - 28}, {size.x, size.y / 2}, 0, 48, 2, rl.WHITE)
for i : u8 = 0; i < game.lives; i += 1 {
draw_ball_virtual({WINDOWF.x / 2 - game_field_zoomed.x / 2 + 16 + f32(i * 40), WINDOWF.y - 28}, 16)
}
}
game_free :: proc(state: ^GameState) {
game := transmute(^Game)state
rl.UnloadTexture(game.background)
delete(game.bricks)
delete(game.balls)
free(state)
}