Compare commits
11 changed files with 14 additions and 451 deletions
|
|
@ -63,7 +63,6 @@ var context_actions_expanded: bool = false
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
get_viewport().size_changed.connect(_on_viewport_resized)
|
||||
_seat_hud_columns()
|
||||
build_button.pressed.connect(_on_build_pressed)
|
||||
action_button.pressed.connect(_on_context_action_pressed)
|
||||
|
|
@ -98,22 +97,6 @@ func _ready() -> void:
|
|||
_layout_context_buttons()
|
||||
|
||||
|
||||
# HudScale reapplies each control's authored offsets whenever the window
|
||||
# changes, which wipes the two layouts this controller writes at runtime.
|
||||
# Re-running them after a resize is what keeps a desktop window that gets
|
||||
# dragged to another size from leaving the context row where it was.
|
||||
func _on_viewport_resized() -> void:
|
||||
# Deferred: HudScale reapplies on the same signal, and which of two
|
||||
# siblings connected first is not a thing to depend on. By the end of the
|
||||
# frame its offsets are in, and these two can seat against them.
|
||||
_relayout_hud.call_deferred()
|
||||
|
||||
|
||||
func _relayout_hud() -> void:
|
||||
_seat_hud_columns()
|
||||
_layout_context_buttons()
|
||||
|
||||
|
||||
func _seat_hud_columns() -> void:
|
||||
var readout: ResourceReadout = get_node_or_null("../UI/ResourceReadout")
|
||||
if readout == null:
|
||||
|
|
@ -126,10 +109,7 @@ func _seat_hud_columns() -> void:
|
|||
highest = minf(highest, button.position.y)
|
||||
if is_inf(highest):
|
||||
return
|
||||
# The margin is authored in the same canvas units as everything else, so
|
||||
# it scales with the HUD (#228) or it would be the one gap that stayed
|
||||
# phone-sized on a desktop.
|
||||
var shift: float = readout.content_bottom() + HUD_COLUMN_MARGIN * readout.scale.y - highest
|
||||
var shift: float = readout.content_bottom() + HUD_COLUMN_MARGIN - highest
|
||||
if shift <= 0.0:
|
||||
return
|
||||
for button in buttons:
|
||||
|
|
@ -242,14 +222,7 @@ func _clear_detected_context() -> void:
|
|||
|
||||
|
||||
func _layout_context_buttons() -> void:
|
||||
# position is in the UI layer's space and size is in the button's own,
|
||||
# and since #228 those differ by the HUD scale on a short canvas. Every
|
||||
# gap and stride below is therefore scaled, while the RECTS keep their
|
||||
# authored size - HudScale renders them down rather than shrinking them.
|
||||
# Mixing the two is what this function got wrong twice before (see
|
||||
# test_hud_layout), and a desktop canvas makes it wrong again.
|
||||
var k: float = build_button.scale.y
|
||||
var row_top: float = build_button.position.y + build_button.size.y * k + HUD_ROW_GAP * k
|
||||
var row_top: float = build_button.position.y + build_button.size.y + HUD_ROW_GAP
|
||||
var row_height: float = build_button.size.y
|
||||
action_button.offset_top = row_top
|
||||
action_button.offset_bottom = row_top + row_height
|
||||
|
|
@ -261,8 +234,8 @@ func _layout_context_buttons() -> void:
|
|||
for i in range(buttons.size()):
|
||||
var button: Control = buttons[i]
|
||||
button.visible = true
|
||||
button.offset_left = (188.0 + float(i) * 164.0) * k
|
||||
button.offset_right = button.offset_left + 148.0
|
||||
button.offset_left = 188 + i * 164
|
||||
button.offset_right = button.offset_left + 148
|
||||
button.offset_top = row_top
|
||||
button.offset_bottom = row_top + row_height
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ const CATALOG_CHECK = preload("res://core/CatalogCheck.gd")
|
|||
# survive the trip has to live here instead.
|
||||
|
||||
var has_return_position: bool = false
|
||||
# Set by the menu when a save has just been loaded, cleared by whoever acts
|
||||
# on it - the same one-shot handoff as has_return_position above, and
|
||||
# deliberately NOT saved: it describes this launch, not the run.
|
||||
var resumed_from_save: bool = false
|
||||
var surviving_ship_count: int = STARTING_SHIP_COUNT
|
||||
var surviving_vessel_types: PackedStringArray = PackedStringArray()
|
||||
# #106: a mirror of what is flying, pushed by Fleet._sync_composition_state()
|
||||
|
|
|
|||
|
|
@ -39,23 +39,6 @@ func _ready() -> void:
|
|||
camera.position = GameState.return_world_position
|
||||
camera.zoom = Vector2(GameState.return_camera_zoom, GameState.return_camera_zoom)
|
||||
GameState.has_return_position = false
|
||||
elif GameState.resumed_from_save:
|
||||
# #227: a resumed run used to fall through to the brand-new-game
|
||||
# branch below, which put the STARTING fleet at the world origin -
|
||||
# so loading a save stranded the player in empty space with the
|
||||
# wrong ships while their base sat wherever they had left it.
|
||||
#
|
||||
# The mothership is where a wiped-out fleet is already rebuilt (see
|
||||
# BuildController._check_fleet_wipe), so it is the run's home whether
|
||||
# or not this is a resume. Falls back to the position the save was
|
||||
# written at, and only then to the origin, because a run can have no
|
||||
# mothership standing.
|
||||
var home: Variant = GameState.get_mothership_position()
|
||||
var at: Vector2 = home if home != null else GameState.fleet_position
|
||||
fleet.spawn_ships(GameState.fleet_vessel_types.size(), at,
|
||||
GameState.fleet_vessel_types)
|
||||
camera.position = at
|
||||
GameState.resumed_from_save = false
|
||||
elif GameState.intro_active:
|
||||
# #108: the prologue opens with a convoy, and crews it itself. This
|
||||
# node still runs first, so spawning the ordinary opening fleet here
|
||||
|
|
|
|||
|
|
@ -83,12 +83,8 @@ func _process(delta: float) -> bool:
|
|||
controller.transfer_button.visible = controller._has_transfer_context()
|
||||
controller._layout_context_buttons()
|
||||
var buttons: Array = [controller.building_action_button, controller.hangar_button]
|
||||
# Rendered width, not the authored rect: #228 shrinks the HUD on a
|
||||
# short canvas by scaling the node and leaving its rect alone, so
|
||||
# offset_right overstates where a button actually ends.
|
||||
for i in range(buttons.size() - 1):
|
||||
var ends: float = buttons[i].offset_left + buttons[i].size.x * buttons[i].scale.x
|
||||
if ends > buttons[i + 1].offset_left:
|
||||
if buttons[i].offset_right > buttons[i + 1].offset_left:
|
||||
_fail("context buttons %d and %d overlap" % [i, i + 1])
|
||||
return true
|
||||
controller._collapse_context_actions()
|
||||
|
|
|
|||
|
|
@ -31,9 +31,6 @@ var controller: Node
|
|||
var readout: Node
|
||||
var stage: int = 0
|
||||
var elapsed: float = 0.0
|
||||
# A desktop canvas (1080 units) and a phone one (3413), where the HUD
|
||||
# scale is 0.45 and 1.0 respectively.
|
||||
var shapes: Array = [Vector2i(1920, 1080), Vector2i(720, 1280)]
|
||||
|
||||
# Deliberately share a row: a building either offers its own action or a
|
||||
# hangar, never both, so their rects coinciding is the design.
|
||||
|
|
@ -55,40 +52,23 @@ func _process(delta: float) -> bool:
|
|||
return false
|
||||
scene.get_node("AsteroidField").set_process(false)
|
||||
scene.get_node("EnemySpawner").set_process(false)
|
||||
# --- E. at BOTH shapes ---------------------------------------
|
||||
# #228 shrinks the HUD on a short canvas, so every relationship
|
||||
# here now has a scale in it. A desktop canvas is what the
|
||||
# default viewport gives, and the phone is where that scale is
|
||||
# 1.0 - the arithmetic has to hold at both or one of them is
|
||||
# being checked by accident.
|
||||
root.size = shapes.pop_front()
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
if elapsed < 0.2:
|
||||
return false
|
||||
elapsed = 0.0
|
||||
var label: String = "%.0f-unit canvas" % readout.get_viewport_rect().size.y
|
||||
if not _check("as laid out at load, %s" % label):
|
||||
if not _check("as laid out at load"):
|
||||
return true
|
||||
|
||||
# --- C. the path that broke it ------------------------------
|
||||
controller.context_actions_expanded = false
|
||||
controller._layout_context_buttons()
|
||||
if not _check("collapsed, %s" % label):
|
||||
if not _check("after _layout_context_buttons() collapsed"):
|
||||
return true
|
||||
|
||||
controller.context_actions_expanded = true
|
||||
controller._layout_context_buttons()
|
||||
if not _check("expanded, %s" % label):
|
||||
if not _check("after _layout_context_buttons() expanded"):
|
||||
return true
|
||||
|
||||
if shapes.is_empty():
|
||||
print("TEST_HUD_LAYOUT: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
root.size = shapes.pop_front()
|
||||
return false
|
||||
print("TEST_HUD_LAYOUT: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
|
|
@ -130,21 +110,13 @@ func _check(context: String) -> bool:
|
|||
var b: Control = buttons[b_name]
|
||||
if a == null or b == null:
|
||||
continue
|
||||
if _rendered(a).intersects(_rendered(b)):
|
||||
if a.get_rect().intersects(b.get_rect()):
|
||||
return _fail("%s %s overlaps %s %s (%s)" \
|
||||
% [a_name, _rendered(a), b_name, _rendered(b), context])
|
||||
% [a_name, a.get_rect(), b_name, b.get_rect(), context])
|
||||
print("clear of the readout (reaches %.0f) and of each other - %s" % [readout_bottom, context])
|
||||
return true
|
||||
|
||||
|
||||
# Where a control actually lands, which since #228 is not its layout rect:
|
||||
# the HUD shrinks on a short canvas by scaling the node and leaving the rect
|
||||
# at its authored size, so position is in scaled parent units while size is
|
||||
# in unscaled local ones.
|
||||
func _rendered(c: Control) -> Rect2:
|
||||
return Rect2(c.position, c.size * c.scale)
|
||||
|
||||
|
||||
func _is_exclusive(a: String, b: String) -> bool:
|
||||
for pair in EXCLUSIVE_PAIRS:
|
||||
if (a == pair[0] and b == pair[1]) or (a == pair[1] and b == pair[0]):
|
||||
|
|
|
|||
|
|
@ -1,118 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
# Forgejo #228: the HUD must not be three times larger on a PC than on a
|
||||
# phone.
|
||||
#
|
||||
# The layout is authored in CANVAS units. With stretch "canvas_items" and
|
||||
# aspect "expand" over a 1920x1080 base, a 720x1280 phone scales by 0.375 and
|
||||
# gets 1920x3413 canvas units, while ANY 16:9-or-wider screen lands on
|
||||
# 1920x1080 units however many pixels it really has. The canvas is three
|
||||
# times shorter, so an unscaled 148-unit button goes from 4.3% of the
|
||||
# screen's height to 13.7% of it.
|
||||
#
|
||||
# Nothing collides - that was checked pair by pair on both canvases and the
|
||||
# same six pairs overlap on each, all of them same-slot alternatives. The
|
||||
# fault is purely that everything is enormous.
|
||||
#
|
||||
# Covers:
|
||||
# A. on a phone the HUD is left exactly as authored
|
||||
# B. on a desktop canvas a control takes a similar FRACTION of the screen
|
||||
# rather than three times more
|
||||
# C. the shrink is bounded, so a landscape phone keeps thumb-sized targets
|
||||
# D. it follows a resize rather than only applying once at startup
|
||||
|
||||
const PHONE: Vector2i = Vector2i(720, 1280)
|
||||
const DESKTOP: Vector2i = Vector2i(1920, 1080)
|
||||
const BIG_DESKTOP: Vector2i = Vector2i(2560, 1440)
|
||||
|
||||
var scene: Node
|
||||
var hud: Node
|
||||
var button: Control
|
||||
var frames: int = 0
|
||||
var stage: int = 0
|
||||
var phone_fraction: float = 0.0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var gs: Node = root.get_node("GameState")
|
||||
gs.buildings.clear()
|
||||
gs.reset_new_game()
|
||||
gs.intro_active = false
|
||||
scene = load("res://world/Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
current_scene = scene
|
||||
hud = scene.get_node("UI/HudScale")
|
||||
button = scene.get_node("UI/MapButton")
|
||||
|
||||
|
||||
func _fraction() -> float:
|
||||
# Height of a HUD control as a share of the screen's height - the number
|
||||
# that actually differs between the two, and the one a player sees.
|
||||
# Rendered height, not the layout rect: the rect keeps its authored size
|
||||
# and the node is scaled, so scale is where the shrink actually lives.
|
||||
return (button.size.y * button.scale.y) / button.get_viewport_rect().size.y
|
||||
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
frames += 1
|
||||
if frames < 10:
|
||||
return false
|
||||
frames = 0
|
||||
match stage:
|
||||
0:
|
||||
root.size = PHONE
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
# --- A. authored size on the shape it was authored for -------
|
||||
var k: float = hud.hud_scale()
|
||||
if not is_equal_approx(k, 1.0):
|
||||
return _fail("the phone HUD is being scaled by %.3f, but it is the authored shape" % k)
|
||||
phone_fraction = _fraction()
|
||||
print("phone %s: canvas %.0f tall, scale %.2f, control is %.1f%% of the screen" % [
|
||||
PHONE, button.get_viewport_rect().size.y, k, phone_fraction * 100.0])
|
||||
root.size = DESKTOP
|
||||
stage = 2
|
||||
return false
|
||||
2:
|
||||
# --- B. a similar share of a desktop screen ------------------
|
||||
var pc_fraction: float = _fraction()
|
||||
var ratio: float = pc_fraction / maxf(phone_fraction, 0.0001)
|
||||
print("desktop %s: canvas %.0f tall, scale %.2f, control is %.1f%% of the screen (%.2fx the phone)" % [
|
||||
DESKTOP, button.get_viewport_rect().size.y, hud.hud_scale(),
|
||||
pc_fraction * 100.0, ratio])
|
||||
if ratio > 1.75:
|
||||
return _fail("a HUD control is %.2fx its phone share on a desktop screen - that is the bug" % ratio)
|
||||
# --- C. and not shrunk into uselessness ----------------------
|
||||
if hud.hud_scale() < 0.4:
|
||||
return _fail("the HUD shrank to %.2f, past anything pressable" % hud.hud_scale())
|
||||
root.size = BIG_DESKTOP
|
||||
stage = 3
|
||||
return false
|
||||
3:
|
||||
# --- D. follows the resize -----------------------------------
|
||||
var big: float = _fraction()
|
||||
print("big desktop %s: control is %.1f%% of the screen" % [BIG_DESKTOP, big * 100.0])
|
||||
if absf(big - phone_fraction) / maxf(phone_fraction, 0.0001) > 0.75:
|
||||
return _fail("a bigger desktop screen did not keep the control proportional (%.1f%% vs %.1f%%)" \
|
||||
% [big * 100.0, phone_fraction * 100.0])
|
||||
# Back to the phone: the HUD must return, not stay shrunk.
|
||||
root.size = PHONE
|
||||
stage = 4
|
||||
return false
|
||||
4:
|
||||
if not is_equal_approx(hud.hud_scale(), 1.0):
|
||||
return _fail("returning to the phone shape left the HUD at %.3f" % hud.hud_scale())
|
||||
if absf(_fraction() - phone_fraction) > 0.001:
|
||||
return _fail("the HUD did not come back to its authored size on resize")
|
||||
print("resizing back to the phone restored the authored size")
|
||||
print("TEST_HUD_SCALES_TO_SCREEN: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _fail(message: String) -> bool:
|
||||
print("TEST_HUD_SCALES_TO_SCREEN: FAIL (%s)" % message)
|
||||
quit(1)
|
||||
return true
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
# Forgejo #227: a resumed run comes back at its own mothership.
|
||||
#
|
||||
# Loading a save used to fall through to the brand-new-game branch in
|
||||
# PlanetAtmosphereController, which spawns STARTING_SHIP_COUNT interceptors
|
||||
# at the world origin. So a resume stranded the player in empty space with
|
||||
# the wrong ships, while their base sat wherever they had left it - which on
|
||||
# a charted map is thousands of units away.
|
||||
#
|
||||
# Covers:
|
||||
# A. resuming puts the fleet at the mothership, not at the origin
|
||||
# B. and crews it with the ships the save actually had
|
||||
# C. with no mothership standing, it falls back to where the save was made
|
||||
# D. a NEW game is untouched - still the starting fleet at the origin
|
||||
|
||||
const HOME: Vector2 = Vector2(7400.0, -3100.0)
|
||||
const AWAY: Vector2 = Vector2(-5200.0, 900.0)
|
||||
|
||||
var game_state: Node
|
||||
var scene: Node
|
||||
var elapsed: float = 0.0
|
||||
var stage: int = 0
|
||||
var expected_crew: int = 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
game_state = root.get_node("GameState")
|
||||
SaveGame.clear()
|
||||
|
||||
|
||||
func _fresh_state() -> void:
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
game_state.reset_progression()
|
||||
game_state.reset_new_game()
|
||||
game_state.reset_quests()
|
||||
game_state.intro_active = false
|
||||
|
||||
|
||||
func _load_scene() -> void:
|
||||
if scene != null and is_instance_valid(scene):
|
||||
scene.free()
|
||||
scene = load("res://world/Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
current_scene = scene
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
elapsed += delta
|
||||
match stage:
|
||||
0:
|
||||
if elapsed < 0.3:
|
||||
return false
|
||||
# A run with a base at HOME and the fleet parked far from it.
|
||||
_fresh_state()
|
||||
game_state.add_building(GameState.BUILDING_MOTHERSHIP, HOME)
|
||||
# Capacity for the crew below. Since #219 the fleet holds
|
||||
# BASE_FLEET_SIZE (1) plus one berth per supplied fleet office,
|
||||
# so a save with three ships flying is a save that had two
|
||||
# offices standing - without them spawn_ships clamps to 1 and
|
||||
# this would assert nothing.
|
||||
game_state.add_building(GameState.BUILDING_FLEET_OFFICE, HOME + Vector2(400.0, 0.0))
|
||||
game_state.add_building(GameState.BUILDING_FLEET_OFFICE, HOME + Vector2(800.0, 0.0))
|
||||
game_state.fleet_vessel_types = PackedStringArray([
|
||||
GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_INTERCEPTOR,
|
||||
GameState.VESSEL_INTERCEPTOR])
|
||||
game_state.fleet_position = AWAY
|
||||
expected_crew = game_state.fleet_vessel_types.size()
|
||||
if game_state.max_fleet_size() < expected_crew:
|
||||
return _fail("the fleet cannot hold %d, so a resumed crew would prove nothing (cap %d)" \
|
||||
% [expected_crew, game_state.max_fleet_size()])
|
||||
if not SaveGame.save(game_state):
|
||||
return _fail("could not write the save this test resumes from")
|
||||
_fresh_state()
|
||||
var result: Dictionary = SaveGame.load_into(game_state)
|
||||
if not bool(result.get("ok", false)):
|
||||
return _fail("the save would not load: %s" % result.get("reason", ""))
|
||||
game_state.resumed_from_save = true
|
||||
_load_scene()
|
||||
elapsed = 0.0
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
if elapsed < 0.6:
|
||||
return false
|
||||
var fleet: Node2D = scene.get_node("Fleet")
|
||||
# --- A. at the mothership, not the origin -------------------
|
||||
var at: Vector2 = fleet.get_centroid()
|
||||
if at.distance_to(HOME) > 400.0:
|
||||
return _fail("resumed at %s, but the mothership is at %s" % [at, HOME])
|
||||
if at.distance_to(Vector2.ZERO) < 400.0:
|
||||
return _fail("resumed at the world origin, which is the new-game behaviour")
|
||||
print("resumed at the mothership (%.0f units from it)" % at.distance_to(HOME))
|
||||
|
||||
# --- B. with the crew the save had --------------------------
|
||||
if fleet.ships.size() != expected_crew:
|
||||
return _fail("resumed with %d ships, the save had %d" \
|
||||
% [fleet.ships.size(), expected_crew])
|
||||
print("resumed with the saved crew of %d" % fleet.ships.size())
|
||||
if game_state.resumed_from_save:
|
||||
return _fail("the resume flag was left raised, so the next scene would re-use it")
|
||||
|
||||
# --- C. no mothership: fall back to where the save was made --
|
||||
_fresh_state()
|
||||
game_state.fleet_vessel_types = PackedStringArray([GameState.VESSEL_INTERCEPTOR])
|
||||
game_state.fleet_position = AWAY
|
||||
game_state.resumed_from_save = true
|
||||
_load_scene()
|
||||
elapsed = 0.0
|
||||
stage = 2
|
||||
return false
|
||||
2:
|
||||
if elapsed < 0.6:
|
||||
return false
|
||||
var fleet2: Node2D = scene.get_node("Fleet")
|
||||
var at2: Vector2 = fleet2.get_centroid()
|
||||
if at2.distance_to(AWAY) > 400.0:
|
||||
return _fail("with no mothership, resumed at %s rather than the saved %s" % [at2, AWAY])
|
||||
print("with no mothership, resumed where the save was written")
|
||||
|
||||
# --- D. a new game is untouched -----------------------------
|
||||
_fresh_state()
|
||||
game_state.resumed_from_save = false
|
||||
_load_scene()
|
||||
elapsed = 0.0
|
||||
stage = 3
|
||||
return false
|
||||
3:
|
||||
if elapsed < 0.6:
|
||||
return false
|
||||
var fleet3: Node2D = scene.get_node("Fleet")
|
||||
if fleet3.get_centroid().distance_to(Vector2.ZERO) > 400.0:
|
||||
return _fail("a new game no longer starts at the origin (%s)" % fleet3.get_centroid())
|
||||
if fleet3.ships.size() != GameState.STARTING_SHIP_COUNT:
|
||||
return _fail("a new game starts with %d ships, expected %d" \
|
||||
% [fleet3.ships.size(), GameState.STARTING_SHIP_COUNT])
|
||||
print("a new game still starts with %d ships at the origin" % fleet3.ships.size())
|
||||
|
||||
SaveGame.clear()
|
||||
print("TEST_RESUME_SPAWN: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _fail(message: String) -> bool:
|
||||
print("TEST_RESUME_SPAWN: FAIL (%s)" % message)
|
||||
quit(1)
|
||||
return true
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
extends Node
|
||||
|
||||
# Forgejo #228: the HUD is proportionally three times too big on a PC.
|
||||
#
|
||||
# The layout is authored in CANVAS units against a portrait phone. With
|
||||
# stretch "canvas_items"/"expand" and a 1920x1080 base, a 720x1280 phone
|
||||
# scales by 0.375 and so gets a canvas 1920x3413 units tall - while ANY
|
||||
# 16:9-or-wider screen lands on 1920x1080 units whatever its pixel count.
|
||||
# The canvas is three times shorter, so a 148-unit button goes from 4.3% of
|
||||
# the screen's height to 13.7% of it. Nothing collides; everything is just
|
||||
# enormous, and the thumb sticks in particular.
|
||||
#
|
||||
# Each HUD element's offsets are measured FROM ITS OWN ANCHOR, so scaling
|
||||
# them scales the element's size and its distance from the edge it hangs off
|
||||
# together. That is the whole trick: one multiplier per control, no layout
|
||||
# rules to re-derive, and anchoring keeps working untouched.
|
||||
|
||||
# The canvas the HUD was drawn for. Not a magic number: 1280 / (720 / 1920).
|
||||
const AUTHORED_CANVAS_HEIGHT: float = 3413.0
|
||||
|
||||
# Below this the controls stop being worth pressing. A landscape phone gets
|
||||
# the same short canvas as a desktop, and there the sticks are still thumb
|
||||
# targets - so the shrink is bounded rather than purely proportional.
|
||||
const MIN_SCALE: float = 0.45
|
||||
|
||||
# Authored offsets and sizes, captured before anything is touched.
|
||||
# Re-reading them after a resize would measure a previous scaling and let the
|
||||
# HUD ratchet away to nothing - the same trap NeonPanel documents.
|
||||
var _authored: Dictionary = {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var layer: Node = get_parent()
|
||||
for child in layer.get_children():
|
||||
if child == self or not (child is Control):
|
||||
continue
|
||||
var c: Control = child
|
||||
# Full-bleed things - the modal menus, the breach overlay - hang off
|
||||
# every edge at once and have nothing to shrink.
|
||||
if is_equal_approx(c.anchor_left, 0.0) and is_equal_approx(c.anchor_right, 1.0) \
|
||||
and is_equal_approx(c.anchor_top, 0.0) and is_equal_approx(c.anchor_bottom, 1.0):
|
||||
continue
|
||||
_authored[c] = [c.offset_left, c.offset_top,
|
||||
c.offset_right - c.offset_left, c.offset_bottom - c.offset_top]
|
||||
get_viewport().size_changed.connect(_apply)
|
||||
_apply()
|
||||
|
||||
|
||||
func hud_scale() -> float:
|
||||
var height: float = get_viewport().get_visible_rect().size.y
|
||||
if height <= 0.0:
|
||||
return 1.0
|
||||
# Only ever shrinks. A canvas TALLER than the phone's is not a reason to
|
||||
# inflate the HUD over the game.
|
||||
return clampf(height / AUTHORED_CANVAS_HEIGHT, MIN_SCALE, 1.0)
|
||||
|
||||
|
||||
func _apply() -> void:
|
||||
var k: float = hud_scale()
|
||||
for control in _authored:
|
||||
if not is_instance_valid(control):
|
||||
continue
|
||||
var a: Array = _authored[control]
|
||||
var c: Control = control
|
||||
# Position moves toward the edge the element hangs off; the RECT keeps
|
||||
# its authored size and the node is scaled instead.
|
||||
#
|
||||
# Scaling the rect alone was wrong: half the HUD draws its own
|
||||
# contents at fixed canvas sizes - ResourceReadout lays its rows out
|
||||
# in code - so a shrunken rect kept full-size content inside it, and
|
||||
# the build button slid up underneath the resource list. Scaling the
|
||||
# node takes the drawn content with it.
|
||||
c.pivot_offset = Vector2.ZERO
|
||||
c.offset_left = float(a[0]) * k
|
||||
c.offset_top = float(a[1]) * k
|
||||
c.offset_right = c.offset_left + float(a[2])
|
||||
c.offset_bottom = c.offset_top + float(a[3])
|
||||
c.scale = Vector2(k, k)
|
||||
|
|
@ -140,7 +140,6 @@ func _on_continue_pressed() -> void:
|
|||
# A resumed run never replays the prologue, whatever it was doing when
|
||||
# it was written.
|
||||
GameState.intro_active = false
|
||||
GameState.resumed_from_save = true
|
||||
_start("res://world/Main.tscn")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -79,13 +79,7 @@ func rows_height() -> float:
|
|||
# Bottom edge of the rows in this Control's parent space - the line anything
|
||||
# stacked below the readout has to clear.
|
||||
func content_bottom() -> float:
|
||||
# Rendered, not laid out. #228 scales the HUD down on a short canvas by
|
||||
# scaling the NODE while leaving its rect at the authored size, so
|
||||
# rows_height() is in unscaled local units while position is already in
|
||||
# scaled parent ones. Mixing the two overstated this by more than a
|
||||
# button's height and pushed the columns down to clear space that was
|
||||
# not occupied.
|
||||
return position.y + rows_height() * scale.y
|
||||
return position.y + rows_height()
|
||||
|
||||
|
||||
func _add_glyph(resource_type: String, at: Vector2) -> void:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
|
||||
[ext_resource type="Script" path="res://world/Starfield.gd" id="1"]
|
||||
[ext_resource type="Script" path="res://ui/HudScale.gd" id="hud_scale"]
|
||||
[ext_resource type="Script" path="res://vessels/Fleet.gd" id="2"]
|
||||
[ext_resource type="Script" path="res://controllers/GameController.gd" id="3"]
|
||||
[ext_resource type="Script" path="res://enemies/EnemySpawner.gd" id="4"]
|
||||
|
|
@ -152,9 +151,6 @@ script = ExtResource("12")
|
|||
[node name="UI" type="CanvasLayer" parent="."]
|
||||
layer = 1
|
||||
|
||||
[node name="HudScale" type="Node" parent="UI"]
|
||||
script = ExtResource("hud_scale")
|
||||
|
||||
[node name="ResourceReadout" type="Control" parent="UI"]
|
||||
mouse_filter = 2
|
||||
offset_left = 28
|
||||
|
|
|
|||
Loading…
Reference in a new issue