From 3cf7f9735765cd66fc1f451165b40b04b699958c Mon Sep 17 00:00:00 2001 From: Lloyd Lobo Date: Sat, 9 Nov 2024 11:37:01 +0530 Subject: [PATCH 1/4] Slow down game fixed step delta time on keypress 'b' (WIP) --- src/main.lua | 152 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 142 insertions(+), 10 deletions(-) diff --git a/src/main.lua b/src/main.lua index 160289d..4d599cb 100644 --- a/src/main.lua +++ b/src/main.lua @@ -591,9 +591,10 @@ function update_player_position_this_frame(dt) end function update_player_vulnerability_timer_this_frame(dt) + local f_dt = 0.5 -- 0..1 local cs = curr_state if cs.player_invulnerability_timer > 0 then - cs.player_invulnerability_timer = cs.player_invulnerability_timer - dt + cs.player_invulnerability_timer = cs.player_invulnerability_timer - f_dt * dt if cs.player_invulnerability_timer <= 0 then cs.player_invulnerability_timer = 0 end end end @@ -999,7 +1000,8 @@ function handle_player_input_this_frame(dt) -- Enhance attributes while spinning like a top. --[[TODO: On init, emit a burst of lasers automatically. Give player some respite]] player_turn_speed = Config.PLAYER_ROT_TURN_SPEED * Config.PLAYER_TURN_SPEED_BESERK_MULTIPLIER - laser_fire_timer = (love.math.random() < 0.05) and 0 or game_timer_dt + -- laser_fire_timer = (love.math.random() < 0.05) and 0 or game_timer_dt + laser_fire_timer = game_timer_dt elseif has_companions then player_turn_speed = Config.PLAYER_ROT_TURN_SPEED * INV_PHI_SQ else @@ -2119,6 +2121,14 @@ function update_game(dt) ---@param dt number # Fixed delta time. music_bgm:setEffect('on_damage_lowpass', not true) music_bgm:setEffect('on_damage_reverb', not true) end + + if is_slowed_game_time then + music_bgm:setEffect('on_bullet_time_lofi', true) + music_bgm:setEffect('on_damage_lowpass', true) + music_bgm:setEffect('on_damage_reverb', true) + else + music_bgm:setEffect('on_bullet_time_lofi', not true) + end end --- FIXME: When I set a refresh rate of 75.00 Hz on a 800 x 600 (4:3) @@ -2137,6 +2147,7 @@ function draw_game(alpha) draw_player_shield_collectible(alpha) draw_player_collectible_indicators(alpha) draw_player(alpha) + LG.print(string.format('bullet_time: %s | dt: %.4f | alpha: %.4f', tostring(is_slowed_game_time), game_timer_dt, alpha), 100, 100) end -- @@ -2241,21 +2252,42 @@ function load_audio() end -- Effects + -- Create a low-pass filter effect -- see https://love2d.org/wiki/EffectType do - local on_damage_lowpass_effect_opts = - { type = 'equalizer', highcut = 4000, highgain = 0.126, highmidgain = 0.126, lowgain = 2, lowmidfrequency = 500, lowmidgain = 0.126, volume = 2.0 } - love.audio.setEffect('on_damage_lowpass', on_damage_lowpass_effect_opts) -- Create a low-pass filter effect -- see https://love2d.org/wiki/EffectType + local on_damage_lowpass_effect_opts = { + type = 'equalizer', + highcut = 4000, + highgain = 0.126, + highmidgain = 0.126, + lowgain = 2, + lowmidfrequency = 500, + lowmidgain = 0.126, + volume = 2.0, + } + love.audio.setEffect('on_damage_lowpass', on_damage_lowpass_effect_opts) -- frequency: `default: 800` ─ I want a hollow eerie effect (100 is great for ross_bugden_chime_chase) -- highcut: ... use a freq which is consistently playing (1000Hz) --- @alias Waveform 'sine' | 'square' | 'sawtooth' local waveform = 'sine'--[[@type Waveform]] - local on_damage_effect_opts = - { type = 'ringmodulator', frequency = 100, highcut = lume.clamp(450, 0, 24000), waveform = waveform, volume = INV_PHI ^ 8 } + local on_damage_effect_opts = { + type = 'ringmodulator', + frequency = 100, + highcut = lume.clamp(450, 0, 24000), + waveform = waveform, + volume = INV_PHI ^ 8, + } love.audio.setEffect('on_damage_reverb', on_damage_effect_opts) - local master_effect_opts = { type = 'reverb', volume = 0.5 } --[[decaytime = lume.clamp(1.49 * (PHI * 1), 0.1, 20), roomrolloff = lume.clamp(PHI, 0, 10), gain = 0.32 * PHI_INV, volume = PHI_INV, ]] + --[[decaytime = lume.clamp(1.49 * (PHI * 1), 0.1, 20), roomrolloff = lume.clamp(PHI, 0, 10), gain = 0.32 * PHI_INV, volume = PHI_INV, ]] + local master_effect_opts = { type = 'reverb', volume = 0.5 } love.audio.setEffect('master_reverb', master_effect_opts) + + love.audio.setEffect('on_bullet_time_lofi', { + type = 'flanger', + rate = 0.27 * PHI ^ 4, -- [0, 10] + -- volume = 1, + }) end do @@ -2562,6 +2594,96 @@ function love.load() end end --< love.load() + +-- #region: bullet time (global) +-- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + +is_slowed_game_time = not true +was_b_key_slowed_game_time_pressed = not true + +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +-- #endregion + +local slowed_game_time_timestamp = 0.0 +local slowed_game_time_sustain_duration_seconds = 3.0 +local slowed_game_time_target_dt = 0.001 + +-- #region love.update variables +-- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + +local fixed_dt = Config.FIXED_DT + +-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +-- #endregion + +--- Rationale: Instead of leaving hands from controls─for quick pause, we use +--- bullet time to slow things down and allow player to turn quickly / avoid +--- faster drone creatures. +function update_slowed_game_time_this_frame(dt) + if not is_slowed_game_time then + fixed_dt = Config.FIXED_DT + slowed_game_time_timestamp = 0.0 + return + end + + if slowed_game_time_timestamp == 0.0 then --[[]] + slowed_game_time_timestamp = love.timer.getTime() + end + + if Config.Debug.IS_ASSERT then assert(love.timer.getTime() > slowed_game_time_timestamp) end + + local f_rate = dt * 0.01 + + Timer.after(f_rate, function() + if not is_slowed_game_time then + error 'unreachable' + return + end + + if Config.Debug.IS_ASSERT then assert(slowed_game_time_timestamp ~= 0) end + + -- Wind rate down. + if fixed_dt - f_rate > slowed_game_time_target_dt then + fixed_dt = fixed_dt - f_rate + else + -- Now we are at desired time rate. + if Config.Debug.IS_ASSERT then--[[]] + assert(fixed_dt >= slowed_game_time_target_dt and fixed_dt + f_rate <= 0.01) + end + + -- Un-wind rate up. + Timer.after(slowed_game_time_sustain_duration_seconds, function() + if Config.Debug.IS_ASSERT then + if is_slowed_game_time then assert(slowed_game_time_timestamp ~= 0) end + end + + if not is_slowed_game_time then return end + + Timer.after(dt, function() + if not is_slowed_game_time then + if Config.Debug.IS_ASSERT then assert(pcall(assert, fixed_dt == Config.FIXED_DT)) end -- assume + return + end + if Config.Debug.IS_ASSERT then + assert(slowed_game_time_timestamp ~= 0) + assert(dt > slowed_game_time_target_dt) + assert(fixed_dt > slowed_game_time_target_dt) + end + if fixed_dt < Config.FIXED_DT then + fixed_dt = fixed_dt + dt + else + if Config.Debug.IS_ASSERT then assert(is_slowed_game_time) end + + -- Finally toggle it off. + is_slowed_game_time = not true + fixed_dt = Config.FIXED_DT + end + end) + end) + end + end) +end + function love.update(dt) if Config.Debug.IS_DEVELOPMENT then -- FIXME: Maybe make stuff global that are not hot-reloading? require('lurker').update() @@ -2595,7 +2717,17 @@ function love.update(dt) end -- #4 Frame Rate Independence: Fixed timestep loop. - local fixed_dt = Config.FIXED_DT + + -- Distort fixed time step `fixed_dt`. + if love.keyboard.isDown 'b' and not was_b_key_slowed_game_time_pressed then + is_slowed_game_time = not is_slowed_game_time + was_b_key_slowed_game_time_pressed = true + elseif not love.keyboard.isDown 'b' then + was_b_key_slowed_game_time_pressed = not true + end + update_slowed_game_time_this_frame(dt) + + -- Run main game loop. dt_accum = dt_accum + dt while dt_accum >= fixed_dt do sync_prev_state() @@ -2661,7 +2793,7 @@ function love.draw() -- #4 Draw debugging tools. -- - -- • Toggled on `h` `keyDown` event. + -- • Toggled on `h` `isDown` event. if is_debug_hud_enable then draw_debug_general_hud() draw_debug_stats_hud() From 219fd95e76470d64f989c9c3b5e05a2ec81fac4b Mon Sep 17 00:00:00 2001 From: Lloyd Lobo Date: Sat, 9 Nov 2024 11:37:34 +0530 Subject: [PATCH 2/4] Apply continuity of player shield/health through each level --- src/main.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.lua b/src/main.lua index 4d599cb..1317637 100644 --- a/src/main.lua +++ b/src/main.lua @@ -2357,7 +2357,8 @@ function reset_game() -- Reset game states. do curr_state.player_invulnerability_timer = 0 - curr_state.player_health = Config.PLAYER_MAX_HEALTH + -- Allow continuity from previous level + if curr_state.player_health <= 0 then curr_state.player_health = Config.PLAYER_MAX_HEALTH end curr_state.player_rot_angle = 0 curr_state.player_vel_x = 0 curr_state.player_vel_y = 0 From ea2a4545e57c0c3b2e30881e382a3992a1e54a94 Mon Sep 17 00:00:00 2001 From: Lloyd Lobo Date: Sat, 9 Nov 2024 11:38:09 +0530 Subject: [PATCH 3/4] Experiment with resizing window size to reflect in shaders --- src/main.lua | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/main.lua b/src/main.lua index 1317637..887d3d3 100644 --- a/src/main.lua +++ b/src/main.lua @@ -2335,13 +2335,13 @@ function load_shaders() } -- Load moonshine shaders - local moonshine = require 'lib.moonshine' - local fx = moonshine.effects + moonshine = require 'lib.moonshine' + moonshine_fx = moonshine.effects --- @class (exact) MoonshineShaders --- @field fog table moonshine_shaders = { - fog = moonshine(arena_w, arena_h, fx.fog).chain(fx.desaturate) --[[ desaturate removes ocataves(frequency) lines ]], + fog = moonshine(arena_w, arena_h, moonshine_fx.fog).chain(moonshine_fx.desaturate) --[[ desaturate removes ocataves(frequency) lines ]], } -- Setup moonshine shaders @@ -2595,6 +2595,12 @@ function love.load() end end --< love.load() +---@diagnostic disable-next-line: unused-local +function update_send_shaders(dt, t, w, h) + glsl_shaders.warp:send('screen', { w, h }) + glsl_shaders.warp:send('time', t) + moonshine_shaders.fog.fog.time = t +end -- #region: bullet time (global) -- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv @@ -2685,6 +2691,21 @@ function update_slowed_game_time_this_frame(dt) end) end +function love.resize(w, h) + -- Called when the window is resized, for example if the user resizes the + -- window, or if love.window.setMode is called with an unsupported width or + -- height in fullscreen and the window chooses the closest appropriate size. + gw = w + gh = h + arena_w = w + arena_h = h + + -- EXPENSIVE OPERATION (SHOULD MANUALLY SET UP GLSL) + moonshine_shaders.fog = moonshine(w, h, moonshine_fx.fog).chain(moonshine_fx.desaturate) --[[ desaturate removes ocataves(frequency) lines ]] + + update_send_shaders(game_timer_dt, game_timer_t, w, h) +end + function love.update(dt) if Config.Debug.IS_DEVELOPMENT then -- FIXME: Maybe make stuff global that are not hot-reloading? require('lurker').update() @@ -2708,14 +2729,7 @@ function love.update(dt) -- #3 Update all timers based on real dt. Timer.update(dt) -- call this every frame to update timers - do - local screen_w, screen_h = LG.getDimensions() - -- glsl_shaders.gradient_timemod:send('screen', { screen_w, screen_h }) - -- glsl_shaders.gradient_timemod:send('time', love.timer.getTime()) - glsl_shaders.warp:send('screen', { screen_w, screen_h }) - glsl_shaders.warp:send('time', love.timer.getTime()) - moonshine_shaders.fog.fog.time = love.timer.getTime() - end + update_send_shaders(dt, game_timer_t, arena_w, arena_h) -- #4 Frame Rate Independence: Fixed timestep loop. From 0acab6bde6f28429b46c0d516e0a7928089dccad Mon Sep 17 00:00:00 2001 From: Lloyd Lobo Date: Sat, 9 Nov 2024 11:38:39 +0530 Subject: [PATCH 4/4] Update magic config numbers to suit slowed game time feature --- src/config.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/config.lua b/src/config.lua index 9133a6c..a2b1c7f 100644 --- a/src/config.lua +++ b/src/config.lua @@ -33,7 +33,7 @@ local _cret_max_speed = 100 -- 32..120 local _cret_min_radius = 8 local _cret_min_speed = 32 local _f_cret_scale = 1. -- factor -local _f_cret_speed = 1. -- factor +local _f_cret_speed = 1.4 -- factor --- @type CreatureStage[] Size decreases as stage progresses. local _CREATURE_STAGES = { @@ -178,8 +178,8 @@ return { FIXED_DT = 1 / _fixed_fps, --- Consistent update frame rate fluctuations. FIXED_DT_INV = 1 / (1 / _fixed_fps), --- Helper constant to avoid dividing on each frame. (same as FIXED_FPS) FIXED_FPS = _fixed_fps, - GAME_MAX_LEVEL = 2 ^ 6, -- > 64 - LASER_FIRE_TIMER_LIMIT = (_inv_phi ^ 2.0) * ({ 0.21, 0.16, 0.14 })[_speed_mode], --- Reduce this to increase fire rate. + GAME_MAX_LEVEL = 2 ^ 4, -- > 16 + LASER_FIRE_TIMER_LIMIT = (_inv_phi ^ 1.0) * ({ 0.2, 0.16, 0.14 })[_speed_mode], --- Reduce this to increase fire rate. LASER_MAX_CAPACITY = 2 ^ 8, -- Choices: 2^4(balanced [nerfs fast fire rate]) | 2^5 (long range) LASER_PROJECTILE_SPEED = ({ 2 ^ 7, 2 ^ 8, 2 ^ 8 + 256 })[_speed_mode], --- 256|512|768 LASER_RADIUS = (_inv_phi ^ 1) * math.max(_inv_phi * _player_radius, math.floor(_player_radius * (_inv_phi ^ (1 * _phi)))), @@ -193,7 +193,7 @@ return { PLAYER_ACCELERATION = math.floor(3 * (true and 1 or 1.25) * ({ 150, 200, 300 })[_speed_mode]), PLAYER_CIRCLE_IRIS_TO_EYE_RATIO = _inv_phi, PLAYER_ROT_TURN_SPEED = ({ (10 * _inv_phi), 10, -2 + (30 / 2) / 4 + (_player_accel / _fixed_fps) })[_speed_mode], - PLAYER_TURN_SPEED_BESERK_MULTIPLIER = _phi * _phi, + PLAYER_TURN_SPEED_BESERK_MULTIPLIER = _phi * 1.0, PLAYER_FIRE_COOLDOWN_TIMER_LIMIT = ({ 4, 6, 12 })[_speed_mode], --- FIXME: Implement this (6 is rough guess, but intend for alpha lifecycle from 0.0 to 1.0.) -- see if this is in love.load() PLAYER_FIRING_EDGE_MAX_RADIUS = (0.6 * math.ceil(_player_radius * (true and 0.328 or (_inv_phi * _inv_phi)))), --- Trigger distance from center of player. PLAYER_FIRING_EDGE_RADIUS = (0.9 * math.ceil(_player_radius * (true and 0.328 or (_inv_phi * _inv_phi)))), --- Trigger distance from center of player.