diff --git a/src/game.odin b/src/game.odin index 8a6ab84..1a4773a 100644 --- a/src/game.odin +++ b/src/game.odin @@ -10,10 +10,8 @@ WINDOW_TITLE :: "Pong" Game :: struct { viewport: rl.RenderTexture2D, - // base scene (gameplay, mainmenu) - scene: Scene, - // active overlay (pause, settings, or nil) - overlay_scene: Maybe(Overlay_Scene), + scene: Scene, + overlay_scene: Maybe(Scene), } // ------------------------------------------------------------------------------ @@ -24,18 +22,21 @@ game_init :: proc(game: ^Game) { rl.SetTargetFPS(60) 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) { dt := rl.GetFrameTime() input := poll_input() - // update the overlay if active, otherwise update base scene - if game.overlay_scene != nil { - overlay_update(game, input, dt) + // Pass direct pointers (^Scene) so modifications persist + if overlay, ok := &game.overlay_scene.?; ok { + scene_update(overlay, game, input, dt) } else { - scene_update(game, input, dt) + scene_update(&game.scene, game, input, dt) } } @@ -43,14 +44,18 @@ game_render :: proc(game: ^Game) { rl.BeginDrawing() rl.ClearBackground(rl.BLACK) - // offscreen pass render everything into virtual 1280x720 target rl.BeginTextureMode(game.viewport) 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() - // upscale pass stretch virtual buffer onto physical window display render_game_to_window(game.viewport.texture) rl.EndDrawing() diff --git a/src/scene_manager.odin b/src/scene_manager.odin index edb1453..b182e08 100644 --- a/src/scene_manager.odin +++ b/src/scene_manager.odin @@ -3,98 +3,135 @@ package game import rl "vendor:raylib" // ------------------------------------------------------------------------------ -// Scene & Overlay Definitions +// Scene Definitions // ------------------------------------------------------------------------------ -Scene_Gameplay :: struct { - level: Level, +Scene_Type :: enum { + Main_Menu, + Gameplay, + Pause_Menu, + Game_Over, } -Scene_Menu :: struct { - // @todo: ui_canvas layout state -} - -Scene_PauseMenu :: struct { - // @todo: pause option state -} - -Scene :: union { - Scene_Menu, - Scene_Gameplay, -} - -Overlay_Scene :: union { - Scene_PauseMenu, +Scene :: struct { + type: Scene_Type, + level: Maybe(Level), + canvas: Maybe(UI_Canvas), + winner_is_player: bool, // Store win state for rendering } // ------------------------------------------------------------------------------ -// Base Scene Dispatchers +// UI Definitions // ------------------------------------------------------------------------------ -scene_update :: proc(game: ^Game, input: Input_Event, dt: f32) { - switch &s in game.scene { - case Scene_Menu: // update_menu(&s, game, input, dt) - case Scene_Gameplay: update_gameplay(&s, game, input, dt) - } -} - -scene_render :: proc(game: ^Game) { - switch &s in game.scene { - case Scene_Menu: // render_menu(s) - case Scene_Gameplay: render_gameplay(s) - } +UI_Canvas :: struct { + bounds: rl.Rectangle, + padding: f32, + item_height: f32, + item_spaceing: f32, } // ------------------------------------------------------------------------------ -// Overlay Scene Dispatchers +// Scene Constructors // ------------------------------------------------------------------------------ -overlay_update :: proc(game: ^Game, input: Input_Event, dt: f32) -> bool { - overlay := game.overlay_scene.? or_return - - 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{ +make_gameplay_scene :: proc() -> Scene { + return Scene{ + type = .Gameplay, level = init_level(), } } -update_gameplay :: proc(scene: ^Scene_Gameplay, game: ^Game, input: Input_Event, dt: f32) { - if input.pause_pressed { - game.overlay_scene = Scene_PauseMenu{} - return +make_pause_overlay :: proc() -> Scene { + return Scene{ + type = .Pause_Menu, + canvas = UI_Canvas{}, } - - update_level(&scene.level, input, dt) } -render_gameplay :: proc(scene: Scene_Gameplay) { - render_level(scene.level) - render_hud(scene.level) +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 { + game.overlay_scene = make_pause_overlay() + } + + case .Pause_Menu: + if input.pause_pressed { + game.overlay_scene = nil + } + + case .Game_Over: + if input.pause_pressed { // Press P or your start key to restart + 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) + } } \ No newline at end of file