Compare commits

...
7 Commits
Author SHA1 Message Date
jasonhilder 72c564b24a Updated scene manager.
Scene manager now uses multiple event types.
UI updates, todo must move to its own file at some point.
2026-09-21 07:32:28 +02:00
jasonhilder 4260b11fdb Setup multiple resolutions. 2026-09-21 07:31:31 +02:00
jasonhilder 1ce0992cf3 Refactor for easier management.
Refactored to have events be a union so we can pack data in cases.
And in others just emit a simple notifying event.
2026-09-21 07:31:06 +02:00
jasonhilder df18e0c8b7 Updated game to use new scene stack. 2026-09-21 07:30:07 +02:00
jasonhilder 5791f14db8 Updated scene manager into a stack 2026-09-21 07:29:24 +02:00
jasonhilder 179a3fa91b Added a new field to ent struct. 2026-09-21 07:27:53 +02:00
jasonhilder 4a45ba3328 Updated ball to have deflection and max speed.
Updated the ball to increment its speed on hit.
Also to calc a better angle on hit to serve back.
Also set a max speed.
2026-09-21 07:27:45 +02:00
5 changed files with 274 additions and 140 deletions
+1
View File
@@ -27,6 +27,7 @@ Entity :: struct {
size: rl.Vector2, size: rl.Vector2,
// Fixed input speed (paddles) // Fixed input speed (paddles)
speed: f32, speed: f32,
max_speed: f32,
// 2D momentum & direction (ball / physics) // 2D momentum & direction (ball / physics)
vel: rl.Vector2, vel: rl.Vector2,
target_y: f32, target_y: f32,
+44 -11
View File
@@ -9,6 +9,7 @@ init_ball :: proc() -> Entity {
active = true, active = true,
pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)}, pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)},
speed = 500, speed = 500,
max_speed = 800,
vel = rl.Vector2{0,0}, vel = rl.Vector2{0,0},
shape = .Circle, shape = .Circle,
size = rl.Vector2{15, 15} size = rl.Vector2{15, 15}
@@ -25,6 +26,7 @@ serve_ball :: proc(ball: ^Entity) {
reset_ball :: proc(ball: ^Entity) { reset_ball :: proc(ball: ^Entity) {
ball.pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)} ball.pos = rl.Vector2{(VIEWPORT_W / 2), (VIEWPORT_H / 2)}
ball.vel = rl.Vector2{0,0} ball.vel = rl.Vector2{0,0}
ball.speed = 500
} }
update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) { update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
@@ -39,18 +41,14 @@ update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
return return
} }
ball.pos += ball.vel * dt
radius := ball.size.x radius := ball.size.x
field_bounds := level.field_bounds field_bounds := level.field_bounds
// paddle collisions detection (player then enemy) // paddle collisions detection (player then enemy)
if check_ball_paddle_collision(ball^, level.player) { if check_ball_paddle_collision(ball, level.player) {
ball.vel.x = abs(ball.vel.x) resolve_collision(ball, level.player, true)
// stp stickiness } else if check_ball_paddle_collision(ball, level.opponent) {
ball.pos.x = level.player.pos.x + level.player.size.x + radius resolve_collision(ball, level.opponent, false)
} else if check_ball_paddle_collision(ball^, level.opponent) {
ball.vel.x = -abs(ball.vel.x)
ball.pos.x = level.opponent.pos.x - radius
} }
// bounds collision detection // bounds collision detection
@@ -68,9 +66,11 @@ update_ball :: proc(ball: ^Entity, level: ^Level, dt: f32) {
reset_ball(ball) reset_ball(ball)
level.serve_timer = level.wait_time level.serve_timer = level.wait_time
} }
ball.pos += ball.vel * dt
} }
check_ball_paddle_collision :: proc(ball: Entity, paddle: Entity) -> bool { check_ball_paddle_collision :: proc(ball: ^Entity, paddle: Entity) -> bool {
paddle_rec := rl.Rectangle{ paddle_rec := rl.Rectangle{
x = paddle.pos.x, x = paddle.pos.x,
y = paddle.pos.y, y = paddle.pos.y,
@@ -78,14 +78,47 @@ check_ball_paddle_collision :: proc(ball: Entity, paddle: Entity) -> bool {
height = paddle.size.y, height = paddle.size.y,
} }
collided := rl.CheckCollisionCircleRec(ball.pos, ball.size.x, paddle_rec) collided := rl.CheckCollisionCircleRec(ball.pos, ball.size.x, paddle_rec)
if collided do emit_event(Event{ type = .Ball_Collision }) if collided do emit_event(.Ball_Collision)
return collided return collided
} }
increment_score :: proc(score_side: ^int, max_score: int, player: bool) { increment_score :: proc(score_side: ^int, max_score: int, player: bool) {
score_side^ += 1 score_side^ += 1
if score_side^ == max_score do emit_event(Game_Over_Data{player})
}
if score_side^ == max_score do emit_event(Event{ .Game_Over, player }) increment_speed :: proc(ball: ^Entity) {
if ball.speed <= ball.max_speed {
ball.speed += 100
ball.vel = rl.Vector2Normalize({ball.vel.x, ball.vel.y}) * ball.speed
}
}
resolve_collision :: proc(ball: ^Entity, paddle: Entity, is_player: bool) {
ball_center_y := ball.pos.y
paddle_center_y := paddle.pos.y + (paddle.size.y / 2.0)
local_y := ball_center_y - paddle_center_y
half_height := paddle.size.y / 2.0
// check if we are in top or bottom half of paddle
hit_t := clamp(local_y / half_height, -1.0, 1.0)
// direction is dependant of if its the player
dir_x: f32 = is_player ? 1.0 : -1.0
// steer the angle by a value using its hit t value
dir_y := hit_t * 1.3
new_dir := rl.Vector2Normalize({dir_x, dir_y})
ball.vel = new_dir * ball.speed
increment_speed(ball)
// Resolve overlap
if is_player {
ball.pos.x = paddle.pos.x + paddle.size.x + ball.size.x
} else {
ball.pos.x = paddle.pos.x - ball.size.x
}
} }
render_ball :: proc(ball: Entity, field_bounds: rl.Rectangle) { render_ball :: proc(ball: Entity, field_bounds: rl.Rectangle) {
+42 -28
View File
@@ -1,28 +1,26 @@
package game package game
// all possible game events Change_Resolution_Data :: struct { res_dir: int }
Event_Type :: enum { Game_Over_Data :: struct { winner_is_player: bool }
// game state
Simple_Event :: enum {
Game_Start, Game_Start,
Game_Over,
Game_Quit,
Game_Pause, Game_Pause,
Game_Resume, Game_Resume,
Game_Back,
Game_Main_Menu, Game_Main_Menu,
Game_Settings, Game_Settings,
Game_Over_Generic,
// ui events Game_Quit,
UI_Select, UI_Select,
// gameplay
Score_Change, Score_Change,
Ball_Collision, Ball_Collision,
} }
// optional details attached to an event Event :: union {
Event :: struct { Simple_Event,
type: Event_Type, Change_Resolution_Data,
winner_is_player: bool, Game_Over_Data,
} }
// global event storage // global event storage
@@ -38,29 +36,44 @@ flush_events :: proc() {
process_events :: proc(game: ^Game) { process_events :: proc(game: ^Game) {
for event in GLOBAL_EVENTS { for event in GLOBAL_EVENTS {
#partial switch event.type { switch e in event {
case Change_Resolution_Data:
new_index := (game.res_index + e.res_dir + len(RESOLUTIONS)) % len(RESOLUTIONS)
game_set_resolution(game, new_index)
// Dynamically update the active UI_Selector label on screen
if active_scene := get_active_scene(game); active_scene != nil {
if canvas, ok := &active_scene.canvas.?; ok {
if menu, menu_ok := &canvas.menu.?; menu_ok {
if selector, sel_ok := &menu.items[1].(UI_Selector); sel_ok {
selector.value_label = RESOLUTIONS[game.res_index].label
}
}
}
}
audio_play_sfx(&game.audio, .UI_Select)
case Game_Over_Data:
push_scene(game, make_game_over_overlay(e.winner_is_player))
case Simple_Event:
#partial switch e {
case .Game_Start: case .Game_Start:
game.overlay_scene = nil set_root_scene(game, make_gameplay_scene())
game.scene = make_gameplay_scene()
audio_fade_in(&game.audio, duration = 1.5) audio_fade_in(&game.audio, duration = 1.5)
case .Game_Pause: case .Game_Pause:
game.overlay_scene = make_pause_overlay() push_scene(game, make_pause_overlay())
case .Game_Resume: case .Game_Resume, .Game_Back:
game.overlay_scene = nil pop_scene(game)
case .Game_Main_Menu:
game.overlay_scene = nil
game.scene = make_main_menu()
audio_fade_out(&game.audio, duration = 1.0)
case .Game_Settings: case .Game_Settings:
game.overlay_scene = nil push_scene(game, make_settings_menu(&game.audio, RESOLUTIONS[game.res_index].label))
game.overlay_scene = make_settings_menu(&game.audio)
case .Game_Over: case .Game_Main_Menu:
game.overlay_scene = make_game_over_overlay(event.winner_is_player) set_root_scene(game, make_main_menu())
audio_fade_out(&game.audio, duration = 1.0)
case .Game_Quit: case .Game_Quit:
game.should_close = true game.should_close = true
@@ -73,3 +86,4 @@ process_events :: proc(game: ^Game) {
} }
} }
} }
}
+64 -26
View File
@@ -4,23 +4,46 @@ import rl "vendor:raylib"
VIEWPORT_W :: 1280 VIEWPORT_W :: 1280
VIEWPORT_H :: 720 VIEWPORT_H :: 720
WINDOW_W :: 2560
WINDOW_H :: 1440
WINDOW_TITLE :: "Pong" WINDOW_TITLE :: "Pong"
Resolution :: struct {
width: i32,
height: i32,
label: cstring,
}
// Preset window dimensions
RESOLUTIONS := [?]Resolution{
{1280, 720, "1280x720"},
{1920, 1080, "1920x1080"},
{2560, 1440, "2560x1440"},
{3840, 2160, "3840x2160"},
}
Game :: struct { Game :: struct {
viewport: rl.RenderTexture2D, viewport: rl.RenderTexture2D,
scene: Scene, scene_stack: [dynamic]Scene,
overlay_scene: Maybe(Scene),
should_close: bool, should_close: bool,
audio: Audio_Manager, audio: Audio_Manager,
// resolution state
window_w: i32,
window_h: i32,
res_index: int
} }
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Game Lifecycle // Game Lifecycle
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
game_init :: proc(game: ^Game) { game_init :: proc(game: ^Game) {
rl.InitWindow(WINDOW_W, WINDOW_H, WINDOW_TITLE) //rl.SetConfigFlags({.WINDOW_RESIZABLE, .MSAA_4X_HINT})
rl.SetConfigFlags({.MSAA_4X_HINT})
game.res_index = 2
game.window_w = RESOLUTIONS[game.res_index].width
game.window_h = RESOLUTIONS[game.res_index].height
rl.InitWindow(game.window_w, game.window_h, WINDOW_TITLE)
rl.SetTargetFPS(60) rl.SetTargetFPS(60)
// init any global managers // init any global managers
@@ -29,22 +52,19 @@ game_init :: proc(game: ^Game) {
// allocate some event capacity to stop early dynamic reallocations // allocate some event capacity to stop early dynamic reallocations
GLOBAL_EVENTS = make([dynamic]Event, 0, 16) GLOBAL_EVENTS = make([dynamic]Event, 0, 16)
game.scene_stack = make([dynamic]Scene, 0, 8)
game.viewport = rl.LoadRenderTexture(VIEWPORT_W, VIEWPORT_H) game.viewport = rl.LoadRenderTexture(VIEWPORT_W, VIEWPORT_H)
game.scene = make_main_menu()
game.overlay_scene = nil
game.should_close = false game.should_close = false
set_root_scene(game, make_main_menu())
} }
game_update :: proc(game: ^Game) { game_update :: proc(game: ^Game) {
dt := rl.GetFrameTime() dt := rl.GetFrameTime()
input := poll_input() input := poll_input()
// Pass direct pointers (^Scene) so modifications persist active_scene := get_active_scene(game)
// only update the overlay, if it currently exists if active_scene != nil {
if overlay, ok := &game.overlay_scene.?; ok { scene_update(active_scene, game, input, dt)
scene_update(overlay, game, input, dt)
} else {
scene_update(&game.scene, game, input, dt)
} }
// background music buffer streaming // background music buffer streaming
@@ -61,28 +81,27 @@ game_render :: proc(game: ^Game) {
rl.BeginTextureMode(game.viewport) rl.BeginTextureMode(game.viewport)
rl.ClearBackground(rl.BLACK) rl.ClearBackground(rl.BLACK)
// Render base scene // @note: fine for now but probably not a good idea...
scene_render(&game.scene) // Render all scenes in the stack from bottom to top
for i in 0 ..< len(game.scene_stack) {
// Render active overlay on top if present scene_render(&game.scene_stack[i])
if overlay, ok := &game.overlay_scene.?; ok {
scene_render(overlay)
} }
rl.EndTextureMode() rl.EndTextureMode()
render_game_to_window(game.viewport.texture) render_game_to_window(game)
rl.EndDrawing() rl.EndDrawing()
} }
game_quit :: proc(game: ^Game) { game_quit :: proc(game: ^Game) {
delete(GLOBAL_EVENTS)
rl.UnloadRenderTexture(game.viewport)
// cleanup global managers
audio_manager_quit(&game.audio) audio_manager_quit(&game.audio)
rl.UnloadRenderTexture(game.viewport)
delete(GLOBAL_EVENTS)
delete(game.scene_stack)
rl.CloseWindow() rl.CloseWindow()
} }
@@ -93,13 +112,32 @@ game_should_run :: proc(game: Game) -> bool {
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Window Upscale Render // Window Upscale Render
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
render_game_to_window :: proc(texture: rl.Texture) { game_set_resolution :: proc(game: ^Game, res_index: int) {
game.res_index = clamp(res_index, 0, len(RESOLUTIONS) - 1)
target_w := RESOLUTIONS[game.res_index].width
target_h := RESOLUTIONS[game.res_index].height
monitor := rl.GetCurrentMonitor()
screen_w := rl.GetMonitorWidth(monitor)
screen_h := rl.GetMonitorHeight(monitor)
// Clamp window size so it doesn't exceed physical screen limits
game.window_w = min(target_w, screen_w)
game.window_h = min(target_h, screen_h)
rl.SetWindowSize(game.window_w, game.window_h)
}
render_game_to_window :: proc(game: ^Game) {
texture := game.viewport.texture
rl.DrawTexturePro( rl.DrawTexturePro(
texture, texture,
// src: inverted y coordinate flips opengl texture buffer correctly // src: inverted y coordinate flips opengl texture buffer correctly
rl.Rectangle{0, 0, f32(texture.width), -f32(texture.height)}, rl.Rectangle{0, 0, f32(texture.width), -f32(texture.height)},
// dst: scales viewport canvas to fill window resolution // dst: scales viewport canvas to fill window resolution
rl.Rectangle{0, 0, f32(WINDOW_W), f32(WINDOW_H)}, rl.Rectangle{0, 0, f32(game.window_w), f32(game.window_h)},
rl.Vector2{}, rl.Vector2{},
0, 0,
rl.WHITE, rl.WHITE,
+114 -66
View File
@@ -1,5 +1,6 @@
package game package game
import "core:fmt"
import rl "vendor:raylib" import rl "vendor:raylib"
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
@@ -20,9 +21,57 @@ Scene :: struct {
winner_is_player: bool, winner_is_player: bool,
} }
push_scene :: proc(game: ^Game, scene: Scene) {
append(&game.scene_stack, scene)
}
pop_scene :: proc(game: ^Game) {
if len(game.scene_stack) > 1 {
pop(&game.scene_stack)
}
}
set_root_scene :: proc(game: ^Game, root_scene: Scene) {
clear(&game.scene_stack)
append(&game.scene_stack, root_scene)
}
get_active_scene :: proc(game: ^Game) -> ^Scene {
if len(game.scene_stack) == 0 do return nil
return &game.scene_stack[len(game.scene_stack) - 1]
}
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Unified Scene Dispatchers // Unified Scene Dispatchers
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
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 .Game_Over:
update_game_over(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(.Game_Pause)
}
}
// Process events triggered during scene updates
process_events(game)
}
scene_render :: proc(scene: ^Scene) { scene_render :: proc(scene: ^Scene) {
if level, ok := scene.level.?; ok { if level, ok := scene.level.?; ok {
render_level(level) render_level(level)
@@ -41,34 +90,6 @@ scene_render :: proc(scene: ^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 // UI Definitions
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
@@ -90,9 +111,15 @@ UI_Slider :: struct {
step: f32, step: f32,
} }
UI_Selector :: struct {
label: cstring,
value_label: cstring,
}
UI_Item :: union { UI_Item :: union {
UI_Button, UI_Button,
UI_Slider, UI_Slider,
UI_Selector,
} }
UI_Menu :: struct { UI_Menu :: struct {
@@ -145,8 +172,17 @@ list_menu_items :: proc(canvas: Canvas, menu: UI_Menu) {
minValue = 0.0, minValue = 0.0,
maxValue = 1.0, maxValue = 1.0,
) )
}
case UI_Selector:
// draw label left half
lbl_y := i32(item_rec.y + (item_rec.height * 0.5)) - 10
rl.DrawText(v.label, i32(item_rec.x + 15), lbl_y, 20, text_color)
display_str := fmt.ctprintf("< %s >", v.value_label)
str_w := rl.MeasureText(display_str, 20)
val_x := i32(item_rec.x + item_rec.width) - str_w - 15
rl.DrawText(display_str, val_x, lbl_y, 20, text_color)
}
} }
} }
@@ -202,21 +238,21 @@ update_main_menu :: proc(scene: ^Scene, input: Input_Event) {
if input.up_pressed { if input.up_pressed {
menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.down_pressed { if input.down_pressed {
menu.selected_index = (menu.selected_index + 1) % menu.count menu.selected_index = (menu.selected_index + 1) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.confirm_pressed { if input.confirm_pressed {
switch menu.selected_index { switch menu.selected_index {
case 0: case 0:
emit_event(Event{ type = .Game_Start }) emit_event(.Game_Start)
case 1: case 1:
emit_event(Event { type = .Game_Settings }) emit_event(.Game_Settings)
case 2: case 2:
emit_event(Event{ type = .Game_Quit }) emit_event(.Game_Quit)
} }
} }
} }
@@ -254,7 +290,8 @@ render_pause_menu :: proc(scene: Scene) {
menu, menu_ok := canvas.menu.? menu, menu_ok := canvas.menu.?
assert(menu_ok, "render_pause_menu: Canvas is missing a UI_Menu component") assert(menu_ok, "render_pause_menu: Canvas is missing a UI_Menu component")
rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 220}) // Semi-transparent overlay so gameplay stays visible behind menu
rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 200})
t_text: cstring = "PAUSE MENU" t_text: cstring = "PAUSE MENU"
t_width := rl.MeasureText(t_text, 40) t_width := rl.MeasureText(t_text, 40)
@@ -273,36 +310,39 @@ update_pause_menu :: proc(scene: ^Scene, input: Input_Event) {
if input.up_pressed { if input.up_pressed {
menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.down_pressed { if input.down_pressed {
menu.selected_index = (menu.selected_index + 1) % menu.count menu.selected_index = (menu.selected_index + 1) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.confirm_pressed { if input.confirm_pressed {
switch menu.selected_index { switch menu.selected_index {
case 0: case 0:
emit_event(Event{ type = .Game_Resume }) emit_event(.Game_Back)
case 1: case 1:
emit_event(Event { type = .Game_Settings }) emit_event(.Game_Settings)
case 2: case 2:
emit_event(Event{ type = .Game_Main_Menu }) emit_event(.Game_Main_Menu)
} }
} }
if input.pause_pressed { if input.pause_pressed {
emit_event(Event{ type = .Game_Resume }) emit_event(.Game_Back)
} }
} }
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Settings Menu // Settings Menu
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
make_settings_menu :: proc(audio: ^Audio_Manager) -> Scene { make_settings_menu :: proc(audio: ^Audio_Manager, current_res_label: cstring) -> Scene {
ui_items:[8]UI_Item = { ui_items:[8]UI_Item = {
0 = UI_Button{"Back"}, 0 = UI_Button{"Back"},
1 = UI_Button{"Resolution"}, 1 = UI_Selector{
label = "Resolution",
value_label = current_res_label,
},
2 = UI_Slider{ 2 = UI_Slider{
label = "Music Volume", label = "Music Volume",
value = &audio.user_music_vol, value = &audio.user_music_vol,
@@ -338,6 +378,7 @@ render_settings_menu :: proc(scene: Scene) {
menu, menu_ok := canvas.menu.? menu, menu_ok := canvas.menu.?
assert(menu_ok, "render_settings_menu: Canvas is missing a UI_Menu component") assert(menu_ok, "render_settings_menu: Canvas is missing a UI_Menu component")
// Dark semi-transparent overlay
rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 255}) rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 255})
t_text: cstring = "SETTINGS" t_text: cstring = "SETTINGS"
@@ -357,11 +398,11 @@ update_settings_menu :: proc(scene: ^Scene, input: Input_Event) {
if input.up_pressed { if input.up_pressed {
menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.down_pressed { if input.down_pressed {
menu.selected_index = (menu.selected_index + 1) % menu.count menu.selected_index = (menu.selected_index + 1) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
current_item := &menu.items[menu.selected_index] current_item := &menu.items[menu.selected_index]
@@ -371,22 +412,29 @@ update_settings_menu :: proc(scene: ^Scene, input: Input_Event) {
if input.confirm_pressed { if input.confirm_pressed {
switch menu.selected_index { switch menu.selected_index {
case 0: case 0:
emit_event(Event{ type = .Game_Main_Menu }) emit_event(.Game_Back)
}
}
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 })
} }
} }
case UI_Selector:
if input.left_pressed {
emit_event(Change_Resolution_Data{-1})
}
if input.right_pressed {
emit_event(Change_Resolution_Data{1})
}
case UI_Slider:
if input.left_pressed {
v.value^ = clamp(v.value^ - v.step, 0.0, 1.0)
emit_event(.UI_Select)
}
if input.right_pressed {
v.value^ = clamp(v.value^ + v.step, 0.0, 1.0)
emit_event(.UI_Select)
}
}
} }
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
@@ -417,12 +465,12 @@ make_game_over_overlay :: proc(winner_is_player: bool) -> Scene {
render_game_over :: proc(scene: Scene) { render_game_over :: proc(scene: Scene) {
canvas, canvas_ok := scene.canvas.? canvas, canvas_ok := scene.canvas.?
assert(canvas_ok, "update_game_over: Scene is missing a Canvas component") assert(canvas_ok, "render_game_over: Scene is missing a Canvas component")
menu, menu_ok := canvas.menu.? menu, menu_ok := canvas.menu.?
assert(menu_ok, "update_game_over: Canvas is missing a UI_Menu component") assert(menu_ok, "render_game_over: Canvas is missing a UI_Menu component")
rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 255}) rl.DrawRectangleRec(canvas.bounds, rl.Color{0, 0, 0, 220})
t_text: cstring = scene.winner_is_player ? "YOU WIN!" : "GAME OVER" t_text: cstring = scene.winner_is_player ? "YOU WIN!" : "GAME OVER"
t_width := rl.MeasureText(t_text, 40) t_width := rl.MeasureText(t_text, 40)
@@ -442,25 +490,25 @@ update_game_over :: proc(scene: ^Scene, input: Input_Event) {
if input.up_pressed { if input.up_pressed {
menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count menu.selected_index = (menu.selected_index - 1 + menu.count) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.down_pressed { if input.down_pressed {
menu.selected_index = (menu.selected_index + 1) % menu.count menu.selected_index = (menu.selected_index + 1) % menu.count
emit_event(Event{ type = .UI_Select }) emit_event(.UI_Select)
} }
if input.confirm_pressed { if input.confirm_pressed {
switch menu.selected_index { switch menu.selected_index {
case 0: case 0:
emit_event(Event{ type = .Game_Start }) emit_event(.Game_Start)
case 1: case 1:
emit_event(Event{ type = .Game_Main_Menu }) emit_event(.Game_Main_Menu)
} }
} }
} }
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
// Game Play Scene // Gameplay Scene
// ------------------------------------------------------------------------------ // ------------------------------------------------------------------------------
make_gameplay_scene :: proc() -> Scene { make_gameplay_scene :: proc() -> Scene {
return Scene{ return Scene{