This commit is contained in:
Nefrace 2024-09-09 23:41:16 +03:00
parent 09b0f28735
commit d568031b82
15 changed files with 582 additions and 53 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

View File

@ -22,7 +22,7 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool {
} else {
ball_position_prev := ball.position
ball.velocity = rl.Vector2Rotate(ball.velocity, ball.angular_velocity * delta)
if abs(ball.velocity.y) < 50 {
if abs(ball.velocity.y) < 100 && ball.angular_velocity == 0 {
ball.velocity.y += math.sign(ball.velocity.y) * 100 * delta
}
ball.position += ball.velocity * delta
@ -55,7 +55,7 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool {
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
ball.angular_velocity = -pad.velocity.x / PAD_MAX_SPEED * math.PI / 3
}
for &brick, i in bricks {
@ -77,11 +77,17 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool {
diff := clamped - ball.position
normal := rl.Vector2Normalize(-diff)
if rl.Vector2LengthSqr(diff) < ball.radius * ball.radius {
brick.hits -= 1
if brick.type != .SOLID{
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
game.score += brick.score
}
if brick.hit_callback != nil {
brick.hit_callback(&brick, game)
}
ball.position = clamped + normal * ball.radius
ball.velocity = linalg.reflect(ball.velocity, normal)
@ -92,3 +98,11 @@ ball_update :: proc(ball: ^Ball, game: ^Game, delta: f32) -> bool {
return true
}
ball_draw :: proc(ball: ^Ball) {
draw_ball_virtual(ball.position, ball.radius)
}
draw_ball_virtual :: proc(position: Vec2, radius: f32) {
rl.DrawCircleV(position, radius, rl.Color{80, 120, 255, 255})
rl.DrawCircleV(position - radius / 3, radius / 5, rl.Color{80, 160, 255, 255})
}

View File

@ -1,6 +1,58 @@
package main
import rl "vendor:raylib"
import "core:fmt"
Brick_Type :: enum {
NORMAL,
EXPLOSIVE,
BONUS,
HARD,
SOLID
}
brick_names := [Brick_Type]cstring {
.NORMAL = "обычный",
.EXPLOSIVE = "взрывной",
.BONUS = "восстанавливает здоровье",
.HARD = "крепкий",
.SOLID = "нерушимый"
}
brick_colors := [Brick_Type]rl.Color {
.NORMAL = rl.Color{190, 190, 190, 255},
.EXPLOSIVE = rl.Color{250, 30, 30, 255},
.BONUS = rl.Color{30, 250, 30, 255},
.HARD = rl.Color{190, 190, 255, 255},
.SOLID = rl.Color{80, 80, 100, 255}
}
brick_scores := [Brick_Type]u32 {
.NORMAL = 100,
.HARD = 500,
.BONUS = 1000,
.EXPLOSIVE = 1000,
.SOLID = 0,
}
brick_hits := [Brick_Type]i32 {
.NORMAL = 1,
.EXPLOSIVE = 1,
.BONUS = 1,
.HARD = 2,
.SOLID = 10000000
}
BrickHitCallback :: proc(brick: ^Brick, game: ^Game)
brick_callbacks := [Brick_Type]BrickHitCallback {
.NORMAL = nil,
.EXPLOSIVE = brick_explode,
.BONUS = nil,
.HARD = nil,
.SOLID = nil
}
@ -13,32 +65,114 @@ Brick :: struct {
is_flying: bool,
is_fixed: bool,
color: rl.Color,
hits: u8,
hits: i32,
is_to_free: bool,
type: Brick_Type,
score: u32,
hit_callback: proc(brick: ^Brick, game: ^Game)
}
spawn_brick :: proc(position: Vec2, size: Vec2, is_fixed: bool = true, color: rl.Color = rl.RED, hits : u8 = 1) -> Brick {
spawn_brick :: proc(position: Vec2, size: Vec2, type: Brick_Type = Brick_Type.NORMAL) -> Brick {
return Brick {
position = position,
velocity = Vec2{},
size = Vec2{30,20},
size = size,
is_flying = false,
is_fixed = is_fixed,
color = color,
hits = hits,
is_fixed = true,
type = type,
color = brick_colors[type],
hits = brick_hits[type],
score = brick_scores[type],
hit_callback = brick_callbacks[type],
angle = 0
}
}
brick_update :: proc(brick: ^Brick, camera: rl.Camera2D, delta: f32) {
brick_update :: proc(brick: ^Brick, game: ^Game, delta: f32) {
if !brick.is_flying { return }
brick.velocity.y += 500 * delta
brick.position += brick.velocity * delta
brick.angle += brick.angular_velocity * delta
screen_pos := rl.GetWorldToScreen2D(brick.position, camera)
screen_pos := rl.GetWorldToScreen2D(brick.position, game.camera)
if brick.position.x < brick.size.x / 2 {
brick.position.x = brick.size.x / 2
brick.velocity.x = -brick.velocity.x
}
if brick.position.x > GameField.x - brick.size.x / 2 {
brick.position.x = GameField.x - brick.size.x / 2
brick.velocity.x = -brick.velocity.x
}
if rl.CheckCollisionCircleRec(brick.position, brick.size.x / 2, rl.Rectangle{
game.pad.position.x - game.pad.size.x / 2,
game.pad.position.y - game.pad.size.y / 2,
game.pad.size.x,
game.pad.size.y
}) {
if brick.type != .BONUS {
game.pad.health -= 30
} else {
game.pad.health = min(game.pad.health + 30, 100)
}
brick.is_to_free = true
}
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}
) {
brick.is_to_free = true
}
}
brick_draw :: proc(brick: ^Brick) {
rl.DrawRectanglePro(
rl.Rectangle{brick.position.x, brick.position.y, brick.size.x, brick.size.y},
brick.size / 2,
brick.angle,
brick.color
)
if brick.type == .HARD && brick.hits <= 1 {
rl.DrawRectanglePro(
rl.Rectangle{brick.position.x, brick.position.y, brick.size.x / 1.5, brick.size.y / 1.5},
brick.size / 3,
brick.angle,
rl.ColorTint(brick.color, rl.Color{180, 180, 180, 255})
)
}
}
brick_explode :: proc(brick: ^Brick, game: ^Game) {
radius := brick.size.x * 2
brick.is_to_free = true
explode(brick.position, radius, 0.4)
for &other, i in game.bricks {
if other.is_to_free {
continue
}
if &other == brick {
continue
}
diff := other.position - brick.position
dir := rl.Vector2Normalize(diff)
dis := rl.Vector2Length(diff)
if dis > radius {
continue
}
switch other.type {
case .NORMAL, .HARD, .SOLID:
other.hits = 0
other.is_flying = true
other.velocity += dir * (radius - dis + 50) * 3
other.angular_velocity = other.velocity.x * 2
game.score += other.score
case .EXPLOSIVE, .BONUS:
if other.hit_callback != nil {
other.hit_callback(&other, game)
}
}
}
}

46
explosion.odin Normal file
View File

@ -0,0 +1,46 @@
package main
import rl "vendor:raylib"
import "core:slice"
import "core:math/ease"
Explosion :: struct{
position: Vec2,
radius: f32,
max_radius: f32,
time: f32,
duration: f32,
to_free: bool,
}
explosions_buf : [256]Explosion
explosions : [dynamic]Explosion
explosions_init :: proc() {
explosions = slice.into_dynamic(explosions_buf[:])
}
explode :: proc(position: Vec2, max_radius: f32, duration: f32) {
append(&explosions, Explosion {
position = position,
max_radius = max_radius,
duration = duration
})
}
explosions_process :: proc(delta: f32) {
#reverse for &e, i in explosions {
e.time += delta
e.radius = ease.back_out(e.time / e.duration) * e.max_radius
if e.time > e.duration {
unordered_remove(&explosions, i)
}
}
}
explosions_draw :: proc() {
#reverse for &e, i in explosions {
color := rl.ColorAlpha(rl.YELLOW, 1 - ease.exponential_in(e.time / e.duration))
rl.DrawCircleV(e.position, e.radius, color)
}
}

158
game.odin
View File

@ -5,6 +5,9 @@ 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}
@ -16,42 +19,93 @@ Game :: struct {
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.lives = 3
state.previous = prev
state.camera = rl.Camera2D{
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,
}
state.pad = Pad{
game.pad = Pad{
position = Vec2{GameField.x / 2, GameField.y - 40},
size = {80, 10},
size = {80, 20},
health = 100,
}
append(&state.balls, Ball{
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, GameField.y - 40 - 8},
position = Vec2{GameField.x / 2, -100},
is_on_pad = true,
pad_offset = Vec2{0, -16},
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 : 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))
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))
}
if offset == 0 { offset = 20 } else { offset = 0 }
}
return state
}
game_update :: proc(state: ^GameState, delta: f32) {
@ -63,6 +117,11 @@ game_update :: proc(state: ^GameState, delta: f32) {
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 {
@ -86,62 +145,97 @@ game_update :: proc(state: ^GameState, delta: f32) {
}
}
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, -16},
pad_offset = Vec2{0, -18},
})
}
only_solid := true
#reverse for &brick, i in bricks {
brick_update(&brick, camera, delta)
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
using game
rl.BeginMode2D(camera)
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)
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 game.bricks {
brick_draw(&brick)
}
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)
explosions_draw()
rl.EndMode2D()
for i : u8 = 0; i < lives; i += 1 {
rl.DrawCircleV(Vec2{20 + 20 * f32(i), 20}, 10, rl.BLUE)
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)
}
rl.DrawTextEx(FontUI, rl.TextFormat("%d", len(bricks)), {}, f32(48), 2, rl.BLACK)
rl.DrawTextEx(FontUI, rl.TextFormat("%d", len(balls)), {0, 100}, f32(48), 2, rl.BLACK)
}
game_free :: proc(state: ^GameState) {
game := transmute(^Game)state
rl.UnloadTexture(game.background)
delete(game.bricks)
delete(game.balls)

84
gameover.odin Normal file
View File

@ -0,0 +1,84 @@
package main
import rl "vendor:raylib"
import "core:math/ease"
GameOver :: struct {
using state: GameState,
position: Vec2,
size: Vec2,
ready_to_go: bool,
}
gameover_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(GameOver)
state.variant = state
state.position = Vec2{WINDOWF.x / 2, WINDOWF.y + 300}
state.size = {700, 500}
state.update = gameover_update
state.draw = gameover_draw
state.free = gameover_free
state.previous = prev
tween_to(&state.position.y, WINDOWF.y / 2, 1, ease.Ease.Back_Out, state, gameover_ready)
return state
}
gameover_update :: proc(state: ^GameState, delta: f32) {
gameover := transmute(^GameOver)state
if gameover.ready_to_go {
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
stack_pop()
stack_pop()
return
}
if rl.IsKeyPressed(rl.KeyboardKey.ENTER) {
stack_pop()
game := transmute(^Game)stack_top()
game_setup(game)
}
}
}
gameover_draw :: proc(state: ^GameState) {
gameover := transmute(^GameOver)state
if state.previous != nil {
state.previous->draw()
}
TitleFontSize :: 96
TitleSpacing :: 3
TitleText :: "ИГРА ОКОНЧЕНА"
TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing)
SubtitleText := [3]cstring{"Нажмите Enter", "чтобы начать сначала", "Или Escape для выхода"}
SubtitleFontSize :: 48
SubtitleSpacing :: 2
SubtitleSizes := [3]Vec2{}
for c, i in SubtitleText {
SubtitleSizes = rl.MeasureTextEx(FontUI, c, SubtitleFontSize, SubtitleSpacing)
}
rl.DrawRectangleV(gameover.position - gameover.size / 2, gameover.size, rl.Color{90, 30, 150, 255})
rl.DrawTextPro(FontTitle, TitleText, gameover.position - {0, 100}, TitleSize / 2, 0, TitleFontSize, TitleSpacing, rl.WHITE)
for c, i in SubtitleText {
rl.DrawTextPro(FontUI, c, gameover.position - {0, f32(10 - i * 50)}, SubtitleSizes[i] / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE)
}
}
gameover_free :: proc(state: ^GameState) {
free(state)
}
gameover_ready :: proc(state: rawptr) {
gameover := transmute(^GameOver)state
gameover.ready_to_go = true
}

62
howtoplay.odin Normal file
View File

@ -0,0 +1,62 @@
package main
import rl "vendor:raylib"
import "core:math/ease"
Howtoplay :: struct {
using state: GameState,
}
howtoplay_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(Howtoplay)
state.variant = state
state.update = howtoplay_update
state.draw = howtoplay_draw
state.free = howtoplay_free
state.previous = prev
return state
}
howtoplay_update :: proc(state: ^GameState, delta: f32) {
howtoplay := transmute(^Howtoplay)state
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
stack_pop()
}
}
howtoplay_draw :: proc(state: ^GameState) {
howtoplay := transmute(^Howtoplay)state
TitleFontSize :: 96
TitleSpacing :: 3
TitleText :: "ARKADODGE"
TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing)
rl.DrawTextPro(FontTitle, "ARKADODGE", {WINDOWF.x - 50, 50}, {TitleSize.x, 0}, 0, 96, 3, rl.WHITE)
Text :: [?]cstring{
"Используй Пробел для запуска шара",
"Стрелки для перемещения биты",
"Не дай шарику упасть вниз!",
"",
"Также блоки, падающие на биту, уменьшают её здоровье!"
}
for t, y in Text {
rl.DrawTextPro(FontUI, t, {10, 100 + f32(y) * 38}, {0, 0}, 0, 32, 1, rl.WHITE)
}
for c, t in brick_names {
y := cast(i32)t
rl.DrawRectangleV({WINDOWF.x / 2, 100 + f32(y) * 40}, {30, 20}, brick_colors[t])
rl.DrawTextPro(FontUI, c, {WINDOWF.x / 2 + 40, 100 + f32(y) * 40}, {0, 14}, 0, 32, 2, rl.WHITE)
}
}
howtoplay_free :: proc(state: ^GameState) {
free(state)
}

79
leveldone.odin Normal file
View File

@ -0,0 +1,79 @@
package main
import rl "vendor:raylib"
import "core:math/ease"
LevelDone :: struct {
using state: GameState,
position: Vec2,
size: Vec2,
ready_to_go: bool,
}
leveldone_init :: proc(prev: ^GameState = nil) -> ^GameState {
state := new(LevelDone)
state.variant = state
state.position = Vec2{WINDOWF.x / 2, WINDOWF.y + 300}
state.size = {800, 400}
state.update = leveldone_update
state.draw = leveldone_draw
state.free = leveldone_free
state.previous = prev
tween_to(&state.position.y, WINDOWF.y / 2, 1, ease.Ease.Back_Out, state, leveldone_ready)
return state
}
leveldone_update :: proc(state: ^GameState, delta: f32) {
leveldone := transmute(^LevelDone)state
if leveldone.ready_to_go {
if rl.IsKeyPressed(rl.KeyboardKey.ENTER) {
stack_pop()
game := transmute(^Game)stack_top()
game.levels_done += 1
if game.levels_done > 0 && game.levels_done % 2 == 0 {
game.levelsize += {2, 1}
game.levelsize.x = min(game.levelsize.x, 13)
game.levelsize.y = min(game.levelsize.y, 8)
}
game_gen_level(game)
}
}
}
leveldone_draw :: proc(state: ^GameState) {
leveldone := transmute(^LevelDone)state
if state.previous != nil {
state.previous->draw()
}
TitleFontSize :: 96
TitleSpacing :: 3
TitleText :: "УРОВЕНЬ ПРОЙДЕН"
TitleSize := rl.MeasureTextEx(FontTitle, TitleText, TitleFontSize, TitleSpacing)
SubtitleText :: "Нажмите Enter чтобы продолжить"
SubtitleFontSize :: 48
SubtitleSpacing :: 2
SubtitleSize := rl.MeasureTextEx(FontUI, SubtitleText, SubtitleFontSize, SubtitleSpacing)
rl.DrawRectangleV(leveldone.position - leveldone.size / 2, leveldone.size, rl.Color{90, 30, 150, 255})
rl.DrawTextPro(FontTitle, TitleText, leveldone.position - {0, 50}, TitleSize / 2, 0, TitleFontSize, TitleSpacing, rl.WHITE)
rl.DrawTextPro(FontUI, SubtitleText, leveldone.position + {0, 20}, SubtitleSize / 2, 0, SubtitleFontSize, SubtitleSpacing, rl.WHITE)
}
leveldone_free :: proc(state: ^GameState) {
free(state)
}
leveldone_ready :: proc(state: rawptr) {
leveldone := transmute(^LevelDone)state
leveldone.ready_to_go = true
}

View File

@ -19,7 +19,7 @@ FontUI: rl.Font
WindowShouldExit := false
main :: proc() {
rl.SetConfigFlags(rl.ConfigFlags{.FULLSCREEN_MODE, .VSYNC_HINT, .WINDOW_MAXIMIZED, .WINDOW_UNDECORATED})
rl.SetConfigFlags(rl.ConfigFlags{.FULLSCREEN_MODE, .VSYNC_HINT, })
monitor := rl.GetCurrentMonitor()
rl.InitWindow(0, 0, "SinePong")
rl.SetTargetFPS(9999)
@ -76,6 +76,7 @@ main :: proc() {
}
/*
TODO:
@ -83,7 +84,7 @@ main :: proc() {
[ ] Проверка завершения уровня
[ ] Генерация уровней
[ ] (Опционально) редактор уровней с сохранением в файл
[ ] Меню
[x] Меню
[ ] Гейм овер
[ ] Бонусы
[ ] Визуальное оформление

View File

@ -7,14 +7,12 @@ import "core:math/ease"
Menu_Buttons :: enum {
START,
HOW_TO_PLAY,
FULLSCREEN,
EXIT
}
menu_strings := [Menu_Buttons]cstring {
.START = "Старт",
.HOW_TO_PLAY = "Как играть?",
.FULLSCREEN = "Полный экран",
.EXIT = "Выход"
}
@ -30,7 +28,7 @@ menu_init :: proc(prev: ^GameState = nil) -> ^GameState {
state.variant = state
state.list = MenuList(Menu_Buttons){
state = state,
position = {300, 300},
position = {100, WINDOWF.y / 2},
line_size = 60,
font_size = 48,
elements = &menu_strings,
@ -47,6 +45,7 @@ menu_init :: proc(prev: ^GameState = nil) -> ^GameState {
menu_update :: proc(state: ^GameState, delta: f32) {
menu := transmute(^Menu)state
menu.list.position.y = WINDOWF.y / 2
menu_list_update(&menu.list)
}
@ -56,8 +55,9 @@ menu_button_pressed :: proc(state: ^GameState, el: Menu_Buttons) {
case .START:
game := game_init(state)
stack_push(game)
case .FULLSCREEN:
case .HOW_TO_PLAY:
howtoplay := howtoplay_init(state)
stack_push(howtoplay)
case .EXIT:
WindowShouldExit = true
return

View File

@ -13,10 +13,12 @@ Pad :: struct {
position: Vec2,
velocity: Vec2,
size: Vec2,
health: f32,
}
pad_update :: proc(pad: ^Pad, delta: f32) {
pad.health = min(pad.health + delta, 100)
target : f32 = 0
if rl.IsKeyDown(rl.KeyboardKey.LEFT) {
target = -PAD_MAX_SPEED
@ -53,4 +55,11 @@ pad_update :: proc(pad: ^Pad, delta: f32) {
pad.position.x = GameField.x - pad_half_width
pad.velocity.x = 0
}
}
pad_draw :: proc(pad: ^Pad) {
rl.DrawRectanglePro(rl.Rectangle{pad.position.x, pad.position.y, pad.size.x, pad.size.y}, pad.size / 2, 0, rl.RED)
hbar_x := pad.health / 100 * pad.size.x
rl.DrawRectanglePro(rl.Rectangle{pad.position.x, pad.position.y, hbar_x, pad.size.y/2}, {hbar_x / 2, pad.size.y / 4}, 0, rl.GREEN)
}

View File

@ -6,13 +6,11 @@ import "core:math/ease"
Pause_Buttons :: enum {
RESUME,
FULLSCREEN,
EXIT
}
pause_strings := [Pause_Buttons]cstring {
.RESUME = "Продолжить",
.FULLSCREEN = "Полный экран",
.EXIT = "Выход"
}
@ -46,6 +44,10 @@ pause_init :: proc(prev: ^GameState = nil) -> ^GameState {
pause_update :: proc(state: ^GameState, delta: f32) {
menu := transmute(^Pause)state
if rl.IsKeyPressed(rl.KeyboardKey.ESCAPE) {
stack_pop()
return
}
menu_list_update(&menu.list)
}
@ -53,7 +55,6 @@ pause_button_pressed :: proc(state: ^GameState, el: Pause_Buttons) {
switch el {
case .RESUME:
stack_pop()
case .FULLSCREEN:
case .EXIT:
stack_pop()
stack_pop()
@ -77,5 +78,6 @@ pause_draw :: proc(state: ^GameState) {
}
pause_free :: proc(state: ^GameState) {
pause := transmute(^Pause)state
free(state)
}

View File

@ -2,7 +2,7 @@ package main
import "core:slice"
StateVariant :: union{^Game, ^Menu, ^Pause}
StateVariant :: union{^Game, ^Menu, ^Pause, ^GameOver, ^LevelDone, ^Howtoplay}
GameState :: struct {
update: proc(state: ^GameState, delta: f32),
@ -46,3 +46,7 @@ stack_pop :: proc() -> (bool) {
state->free()
return true
}
stack_top :: proc() -> ^GameState {
return state_stack[len(state_stack)-1]
}