Make research tree vertically scrollable
This commit is contained in:
parent
06081c41b0
commit
13492a52e2
6 changed files with 70 additions and 13 deletions
|
|
@ -282,6 +282,8 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
if skill_menu.visible:
|
||||
if event is InputEventScreenTouch:
|
||||
skill_tree.handle_touch(event)
|
||||
elif event is InputEventScreenDrag:
|
||||
skill_tree.handle_drag(event)
|
||||
# Handled either way. A touch that lands on the panel but misses a
|
||||
# node is still a touch ON THE PANEL - letting it through moved the
|
||||
# fleet and pinch-zoomed the world behind the menu, because
|
||||
|
|
@ -409,6 +411,7 @@ func _on_action_pressed() -> void:
|
|||
|
||||
if current_building.building_type == GameState.BUILDING_MOTHERSHIP:
|
||||
skill_menu.visible = true
|
||||
skill_tree.reset_scroll()
|
||||
_refresh_skill_menu()
|
||||
elif current_building.building_type == GameState.BUILDING_SHIP_CRAFTER:
|
||||
# Remembered explicitly: _process() clears current_building while any
|
||||
|
|
|
|||
10
GameState.gd
10
GameState.gd
|
|
@ -157,7 +157,7 @@ const SKILL_TREE: Array = [
|
|||
},
|
||||
{
|
||||
"id": "coordination", "name": "Coordination", "cost": 20, "requires": "shipyards",
|
||||
"x": 0.82, "unlocks": BUILDING_COMMAND_CENTER,
|
||||
"x": 0.50, "unlocks": BUILDING_COMMAND_CENTER,
|
||||
"desc": "Build command centers, where the fleet formation is arranged.",
|
||||
},
|
||||
{
|
||||
|
|
@ -167,12 +167,12 @@ const SKILL_TREE: Array = [
|
|||
},
|
||||
{
|
||||
"id": "vessel_science", "name": "Vessel Science", "cost": 35, "requires": "shipyards",
|
||||
"x": 0.62, "unlocks": BUILDING_SHIP_LAB,
|
||||
"x": 0.82, "unlocks": BUILDING_SHIP_LAB,
|
||||
"desc": "Build ship labs, where each vessel type is upgraded and new ones unlocked.",
|
||||
},
|
||||
{
|
||||
"id": "freight_yards", "name": "Freight Yards", "cost": 80, "requires": "freight",
|
||||
"x": 0.05, "hub_bonus": 1,
|
||||
"x": 0.18, "hub_bonus": 1,
|
||||
"desc": "Permanent: +1 transporter stationed at every hub, including ones already built.",
|
||||
},
|
||||
{
|
||||
|
|
@ -182,12 +182,12 @@ const SKILL_TREE: Array = [
|
|||
},
|
||||
{
|
||||
"id": "squadron", "name": "Squadron Doctrine", "cost": 70, "requires": "wing",
|
||||
"x": 0.65, "fleet_bonus": 1,
|
||||
"x": 0.50, "fleet_bonus": 1,
|
||||
"desc": "Permanent: +1 vessel in the fleet.",
|
||||
},
|
||||
{
|
||||
"id": "armada", "name": "Armada Doctrine", "cost": 150, "requires": "squadron",
|
||||
"x": 0.3, "fleet_bonus": 1,
|
||||
"x": 0.50, "fleet_bonus": 1,
|
||||
"desc": "Permanent: +1 vessel in the fleet.",
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1556,6 +1556,7 @@ script = ExtResource("24")
|
|||
|
||||
[node name="Tree" type="Control" parent="UI/SkillMenu"]
|
||||
mouse_filter = 2
|
||||
clip_contents = true
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 24.0
|
||||
|
|
|
|||
|
|
@ -28,25 +28,45 @@ const LOCKED_CORE: Color = Color(0.17, 0.19, 0.26)
|
|||
const LOCKED_PULSE: float = 0.5
|
||||
|
||||
@export var node_radius: float = 46.0
|
||||
@export var row_spacing: float = 250.0
|
||||
|
||||
var selected_id: String = ""
|
||||
var scroll_y: float = 0.0
|
||||
var _dragging: bool = false
|
||||
|
||||
|
||||
func _content_height() -> float:
|
||||
# The canvas deliberately exceeds the viewport. Four depths at this
|
||||
# spacing leave clear breathing room around 92px-wide nodes and make each
|
||||
# research branch read as a route rising away from Shipyards.
|
||||
return maxf(size.y, GameState.max_skill_depth() * row_spacing + node_radius * 3.0)
|
||||
|
||||
|
||||
func _max_scroll() -> float:
|
||||
return maxf(0.0, _content_height() - size.y)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
call_deferred("reset_scroll")
|
||||
|
||||
|
||||
func reset_scroll() -> void:
|
||||
# Start at Shipyards, the base of the tree, whenever the menu opens.
|
||||
# The player then swipes up to travel along the branches.
|
||||
scroll_y = _max_scroll()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func node_screen_pos(node: Dictionary) -> Vector2:
|
||||
# Height comes from dependency depth, not from the data: the root sits at
|
||||
# the bottom and each further prerequisite climbs a row, so the tree grows
|
||||
# upwards as it is researched and stays correct when nodes are added.
|
||||
var deepest: int = maxi(GameState.max_skill_depth(), 1)
|
||||
var climb: float = float(GameState.skill_depth(node.id)) / float(deepest)
|
||||
# Inset so a node on row 0 or column 0 is not clipped in half.
|
||||
# Every dependency depth gets its own widely-spaced horizontal row. The
|
||||
# x lane is stable within a branch (set beside SKILL_TREE), so Logistics,
|
||||
# Freight and Freight Yards rise vertically instead of zig-zagging across
|
||||
# the display. scroll_y exposes the part of this tall canvas in view.
|
||||
var margin: float = node_radius * 1.4
|
||||
return Vector2(
|
||||
lerpf(margin, size.x - margin, float(node.x)),
|
||||
lerpf(size.y - margin, margin, climb)
|
||||
_content_height() - margin - float(GameState.skill_depth(node.id)) * row_spacing - scroll_y
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -61,8 +81,12 @@ func _state_color(node: Dictionary) -> Color:
|
|||
# Returns true when the press landed on a node, so the caller knows the
|
||||
# touch was consumed.
|
||||
func handle_touch(event: InputEventScreenTouch) -> bool:
|
||||
if not event.pressed or not visible:
|
||||
if not visible:
|
||||
return false
|
||||
if not event.pressed:
|
||||
_dragging = false
|
||||
return true
|
||||
_dragging = true
|
||||
var local: Vector2 = event.position - global_position
|
||||
for node in GameState.SKILL_TREE:
|
||||
if local.distance_to(node_screen_pos(node)) <= node_radius * 1.25:
|
||||
|
|
@ -73,6 +97,16 @@ func handle_touch(event: InputEventScreenTouch) -> bool:
|
|||
return false
|
||||
|
||||
|
||||
func handle_drag(event: InputEventScreenDrag) -> bool:
|
||||
if not visible:
|
||||
return false
|
||||
# Dragging a finger upward reveals higher research; dragging downward
|
||||
# returns to the root. Clamp hard so the canvas never leaves an empty gap.
|
||||
scroll_y = clampf(scroll_y - event.relative.y, 0.0, _max_scroll())
|
||||
queue_redraw()
|
||||
return true
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
# Links first, so nodes sit on top of them.
|
||||
for node in GameState.SKILL_TREE:
|
||||
|
|
|
|||
1
tests/test_hangar_management.gd.uid
Normal file
1
tests/test_hangar_management.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://xc7cie5ogmtp
|
||||
|
|
@ -105,6 +105,24 @@ func _process(delta: float) -> bool:
|
|||
print("TEST_SKILL_TREE: FAIL (mothership has no research action)")
|
||||
quit(1)
|
||||
return true
|
||||
# The tree is a tall, vertically scrolling canvas. A branch keeps a
|
||||
# stable lane as it rises, and dragging exposes higher depths.
|
||||
var freight: Vector2 = controller.skill_tree.node_screen_pos(game_state.skill_node("freight"))
|
||||
var yards: Vector2 = controller.skill_tree.node_screen_pos(game_state.skill_node("freight_yards"))
|
||||
if absf(freight.x - yards.x) > 1.0 or yards.y >= freight.y:
|
||||
print("TEST_SKILL_TREE: FAIL (freight branch does not rise vertically)")
|
||||
quit(1)
|
||||
return true
|
||||
var before_scroll: float = controller.skill_tree.scroll_y
|
||||
var drag: InputEventScreenDrag = InputEventScreenDrag.new()
|
||||
# Start at the root at the bottom; pull the canvas down to reveal
|
||||
# the branches rising above it.
|
||||
drag.relative = Vector2(0.0, 180.0)
|
||||
controller.skill_tree.handle_drag(drag)
|
||||
if controller.skill_tree.scroll_y >= before_scroll:
|
||||
print("TEST_SKILL_TREE: FAIL (tree did not scroll upward)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
# --- prerequisites --------------------------------------------
|
||||
if game_state.can_unlock("wing"):
|
||||
|
|
|
|||
Loading…
Reference in a new issue