feat: added new functionality with focus, added base background to screens, created Focus.close(), Focus.start(), Focus.driven() methods for different use-cases, added focus to screens
This commit is contained in:
@@ -7,6 +7,7 @@ init/init.context.lua
|
||||
system/system.util.lua
|
||||
system/system.print.lua
|
||||
system/system.input.lua
|
||||
system/system.focus.lua
|
||||
system/system.ui.lua
|
||||
audio/audio.manager.lua
|
||||
audio/audio.songs.lua
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
Decision.register({
|
||||
id = "play_button_mash",
|
||||
label = "Play Button Mash",
|
||||
handle = function() Meter.hide() MinigameButtonMashWindow.start("game") end,
|
||||
handle = function()
|
||||
Meter.hide()
|
||||
MinigameButtonMashWindow.start("game", {
|
||||
focus_center_x = Config.screen.width / 2,
|
||||
focus_center_y = Config.screen.height / 2,
|
||||
focus_initial_radius = 0,
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
Decision.register({
|
||||
id = "play_rhythm",
|
||||
label = "Play Rhythm Game",
|
||||
handle = function() Meter.hide() MinigameRhythmWindow.start("game") end,
|
||||
handle = function()
|
||||
Meter.hide()
|
||||
MinigameRhythmWindow.start("game", {
|
||||
focus_center_x = Config.screen.width / 2,
|
||||
focus_center_y = Config.screen.height / 2,
|
||||
focus_initial_radius = 0,
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -115,7 +115,10 @@ function Minigame.get_default_button_mash()
|
||||
bar_height = 12,
|
||||
button_x = 20,
|
||||
button_y = 110,
|
||||
button_size = 12
|
||||
button_size = 12,
|
||||
focus_center_x = nil,
|
||||
focus_center_y = nil,
|
||||
focus_initial_radius = 0
|
||||
}
|
||||
end
|
||||
|
||||
@@ -167,7 +170,10 @@ function Minigame.get_default_rhythm()
|
||||
button_y = 110,
|
||||
button_size = 10,
|
||||
press_cooldown = 0,
|
||||
press_cooldown_duration = 15
|
||||
press_cooldown_duration = 15,
|
||||
focus_center_x = nil,
|
||||
focus_center_y = nil,
|
||||
focus_initial_radius = 0
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -11,3 +11,4 @@ Print = {}
|
||||
Input = {}
|
||||
Sprite = {}
|
||||
Audio = {}
|
||||
Focus = {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Screen.register({
|
||||
id = "office",
|
||||
name = "Office",
|
||||
background_color = Config.colors.dark_grey,
|
||||
decisions = {
|
||||
"play_button_mash",
|
||||
"play_rhythm",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Screen.register({
|
||||
id = "walking_to_home",
|
||||
name = "Walking to home",
|
||||
background_color = Config.colors.dark_grey,
|
||||
decisions = {
|
||||
"go_to_home",
|
||||
"go_to_office",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Screen.register({
|
||||
id = "walking_to_office",
|
||||
name = "Walking to office",
|
||||
background_color = Config.colors.dark_grey,
|
||||
decisions = {
|
||||
"go_to_home",
|
||||
"go_to_office",
|
||||
|
||||
166
inc/system/system.focus.lua
Normal file
166
inc/system/system.focus.lua
Normal file
@@ -0,0 +1,166 @@
|
||||
--- @section Focus
|
||||
|
||||
local FOCUS_DEFAULT_SPEED = 5
|
||||
|
||||
local active = false
|
||||
local closing = false
|
||||
local driven = false
|
||||
local center_x = 0
|
||||
local center_y = 0
|
||||
local radius = 0
|
||||
local speed = FOCUS_DEFAULT_SPEED
|
||||
local on_complete = nil
|
||||
local driven_initial_r = 0
|
||||
local driven_max_r = 0
|
||||
|
||||
local function max_radius(cx, cy)
|
||||
local dx = math.max(cx, Config.screen.width - cx)
|
||||
local dy = math.max(cy, Config.screen.height - cy)
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
--- Starts a focus overlay that reveals content through an expanding circle.
|
||||
--- @within Focus
|
||||
--- @param center_x number The x-coordinate of the circle center.
|
||||
--- @param center_y number The y-coordinate of the circle center.
|
||||
--- @param[opt] params table Optional parameters: `speed` (number) expansion rate in pixels/frame, `initial_radius` (number) starting radius in pixels (default 0), `on_complete` (function) callback when overlay disperses.
|
||||
function Focus.start(cx, cy, params)
|
||||
params = params or {}
|
||||
active = true
|
||||
closing = false
|
||||
driven = false
|
||||
center_x = cx
|
||||
center_y = cy
|
||||
radius = params.initial_radius or 0
|
||||
speed = params.speed or FOCUS_DEFAULT_SPEED
|
||||
on_complete = params.on_complete
|
||||
end
|
||||
|
||||
--- Starts a closing focus overlay that hides content by shrinking the visible circle.
|
||||
--- @within Focus
|
||||
--- @param center_x number The x-coordinate of the circle center.
|
||||
--- @param center_y number The y-coordinate of the circle center.
|
||||
--- @param[opt] params table Optional parameters: `speed` (number) shrink rate in pixels/frame, `on_complete` (function) callback when screen is fully covered.
|
||||
function Focus.close(cx, cy, params)
|
||||
params = params or {}
|
||||
active = true
|
||||
closing = true
|
||||
driven = false
|
||||
center_x = cx
|
||||
center_y = cy
|
||||
radius = max_radius(cx, cy)
|
||||
speed = params.speed or FOCUS_DEFAULT_SPEED
|
||||
on_complete = params.on_complete
|
||||
end
|
||||
|
||||
--- Starts a driven focus overlay whose radius is controlled externally via Focus.set_percentage().
|
||||
--- The radius maps linearly from initial_radius (at 0%) to the screen corner distance (at 100%).
|
||||
--- @within Focus
|
||||
--- @param center_x number The x-coordinate of the circle center.
|
||||
--- @param center_y number The y-coordinate of the circle center.
|
||||
--- @param[opt] params table Optional parameters: `initial_radius` (number) radius at 0% (default 0).
|
||||
function Focus.start_driven(cx, cy, params)
|
||||
params = params or {}
|
||||
active = true
|
||||
closing = false
|
||||
driven = true
|
||||
center_x = cx
|
||||
center_y = cy
|
||||
driven_initial_r = params.initial_radius or 0
|
||||
driven_max_r = max_radius(cx, cy)
|
||||
radius = driven_initial_r
|
||||
on_complete = nil
|
||||
end
|
||||
|
||||
--- Sets the visible radius as a percentage of the full screen extent.
|
||||
--- Only has effect when the overlay is in driven mode (started via Focus.start_driven).
|
||||
--- @within Focus
|
||||
--- @param pct number A value from 0 to 1 (0 = initial_radius, 1 = full screen).
|
||||
function Focus.set_percentage(pct)
|
||||
if not driven then return end
|
||||
radius = driven_initial_r + pct * (driven_max_r - driven_initial_r)
|
||||
end
|
||||
|
||||
--- Checks whether the focus overlay is currently active.
|
||||
--- @within Focus
|
||||
--- @return boolean Whether the focus overlay is active.
|
||||
function Focus.is_active()
|
||||
return active
|
||||
end
|
||||
|
||||
--- Stops the focus overlay immediately.
|
||||
--- @within Focus
|
||||
function Focus.stop()
|
||||
active = false
|
||||
closing = false
|
||||
driven = false
|
||||
radius = 0
|
||||
on_complete = nil
|
||||
end
|
||||
|
||||
--- Updates the focus overlay animation. No-op in driven mode.
|
||||
--- @within Focus
|
||||
function Focus.update()
|
||||
if not active then return end
|
||||
if driven then return end
|
||||
|
||||
if closing then
|
||||
radius = radius - speed
|
||||
if radius <= 0 then
|
||||
local cb = on_complete
|
||||
Focus.stop()
|
||||
if cb then cb() end
|
||||
end
|
||||
else
|
||||
radius = radius + speed
|
||||
if radius >= max_radius(center_x, center_y) then
|
||||
local cb = on_complete
|
||||
Focus.stop()
|
||||
if cb then cb() end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws the focus overlay (black screen with circular cutout).
|
||||
--- Must be called after all other drawing to appear on top of every visual layer.
|
||||
--- @within Focus
|
||||
function Focus.draw()
|
||||
if not active then return end
|
||||
|
||||
local cx = center_x
|
||||
local cy = center_y
|
||||
local r = radius
|
||||
local w = Config.screen.width
|
||||
local h = Config.screen.height
|
||||
local color = Config.colors.black
|
||||
|
||||
if closing and r <= 0 then
|
||||
rect(0, 0, w, h, color)
|
||||
return
|
||||
end
|
||||
|
||||
local top = math.max(0, math.floor(cy - r))
|
||||
local bottom = math.min(h - 1, math.ceil(cy + r))
|
||||
|
||||
if top > 0 then
|
||||
rect(0, 0, w, top, color)
|
||||
end
|
||||
|
||||
if bottom < h - 1 then
|
||||
rect(0, bottom + 1, w, h - bottom - 1, color)
|
||||
end
|
||||
|
||||
for y = top, bottom do
|
||||
local dy = y - cy
|
||||
local half_w = math.sqrt(math.max(0, r * r - dy * dy))
|
||||
local left = math.floor(cx - half_w)
|
||||
local right = math.ceil(cx + half_w)
|
||||
|
||||
if left > 0 then
|
||||
rect(0, y, left, 1, color)
|
||||
end
|
||||
if right < w then
|
||||
rect(right, y, w - right, 1, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,17 +6,23 @@ local _selected_decision_index = 1
|
||||
--- @within GameWindow
|
||||
function GameWindow.draw()
|
||||
local screen = Screen.get_by_id(Context.game.current_screen)
|
||||
if screen.background then Map.draw(screen.background) end
|
||||
if screen.background then
|
||||
Map.draw(screen.background)
|
||||
elseif screen.background_color then
|
||||
rect(0, 0, Config.screen.width, Config.screen.height, screen.background_color)
|
||||
end
|
||||
UI.draw_top_bar(screen.name)
|
||||
if #_available_decisions > 0 then
|
||||
UI.draw_decision_selector(_available_decisions, _selected_decision_index)
|
||||
end
|
||||
Sprite.draw()
|
||||
Focus.draw()
|
||||
end
|
||||
|
||||
--- Updates the game window logic.
|
||||
--- @within GameWindow
|
||||
function GameWindow.update()
|
||||
Focus.update()
|
||||
if Input.menu_back() then
|
||||
Window.set_current("menu")
|
||||
MenuWindow.refresh_menu_items()
|
||||
@@ -60,6 +66,8 @@ function GameWindow.update()
|
||||
selected_decision.handle()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Sets the active window.
|
||||
|
||||
@@ -13,7 +13,13 @@ end
|
||||
--- @param[opt] params table Optional parameters for minigame configuration.
|
||||
function MinigameButtonMashWindow.start(return_window, params)
|
||||
MinigameButtonMashWindow.init(params)
|
||||
Context.minigame_button_mash.return_window = return_window or "game"
|
||||
local mg = Context.minigame_button_mash
|
||||
mg.return_window = return_window or "game"
|
||||
if mg.focus_center_x then
|
||||
Focus.start_driven(mg.focus_center_x, mg.focus_center_y, {
|
||||
initial_radius = mg.focus_initial_radius
|
||||
})
|
||||
end
|
||||
Window.set_current("minigame_button_mash")
|
||||
end
|
||||
|
||||
@@ -31,6 +37,7 @@ function MinigameButtonMashWindow.update()
|
||||
if mg.bar_fill >= mg.max_fill then
|
||||
Meter.on_minigame_complete()
|
||||
Meter.show()
|
||||
if mg.focus_center_x then Focus.stop() end
|
||||
Window.set_current(mg.return_window)
|
||||
return
|
||||
end
|
||||
@@ -42,6 +49,9 @@ function MinigameButtonMashWindow.update()
|
||||
if mg.button_pressed_timer > 0 then
|
||||
mg.button_pressed_timer = mg.button_pressed_timer - 1
|
||||
end
|
||||
if mg.focus_center_x then
|
||||
Focus.set_percentage(mg.bar_fill / mg.max_fill)
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws button mash minigame.
|
||||
@@ -51,7 +61,9 @@ function MinigameButtonMashWindow.draw()
|
||||
if mg.return_window == "game" then
|
||||
GameWindow.draw()
|
||||
end
|
||||
if not mg.focus_center_x then
|
||||
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
|
||||
end
|
||||
rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey)
|
||||
rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey)
|
||||
local fill_width = (mg.bar_fill / mg.max_fill) * mg.bar_width
|
||||
|
||||
@@ -13,7 +13,13 @@ end
|
||||
--- @param[opt] params table Optional parameters for minigame configuration.
|
||||
function MinigameRhythmWindow.start(return_window, params)
|
||||
MinigameRhythmWindow.init(params)
|
||||
Context.minigame_rhythm.return_window = return_window or "game"
|
||||
local mg = Context.minigame_rhythm
|
||||
mg.return_window = return_window or "game"
|
||||
if mg.focus_center_x then
|
||||
Focus.start_driven(mg.focus_center_x, mg.focus_center_y, {
|
||||
initial_radius = mg.focus_initial_radius
|
||||
})
|
||||
end
|
||||
Window.set_current("minigame_rhythm")
|
||||
end
|
||||
|
||||
@@ -53,12 +59,16 @@ function MinigameRhythmWindow.update()
|
||||
if mg.score >= mg.max_score then
|
||||
Meter.on_minigame_complete()
|
||||
Meter.show()
|
||||
if mg.focus_center_x then Focus.stop() end
|
||||
Window.set_current(mg.return_window)
|
||||
return
|
||||
end
|
||||
if mg.button_pressed_timer > 0 then
|
||||
mg.button_pressed_timer = mg.button_pressed_timer - 1
|
||||
end
|
||||
if mg.focus_center_x then
|
||||
Focus.set_percentage(1 - mg.score / mg.max_score)
|
||||
end
|
||||
end
|
||||
|
||||
--- Draws rhythm minigame.
|
||||
@@ -68,7 +78,9 @@ function MinigameRhythmWindow.draw()
|
||||
if mg.return_window == "game" then
|
||||
GameWindow.draw()
|
||||
end
|
||||
if not mg.focus_center_x then
|
||||
rect(0, 0, Config.screen.width, Config.screen.height, Config.colors.black)
|
||||
end
|
||||
rect(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.light_grey)
|
||||
rectb(mg.bar_x - 2, mg.bar_y - 2, mg.bar_width + 4, mg.bar_height + 4, Config.colors.dark_grey)
|
||||
rect(mg.bar_x, mg.bar_y, mg.bar_width, mg.bar_height, Config.colors.dark_grey)
|
||||
|
||||
Reference in New Issue
Block a user