Compare commits
3
Commits
fb1702190d
...
cb523a3d6f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb523a3d6f | ||
|
|
106f8195e2 | ||
|
|
0000ca4f12 |
@@ -2,7 +2,6 @@ package game
|
|||||||
|
|
||||||
import "core:math/rand"
|
import "core:math/rand"
|
||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
import "core:log"
|
|
||||||
|
|
||||||
init_ball :: proc() -> Entity {
|
init_ball :: proc() -> Entity {
|
||||||
return Entity{
|
return Entity{
|
||||||
@@ -57,13 +56,13 @@ update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
|
|||||||
if ball.pos.y <= field_bounds.y + ball.size.x { ball.vel.y = -ball.vel.y }
|
if ball.pos.y <= field_bounds.y + ball.size.x { ball.vel.y = -ball.vel.y }
|
||||||
|
|
||||||
if ball.pos.x >= (field_bounds.x + field_bounds.width) - ball.size.x {
|
if ball.pos.x >= (field_bounds.x + field_bounds.width) - ball.size.x {
|
||||||
level.score_player += 1
|
increment_score(&level.score_player, level.score_win, true)
|
||||||
reset_ball(ball)
|
reset_ball(ball)
|
||||||
level.serve_timer = level.wait_time
|
level.serve_timer = level.wait_time
|
||||||
}
|
}
|
||||||
|
|
||||||
if ball.pos.x <= field_bounds.x + ball.size.x {
|
if ball.pos.x <= field_bounds.x + ball.size.x {
|
||||||
level.score_enemy += 1
|
increment_score(&level.score_enemy, level.score_win, false)
|
||||||
reset_ball(ball)
|
reset_ball(ball)
|
||||||
level.serve_timer = level.wait_time
|
level.serve_timer = level.wait_time
|
||||||
}
|
}
|
||||||
@@ -79,6 +78,12 @@ check_ball_paddle_collision :: proc(ball: Entity, paddle: Entity) -> bool {
|
|||||||
return rl.CheckCollisionCircleRec(ball.pos, ball.size.x, paddle_rec)
|
return rl.CheckCollisionCircleRec(ball.pos, ball.size.x, paddle_rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
increment_score :: proc(score_side: ^int, max_score: int, player: bool) {
|
||||||
|
score_side^ += 1
|
||||||
|
|
||||||
|
if score_side^ == max_score do emit_event(Event{ .Game_Over, player })
|
||||||
|
}
|
||||||
|
|
||||||
render_ball :: proc(ball: Entity, field_bounds: rl.Rectangle) {
|
render_ball :: proc(ball: Entity, field_bounds: rl.Rectangle) {
|
||||||
rl.DrawCircleV(ball.pos, ball.size.x, rl.MAROON)
|
rl.DrawCircleV(ball.pos, ball.size.x, rl.MAROON)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
// all possible game events
|
||||||
|
Event_Type :: enum {
|
||||||
|
Game_Over,
|
||||||
|
Score_Change,
|
||||||
|
Ball_Collision,
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional details attached to an event
|
||||||
|
Event :: struct {
|
||||||
|
type: Event_Type,
|
||||||
|
winner_is_player: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// global event storage
|
||||||
|
GLOBAL_EVENTS: [dynamic]Event
|
||||||
|
|
||||||
|
emit_event :: proc(event: Event) {
|
||||||
|
append(&GLOBAL_EVENTS, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
flush_events :: proc() {
|
||||||
|
clear(&GLOBAL_EVENTS)
|
||||||
|
}
|
||||||
+20
-12
@@ -10,10 +10,8 @@ WINDOW_TITLE :: "Pong"
|
|||||||
|
|
||||||
Game :: struct {
|
Game :: struct {
|
||||||
viewport: rl.RenderTexture2D,
|
viewport: rl.RenderTexture2D,
|
||||||
// base scene (gameplay, mainmenu)
|
|
||||||
scene: Scene,
|
scene: Scene,
|
||||||
// active overlay (pause, settings, or nil)
|
overlay_scene: Maybe(Scene),
|
||||||
overlay_scene: Maybe(Overlay_Scene),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
@@ -24,33 +22,43 @@ game_init :: proc(game: ^Game) {
|
|||||||
rl.SetTargetFPS(60)
|
rl.SetTargetFPS(60)
|
||||||
|
|
||||||
game.viewport = rl.LoadRenderTexture(VIEWPORT_W, VIEWPORT_H)
|
game.viewport = rl.LoadRenderTexture(VIEWPORT_W, VIEWPORT_H)
|
||||||
game.scene = init_gameplay_scene()
|
game.scene = make_gameplay_scene()
|
||||||
|
// game.scene = make_main_menu()
|
||||||
|
|
||||||
|
debug_init()
|
||||||
}
|
}
|
||||||
|
|
||||||
game_update :: proc(game: ^Game) {
|
game_update :: proc(game: ^Game) {
|
||||||
dt := rl.GetFrameTime()
|
dt := rl.GetFrameTime()
|
||||||
input := poll_input()
|
input := poll_input()
|
||||||
|
|
||||||
// update the overlay if active, otherwise update base scene
|
// Pass direct pointers (^Scene) so modifications persist
|
||||||
if game.overlay_scene != nil {
|
if overlay, ok := &game.overlay_scene.?; ok {
|
||||||
overlay_update(game, input, dt)
|
scene_update(overlay, game, input, dt)
|
||||||
} else {
|
} else {
|
||||||
scene_update(game, input, dt)
|
scene_update(&game.scene, game, input, dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flush any events triggered in this loop
|
||||||
|
flush_events()
|
||||||
}
|
}
|
||||||
|
|
||||||
game_render :: proc(game: ^Game) {
|
game_render :: proc(game: ^Game) {
|
||||||
rl.BeginDrawing()
|
rl.BeginDrawing()
|
||||||
rl.ClearBackground(rl.BLACK)
|
rl.ClearBackground(rl.BLACK)
|
||||||
|
|
||||||
// offscreen pass render everything into virtual 1280x720 target
|
|
||||||
rl.BeginTextureMode(game.viewport)
|
rl.BeginTextureMode(game.viewport)
|
||||||
rl.ClearBackground(rl.BLACK)
|
rl.ClearBackground(rl.BLACK)
|
||||||
scene_render(game) // Renders base scene (Level field or Main Menu)
|
|
||||||
overlay_render(game) // Renders active overlay on top if present
|
// Render base scene
|
||||||
|
scene_render(&game.scene)
|
||||||
|
|
||||||
|
// Render active overlay on top if present
|
||||||
|
if overlay, ok := &game.overlay_scene.?; ok {
|
||||||
|
scene_render(overlay)
|
||||||
|
}
|
||||||
rl.EndTextureMode()
|
rl.EndTextureMode()
|
||||||
|
|
||||||
// upscale pass stretch virtual buffer onto physical window display
|
|
||||||
render_game_to_window(game.viewport.texture)
|
render_game_to_window(game.viewport.texture)
|
||||||
|
|
||||||
rl.EndDrawing()
|
rl.EndDrawing()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ Level :: struct {
|
|||||||
wait_time: f32,
|
wait_time: f32,
|
||||||
score_player: int,
|
score_player: int,
|
||||||
score_enemy: int,
|
score_enemy: int,
|
||||||
|
score_win: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
FIELD_BORDER :: 3
|
FIELD_BORDER :: 3
|
||||||
@@ -50,6 +51,7 @@ init_level :: proc() -> Level {
|
|||||||
wait_time = SERVE_WAIT_TIME,
|
wait_time = SERVE_WAIT_TIME,
|
||||||
score_player = 0,
|
score_player = 0,
|
||||||
score_enemy = 0,
|
score_enemy = 0,
|
||||||
|
score_win = 2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,17 +91,14 @@ render_hud :: proc(level: Level) {
|
|||||||
opp_box := rl.Rectangle{(VIEWPORT_W - 5 - opp_size.x), 5, (opp_size.x), (opp_size.y)}
|
opp_box := rl.Rectangle{(VIEWPORT_W - 5 - opp_size.x), 5, (opp_size.x), (opp_size.y)}
|
||||||
opp_pos := rl.Vector2{opp_box.x, opp_box.y}
|
opp_pos := rl.Vector2{opp_box.x, opp_box.y}
|
||||||
rl.DrawTextEx(font, "COMPUTER", opp_pos, 20, 2, rl.WHITE)
|
rl.DrawTextEx(font, "COMPUTER", opp_pos, 20, 2, rl.WHITE)
|
||||||
// rl.DrawRectangleLinesEx(opp_box, 1.0, rl.RED)
|
|
||||||
|
|
||||||
// score box
|
// score box
|
||||||
// DYNAMIC SCORE BOX
|
|
||||||
// Format the score dynamically into a C-string allocated on the temp allocator
|
// Format the score dynamically into a C-string allocated on the temp allocator
|
||||||
score_text := fmt.ctprintf("%d : %d", level.score_player, level.score_enemy)
|
score_text := fmt.ctprintf("%d : %d", level.score_player, level.score_enemy)
|
||||||
score_size := rl.MeasureTextEx(font, score_text, 20, 2)
|
score_size := rl.MeasureTextEx(font, score_text, 20, 2)
|
||||||
score_box := rl.Rectangle{(VIEWPORT_W / 2 - score_size.x / 2), 5, (score_size.x), (score_size.y)}
|
score_box := rl.Rectangle{(VIEWPORT_W / 2 - score_size.x / 2), 5, (score_size.x), (score_size.y)}
|
||||||
score_pos := rl.Vector2{score_box.x, score_box.y}
|
score_pos := rl.Vector2{score_box.x, score_box.y}
|
||||||
rl.DrawTextEx(font, score_text, score_pos, 20, 2, rl.WHITE)
|
rl.DrawTextEx(font, score_text, score_pos, 20, 2, rl.WHITE)
|
||||||
// rl.DrawRectangleLinesEx(score_box, 1.0, rl.RED)
|
|
||||||
|
|
||||||
// game name
|
// game name
|
||||||
// rl.DrawRectangleLines(VIEWPORT_W / 2 - 50, VIEWPORT_H - 50, 100, 50, rl.RED)
|
// rl.DrawRectangleLines(VIEWPORT_W / 2 - 50, VIEWPORT_H - 50, 100, 50, rl.RED)
|
||||||
|
|||||||
+111
-74
@@ -3,98 +3,135 @@ package game
|
|||||||
import rl "vendor:raylib"
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Scene & Overlay Definitions
|
// Scene Definitions
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
Scene_Gameplay :: struct {
|
Scene_Type :: enum {
|
||||||
level: Level,
|
Main_Menu,
|
||||||
|
Gameplay,
|
||||||
|
Pause_Menu,
|
||||||
|
Game_Over,
|
||||||
}
|
}
|
||||||
|
|
||||||
Scene_Menu :: struct {
|
Scene :: struct {
|
||||||
// @todo: ui_canvas layout state
|
type: Scene_Type,
|
||||||
}
|
level: Maybe(Level),
|
||||||
|
canvas: Maybe(UI_Canvas),
|
||||||
Scene_PauseMenu :: struct {
|
winner_is_player: bool, // Store win state for rendering
|
||||||
// @todo: pause option state
|
|
||||||
}
|
|
||||||
|
|
||||||
Scene :: union {
|
|
||||||
Scene_Menu,
|
|
||||||
Scene_Gameplay,
|
|
||||||
}
|
|
||||||
|
|
||||||
Overlay_Scene :: union {
|
|
||||||
Scene_PauseMenu,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Base Scene Dispatchers
|
// UI Definitions
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
scene_update :: proc(game: ^Game, input: Input_Event, dt: f32) {
|
UI_Canvas :: struct {
|
||||||
switch &s in game.scene {
|
bounds: rl.Rectangle,
|
||||||
case Scene_Menu: // update_menu(&s, game, input, dt)
|
padding: f32,
|
||||||
case Scene_Gameplay: update_gameplay(&s, game, input, dt)
|
item_height: f32,
|
||||||
}
|
item_spaceing: f32,
|
||||||
}
|
|
||||||
|
|
||||||
scene_render :: proc(game: ^Game) {
|
|
||||||
switch &s in game.scene {
|
|
||||||
case Scene_Menu: // render_menu(s)
|
|
||||||
case Scene_Gameplay: render_gameplay(s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Overlay Scene Dispatchers
|
// Scene Constructors
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
overlay_update :: proc(game: ^Game, input: Input_Event, dt: f32) -> bool {
|
make_gameplay_scene :: proc() -> Scene {
|
||||||
overlay := game.overlay_scene.? or_return
|
return Scene{
|
||||||
|
type = .Gameplay,
|
||||||
switch &o in overlay {
|
|
||||||
case Scene_PauseMenu:
|
|
||||||
if input.pause_pressed {
|
|
||||||
game.overlay_scene = nil
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// @todo: handle overlay menu option selection (resume, settings, quit)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_render :: proc(game: ^Game) -> bool {
|
|
||||||
overlay := game.overlay_scene.? or_return
|
|
||||||
|
|
||||||
switch &o in overlay {
|
|
||||||
case Scene_PauseMenu:
|
|
||||||
// Translucent dark tint across virtual canvas space
|
|
||||||
rl.DrawRectangleRec(rl.Rectangle{0, 0, VIEWPORT_W, VIEWPORT_H}, rl.Color{0, 0, 0, 140})
|
|
||||||
// Pause Menu UI elements
|
|
||||||
rl.DrawText("PAUSED", VIEWPORT_W / 2 - 80, VIEWPORT_H / 2 - 40, 40, rl.WHITE)
|
|
||||||
rl.DrawText("Press Q to Quit to Menu", VIEWPORT_W / 2 - 110, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
|
||||||
// Gameplay Scene Procedures
|
|
||||||
// ------------------------------------------------------------------------------
|
|
||||||
init_gameplay_scene :: proc() -> Scene_Gameplay {
|
|
||||||
return Scene_Gameplay{
|
|
||||||
level = init_level(),
|
level = init_level(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_gameplay :: proc(scene: ^Scene_Gameplay, game: ^Game, input: Input_Event, dt: f32) {
|
make_pause_overlay :: proc() -> Scene {
|
||||||
|
return Scene{
|
||||||
|
type = .Pause_Menu,
|
||||||
|
canvas = UI_Canvas{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_main_menu :: proc() -> Scene {
|
||||||
|
return Scene{
|
||||||
|
type = .Main_Menu,
|
||||||
|
canvas = UI_Canvas{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_game_over_overlay :: proc(winner_is_player: bool) -> Scene {
|
||||||
|
return Scene{
|
||||||
|
type = .Game_Over,
|
||||||
|
canvas = UI_Canvas{},
|
||||||
|
winner_is_player = winner_is_player,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
// Unified Scene Dispatchers
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
scene_update :: proc(scene: ^Scene, game: ^Game, input: Input_Event, dt: f32) {
|
||||||
|
// mutate level state through pointer if level exists
|
||||||
|
if level, ok := &scene.level.?; ok {
|
||||||
|
update_level(level, input, dt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dispatch scene behavior
|
||||||
|
#partial switch scene.type {
|
||||||
|
case .Gameplay:
|
||||||
if input.pause_pressed {
|
if input.pause_pressed {
|
||||||
game.overlay_scene = Scene_PauseMenu{}
|
game.overlay_scene = make_pause_overlay()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update_level(&scene.level, input, dt)
|
case .Pause_Menu:
|
||||||
|
if input.pause_pressed {
|
||||||
|
game.overlay_scene = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
render_gameplay :: proc(scene: Scene_Gameplay) {
|
case .Game_Over:
|
||||||
render_level(scene.level)
|
if input.pause_pressed { // Press P or your start key to restart
|
||||||
render_hud(scene.level)
|
game.overlay_scene = nil
|
||||||
|
game.scene = make_gameplay_scene() // Reset the base level
|
||||||
|
}
|
||||||
|
|
||||||
|
case .Main_Menu:
|
||||||
|
// @todo: handle menu selection / start game
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over global frame events
|
||||||
|
for event in GLOBAL_EVENTS {
|
||||||
|
#partial switch event.type {
|
||||||
|
case .Game_Over:
|
||||||
|
game.overlay_scene = make_game_over_overlay(event.winner_is_player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scene_render :: proc(scene: ^Scene) {
|
||||||
|
// render gameplay field & hud if present
|
||||||
|
if level, ok := scene.level.?; ok {
|
||||||
|
render_level(level)
|
||||||
|
render_hud(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// render scene/overlay ui elements
|
||||||
|
#partial switch scene.type {
|
||||||
|
case .Pause_Menu:
|
||||||
|
rl.DrawRectangleRec(
|
||||||
|
rl.Rectangle{0, 0, f32(VIEWPORT_W), f32(VIEWPORT_H)},
|
||||||
|
rl.Color{0, 0, 0, 140},
|
||||||
|
)
|
||||||
|
rl.DrawText("PAUSED", VIEWPORT_W / 2 - 80, VIEWPORT_H / 2 - 40, 40, rl.WHITE)
|
||||||
|
rl.DrawText("Press P to Resume", VIEWPORT_W / 2 - 90, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY)
|
||||||
|
|
||||||
|
case .Game_Over:
|
||||||
|
rl.DrawRectangleRec(
|
||||||
|
rl.Rectangle{0, 0, f32(VIEWPORT_W), f32(VIEWPORT_H)},
|
||||||
|
rl.Color{0, 0, 0, 200},
|
||||||
|
)
|
||||||
|
|
||||||
|
msg :cstring = scene.winner_is_player ? "YOU WIN!" : "GAME OVER"
|
||||||
|
text_color := scene.winner_is_player ? rl.GREEN : rl.RED
|
||||||
|
|
||||||
|
rl.DrawText(msg, VIEWPORT_W / 2 - 100, VIEWPORT_H / 2 - 40, 40, text_color)
|
||||||
|
rl.DrawText("Press P to Play Again", VIEWPORT_W / 2 - 110, VIEWPORT_H / 2 + 10, 20, rl.LIGHTGRAY)
|
||||||
|
|
||||||
|
case .Main_Menu:
|
||||||
|
rl.DrawText("PONG", VIEWPORT_W / 2 - 50, VIEWPORT_H / 3, 40, rl.WHITE)
|
||||||
|
rl.DrawText("Press ENTER to Start", VIEWPORT_W / 2 - 110, VIEWPORT_H / 2, 20, rl.LIGHTGRAY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user