Refine boss reveal and dash motion
This commit is contained in:
parent
8fbcc340ce
commit
e5439f7183
4 changed files with 48 additions and 11 deletions
|
|
@ -63,7 +63,7 @@ func _process(delta: float) -> void:
|
||||||
return
|
return
|
||||||
if not reveal_started:
|
if not reveal_started:
|
||||||
_update_indicator()
|
_update_indicator()
|
||||||
if _boss_reaches_view():
|
if _boss_is_fully_visible():
|
||||||
begin_reveal()
|
begin_reveal()
|
||||||
return
|
return
|
||||||
if encounter_active:
|
if encounter_active:
|
||||||
|
|
@ -98,17 +98,18 @@ func _set_combat_ui_visible(shown: bool) -> void:
|
||||||
control.visible = shown
|
control.visible = shown
|
||||||
|
|
||||||
|
|
||||||
func _boss_reaches_view() -> bool:
|
func _boss_is_fully_visible() -> bool:
|
||||||
# World-space math is immediate. The viewport canvas transform trails a
|
# World-space math is immediate. The viewport canvas transform trails a
|
||||||
# Camera2D assignment until the render server updates, which made a fast
|
# Camera2D assignment until the render server updates, which made a fast
|
||||||
# approach able to cross the threshold one frame before the detector saw
|
# approach able to cross the threshold one frame before the detector saw
|
||||||
# it. Including the tentacle reach makes the reveal begin when the first
|
# it. The tentacles deliberately reach beyond the framing, so the authored
|
||||||
# part of the creature actually touches the view.
|
# body radius is the useful cinematic boundary: the roar must wait until
|
||||||
|
# the whole central creature is on screen, not fire when a tentacle enters.
|
||||||
var half_view: Vector2 = get_viewport_rect().size * 0.5 / camera.zoom
|
var half_view: Vector2 = get_viewport_rect().size * 0.5 / camera.zoom
|
||||||
var extent: float = boss.TENTACLE_LENGTH * boss.scale.x
|
var extent: float = boss.BODY_RADIUS * maxf(boss.scale.x, boss.scale.y)
|
||||||
var separation: Vector2 = (boss.global_position - camera.global_position).abs()
|
var separation: Vector2 = (boss.global_position - camera.global_position).abs()
|
||||||
return separation.x <= half_view.x + extent \
|
return separation.x + extent <= half_view.x \
|
||||||
and separation.y <= half_view.y + extent
|
and separation.y + extent <= half_view.y
|
||||||
|
|
||||||
|
|
||||||
func _update_indicator() -> void:
|
func _update_indicator() -> void:
|
||||||
|
|
|
||||||
|
|
@ -124,12 +124,16 @@ func _process(delta: float) -> bool:
|
||||||
return _fail("the off-screen boss has no directional border indicator")
|
return _fail("the off-screen boss has no directional border indicator")
|
||||||
if state.encounter_locks_movement or boss.combat_active:
|
if state.encounter_locks_movement or boss.combat_active:
|
||||||
return _fail("the off-screen approach locked player movement or already started combat")
|
return _fail("the off-screen approach locked player movement or already started combat")
|
||||||
# Put the camera where the player can carry the fleet; the encounter
|
# Seeing a tentacle is not enough to trigger the cinematic or its roar.
|
||||||
# itself must detect the boss crossing into view.
|
|
||||||
surface.get_node("Camera2D").global_position = boss.global_position + Vector2(0.0, 1000.0)
|
surface.get_node("Camera2D").global_position = boss.global_position + Vector2(0.0, 1000.0)
|
||||||
encounter._process(0.0)
|
encounter._process(0.0)
|
||||||
|
if encounter.reveal_started:
|
||||||
|
return _fail("a partially visible boss triggered its reveal and scream")
|
||||||
|
# Carry the fleet close enough to frame the complete central body.
|
||||||
|
surface.get_node("Camera2D").global_position = boss.global_position + Vector2(0.0, 250.0)
|
||||||
|
encounter._process(0.0)
|
||||||
if not encounter.reveal_started:
|
if not encounter.reveal_started:
|
||||||
return _fail("bringing the boss into view did not trigger its reveal")
|
return _fail("fully framing the boss did not trigger its reveal")
|
||||||
if not state.encounter_locks_movement:
|
if not state.encounter_locks_movement:
|
||||||
return _fail("the reveal did not briefly lock movement for the cinematic")
|
return _fail("the reveal did not briefly lock movement for the cinematic")
|
||||||
if not encounter.health_bar.visible or encounter.indicator.visible:
|
if not encounter.health_bar.visible or encounter.indicator.visible:
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,16 @@ func _process(delta: float) -> bool:
|
||||||
return _fail("first dash did not consume %d energy" % DASH_COST)
|
return _fail("first dash did not consume %d energy" % DASH_COST)
|
||||||
if first_component.is_ready():
|
if first_component.is_ready():
|
||||||
return _fail("the Propulser that dashed did not enter cooldown")
|
return _fail("the Propulser that dashed did not enter cooldown")
|
||||||
|
# The burst should cover substantially more than a linear quarter of
|
||||||
|
# its distance at a quarter of its duration, then ease into its stop.
|
||||||
|
fleet.set_process(false)
|
||||||
|
fleet._advance_dash(fleet.dash_duration * 0.25)
|
||||||
|
var early_distance: float = fleet.get_centroid().distance_to(first_origin)
|
||||||
|
if early_distance <= DASH_DISTANCE * 0.45 \
|
||||||
|
or early_distance >= DASH_DISTANCE * 0.7:
|
||||||
|
return _fail("dash has no useful ease-out curve (quarter-time distance %.1f)" \
|
||||||
|
% early_distance)
|
||||||
|
fleet.set_process(true)
|
||||||
dash._process(0.0)
|
dash._process(0.0)
|
||||||
if not main.get_node("UI/DashButton").enabled:
|
if not main.get_node("UI/DashButton").enabled:
|
||||||
return _fail("one cooling Propulser disabled a second ready one")
|
return _fail("one cooling Propulser disabled a second ready one")
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ var steer_direction: Vector2 = Vector2.ZERO
|
||||||
# An aimed Propulser burst translates the complete formation as one body.
|
# An aimed Propulser burst translates the complete formation as one body.
|
||||||
var dash_direction: Vector2 = Vector2.ZERO
|
var dash_direction: Vector2 = Vector2.ZERO
|
||||||
var dash_remaining: float = 0.0
|
var dash_remaining: float = 0.0
|
||||||
|
var dash_distance: float = 0.0
|
||||||
|
var dash_elapsed: float = 0.0
|
||||||
|
var dash_duration: float = 0.0
|
||||||
|
|
||||||
const DASH_SPEED: float = 2800.0
|
const DASH_SPEED: float = 2800.0
|
||||||
|
|
||||||
|
|
@ -230,6 +233,9 @@ func teleport_to(world_pos: Vector2) -> void:
|
||||||
steer_direction = Vector2.ZERO
|
steer_direction = Vector2.ZERO
|
||||||
dash_remaining = 0.0
|
dash_remaining = 0.0
|
||||||
dash_direction = Vector2.ZERO
|
dash_direction = Vector2.ZERO
|
||||||
|
dash_distance = 0.0
|
||||||
|
dash_elapsed = 0.0
|
||||||
|
dash_duration = 0.0
|
||||||
fleet_target = world_pos
|
fleet_target = world_pos
|
||||||
anchor = world_pos
|
anchor = world_pos
|
||||||
GameState.fleet_position = get_centroid()
|
GameState.fleet_position = get_centroid()
|
||||||
|
|
@ -259,6 +265,9 @@ func dash(direction: Vector2, distance: float) -> bool:
|
||||||
anchor = get_formation_origin()
|
anchor = get_formation_origin()
|
||||||
dash_direction = direction.normalized()
|
dash_direction = direction.normalized()
|
||||||
dash_remaining = distance
|
dash_remaining = distance
|
||||||
|
dash_distance = distance
|
||||||
|
dash_elapsed = 0.0
|
||||||
|
dash_duration = distance / DASH_SPEED
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -287,7 +296,15 @@ func _process(delta: float) -> void:
|
||||||
|
|
||||||
|
|
||||||
func _advance_dash(delta: float) -> void:
|
func _advance_dash(delta: float) -> void:
|
||||||
var step: float = minf(DASH_SPEED * delta, dash_remaining)
|
var previous_progress: float = dash_elapsed / dash_duration
|
||||||
|
dash_elapsed = minf(dash_elapsed + delta, dash_duration)
|
||||||
|
var progress: float = dash_elapsed / dash_duration
|
||||||
|
# Cubic ease-out gives the Propulser an immediate punch, then lets the
|
||||||
|
# formation settle smoothly at the aimed point. Applying the difference
|
||||||
|
# between eased positions keeps the final distance frame-rate independent.
|
||||||
|
var previous_distance: float = dash_distance * _dash_curve(previous_progress)
|
||||||
|
var target_distance: float = dash_distance * _dash_curve(progress)
|
||||||
|
var step: float = minf(target_distance - previous_distance, dash_remaining)
|
||||||
var displacement: Vector2 = dash_direction * step
|
var displacement: Vector2 = dash_direction * step
|
||||||
dash_remaining -= step
|
dash_remaining -= step
|
||||||
anchor += displacement
|
anchor += displacement
|
||||||
|
|
@ -309,6 +326,11 @@ func _advance_dash(delta: float) -> void:
|
||||||
_assign_slot_targets()
|
_assign_slot_targets()
|
||||||
|
|
||||||
|
|
||||||
|
func _dash_curve(progress: float) -> float:
|
||||||
|
var remaining: float = 1.0 - clampf(progress, 0.0, 1.0)
|
||||||
|
return 1.0 - remaining * remaining * remaining
|
||||||
|
|
||||||
|
|
||||||
func _effective_cruise_speed() -> float:
|
func _effective_cruise_speed() -> float:
|
||||||
return cruise_speed * GameState.fleet_speed_multiplier
|
return cruise_speed * GameState.fleet_speed_multiplier
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue