Improved menus

This commit is contained in:
Gordon Pedersen 2023-04-11 16:26:39 +10:00
parent dbe7b3401d
commit 386e4cd650
16 changed files with 95 additions and 68 deletions

View file

@ -4,9 +4,11 @@
class SceneInstance class SceneInstance
def initialize(_args, opts = {}) def initialize(_args, opts = {})
@tick_in_background = opts.tick_in_background._? false @tick_in_background = opts.tick_in_background._? false
@reset_on_pop = opts.reset_on_pop._? false
end end
attr_reader :tick_in_background attr_reader :tick_in_background
attr_accessor :reset_on_pop
# called every tick of the game loop # called every tick of the game loop
def tick(args) end def tick(args) end

View file

@ -26,7 +26,7 @@ ORANGE = { r: 255, g: 173, b: 31 }
PINK = { r: 245, g: 146, b: 198 } PINK = { r: 245, g: 146, b: 198 }
PURPLE = { r: 133, g: 42, b: 216 } PURPLE = { r: 133, g: 42, b: 216 }
RED = { r: 231, g: 89, b: 82 } RED = { r: 231, g: 89, b: 82 }
YELLOW = { r: 240, g: 232, b: 89 } YELLOW = { r: 255, g: 200, b: 5 }
DARK_BLUE = { r: 22, g: 122, b: 188 } DARK_BLUE = { r: 22, g: 122, b: 188 }
DARK_GREEN = { r: 5, g: 84, b: 12 } DARK_GREEN = { r: 5, g: 84, b: 12 }

View file

@ -11,7 +11,7 @@ class MainMenu < MenuScene
}, },
{ {
key: :settings, key: :settings,
on_select: ->(iargs) { Scene.push(iargs, :settings, reset: true) } on_select: ->(iargs) { Scene.push(iargs, :settings, reset: true, reset_on_pop: true) }
} }
] ]
@ -22,13 +22,11 @@ class MainMenu < MenuScene
} }
end end
super args, opts, menu_options super args, opts, title, menu_options
end end
# called every tick of the game loop # called every tick of the game loop
def tick(args) def tick(args)
draw_bg(args, DARK_PURPLE)
# actual menu logic is handled by the MenuScene super class # actual menu logic is handled by the MenuScene super class
super super
@ -36,28 +34,30 @@ class MainMenu < MenuScene
labels = [] labels = []
labels << label( labels << label(
"v#{version}", "v#{version}",
x: 32.from_left, y: 32.from_top, x: 400.from_right, y: 150.from_bottom,
size: SIZE_XS, align: ALIGN_LEFT size: SIZE_XS, align: ALIGN_RIGHT,
) font: FONT_DOTMATRIX
labels << label( ).merge(YELLOW)
title.upcase, x: args.grid.w / 2, y: args.grid.top - 100, # labels << label(
size: SIZE_LG, align: ALIGN_CENTER, font: FONT_BOLD_ITALIC # title.upcase, x: args.grid.w / 2, y: args.grid.top - 100,
) # size: SIZE_LG, align: ALIGN_CENTER, font: FONT_BOLD_ITALIC
# )
labels << label( labels << label(
"#{text(:made_by)} #{dev_title}", "#{text(:made_by)} #{dev_title}",
x: args.grid.left + 24, y: 48, x: 242.from_left, y: 150.from_bottom,
size: SIZE_XS, align: ALIGN_LEFT size: SIZE_XS, align: ALIGN_LEFT,
) font: FONT_DOTMATRIX
labels << label( ).merge(YELLOW)
:controls_title, # labels << label(
x: args.grid.right - 24, y: 84, # :controls_title,
size: SIZE_SM, align: ALIGN_RIGHT # x: args.grid.right - 24, y: 84,
) # size: SIZE_SM, align: ALIGN_RIGHT
labels << label( # )
args.inputs.controller_one.connected ? :controls_gamepad : :controls_keyboard, # labels << label(
x: args.grid.right - 24, y: 48, # args.inputs.controller_one.connected ? :controls_gamepad : :controls_keyboard,
size: SIZE_XS, align: ALIGN_RIGHT # x: args.grid.right - 24, y: 48,
) # size: SIZE_XS, align: ALIGN_RIGHT
# )
args.outputs.labels << labels args.outputs.labels << labels
end end

View file

@ -10,21 +10,28 @@
# } # }
# ] # ]
class MenuScene < SceneInstance class MenuScene < SceneInstance
def initialize(args, opts = {}, menu_options = []) def initialize(args, opts = {}, title = title(), menu_options = [])
super args, opts super args, opts
@menu_state ||= { @menu_state ||= {
current_option_i: 0, current_option_i: 0,
hold_delay: 0 hold_delay: 0
} }
@spacer ||= mobile? ? 100 : 60 @spacer ||= mobile? ? 100 : 80
@menu_options ||= menu_options @menu_options ||= menu_options
@menu_y = opts.menu_y._?(420) @menu_y = opts.menu_y._?(440)
@title ||= title
@rand_strings = (0..@menu_options.length).map do |i|
(0...@title.length).map { ('A'..'Z').to_a[rand(26)] }.join
end
@first_render = nil
end end
def render_options(args) def render_options(args)
labels = [] labels = []
@menu_options.each.with_index do |option, i| @menu_options.each.with_index do |option, i|
active = @menu_state.current_option_i == i && (!mobile? || (mobile? && args.inputs.controller_one.connected))
text = case option.kind text = case option.kind
when :toggle when :toggle
"#{text(option[:key])}: #{text_for_setting_val(args, option[:key])}" "#{text(option[:key])}: #{text_for_setting_val(args, option[:key])}"
@ -32,24 +39,36 @@ class MenuScene < SceneInstance
text(option[:key]) text(option[:key])
end end
if (args.state.tick_count - @first_render) < 60 * (1.5 + i) * 0.2
if args.state.tick_count % 4 == 0
@rand_strings[i] = (0...(rand(text.length >= 3 ? text.length : 3) + 3)).map { ('A'..'Z').to_a[rand(26)] }.join
end
text = @rand_strings[i]
active = false
end
l = label( l = label(
text, text.upcase,
x: args.grid.w / 2, x: (args.grid.w / 2) - 70,
y: @menu_y + (@menu_options.length - (i * @spacer)), y: @menu_y + (@menu_options.length - (i * @spacer)),
align: ALIGN_CENTER, align: ALIGN_CENTER,
size: SIZE_MD size: 16,
font: FONT_DOTMATRIX_BOLD
) )
l.key = option[:key] l.key = option[:key]
l.width, l.height = args.gtk.calcstringbox(l.text, l.size_enum) l.width, l.height = args.gtk.calcstringbox(l.text, l.size_enum, l.font)
labels << l
if @menu_state.current_option_i == i && (!mobile? || (mobile? && args.inputs.controller_one.connected)) labels << l.merge(active ? WHITE : YELLOW)
args.outputs.solids << {
x: l.x - (l.width / 1.4) - 24 + (Math.sin(args.state.tick_count / 8) * 4), if active
y: l.y - 22, labels << label(
w: 16, '.',
h: 16 x: l.x - (l.width / 2) - 26 - (Math.sin(args.state.tick_count / 8) * 4),
}.merge(WHITE) y: l.y + 15,
align: ALIGN_CENTER,
size: 18,
font: FONT_DOTMATRIX
).merge(WHITE)
end end
button_border = { w: 340, h: 80, x: l.x - 170, y: l.y - 55 }.merge(WHITE) button_border = { w: 340, h: 80, x: l.x - 170, y: l.y - 55 }.merge(WHITE)
@ -67,6 +86,18 @@ class MenuScene < SceneInstance
# called every tick of the game loop # called every tick of the game loop
def tick(args) def tick(args)
super super
@first_render = args.state.tick_count if @first_render.nil?
Sprite.for(:menu).render(args)
args.outputs.labels << label(
@title.to_s.upcase,
x: (args.grid.w / 2) - 70,
y: args.grid.top - 175,
align: ALIGN_CENTER,
size: SIZE_LG,
font: FONT_RUBIK_BLACK
).merge(TRUE_BLACK)
render_options(args) render_options(args)
@ -118,6 +149,7 @@ class MenuScene < SceneInstance
@menu_state.current_option_i = 0 @menu_state.current_option_i = 0
@menu_state.hold_delay = 0 @menu_state.hold_delay = 0
@first_render = nil
end end
def text_for_setting_val(args, key) def text_for_setting_val(args, key)

View file

@ -11,11 +11,11 @@ class PauseMenu < MenuScene
}, },
{ {
key: :settings, key: :settings,
on_select: ->(args) { Scene.push(args, :settings, reset: true) } on_select: ->(args) { Scene.push(args, :settings, reset: true, reset_on_pop: true) }
}, },
{ {
key: :return_to_main_menu, key: :return_to_main_menu,
on_select: ->(args) { Scene.switch(args, :main_menu) } on_select: ->(args) { Scene.switch(args, :main_menu, reset: true) }
} }
] ]
@ -26,21 +26,12 @@ class PauseMenu < MenuScene
} }
end end
super args, opts, menu_options super args, opts, :paused, menu_options
end end
# called every tick of the game loop # called every tick of the game loop
def tick(args) def tick(args)
super super
Music.pause(args) unless Music.stopped(args) || Music.paused(args) Music.pause(args) unless Music.stopped(args) || Music.paused(args)
args.outputs.labels << label(
:paused,
x: args.grid.w / 2,
y: args.grid.top - 200,
align: ALIGN_CENTER,
size: SIZE_LG,
font: FONT_BOLD
)
end end
end end

View file

@ -45,7 +45,7 @@ class SettingsMenu < MenuScene
) )
end end
super args, opts, menu_options super args, opts, :settings, menu_options
end end
# called every tick of the game loop # called every tick of the game loop
@ -54,14 +54,5 @@ class SettingsMenu < MenuScene
# actual menu logic is handled by the MenuScene super class # actual menu logic is handled by the MenuScene super class
super super
args.outputs.labels << label(
:settings,
x: args.grid.w / 2,
y: args.grid.top - 200,
align: ALIGN_CENTER,
size: SIZE_LG,
font: FONT_BOLD
)
end end
end end

View file

@ -16,10 +16,11 @@ TEXT = {
paused: "Paused", paused: "Paused",
quit: "Quit", quit: "Quit",
resume: "Resume", resume: "Resume",
return_to_main_menu: "Return to Main Menu", return_to_main_menu: "Main Menu",
settings: "Settings", settings: "Settings",
sfx: "Sound Effects", sfx: "Sound Effects",
start: "Start", start: "Start",
cube_tube: "Cube tube",
} }
# Gets the text for the passed in `key`. Raises if it does not exist. We don't # Gets the text for the passed in `key`. Raises if it does not exist. We don't
@ -31,12 +32,15 @@ end
SIZE_XS = 0 SIZE_XS = 0
SIZE_SM = 4 SIZE_SM = 4
SIZE_MD = 6 SIZE_MD = 6
SIZE_LG = 10 SIZE_LG = 14
FONT_REGULAR = "fonts/Atkinson-Hyperlegible-Regular-102.ttf" FONT_REGULAR = "fonts/Atkinson-Hyperlegible-Regular-102.ttf"
FONT_ITALIC = "fonts/Atkinson-Hyperlegible-Italic-102.ttf" FONT_ITALIC = "fonts/Atkinson-Hyperlegible-Italic-102.ttf"
FONT_BOLD = "fonts/Atkinson-Hyperlegible-Bold-102.ttf" FONT_BOLD = "fonts/Atkinson-Hyperlegible-Bold-102.ttf"
FONT_BOLD_ITALIC = "fonts/Atkinson-Hyperlegible-BoldItalic-102.ttf" FONT_BOLD_ITALIC = "fonts/Atkinson-Hyperlegible-BoldItalic-102.ttf"
FONT_DOTMATRIX = "fonts/DotMatrix.ttf"
FONT_DOTMATRIX_BOLD = "fonts/DotMatrix-Bold.ttf"
FONT_RUBIK_BLACK = "fonts/Rubik-Black.ttf"
# Friendly method with sensible defaults for creating DRGTK label data # Friendly method with sensible defaults for creating DRGTK label data
# structures. # structures.

View file

@ -40,8 +40,10 @@ module Scene
end end
# Change the current scene by pushing it onto the scene stack # Change the current scene by pushing it onto the scene stack
def push(args, scene, reset: false) def push(args, scene, reset: false, reset_on_pop: false)
args.state.scene_stack ||= [] args.state.scene_stack ||= []
prev_scene = args.state.scene_stack.last
prev_scene.reset_on_pop = reset_on_pop unless prev_scene.nil?
the_scene = scene.is_a?(SceneInstance) ? scene : SCENES[scene].new(args) the_scene = scene.is_a?(SceneInstance) ? scene : SCENES[scene].new(args)
args.state.scene_stack.push(the_scene) args.state.scene_stack.push(the_scene)
@ -49,10 +51,14 @@ module Scene
end end
# Return to the previous scene on the stack # Return to the previous scene on the stack
def pop(args, reset: false) def pop(args, reset: nil)
scene = args.state.scene_stack&.pop args.state.scene_stack&.pop
scene = args.state.scene_stack.last
scene = scene._?(default(args))
reset = scene.reset_on_pop if reset.nil?
puts reset, scene, scene.reset_on_pop
switch(args, scene._?(default(args)), reset: reset, push_or_pop: true) switch(args, scene, reset: reset, push_or_pop: true)
end end
def default(args) def default(args)

BIN
fonts/DotMatrix-Bold.ttf Normal file

Binary file not shown.

BIN
fonts/DotMatrix.ttf Normal file

Binary file not shown.

BIN
fonts/Rubik-Black.ttf Normal file

Binary file not shown.

View file

@ -24,5 +24,6 @@ module Sprite
screen_s2: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s2.png' }), screen_s2: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s2.png' }),
screen_s3: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s3.png' }), screen_s3: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s3.png' }),
screen_s4: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s4.png' }), screen_s4: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s4.png' }),
menu: SpriteInstance.new({ w: 1280, h: 720, path: 'sprites/menu.png' }),
} }
end end

BIN
sprites/menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 KiB

BIN
wip-assets/sprites/menu.pdn Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.