Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34a52f2076 | ||
|
|
b66442f55f | ||
|
|
777e9c511f | ||
|
|
d78117441c | ||
|
|
3fb194c57d | ||
|
|
6b76ad6874 | ||
|
|
f6f8e99e6b | ||
|
|
fa21fd60b8 |
@@ -74,7 +74,6 @@ else
|
|||||||
echo "Building debug ($SRC_DIR -> $OUT_DIR/$OUT_NAME)..."
|
echo "Building debug ($SRC_DIR -> $OUT_DIR/$OUT_NAME)..."
|
||||||
odin build "$SRC_DIR" \
|
odin build "$SRC_DIR" \
|
||||||
-out:"$OUT_DIR/$OUT_NAME" \
|
-out:"$OUT_DIR/$OUT_NAME" \
|
||||||
-debug \
|
|
||||||
-define:DEBUG_MODE=true
|
-define:DEBUG_MODE=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+17421
-2237
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
|
|||||||
|
package game
|
||||||
|
|
||||||
|
import rl "vendor:raylib"
|
||||||
|
|
||||||
|
Fade_State :: enum {
|
||||||
|
None,
|
||||||
|
In,
|
||||||
|
Out,
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup sound IDs so gameplay code never
|
||||||
|
// touches raw Raylib sound handles
|
||||||
|
Sound_ID :: enum {
|
||||||
|
Ball_Collision,
|
||||||
|
Score_Goal,
|
||||||
|
UI_Select,
|
||||||
|
UI_Confirm,
|
||||||
|
}
|
||||||
|
|
||||||
|
Audio_Manager :: struct {
|
||||||
|
sounds: map[Sound_ID]rl.Sound,
|
||||||
|
music: rl.Music,
|
||||||
|
music_loaded: bool,
|
||||||
|
|
||||||
|
sfx_vol: f32, // User SFX volume setting
|
||||||
|
user_music_vol: f32, // User Music volume setting
|
||||||
|
current_music_vol: f32, // Live interpolated output volume
|
||||||
|
|
||||||
|
// Runtime Fade State
|
||||||
|
fade_state: Fade_State,
|
||||||
|
fade_elapsed_sec: f32,
|
||||||
|
fade_duration_sec: f32,
|
||||||
|
}
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
// Lifecycle procs
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
audio_init :: proc(audio: ^Audio_Manager) {
|
||||||
|
rl.InitAudioDevice()
|
||||||
|
audio.sounds = make(map[Sound_ID]rl.Sound)
|
||||||
|
|
||||||
|
// Load SFX
|
||||||
|
audio.sfx_vol = 0.5
|
||||||
|
audio.sounds[.UI_Select] = rl.LoadSound("assets/audio/ui/select.ogg")
|
||||||
|
audio.sounds[.Ball_Collision] = rl.LoadSound("assets/audio/gameplay/ballhit.ogg")
|
||||||
|
|
||||||
|
// Load Music Stream
|
||||||
|
audio.user_music_vol = 0.1
|
||||||
|
audio.current_music_vol = 0.0
|
||||||
|
audio.fade_state = .None
|
||||||
|
audio.music = rl.LoadMusicStream("assets/audio/gameplay/music/track_1.wav")
|
||||||
|
audio.music_loaded = true
|
||||||
|
rl.SetMusicVolume(audio.music, audio.current_music_vol)
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_update :: proc(audio: ^Audio_Manager, dt: f32) {
|
||||||
|
if !audio.music_loaded do return
|
||||||
|
|
||||||
|
// Process Fade if the audio state machine requires
|
||||||
|
if audio.fade_state != .None {
|
||||||
|
audio.fade_elapsed_sec += dt
|
||||||
|
// convert elapsed seconds over target seconds into a 0.0 -> 1.0 percentage
|
||||||
|
progress_pct := clamp(audio.fade_elapsed_sec / audio.fade_duration_sec, 0.0, 1.0)
|
||||||
|
|
||||||
|
#partial switch audio.fade_state {
|
||||||
|
case .In:
|
||||||
|
audio.current_music_vol = rl.Lerp(0.0, audio.user_music_vol, progress_pct)
|
||||||
|
// progress is 100% or more stop fading in music
|
||||||
|
if progress_pct >= 1.0 do audio.fade_state = .None
|
||||||
|
|
||||||
|
case .Out:
|
||||||
|
audio.current_music_vol = rl.Lerp(audio.user_music_vol, 0.0, progress_pct)
|
||||||
|
// progress is 100% or more stop fading out music and stop the stream
|
||||||
|
if progress_pct >= 1.0 {
|
||||||
|
audio.fade_state = .None
|
||||||
|
rl.StopMusicStream(audio.music)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// not fading, output matches the user setting directly
|
||||||
|
audio.current_music_vol = audio.user_music_vol
|
||||||
|
}
|
||||||
|
|
||||||
|
rl.SetMusicVolume(audio.music, audio.current_music_vol)
|
||||||
|
|
||||||
|
// keep audio stream buffer fed
|
||||||
|
if rl.IsMusicStreamPlaying(audio.music) {
|
||||||
|
rl.UpdateMusicStream(audio.music)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_manager_quit :: proc(audio: ^Audio_Manager) {
|
||||||
|
for _, sound in audio.sounds {
|
||||||
|
rl.UnloadSound(sound)
|
||||||
|
}
|
||||||
|
delete(audio.sounds)
|
||||||
|
rl.UnloadMusicStream(audio.music)
|
||||||
|
rl.CloseAudioDevice()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
// Utilities
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
audio_play_music :: proc(audio: ^Audio_Manager) {
|
||||||
|
if audio.music_loaded {
|
||||||
|
// Only trigger start if it isn't playing already
|
||||||
|
if !rl.IsMusicStreamPlaying(audio.music) {
|
||||||
|
rl.PlayMusicStream(audio.music)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_stop_music :: proc(audio: ^Audio_Manager) {
|
||||||
|
if audio.music_loaded && rl.IsMusicStreamPlaying(audio.music) {
|
||||||
|
rl.StopMusicStream(audio.music)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_play_sfx :: proc(audio: ^Audio_Manager, id: Sound_ID) {
|
||||||
|
if sound, ok := audio.sounds[id]; ok {
|
||||||
|
rl.SetSoundVolume(sound, audio.sfx_vol)
|
||||||
|
rl.PlaySound(sound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
// Audio Fading
|
||||||
|
// ------------------------------------------------------------------------------
|
||||||
|
audio_fade_in :: proc(audio: ^Audio_Manager, duration: f32 = 1.0) {
|
||||||
|
if !audio.music_loaded do return
|
||||||
|
|
||||||
|
if !rl.IsMusicStreamPlaying(audio.music) {
|
||||||
|
rl.PlayMusicStream(audio.music)
|
||||||
|
}
|
||||||
|
|
||||||
|
audio.fade_state = .In
|
||||||
|
audio.fade_duration_sec = max(0.001, duration)
|
||||||
|
audio.fade_elapsed_sec = 0.0
|
||||||
|
audio.current_music_vol = 0.0
|
||||||
|
rl.SetMusicVolume(audio.music, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_fade_out :: proc(audio: ^Audio_Manager, duration: f32 = 1.0) {
|
||||||
|
if !audio.music_loaded || !rl.IsMusicStreamPlaying(audio.music) do return
|
||||||
|
|
||||||
|
audio.fade_state = .Out
|
||||||
|
audio.fade_duration_sec = max(0.001, duration)
|
||||||
|
audio.fade_elapsed_sec = 0.0
|
||||||
|
}
|
||||||
@@ -77,7 +77,9 @@ check_ball_paddle_collision :: proc(ball: Entity, paddle: Entity) -> bool {
|
|||||||
width = paddle.size.x,
|
width = paddle.size.x,
|
||||||
height = paddle.size.y,
|
height = paddle.size.y,
|
||||||
}
|
}
|
||||||
return 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 })
|
||||||
|
return collided
|
||||||
}
|
}
|
||||||
|
|
||||||
increment_score :: proc(score_side: ^int, max_score: int, player: bool) {
|
increment_score :: proc(score_side: ^int, max_score: int, player: bool) {
|
||||||
|
|||||||
@@ -2,12 +2,19 @@ package game
|
|||||||
|
|
||||||
// all possible game events
|
// all possible game events
|
||||||
Event_Type :: enum {
|
Event_Type :: enum {
|
||||||
|
// game state
|
||||||
Game_Start,
|
Game_Start,
|
||||||
Game_Over,
|
Game_Over,
|
||||||
Game_Quit,
|
Game_Quit,
|
||||||
Game_Pause,
|
Game_Pause,
|
||||||
Game_Resume,
|
Game_Resume,
|
||||||
Game_Main_Menu,
|
Game_Main_Menu,
|
||||||
|
Game_Settings,
|
||||||
|
|
||||||
|
// ui events
|
||||||
|
UI_Select,
|
||||||
|
|
||||||
|
// gameplay
|
||||||
Score_Change,
|
Score_Change,
|
||||||
Ball_Collision,
|
Ball_Collision,
|
||||||
}
|
}
|
||||||
@@ -27,4 +34,42 @@ emit_event :: proc(event: Event) {
|
|||||||
|
|
||||||
flush_events :: proc() {
|
flush_events :: proc() {
|
||||||
clear(&GLOBAL_EVENTS)
|
clear(&GLOBAL_EVENTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
process_events :: proc(game: ^Game) {
|
||||||
|
for event in GLOBAL_EVENTS {
|
||||||
|
#partial switch event.type {
|
||||||
|
case .Game_Start:
|
||||||
|
game.overlay_scene = nil
|
||||||
|
game.scene = make_gameplay_scene()
|
||||||
|
audio_fade_in(&game.audio, duration = 1.5)
|
||||||
|
|
||||||
|
case .Game_Pause:
|
||||||
|
game.overlay_scene = make_pause_overlay()
|
||||||
|
|
||||||
|
case .Game_Resume:
|
||||||
|
game.overlay_scene = nil
|
||||||
|
|
||||||
|
case .Game_Main_Menu:
|
||||||
|
game.overlay_scene = nil
|
||||||
|
game.scene = make_main_menu()
|
||||||
|
audio_fade_out(&game.audio, duration = 1.0)
|
||||||
|
|
||||||
|
case .Game_Settings:
|
||||||
|
game.overlay_scene = nil
|
||||||
|
game.overlay_scene = make_settings_menu(&game.audio)
|
||||||
|
|
||||||
|
case .Game_Over:
|
||||||
|
game.overlay_scene = make_game_over_overlay(event.winner_is_player)
|
||||||
|
|
||||||
|
case .Game_Quit:
|
||||||
|
game.should_close = true
|
||||||
|
|
||||||
|
case .UI_Select:
|
||||||
|
audio_play_sfx(&game.audio, .UI_Select)
|
||||||
|
|
||||||
|
case .Ball_Collision:
|
||||||
|
audio_play_sfx(&game.audio, .Ball_Collision)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ Game :: struct {
|
|||||||
scene: Scene,
|
scene: Scene,
|
||||||
overlay_scene: Maybe(Scene),
|
overlay_scene: Maybe(Scene),
|
||||||
should_close: bool,
|
should_close: bool,
|
||||||
|
audio: Audio_Manager,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
@@ -22,6 +23,9 @@ game_init :: proc(game: ^Game) {
|
|||||||
rl.InitWindow(WINDOW_W, WINDOW_H, WINDOW_TITLE)
|
rl.InitWindow(WINDOW_W, WINDOW_H, WINDOW_TITLE)
|
||||||
rl.SetTargetFPS(60)
|
rl.SetTargetFPS(60)
|
||||||
|
|
||||||
|
// init any global managers
|
||||||
|
audio_init(&game.audio)
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
||||||
@@ -43,6 +47,9 @@ game_update :: proc(game: ^Game) {
|
|||||||
scene_update(&game.scene, game, input, dt)
|
scene_update(&game.scene, game, input, dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// background music buffer streaming
|
||||||
|
audio_update(&game.audio, dt)
|
||||||
|
|
||||||
// flush any events triggered in this loop
|
// flush any events triggered in this loop
|
||||||
flush_events()
|
flush_events()
|
||||||
}
|
}
|
||||||
@@ -72,6 +79,10 @@ game_render :: proc(game: ^Game) {
|
|||||||
game_quit :: proc(game: ^Game) {
|
game_quit :: proc(game: ^Game) {
|
||||||
delete(GLOBAL_EVENTS)
|
delete(GLOBAL_EVENTS)
|
||||||
rl.UnloadRenderTexture(game.viewport)
|
rl.UnloadRenderTexture(game.viewport)
|
||||||
|
|
||||||
|
// cleanup global managers
|
||||||
|
audio_manager_quit(&game.audio)
|
||||||
|
|
||||||
rl.CloseWindow()
|
rl.CloseWindow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ Input_Event :: struct {
|
|||||||
// single frame actions UI and triggers
|
// single frame actions UI and triggers
|
||||||
up_pressed: bool,
|
up_pressed: bool,
|
||||||
down_pressed: bool,
|
down_pressed: bool,
|
||||||
|
left_pressed: bool,
|
||||||
|
right_pressed: bool,
|
||||||
confirm_pressed: bool,
|
confirm_pressed: bool,
|
||||||
pause_pressed: bool,
|
pause_pressed: bool,
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,8 @@ poll_input :: proc() -> Input_Event {
|
|||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
input_event.up_pressed = rl.IsKeyPressed(.W) || rl.IsKeyPressed(.UP)
|
input_event.up_pressed = rl.IsKeyPressed(.W) || rl.IsKeyPressed(.UP)
|
||||||
input_event.down_pressed = rl.IsKeyPressed(.S) || rl.IsKeyPressed(.DOWN)
|
input_event.down_pressed = rl.IsKeyPressed(.S) || rl.IsKeyPressed(.DOWN)
|
||||||
|
input_event.left_pressed = rl.IsKeyPressed(.A) || rl.IsKeyPressed(.LEFT)
|
||||||
|
input_event.right_pressed = rl.IsKeyPressed(.D) || rl.IsKeyPressed(.RIGHT)
|
||||||
input_event.confirm_pressed = rl.IsKeyPressed(.ENTER) || rl.IsKeyPressed(.SPACE)
|
input_event.confirm_pressed = rl.IsKeyPressed(.ENTER) || rl.IsKeyPressed(.SPACE)
|
||||||
input_event.pause_pressed = rl.IsKeyPressed(.P)
|
input_event.pause_pressed = rl.IsKeyPressed(.P)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ Level :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init_level :: proc() -> Level {
|
init_level :: proc() -> Level {
|
||||||
// positions work off viewport resolution, independent of target display size
|
// positions work off viewport resolution,
|
||||||
|
// independent of target display size
|
||||||
// 10% padding around the field
|
// 10% padding around the field
|
||||||
pad_x := f32(VIEWPORT_W) * 0.10
|
pad_x := f32(VIEWPORT_W) * 0.10
|
||||||
pad_y := f32(VIEWPORT_H) * 0.10
|
pad_y := f32(VIEWPORT_H) * 0.10
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ main :: proc() {
|
|||||||
defer mem.tracking_allocator_destroy(&track)
|
defer mem.tracking_allocator_destroy(&track)
|
||||||
context.allocator = mem.tracking_allocator(&track)
|
context.allocator = mem.tracking_allocator(&track)
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
// Game Loop
|
// Game Loop
|
||||||
|
|||||||
+215
-80
@@ -9,6 +9,7 @@ Scene_Type :: enum {
|
|||||||
Main_Menu,
|
Main_Menu,
|
||||||
Gameplay,
|
Gameplay,
|
||||||
Pause_Menu,
|
Pause_Menu,
|
||||||
|
Settings_Menu,
|
||||||
Game_Over,
|
Game_Over,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +20,55 @@ Scene :: struct {
|
|||||||
winner_is_player: bool,
|
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
|
// UI Definitions
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
@@ -30,8 +80,23 @@ Canvas :: struct {
|
|||||||
menu: Maybe(UI_Menu),
|
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 {
|
UI_Menu :: struct {
|
||||||
items: [8]cstring,
|
items: [8]UI_Item,
|
||||||
count: int,
|
count: int,
|
||||||
selected_index: int,
|
selected_index: int,
|
||||||
}
|
}
|
||||||
@@ -53,16 +118,35 @@ list_menu_items :: proc(canvas: Canvas, menu: UI_Menu) {
|
|||||||
item := menu.items[i]
|
item := menu.items[i]
|
||||||
item_rec := get_canvas_item_rec(canvas, i, top_margin = 150.0)
|
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)
|
is_selected := (i == menu.selected_index)
|
||||||
border_color := is_selected ? rl.ORANGE : rl.DARKGRAY
|
border_color := is_selected ? rl.ORANGE : rl.DARKGRAY
|
||||||
text_color := is_selected ? rl.ORANGE : rl.WHITE
|
text_color := is_selected ? rl.ORANGE : rl.WHITE
|
||||||
|
|
||||||
rl.DrawRectangleLinesEx(item_rec, 3, border_color)
|
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
|
// Main Menu
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
make_main_menu :: proc() -> Scene {
|
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{
|
return Scene{
|
||||||
type = .Main_Menu,
|
type = .Main_Menu,
|
||||||
canvas = Canvas{
|
canvas = Canvas{
|
||||||
@@ -78,7 +168,7 @@ make_main_menu :: proc() -> Scene {
|
|||||||
item_height = 40,
|
item_height = 40,
|
||||||
item_spaceing = 15,
|
item_spaceing = 15,
|
||||||
menu = UI_Menu{
|
menu = UI_Menu{
|
||||||
items = {0 = "Start Game", 1 = "Options", 2 = "Quit"},
|
items = ui_items,
|
||||||
count = 3,
|
count = 3,
|
||||||
selected_index = 0,
|
selected_index = 0,
|
||||||
},
|
},
|
||||||
@@ -112,9 +202,11 @@ 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 })
|
||||||
}
|
}
|
||||||
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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.confirm_pressed {
|
if input.confirm_pressed {
|
||||||
@@ -122,7 +214,7 @@ update_main_menu :: proc(scene: ^Scene, input: Input_Event) {
|
|||||||
case 0:
|
case 0:
|
||||||
emit_event(Event{ type = .Game_Start })
|
emit_event(Event{ type = .Game_Start })
|
||||||
case 1:
|
case 1:
|
||||||
// @todo open options
|
emit_event(Event { type = .Game_Settings })
|
||||||
case 2:
|
case 2:
|
||||||
emit_event(Event{ type = .Game_Quit })
|
emit_event(Event{ type = .Game_Quit })
|
||||||
}
|
}
|
||||||
@@ -133,6 +225,12 @@ update_main_menu :: proc(scene: ^Scene, input: Input_Event) {
|
|||||||
// Pause Menu
|
// Pause Menu
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
make_pause_overlay :: proc() -> Scene {
|
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{
|
return Scene{
|
||||||
type = .Pause_Menu,
|
type = .Pause_Menu,
|
||||||
canvas = Canvas{
|
canvas = Canvas{
|
||||||
@@ -141,7 +239,7 @@ make_pause_overlay :: proc() -> Scene {
|
|||||||
item_height = 40,
|
item_height = 40,
|
||||||
item_spaceing = 15,
|
item_spaceing = 15,
|
||||||
menu = UI_Menu{
|
menu = UI_Menu{
|
||||||
items = {0 = "Resume Game", 1 = "Options", 2 = "Main Menu"},
|
items = ui_items,
|
||||||
count = 3,
|
count = 3,
|
||||||
selected_index = 0,
|
selected_index = 0,
|
||||||
},
|
},
|
||||||
@@ -175,9 +273,11 @@ 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 })
|
||||||
}
|
}
|
||||||
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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.confirm_pressed {
|
if input.confirm_pressed {
|
||||||
@@ -185,7 +285,7 @@ update_pause_menu :: proc(scene: ^Scene, input: Input_Event) {
|
|||||||
case 0:
|
case 0:
|
||||||
emit_event(Event{ type = .Game_Resume })
|
emit_event(Event{ type = .Game_Resume })
|
||||||
case 1:
|
case 1:
|
||||||
// @todo open options
|
emit_event(Event { type = .Game_Settings })
|
||||||
case 2:
|
case 2:
|
||||||
emit_event(Event{ type = .Game_Main_Menu })
|
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
|
// Game Over Menu
|
||||||
// ------------------------------------------------------------------------------
|
// ------------------------------------------------------------------------------
|
||||||
make_game_over_overlay :: proc(winner_is_player: bool) -> Scene {
|
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{
|
return Scene{
|
||||||
type = .Game_Over,
|
type = .Game_Over,
|
||||||
winner_is_player = winner_is_player,
|
winner_is_player = winner_is_player,
|
||||||
@@ -209,7 +407,7 @@ make_game_over_overlay :: proc(winner_is_player: bool) -> Scene {
|
|||||||
item_height = 40,
|
item_height = 40,
|
||||||
item_spaceing = 15,
|
item_spaceing = 15,
|
||||||
menu = UI_Menu{
|
menu = UI_Menu{
|
||||||
items = {0 = "Restart Game", 1 = "Main Menu"},
|
items = ui_items,
|
||||||
count = 2,
|
count = 2,
|
||||||
selected_index = 0,
|
selected_index = 0,
|
||||||
},
|
},
|
||||||
@@ -219,10 +417,10 @@ 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, "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.?
|
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})
|
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) {
|
update_game_over :: proc(scene: ^Scene, input: Input_Event) {
|
||||||
canvas, canvas_ok := &scene.canvas.?
|
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.?
|
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 {
|
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 })
|
||||||
}
|
}
|
||||||
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 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.confirm_pressed {
|
if input.confirm_pressed {
|
||||||
@@ -267,69 +467,4 @@ make_gameplay_scene :: proc() -> Scene {
|
|||||||
type = .Gameplay,
|
type = .Gameplay,
|
||||||
level = init_level(),
|
level = init_level(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------------
|
|
||||||
// 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 .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 .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)
|
|
||||||
}
|
|
||||||
|
|
||||||
for event in GLOBAL_EVENTS {
|
|
||||||
#partial switch event.type {
|
|
||||||
case .Game_Start:
|
|
||||||
game.overlay_scene = nil
|
|
||||||
game.scene = make_gameplay_scene()
|
|
||||||
|
|
||||||
case .Game_Pause:
|
|
||||||
game.overlay_scene = make_pause_overlay()
|
|
||||||
|
|
||||||
case .Game_Resume:
|
|
||||||
game.overlay_scene = nil
|
|
||||||
|
|
||||||
case .Game_Main_Menu:
|
|
||||||
game.overlay_scene = nil
|
|
||||||
game.scene = make_main_menu()
|
|
||||||
|
|
||||||
case .Game_Over:
|
|
||||||
game.overlay_scene = make_game_over_overlay(event.winner_is_player)
|
|
||||||
|
|
||||||
case .Game_Quit:
|
|
||||||
game.should_close = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user