Improved menus
This commit is contained in:
parent
dbe7b3401d
commit
386e4cd650
16 changed files with 95 additions and 68 deletions
|
@ -4,9 +4,11 @@
|
|||
class SceneInstance
|
||||
def initialize(_args, opts = {})
|
||||
@tick_in_background = opts.tick_in_background._? false
|
||||
@reset_on_pop = opts.reset_on_pop._? false
|
||||
end
|
||||
|
||||
attr_reader :tick_in_background
|
||||
attr_accessor :reset_on_pop
|
||||
|
||||
# called every tick of the game loop
|
||||
def tick(args) end
|
||||
|
|
|
@ -26,7 +26,7 @@ ORANGE = { r: 255, g: 173, b: 31 }
|
|||
PINK = { r: 245, g: 146, b: 198 }
|
||||
PURPLE = { r: 133, g: 42, b: 216 }
|
||||
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_GREEN = { r: 5, g: 84, b: 12 }
|
||||
|
|
|
@ -11,7 +11,7 @@ class MainMenu < MenuScene
|
|||
},
|
||||
{
|
||||
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
|
||||
|
||||
super args, opts, menu_options
|
||||
super args, opts, title, menu_options
|
||||
end
|
||||
|
||||
# called every tick of the game loop
|
||||
def tick(args)
|
||||
draw_bg(args, DARK_PURPLE)
|
||||
|
||||
# actual menu logic is handled by the MenuScene super class
|
||||
super
|
||||
|
||||
|
@ -36,28 +34,30 @@ class MainMenu < MenuScene
|
|||
labels = []
|
||||
labels << label(
|
||||
"v#{version}",
|
||||
x: 32.from_left, y: 32.from_top,
|
||||
size: SIZE_XS, align: ALIGN_LEFT
|
||||
)
|
||||
labels << label(
|
||||
title.upcase, x: args.grid.w / 2, y: args.grid.top - 100,
|
||||
size: SIZE_LG, align: ALIGN_CENTER, font: FONT_BOLD_ITALIC
|
||||
)
|
||||
x: 400.from_right, y: 150.from_bottom,
|
||||
size: SIZE_XS, align: ALIGN_RIGHT,
|
||||
font: FONT_DOTMATRIX
|
||||
).merge(YELLOW)
|
||||
# labels << label(
|
||||
# title.upcase, x: args.grid.w / 2, y: args.grid.top - 100,
|
||||
# size: SIZE_LG, align: ALIGN_CENTER, font: FONT_BOLD_ITALIC
|
||||
# )
|
||||
labels << label(
|
||||
"#{text(:made_by)} #{dev_title}",
|
||||
x: args.grid.left + 24, y: 48,
|
||||
size: SIZE_XS, align: ALIGN_LEFT
|
||||
)
|
||||
labels << label(
|
||||
:controls_title,
|
||||
x: args.grid.right - 24, y: 84,
|
||||
size: SIZE_SM, align: ALIGN_RIGHT
|
||||
)
|
||||
labels << label(
|
||||
args.inputs.controller_one.connected ? :controls_gamepad : :controls_keyboard,
|
||||
x: args.grid.right - 24, y: 48,
|
||||
size: SIZE_XS, align: ALIGN_RIGHT
|
||||
)
|
||||
x: 242.from_left, y: 150.from_bottom,
|
||||
size: SIZE_XS, align: ALIGN_LEFT,
|
||||
font: FONT_DOTMATRIX
|
||||
).merge(YELLOW)
|
||||
# labels << label(
|
||||
# :controls_title,
|
||||
# x: args.grid.right - 24, y: 84,
|
||||
# size: SIZE_SM, align: ALIGN_RIGHT
|
||||
# )
|
||||
# labels << label(
|
||||
# args.inputs.controller_one.connected ? :controls_gamepad : :controls_keyboard,
|
||||
# x: args.grid.right - 24, y: 48,
|
||||
# size: SIZE_XS, align: ALIGN_RIGHT
|
||||
# )
|
||||
|
||||
args.outputs.labels << labels
|
||||
end
|
||||
|
|
|
@ -10,21 +10,28 @@
|
|||
# }
|
||||
# ]
|
||||
class MenuScene < SceneInstance
|
||||
def initialize(args, opts = {}, menu_options = [])
|
||||
def initialize(args, opts = {}, title = title(), menu_options = [])
|
||||
super args, opts
|
||||
|
||||
@menu_state ||= {
|
||||
current_option_i: 0,
|
||||
hold_delay: 0
|
||||
}
|
||||
@spacer ||= mobile? ? 100 : 60
|
||||
@spacer ||= mobile? ? 100 : 80
|
||||
@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
|
||||
|
||||
def render_options(args)
|
||||
labels = []
|
||||
@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
|
||||
when :toggle
|
||||
"#{text(option[:key])}: #{text_for_setting_val(args, option[:key])}"
|
||||
|
@ -32,24 +39,36 @@ class MenuScene < SceneInstance
|
|||
text(option[:key])
|
||||
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(
|
||||
text,
|
||||
x: args.grid.w / 2,
|
||||
text.upcase,
|
||||
x: (args.grid.w / 2) - 70,
|
||||
y: @menu_y + (@menu_options.length - (i * @spacer)),
|
||||
align: ALIGN_CENTER,
|
||||
size: SIZE_MD
|
||||
size: 16,
|
||||
font: FONT_DOTMATRIX_BOLD
|
||||
)
|
||||
l.key = option[:key]
|
||||
l.width, l.height = args.gtk.calcstringbox(l.text, l.size_enum)
|
||||
labels << l
|
||||
l.width, l.height = args.gtk.calcstringbox(l.text, l.size_enum, l.font)
|
||||
|
||||
if @menu_state.current_option_i == i && (!mobile? || (mobile? && args.inputs.controller_one.connected))
|
||||
args.outputs.solids << {
|
||||
x: l.x - (l.width / 1.4) - 24 + (Math.sin(args.state.tick_count / 8) * 4),
|
||||
y: l.y - 22,
|
||||
w: 16,
|
||||
h: 16
|
||||
}.merge(WHITE)
|
||||
labels << l.merge(active ? WHITE : YELLOW)
|
||||
|
||||
if active
|
||||
labels << label(
|
||||
'.',
|
||||
x: l.x - (l.width / 2) - 26 - (Math.sin(args.state.tick_count / 8) * 4),
|
||||
y: l.y + 15,
|
||||
align: ALIGN_CENTER,
|
||||
size: 18,
|
||||
font: FONT_DOTMATRIX
|
||||
).merge(WHITE)
|
||||
end
|
||||
|
||||
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
|
||||
def tick(args)
|
||||
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)
|
||||
|
||||
|
@ -118,6 +149,7 @@ class MenuScene < SceneInstance
|
|||
|
||||
@menu_state.current_option_i = 0
|
||||
@menu_state.hold_delay = 0
|
||||
@first_render = nil
|
||||
end
|
||||
|
||||
def text_for_setting_val(args, key)
|
||||
|
|
|
@ -11,11 +11,11 @@ class PauseMenu < MenuScene
|
|||
},
|
||||
{
|
||||
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,
|
||||
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
|
||||
|
||||
super args, opts, menu_options
|
||||
super args, opts, :paused, menu_options
|
||||
end
|
||||
|
||||
# called every tick of the game loop
|
||||
def tick(args)
|
||||
super
|
||||
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
|
||||
|
|
|
@ -45,7 +45,7 @@ class SettingsMenu < MenuScene
|
|||
)
|
||||
end
|
||||
|
||||
super args, opts, menu_options
|
||||
super args, opts, :settings, menu_options
|
||||
end
|
||||
|
||||
# called every tick of the game loop
|
||||
|
@ -54,14 +54,5 @@ class SettingsMenu < MenuScene
|
|||
|
||||
# actual menu logic is handled by the MenuScene super class
|
||||
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
|
||||
|
|
|
@ -16,10 +16,11 @@ TEXT = {
|
|||
paused: "Paused",
|
||||
quit: "Quit",
|
||||
resume: "Resume",
|
||||
return_to_main_menu: "Return to Main Menu",
|
||||
return_to_main_menu: "Main Menu",
|
||||
settings: "Settings",
|
||||
sfx: "Sound Effects",
|
||||
start: "Start",
|
||||
cube_tube: "Cube tube",
|
||||
}
|
||||
|
||||
# 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_SM = 4
|
||||
SIZE_MD = 6
|
||||
SIZE_LG = 10
|
||||
SIZE_LG = 14
|
||||
|
||||
FONT_REGULAR = "fonts/Atkinson-Hyperlegible-Regular-102.ttf"
|
||||
FONT_ITALIC = "fonts/Atkinson-Hyperlegible-Italic-102.ttf"
|
||||
FONT_BOLD = "fonts/Atkinson-Hyperlegible-Bold-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
|
||||
# structures.
|
||||
|
|
|
@ -40,8 +40,10 @@ module Scene
|
|||
end
|
||||
|
||||
# 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 ||= []
|
||||
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)
|
||||
args.state.scene_stack.push(the_scene)
|
||||
|
||||
|
@ -49,10 +51,14 @@ module Scene
|
|||
end
|
||||
|
||||
# Return to the previous scene on the stack
|
||||
def pop(args, reset: false)
|
||||
scene = args.state.scene_stack&.pop
|
||||
def pop(args, reset: nil)
|
||||
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
|
||||
|
||||
def default(args)
|
||||
|
|
BIN
fonts/DotMatrix-Bold.ttf
Normal file
BIN
fonts/DotMatrix-Bold.ttf
Normal file
Binary file not shown.
BIN
fonts/DotMatrix.ttf
Normal file
BIN
fonts/DotMatrix.ttf
Normal file
Binary file not shown.
BIN
fonts/Rubik-Black.ttf
Normal file
BIN
fonts/Rubik-Black.ttf
Normal file
Binary file not shown.
|
@ -24,5 +24,6 @@ module Sprite
|
|||
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_s4: SpriteInstance.new({ w: 250, h: 210, path: 'sprites/screen-s4.png' }),
|
||||
menu: SpriteInstance.new({ w: 1280, h: 720, path: 'sprites/menu.png' }),
|
||||
}
|
||||
end
|
||||
|
|
BIN
sprites/menu.png
Normal file
BIN
sprites/menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 673 KiB |
BIN
wip-assets/sprites/menu.pdn
Normal file
BIN
wip-assets/sprites/menu.pdn
Normal file
Binary file not shown.
BIN
wip-assets/sprites/subway-title.vox
Normal file
BIN
wip-assets/sprites/subway-title.vox
Normal file
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue