improve settings menu
This commit is contained in:
parent
ad099d79b5
commit
1e7078c168
@ -11,6 +11,9 @@ cam : Camera = .{
|
|||||||
};
|
};
|
||||||
|
|
||||||
#scope_export
|
#scope_export
|
||||||
|
game_engine_config :: () {
|
||||||
|
}
|
||||||
|
|
||||||
game_init :: () {
|
game_init :: () {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -147,6 +147,9 @@ init_after_core :: () {
|
|||||||
|
|
||||||
audio_init();
|
audio_init();
|
||||||
|
|
||||||
|
// Let the game configure engine settings before full init.
|
||||||
|
game_engine_config();
|
||||||
|
|
||||||
// We want to do this last.
|
// We want to do this last.
|
||||||
game_init();
|
game_init();
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
#scope_file
|
#scope_file
|
||||||
|
|
||||||
TRANSITION_SPEED :: 3.0;
|
Random :: #import "Random";
|
||||||
|
|
||||||
|
TRANSITION_SPEED :: 7.0;
|
||||||
|
MAX_BLOBS :: 32;
|
||||||
|
|
||||||
Settings_Page :: enum {
|
Settings_Page :: enum {
|
||||||
MAIN;
|
MAIN;
|
||||||
|
SETTINGS;
|
||||||
AUDIO;
|
AUDIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -14,11 +18,27 @@ Settings_State :: struct {
|
|||||||
cursor : s32 = 0;
|
cursor : s32 = 0;
|
||||||
title_font : *Font;
|
title_font : *Font;
|
||||||
item_font : *Font;
|
item_font : *Font;
|
||||||
|
|
||||||
|
blobs : [MAX_BLOBS]Blob;
|
||||||
|
blob_count : s32 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Blob :: struct {
|
||||||
|
cx : float;
|
||||||
|
cy : float;
|
||||||
|
radius : float;
|
||||||
|
r, g, b: float;
|
||||||
|
freq : float;
|
||||||
|
drift_x: float;
|
||||||
|
drift_y: float;
|
||||||
|
phase : float;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_settings : Settings_State;
|
g_settings : Settings_State;
|
||||||
|
g_settings_config : Settings_Menu_Config;
|
||||||
|
|
||||||
MAIN_ITEMS :: string.["Audio"];
|
MAIN_ITEMS :: string.["Resume", "Settings", "Exit"];
|
||||||
|
SETTINGS_ITEMS :: string.["Audio"];
|
||||||
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
||||||
|
|
||||||
audio_get :: (i: s32) -> float {
|
audio_get :: (i: s32) -> float {
|
||||||
@ -36,17 +56,86 @@ audio_set :: (i: s32, v: float) {
|
|||||||
|
|
||||||
page_count :: () -> s32 {
|
page_count :: () -> s32 {
|
||||||
if g_settings.page == .MAIN return MAIN_ITEMS.count;
|
if g_settings.page == .MAIN return MAIN_ITEMS.count;
|
||||||
|
if g_settings.page == .SETTINGS return SETTINGS_ITEMS.count;
|
||||||
if g_settings.page == .AUDIO return AUDIO_LABELS.count;
|
if g_settings.page == .AUDIO return AUDIO_LABELS.count;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rand_range :: (lo: float, hi: float) -> float {
|
||||||
|
return lo + Random.random_get_zero_to_one() * (hi - lo);
|
||||||
|
}
|
||||||
|
|
||||||
|
generate_blobs :: () {
|
||||||
|
cfg := *g_settings_config.phosphenes;
|
||||||
|
count := clamp(cfg.blob_count, 0, MAX_BLOBS);
|
||||||
|
g_settings.blob_count = count;
|
||||||
|
|
||||||
|
for i: 0..count-1 {
|
||||||
|
blob := *g_settings.blobs[i];
|
||||||
|
|
||||||
|
blob.cx = rand_range(0.05, 0.95);
|
||||||
|
blob.cy = rand_range(0.15, 0.85);
|
||||||
|
blob.radius = rand_range(cfg.radius_min, cfg.radius_max);
|
||||||
|
blob.freq = rand_range(cfg.freq_min, cfg.freq_max);
|
||||||
|
blob.drift_x = rand_range(cfg.drift_min, cfg.drift_max);
|
||||||
|
blob.drift_y = rand_range(cfg.drift_min, cfg.drift_max);
|
||||||
|
blob.phase = rand_range(0.0, 6.28);
|
||||||
|
|
||||||
|
if cfg.palette.count > 0 {
|
||||||
|
idx := cast(s32)(Random.random_get_zero_to_one() * cast(float)(cfg.palette.count - 1) + 0.5);
|
||||||
|
idx = clamp(idx, 0, cast(s32)(cfg.palette.count - 1));
|
||||||
|
base := cfg.palette[idx];
|
||||||
|
blob.r = clamp(base.x + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
||||||
|
blob.g = clamp(base.y + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
||||||
|
blob.b = clamp(base.z + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
||||||
|
} else {
|
||||||
|
blob.r = rand_range(0.1, 0.5);
|
||||||
|
blob.g = rand_range(0.1, 0.5);
|
||||||
|
blob.b = rand_range(0.1, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#scope_export
|
#scope_export
|
||||||
|
|
||||||
|
Phosphene_Config :: struct {
|
||||||
|
blob_count : s32 = 12;
|
||||||
|
alpha : float = 0.06;
|
||||||
|
layers : s32 = 4;
|
||||||
|
|
||||||
|
radius_min : float = 0.08;
|
||||||
|
radius_max : float = 0.28;
|
||||||
|
freq_min : float = 0.1;
|
||||||
|
freq_max : float = 0.4;
|
||||||
|
drift_min : float = 0.02;
|
||||||
|
drift_max : float = 0.07;
|
||||||
|
|
||||||
|
color_jitter : float = 0.1;
|
||||||
|
palette : []Vector3;
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings_Menu_Config :: struct {
|
||||||
|
game_title : string = "TRUENO";
|
||||||
|
credits : string = "Trueno engine by Tuomas Katajisto 2026";
|
||||||
|
bg_color : Vector4 = .{0.04, 0.04, 0.06, 1.0};
|
||||||
|
title_color : Vector4 = .{1.0, 1.0, 1.0, 1.0};
|
||||||
|
item_color : Vector4 = .{0.45, 0.45, 0.45, 1.0};
|
||||||
|
selected_color : Vector4 = .{1.0, 0.95, 0.65, 1.0};
|
||||||
|
hint_color : Vector4 = .{0.45, 0.45, 0.45, 1.0};
|
||||||
|
credits_color : Vector4 = .{0.3, 0.3, 0.3, 1.0};
|
||||||
|
phosphenes : Phosphene_Config;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_settings_config :: (config: Settings_Menu_Config) {
|
||||||
|
g_settings_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
settings_menu_blocks_game :: () -> bool {
|
settings_menu_blocks_game :: () -> bool {
|
||||||
return g_settings.open;
|
return g_settings.open;
|
||||||
}
|
}
|
||||||
|
|
||||||
init_settings_menu :: () {
|
init_settings_menu :: () {
|
||||||
|
// TODO: scale font sizes with screen resolution
|
||||||
g_settings.title_font = get_font_at_size(60);
|
g_settings.title_font = get_font_at_size(60);
|
||||||
g_settings.item_font = get_font_at_size(30);
|
g_settings.item_font = get_font_at_size(30);
|
||||||
}
|
}
|
||||||
@ -59,7 +148,11 @@ tick_settings_menu :: () {
|
|||||||
g_settings.open = true;
|
g_settings.open = true;
|
||||||
g_settings.page = .MAIN;
|
g_settings.page = .MAIN;
|
||||||
g_settings.cursor = 0;
|
g_settings.cursor = 0;
|
||||||
} else if g_settings.page != .MAIN {
|
generate_blobs();
|
||||||
|
} else if g_settings.page == .AUDIO {
|
||||||
|
g_settings.page = .SETTINGS;
|
||||||
|
g_settings.cursor = 0;
|
||||||
|
} else if g_settings.page == .SETTINGS {
|
||||||
g_settings.page = .MAIN;
|
g_settings.page = .MAIN;
|
||||||
g_settings.cursor = 0;
|
g_settings.cursor = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -89,6 +182,18 @@ tick_settings_menu :: () {
|
|||||||
if down then g_settings.cursor = (g_settings.cursor + 1) % count;
|
if down then g_settings.cursor = (g_settings.cursor + 1) % count;
|
||||||
|
|
||||||
if g_settings.page == .MAIN && enter {
|
if g_settings.page == .MAIN && enter {
|
||||||
|
if g_settings.cursor == 0 {
|
||||||
|
// Resume
|
||||||
|
g_settings.open = false;
|
||||||
|
} else if g_settings.cursor == 1 {
|
||||||
|
// Settings
|
||||||
|
g_settings.page = .SETTINGS;
|
||||||
|
g_settings.cursor = 0;
|
||||||
|
} else if g_settings.cursor == 2 {
|
||||||
|
// Exit
|
||||||
|
sapp_request_quit();
|
||||||
|
}
|
||||||
|
} else if g_settings.page == .SETTINGS && enter {
|
||||||
if g_settings.cursor == 0 {
|
if g_settings.cursor == 0 {
|
||||||
g_settings.page = .AUDIO;
|
g_settings.page = .AUDIO;
|
||||||
g_settings.cursor = 0;
|
g_settings.cursor = 0;
|
||||||
@ -112,52 +217,61 @@ draw_settings_menu :: (theme: *GR.Overall_Theme) {
|
|||||||
half_h := fh * 0.5;
|
half_h := fh * 0.5;
|
||||||
bar_h := t * half_h;
|
bar_h := t * half_h;
|
||||||
|
|
||||||
bg := Vector4.{0.04, 0.04, 0.06, 1.0};
|
cfg := *g_settings_config;
|
||||||
|
bg := cfg.bg_color;
|
||||||
set_shader_for_color();
|
set_shader_for_color();
|
||||||
|
|
||||||
// Top eyelid sliding down
|
|
||||||
immediate_quad(.{0, 0}, .{fw, 0}, .{fw, bar_h}, .{0, bar_h}, bg);
|
immediate_quad(.{0, 0}, .{fw, 0}, .{fw, bar_h}, .{0, bar_h}, bg);
|
||||||
immediate_flush();
|
immediate_flush();
|
||||||
|
|
||||||
// Bottom eyelid sliding up
|
|
||||||
bottom_y := fh - bar_h;
|
bottom_y := fh - bar_h;
|
||||||
immediate_quad(.{0, bottom_y}, .{fw, bottom_y}, .{fw, fh}, .{0, fh}, bg);
|
immediate_quad(.{0, bottom_y}, .{fw, bottom_y}, .{fw, fh}, .{0, fh}, bg);
|
||||||
immediate_flush();
|
immediate_flush();
|
||||||
|
|
||||||
// Content fades in during the last quarter of the transition
|
if t > 0.5 {
|
||||||
|
phosphene_fade := clamp((t - 0.5) / 0.5, 0.0, 1.0);
|
||||||
|
time := cast(float) get_time();
|
||||||
|
draw_phosphenes(fw, fh, bar_h, bottom_y, time, phosphene_fade);
|
||||||
|
}
|
||||||
|
|
||||||
content_alpha := clamp((t - 0.75) / 0.25, 0.0, 1.0);
|
content_alpha := clamp((t - 0.75) / 0.25, 0.0, 1.0);
|
||||||
if content_alpha < 0.01 then return;
|
if content_alpha < 0.01 then return;
|
||||||
|
|
||||||
white := Vector4.{1.0, 1.0, 1.0, content_alpha};
|
apply_alpha :: (base: Vector4, a: float) -> Vector4 {
|
||||||
dim := Vector4.{0.45, 0.45, 0.45, content_alpha};
|
return .{base.x, base.y, base.z, base.w * a};
|
||||||
selected := Vector4.{1.0, 0.95, 0.65, content_alpha};
|
}
|
||||||
|
|
||||||
// Game title
|
title_col := apply_alpha(cfg.title_color, content_alpha);
|
||||||
prepare_text(g_settings.title_font, "BEACHBALL");
|
item_col := apply_alpha(cfg.item_color, content_alpha);
|
||||||
|
sel_col := apply_alpha(cfg.selected_color, content_alpha);
|
||||||
|
hint_col := apply_alpha(cfg.hint_color, content_alpha);
|
||||||
|
credits_col := apply_alpha(cfg.credits_color, content_alpha);
|
||||||
|
|
||||||
|
prepare_text(g_settings.title_font, cfg.game_title);
|
||||||
title_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
title_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||||
draw_prepared_text(g_settings.title_font, xx title_x, xx (fh * 0.22), white);
|
draw_prepared_text(g_settings.title_font, xx title_x, xx (fh * 0.22), title_col);
|
||||||
|
|
||||||
item_h := cast(float)(g_settings.item_font.character_height) * 1.7;
|
item_h := cast(float)(g_settings.item_font.character_height) * 1.7;
|
||||||
|
|
||||||
if g_settings.page == .MAIN {
|
if g_settings.page == .MAIN {
|
||||||
total_h := MAIN_ITEMS.count * item_h;
|
draw_item_list(MAIN_ITEMS, item_h, fw, fh, item_col, sel_col);
|
||||||
start_y := half_h - total_h * 0.5;
|
draw_hint("Up/Down navigate Enter select", fw, fh, hint_col);
|
||||||
for i: 0..MAIN_ITEMS.count-1 {
|
|
||||||
col := ifx cast(s32)i == g_settings.cursor then selected else dim;
|
if cfg.credits.count > 0 {
|
||||||
prepare_text(g_settings.item_font, MAIN_ITEMS[i]);
|
prepare_text(g_settings.item_font, cfg.credits);
|
||||||
x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
cx := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||||
draw_prepared_text(g_settings.item_font, xx x, xx (start_y + cast(float)i * item_h), col);
|
draw_prepared_text(g_settings.item_font, xx cx, xx (fh * 0.92), credits_col);
|
||||||
}
|
}
|
||||||
hint_str := "↑↓ navigate Enter select";
|
|
||||||
prepare_text(g_settings.item_font, hint_str);
|
} else if g_settings.page == .SETTINGS {
|
||||||
hint_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
draw_item_list(SETTINGS_ITEMS, item_h, fw, fh, item_col, sel_col);
|
||||||
draw_prepared_text(g_settings.item_font, xx hint_x, xx (fh * 0.82), dim);
|
draw_hint("Up/Down navigate Enter select Esc back", fw, fh, hint_col);
|
||||||
|
|
||||||
} else if g_settings.page == .AUDIO {
|
} else if g_settings.page == .AUDIO {
|
||||||
total_h := AUDIO_LABELS.count * item_h;
|
total_h := AUDIO_LABELS.count * item_h;
|
||||||
start_y := half_h - total_h * 0.5;
|
start_y := fh * 0.5 - total_h * 0.5;
|
||||||
for i: 0..AUDIO_LABELS.count-1 {
|
for i: 0..AUDIO_LABELS.count-1 {
|
||||||
col := ifx cast(s32)i == g_settings.cursor then selected else dim;
|
col := ifx cast(s32)i == g_settings.cursor then sel_col else item_col;
|
||||||
pct := cast(s32)(audio_get(cast(s32)i) * 100.0 + 0.5);
|
pct := cast(s32)(audio_get(cast(s32)i) * 100.0 + 0.5);
|
||||||
bar := make_volume_bar(pct, cast(s32)i == g_settings.cursor);
|
bar := make_volume_bar(pct, cast(s32)i == g_settings.cursor);
|
||||||
label := tprint("% %", AUDIO_LABELS[i], bar);
|
label := tprint("% %", AUDIO_LABELS[i], bar);
|
||||||
@ -165,25 +279,78 @@ draw_settings_menu :: (theme: *GR.Overall_Theme) {
|
|||||||
x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||||
draw_prepared_text(g_settings.item_font, xx x, xx (start_y + cast(float)i * item_h), col);
|
draw_prepared_text(g_settings.item_font, xx x, xx (start_y + cast(float)i * item_h), col);
|
||||||
}
|
}
|
||||||
hint_str := "↑↓ navigate ← → adjust Esc back";
|
draw_hint("Up/Down navigate Left/Right adjust Esc back", fw, fh, hint_col);
|
||||||
prepare_text(g_settings.item_font, hint_str);
|
|
||||||
hint_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
|
||||||
draw_prepared_text(g_settings.item_font, xx hint_x, xx (fh * 0.82), dim);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#scope_file
|
#scope_file
|
||||||
|
|
||||||
|
draw_item_list :: (items: []string, item_h: float, fw: float, fh: float, item_col: Vector4, sel_col: Vector4) {
|
||||||
|
total_h := items.count * item_h;
|
||||||
|
start_y := fh * 0.5 - total_h * 0.5;
|
||||||
|
for i: 0..items.count-1 {
|
||||||
|
col := ifx cast(s32)i == g_settings.cursor then sel_col else item_col;
|
||||||
|
prepare_text(g_settings.item_font, items[i]);
|
||||||
|
x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||||
|
draw_prepared_text(g_settings.item_font, xx x, xx (start_y + cast(float)i * item_h), col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_hint :: (text: string, fw: float, fh: float, color: Vector4) {
|
||||||
|
prepare_text(g_settings.item_font, text);
|
||||||
|
hint_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||||
|
draw_prepared_text(g_settings.item_font, xx hint_x, xx (fh * 0.82), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_phosphenes :: (fw: float, fh: float, bar_h: float, bottom_y: float, time: float, fade: float) {
|
||||||
|
if g_settings.blob_count < 1 then return;
|
||||||
|
|
||||||
|
set_shader_for_color();
|
||||||
|
|
||||||
|
max_alpha := g_settings_config.phosphenes.alpha;
|
||||||
|
num_layers := g_settings_config.phosphenes.layers;
|
||||||
|
if num_layers < 1 then num_layers = 4;
|
||||||
|
|
||||||
|
for i: 0..g_settings.blob_count-1 {
|
||||||
|
blob := g_settings.blobs[i];
|
||||||
|
|
||||||
|
// Smooth, gentle pulse — stays in 0.6..1.0 range to avoid harsh pop
|
||||||
|
pulse := 0.5 + 0.5 * sin(time * blob.freq * 6.28 + blob.phase);
|
||||||
|
alpha := fade * max_alpha * (0.6 + 0.4 * pulse);
|
||||||
|
|
||||||
|
cx := (blob.cx + sin(time * 0.3 + blob.phase) * blob.drift_x) * fw;
|
||||||
|
cy := (blob.cy + cos(time * 0.2 + blob.phase * 1.3) * blob.drift_y) * fh;
|
||||||
|
|
||||||
|
blob_r := blob.radius * fh;
|
||||||
|
|
||||||
|
// Draw concentric layers for soft falloff
|
||||||
|
for li: 0..num_layers-1 {
|
||||||
|
frac := cast(float)(num_layers - li) / cast(float)num_layers;
|
||||||
|
r := blob_r * frac;
|
||||||
|
layer_alpha := alpha * (1.0 - frac * 0.7);
|
||||||
|
col := Vector4.{blob.r, blob.g, blob.b, layer_alpha};
|
||||||
|
immediate_quad(
|
||||||
|
.{cx - r, cy - r},
|
||||||
|
.{cx + r, cy - r},
|
||||||
|
.{cx + r, cy + r},
|
||||||
|
.{cx - r, cy + r},
|
||||||
|
col
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
immediate_flush();
|
||||||
|
}
|
||||||
|
|
||||||
make_volume_bar :: (pct: s32, active: bool) -> string {
|
make_volume_bar :: (pct: s32, active: bool) -> string {
|
||||||
STEPS :: 10;
|
STEPS :: 10;
|
||||||
filled := (pct + 5) / STEPS;
|
filled := (pct + 5) / STEPS;
|
||||||
builder : String_Builder;
|
builder : String_Builder;
|
||||||
append(*builder, "[ ");
|
append(*builder, "[ ");
|
||||||
for i: 0..STEPS-1 {
|
for i: 0..STEPS-1 {
|
||||||
if i < filled then append(*builder, "■");
|
if i < filled then append(*builder, "=");
|
||||||
else append(*builder, "·");
|
else append(*builder, "-");
|
||||||
}
|
}
|
||||||
append(*builder, tprint(" %", pct));
|
append(*builder, tprint(" %\%%", pct));
|
||||||
append(*builder, " ]");
|
append(*builder, " ]");
|
||||||
return builder_to_string(*builder,, allocator = temp);
|
return builder_to_string(*builder,, allocator = temp);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user