Twin-stick combat: aim and fire with a second stick, MOBA-style ability ring
The fleet's guns picked their own targets and fired on their own, so combat happened whether or not the player did anything - there was nothing to aim and nothing to decide, and the right half of the screen held nothing at all. Firing is now an action. VesselWeaponComponent no longer seeks a target; it shoots along a heading, and the only thing that supplies one is the new right-hand stick via FireStickController. A fleet arriving from another scene starts with its guns silent, which is the correct resting state. A subtle dashed guide runs from the fleet along the aimed heading while the stick is held, out to the guns' OWN range - read from a weapon that is actually aboard, so the line can never promise reach the fleet does not have. Layout: the two sticks take the bottom corners, the pivot pair is centred between them at the bottom edge, and the laser and gas bomb sit on an arc up and left of the fire stick where a thumb already is. Those four are now round - IconButton grew a round_frame mode, and the in-world action controls use it while panel and menu buttons stay square, so the two kinds of button read as different kinds of thing. Worth recording, because it cost a screenshot to find: a script-declared property set BEFORE the `script = ExtResource(...)` line in a .tscn is silently dropped. Godot's parser has no idea the property exists yet. The round frames were all correctly set in both scenes and none of them applied. Also fixed researtch_lab_garrison.tres, which was authored under the void profile - that is the black hole surface, so a lab garrison was spawning on black holes. Suite 110/110. test_enemy_combat's whole premise was auto-fire; it now holds the real stick toward the enemy rather than poking weapon components, so it exercises the path the player uses. test_fire_stick is new and covers the change itself: an idle stick leaves a target parked well inside range untouched, which is precisely what the old gun would never have done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8ba2cca6b6
commit
0fb8f9300b
15 changed files with 561 additions and 131 deletions
|
|
@ -136,6 +136,33 @@ func _gui_input(event: InputEvent) -> void:
|
|||
# delivers releases keeps it.
|
||||
@export var fire_on_press: bool = OS.has_feature("web")
|
||||
|
||||
# Round rather than square. The in-world action controls - the abilities
|
||||
# arranged around the fire stick, and the pivot pair - read as a different
|
||||
# kind of thing from the square panel and menu buttons, and a round target is
|
||||
# what a thumb reaching for an ability expects. Purely how the frame is drawn;
|
||||
# the hit area is still the whole rect.
|
||||
@export var round_frame: bool = false
|
||||
|
||||
|
||||
# The frame outline, inset by `inset` pixels on every side. Round buttons trace
|
||||
# a circle through the same inset box, so a round and a square button of the
|
||||
# same size present the same footprint.
|
||||
func _frame_points(inset: float) -> PackedVector2Array:
|
||||
if not round_frame:
|
||||
return PackedVector2Array([
|
||||
Vector2(inset, inset),
|
||||
Vector2(size.x - inset, inset),
|
||||
Vector2(size.x - inset, size.y - inset),
|
||||
Vector2(inset, size.y - inset),
|
||||
])
|
||||
var centre: Vector2 = size / 2.0
|
||||
var radius: Vector2 = centre - Vector2(inset, inset)
|
||||
var points: PackedVector2Array = PackedVector2Array()
|
||||
for i in range(32):
|
||||
var angle: float = TAU * float(i) / 32.0
|
||||
points.append(centre + Vector2(cos(angle) * radius.x, sin(angle) * radius.y))
|
||||
return points
|
||||
|
||||
|
||||
func _handle_press(is_pressed: bool, at_position: Vector2) -> void:
|
||||
if is_pressed:
|
||||
|
|
@ -167,23 +194,13 @@ func _draw() -> void:
|
|||
var pulse: float = 1.0 if pressed_down else 0.85
|
||||
|
||||
var margin: float = 4.0 * s
|
||||
var square: PackedVector2Array = PackedVector2Array([
|
||||
Vector2(margin, margin),
|
||||
Vector2(rect_size.x - margin, margin),
|
||||
Vector2(rect_size.x - margin, rect_size.y - margin),
|
||||
Vector2(margin, rect_size.y - margin),
|
||||
])
|
||||
NeonDraw.draw_glowing_polygon(self, square, _active_color(), pulse, core_color)
|
||||
NeonDraw.draw_glowing_polygon(self, _frame_points(margin), _active_color(), pulse, core_color)
|
||||
if highlighted:
|
||||
# The inset frame reads as a selection focus ring. It deliberately uses
|
||||
# the bright core rather than the button's glow colour, so selecting a
|
||||
# blue shield or pink transporter does not change what that colour means.
|
||||
var inset: float = 10.0 * s
|
||||
var focus: PackedVector2Array = PackedVector2Array([
|
||||
Vector2(inset, inset), Vector2(rect_size.x - inset, inset),
|
||||
Vector2(rect_size.x - inset, rect_size.y - inset), Vector2(inset, rect_size.y - inset),
|
||||
])
|
||||
NeonDraw.draw_glowing_polygon(self, focus, core_color, 1.0, core_color, 0.72 * s)
|
||||
NeonDraw.draw_glowing_polygon(self, _frame_points(10.0 * s), core_color, 1.0,
|
||||
core_color, 0.72 * s)
|
||||
|
||||
var center: Vector2 = rect_size / 2.0
|
||||
match icon:
|
||||
|
|
|
|||
Loading…
Reference in a new issue