diff --git a/src/scene_manager.odin b/src/scene_manager.odin index 7adb565..1bac368 100644 --- a/src/scene_manager.odin +++ b/src/scene_manager.odin @@ -9,6 +9,7 @@ Scene_Type :: enum { Main_Menu, Gameplay, Pause_Menu, + Settings_Menu, Game_Over, } @@ -19,6 +20,55 @@ Scene :: struct { winner_is_player: bool, } +// ------------------------------------------------------------------------------ +// Unified Scene Dispatchers +// ------------------------------------------------------------------------------ +scene_render :: proc(scene: ^Scene) { + if level, ok := scene.level.?; ok { + render_level(level) + render_hud(level) + } + + #partial switch scene.type { + case .Pause_Menu: + render_pause_menu(scene^) + case .Settings_Menu: + render_settings_menu(scene^) + case .Game_Over: + render_game_over(scene^) + case .Main_Menu: + render_main_menu(scene^) + } +} + +scene_update :: proc(scene: ^Scene, game: ^Game, input: Input_Event, dt: f32) { + #partial switch scene.type { + case .Main_Menu: + update_main_menu(scene, input) + + case .Pause_Menu: + update_pause_menu(scene, input) + + case .Settings_Menu: + update_settings_menu(scene, input) + + case .Gameplay: + level, level_ok := &scene.level.? + assert(level_ok, "scene_update: .Gameplay scene is missing a Level component") + update_level(level, input, dt) + + if input.pause_pressed { + emit_event(Event{ type = .Game_Pause }) + } + + case .Game_Over: + update_game_over(scene, input) + } + + // Now handle any events triggered + process_events(game) +} + // ------------------------------------------------------------------------------ // UI Definitions // ------------------------------------------------------------------------------ @@ -30,8 +80,23 @@ Canvas :: struct { menu: Maybe(UI_Menu), } +UI_Button :: struct { + label: cstring, +} + +UI_Slider :: struct { + label: cstring, + value: ^f32, + step: f32, +} + +UI_Item :: union { + UI_Button, + UI_Slider, +} + UI_Menu :: struct { - items: [8]cstring, + items: [8]UI_Item, count: int, selected_index: int, } @@ -53,16 +118,35 @@ list_menu_items :: proc(canvas: Canvas, menu: UI_Menu) { item := menu.items[i] item_rec := get_canvas_item_rec(canvas, i, top_margin = 150.0) - text_width := rl.MeasureText(item, 20) - text_x := i32(item_rec.x + item_rec.width * 0.5) - (text_width / 2) - text_y := i32(item_rec.y + (item_rec.height * 0.5)) - 10 - is_selected := (i == menu.selected_index) border_color := is_selected ? rl.ORANGE : rl.DARKGRAY text_color := is_selected ? rl.ORANGE : rl.WHITE rl.DrawRectangleLinesEx(item_rec, 3, border_color) - rl.DrawText(item, text_x, text_y, 20, text_color) + + #partial switch v in item { + case UI_Button: + text_width := rl.MeasureText(v.label, 20) + text_x := i32(item_rec.x + item_rec.width * 0.5) - (text_width / 2) + text_y := i32(item_rec.y + (item_rec.height * 0.5)) - 10 + rl.DrawText(v.label, text_x, text_y, 20, text_color) + + case UI_Slider: + slider_rec := item_rec + text_width := rl.MeasureText(v.label, 20) + slider_rec.x = slider_rec.x + f32(text_width) + slider_rec.width = (slider_rec.width - f32(text_width)) + + rl.GuiSlider( + bounds = slider_rec, + textLeft = v.label, + textRight = nil, + value = v.value, + minValue = 0.0, + maxValue = 1.0, + ) + } + } } @@ -70,6 +154,12 @@ list_menu_items :: proc(canvas: Canvas, menu: UI_Menu) { // Main Menu // ------------------------------------------------------------------------------ make_main_menu :: proc() -> Scene { + ui_items:[8]UI_Item = { + 0 = UI_Button{"Start Game"}, + 1 = UI_Button{"Options"}, + 2 = UI_Button{"Quit"}, + } + return Scene{ type = .Main_Menu, canvas = Canvas{ @@ -78,7 +168,7 @@ make_main_menu :: proc() -> Scene { item_height = 40, item_spaceing = 15, menu = UI_Menu{ - items = {0 = "Start Game", 1 = "Options", 2 = "Quit"}, + items = ui_items, count = 3, selected_index = 0, }, @@ -112,9 +202,11 @@ update_main_menu :: proc(scene: ^Scene, input: Input_Event) { if input.up_pressed { menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count + emit_event(Event{ type = .UI_Select }) } if input.down_pressed { menu.selected_index = (menu.selected_index + 1) % menu.count + emit_event(Event{ type = .UI_Select }) } if input.confirm_pressed { @@ -122,7 +214,7 @@ update_main_menu :: proc(scene: ^Scene, input: Input_Event) { case 0: emit_event(Event{ type = .Game_Start }) case 1: - // @todo open options + emit_event(Event { type = .Game_Settings }) case 2: emit_event(Event{ type = .Game_Quit }) } @@ -133,6 +225,12 @@ update_main_menu :: proc(scene: ^Scene, input: Input_Event) { // Pause Menu // ------------------------------------------------------------------------------ make_pause_overlay :: proc() -> Scene { + ui_items:[8]UI_Item = { + 0 = UI_Button{"Resume Game"}, + 1 = UI_Button{"Options"}, + 2 = UI_Button{"Main Menu"}, + } + return Scene{ type = .Pause_Menu, canvas = Canvas{ @@ -141,7 +239,7 @@ make_pause_overlay :: proc() -> Scene { item_height = 40, item_spaceing = 15, menu = UI_Menu{ - items = {0 = "Resume Game", 1 = "Options", 2 = "Main Menu"}, + items = ui_items, count = 3, selected_index = 0, }, @@ -175,9 +273,11 @@ update_pause_menu :: proc(scene: ^Scene, input: Input_Event) { if input.up_pressed { menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count + emit_event(Event{ type = .UI_Select }) } if input.down_pressed { menu.selected_index = (menu.selected_index + 1) % menu.count + emit_event(Event{ type = .UI_Select }) } if input.confirm_pressed { @@ -185,7 +285,7 @@ update_pause_menu :: proc(scene: ^Scene, input: Input_Event) { case 0: emit_event(Event{ type = .Game_Resume }) case 1: - // @todo open options + emit_event(Event { type = .Game_Settings }) case 2: emit_event(Event{ type = .Game_Main_Menu }) } @@ -196,10 +296,108 @@ update_pause_menu :: proc(scene: ^Scene, input: Input_Event) { } } +// ------------------------------------------------------------------------------ +// Settings Menu +// ------------------------------------------------------------------------------ +make_settings_menu :: proc(audio: ^Audio_Manager) -> Scene { + ui_items:[8]UI_Item = { + 0 = UI_Button{"Back"}, + 1 = UI_Button{"Resolution"}, + 2 = UI_Slider{ + label = "Music Volume", + value = &audio.user_music_vol, + step = 0.1, + }, + 3 = UI_Slider{ + label = "SFX Volume", + value = &audio.sfx_vol, + step = 0.1, + }, + } + + return Scene{ + type = .Settings_Menu, + canvas = Canvas{ + bounds = rl.Rectangle{0, 0, VIEWPORT_W, VIEWPORT_H}, + padding = 20, + item_height = 40, + item_spaceing = 15, + menu = UI_Menu{ + items = ui_items, + count = 4, + selected_index = 0, + }, + }, + } +} + +render_settings_menu :: proc(scene: Scene) { + canvas, canvas_ok := scene.canvas.? + assert(canvas_ok, "render_settings_menu: Scene is missing a Canvas component") + + menu, menu_ok := canvas.menu.? + assert(menu_ok, "render_settings_menu: Canvas is missing a UI_Menu component") + + rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 255}) + + t_text: cstring = "SETTINGS" + t_width := rl.MeasureText(t_text, 40) + t_x := i32(canvas.bounds.width * 0.5) - (t_width / 2) + rl.DrawText(t_text, t_x, i32(canvas.padding), 40, rl.WHITE) + + list_menu_items(canvas, menu) +} + +update_settings_menu :: proc(scene: ^Scene, input: Input_Event) { + canvas, canvas_ok := &scene.canvas.? + assert(canvas_ok, "update_settings_menu: Scene is missing a Canvas component") + + menu, menu_ok := &canvas.menu.? + assert(menu_ok, "update_settings_menu: Canvas is missing a UI_Menu component") + + if input.up_pressed { + menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count + emit_event(Event{ type = .UI_Select }) + } + if input.down_pressed { + menu.selected_index = (menu.selected_index + 1) % menu.count + emit_event(Event{ type = .UI_Select }) + } + + current_item := &menu.items[menu.selected_index] + + #partial switch v in current_item { + case UI_Button: + if input.confirm_pressed { + switch menu.selected_index { + case 0: + emit_event(Event{ type = .Game_Main_Menu }) + } + } + case UI_Slider: + if input.left_pressed { + // Decrement value by step and keep within normalized 0.0 - 1.0 bounds + v.value^ = clamp(v.value^ - v.step, 0.0, 1.0) + emit_event(Event{ type = .UI_Select }) + } + if input.right_pressed { + // Increment value by step and keep within normalized 0.0 - 1.0 bounds + v.value^ = clamp(v.value^ + v.step, 0.0, 1.0) + emit_event(Event{ type = .UI_Select }) + } + } + +} + // ------------------------------------------------------------------------------ // Game Over Menu // ------------------------------------------------------------------------------ make_game_over_overlay :: proc(winner_is_player: bool) -> Scene { + ui_items:[8]UI_Item = { + 0 = UI_Button{"Restart Game"}, + 1 = UI_Button{"Main Menu"}, + } + return Scene{ type = .Game_Over, winner_is_player = winner_is_player, @@ -209,7 +407,7 @@ make_game_over_overlay :: proc(winner_is_player: bool) -> Scene { item_height = 40, item_spaceing = 15, menu = UI_Menu{ - items = {0 = "Restart Game", 1 = "Main Menu"}, + items = ui_items, count = 2, selected_index = 0, }, @@ -219,10 +417,10 @@ make_game_over_overlay :: proc(winner_is_player: bool) -> Scene { render_game_over :: proc(scene: Scene) { canvas, canvas_ok := scene.canvas.? - assert(canvas_ok, "render_pause_menu: Scene is missing a Canvas component") + assert(canvas_ok, "update_game_over: Scene is missing a Canvas component") menu, menu_ok := canvas.menu.? - assert(menu_ok, "render_pause_menu: Canvas is missing a UI_Menu component") + assert(menu_ok, "update_game_over: Canvas is missing a UI_Menu component") rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 255}) @@ -237,16 +435,18 @@ render_game_over :: proc(scene: Scene) { update_game_over :: proc(scene: ^Scene, input: Input_Event) { canvas, canvas_ok := &scene.canvas.? - assert(canvas_ok, "update_pause_menu: Scene is missing a Canvas component") + assert(canvas_ok, "update_game_over: Scene is missing a Canvas component") menu, menu_ok := &canvas.menu.? - assert(menu_ok, "update_pause_menu: Canvas is missing a UI_Menu component") + assert(menu_ok, "update_game_over: Canvas is missing a UI_Menu component") if input.up_pressed { menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count + emit_event(Event{ type = .UI_Select }) } if input.down_pressed { menu.selected_index = (menu.selected_index + 1) % menu.count + emit_event(Event{ type = .UI_Select }) } if input.confirm_pressed {