Show the laser's whole reach while aiming
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
This commit is contained in:
parent
571f01dc31
commit
a902fddba0
4 changed files with 70 additions and 7 deletions
|
|
@ -16,6 +16,9 @@ const BEAM_EFFECT = preload("res://vfx/LaserBeamEffect.gd")
|
||||||
const WEAPON_COMPONENT = preload("res://vessels/VesselWeaponComponent.gd")
|
const WEAPON_COMPONENT = preload("res://vessels/VesselWeaponComponent.gd")
|
||||||
const BEAM_COLOR := BEAM_EFFECT.BEAM_COLOR
|
const BEAM_COLOR := BEAM_EFFECT.BEAM_COLOR
|
||||||
const TARGET_COLOR := Color(0.6, 0.95, 1.0)
|
const TARGET_COLOR := Color(0.6, 0.95, 1.0)
|
||||||
|
# #203's range mark and the assist's target ring, in screen pixels.
|
||||||
|
const END_TICK_PIXELS: float = 17.0
|
||||||
|
const TARGET_RING_PIXELS: float = 15.0
|
||||||
const BEAM_HALF_WIDTH := BEAM_EFFECT.BEAM_HALF_WIDTH
|
const BEAM_HALF_WIDTH := BEAM_EFFECT.BEAM_HALF_WIDTH
|
||||||
const FLASH_DURATION := BEAM_EFFECT.FLASH_DURATION
|
const FLASH_DURATION := BEAM_EFFECT.FLASH_DURATION
|
||||||
const ENERGY_PER_SHOT: int = VesselLaserComponent.ENERGY_PER_SHOT
|
const ENERGY_PER_SHOT: int = VesselLaserComponent.ENERGY_PER_SHOT
|
||||||
|
|
@ -114,8 +117,48 @@ func beam_tip(beam: Node2D) -> Vector2:
|
||||||
return beam.tip()
|
return beam.tip()
|
||||||
|
|
||||||
|
|
||||||
|
# #203: the aim view frames the beam's WHOLE reach, not the point the aim
|
||||||
|
# happens to sit on. The assist can settle that point on an enemy a few
|
||||||
|
# hundred units away (#201), and fitting the camera to it would say the shot
|
||||||
|
# stops there. Fitting the far end instead is also what makes the range
|
||||||
|
# visible at all: the beam is 2900 units long and the inherited floor on the
|
||||||
|
# zoom - sized for the gas bomb's 950 - left three quarters of it off screen.
|
||||||
|
func _update_aim_zoom(origin: Vector2, target: Vector2) -> void:
|
||||||
|
super(origin, origin + _aim_heading(origin, target) * _aim_max_range())
|
||||||
|
|
||||||
|
|
||||||
|
func _aim_heading(origin: Vector2, target: Vector2) -> Vector2:
|
||||||
|
var direction: Vector2 = target - origin
|
||||||
|
return direction.normalized() if direction.length() > 0.001 else Vector2.UP
|
||||||
|
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
# Only the aim guide. Each beam draws itself, in its own node.
|
# Only the aim guide. Each beam draws itself, in its own node.
|
||||||
if aiming:
|
if not aiming:
|
||||||
NeonDraw.draw_glowing_line(self, _aim_origin(), aim_target,
|
return
|
||||||
TARGET_COLOR, 0.7, 0.6)
|
var origin: Vector2 = _aim_origin()
|
||||||
|
var reach: float = _aim_max_range()
|
||||||
|
var far_end: Vector2 = origin + _aim_heading(origin, aim_target) * reach
|
||||||
|
# Drawn to the far end rather than to aim_target, for the same reason the
|
||||||
|
# camera frames it: this is the line the beam will occupy (#203).
|
||||||
|
NeonDraw.draw_glowing_line(self, origin, far_end, TARGET_COLOR, 0.7, 0.6)
|
||||||
|
# A cross-tick where it stops, so the reach is a mark on the screen rather
|
||||||
|
# than a line that merely runs out. Sized in SCREEN pixels: framing 2900
|
||||||
|
# units puts the camera far enough out that a mark measured in world units
|
||||||
|
# would be a few pixels of nothing at the moment it matters most.
|
||||||
|
var across: Vector2 = far_end.direction_to(origin).orthogonal() \
|
||||||
|
* _screen_units(END_TICK_PIXELS)
|
||||||
|
NeonDraw.draw_glowing_line(self, far_end - across, far_end + across,
|
||||||
|
TARGET_COLOR, 0.7, 0.5)
|
||||||
|
# What the assist picked, when that is nearer than the far end. The line
|
||||||
|
# still runs past it - everything behind it on the same line is hit too.
|
||||||
|
if origin.distance_to(aim_target) < reach - 1.0:
|
||||||
|
NeonDraw.draw_glowing_circle(self, aim_target,
|
||||||
|
maxf(BEAM_HALF_WIDTH, _screen_units(TARGET_RING_PIXELS)),
|
||||||
|
TARGET_COLOR, 0.7)
|
||||||
|
|
||||||
|
|
||||||
|
# World units that cover a given number of screen pixels at the camera's
|
||||||
|
# current zoom.
|
||||||
|
func _screen_units(pixels: float) -> float:
|
||||||
|
return pixels / maxf(camera.zoom.x, 0.001)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@ func _ready() -> void:
|
||||||
GameState.extractors.clear()
|
GameState.extractors.clear()
|
||||||
GameState.transporters.clear()
|
GameState.transporters.clear()
|
||||||
GameState.vessel_levels["unlock_laser"] = 1
|
GameState.vessel_levels["unlock_laser"] = 1
|
||||||
|
GameState.intro_active = false
|
||||||
|
# A shot the fleet cannot pay for is refused before aiming starts, so
|
||||||
|
# without this the preview drew no guide at all - the state it exists
|
||||||
|
# to show. Deposited before Main is built, like test_laser_vessel.gd.
|
||||||
|
GameState.deposit_fleet_resource(GameState.RESOURCE_ENERGY, 40)
|
||||||
var main: Node = load("res://world/Main.tscn").instantiate()
|
var main: Node = load("res://world/Main.tscn").instantiate()
|
||||||
add_child(main)
|
add_child(main)
|
||||||
main.get_node("BackgroundLayer").visible = false
|
main.get_node("BackgroundLayer").visible = false
|
||||||
|
|
@ -21,14 +26,17 @@ func _ready() -> void:
|
||||||
main.get_node("EnemySpawner").set_process(false)
|
main.get_node("EnemySpawner").set_process(false)
|
||||||
main.get_node("AsteroidField").set_process(false)
|
main.get_node("AsteroidField").set_process(false)
|
||||||
var fleet: Node = main.get_node("Fleet")
|
var fleet: Node = main.get_node("Fleet")
|
||||||
fleet.spawn_ships(4, Vector2.ZERO, PackedStringArray([
|
# Three, not four: the starting fleet holds three hulls until the skill
|
||||||
GameState.VESSEL_INTERCEPTOR, GameState.VESSEL_SHIELD,
|
# tree widens it, and a fourth entry was silently dropped - which took the
|
||||||
GameState.VESSEL_LASER, GameState.VESSEL_LASER]))
|
# ready laser with it and left this preview with nothing to aim.
|
||||||
|
fleet.spawn_ships(3, Vector2.ZERO, PackedStringArray([
|
||||||
|
GameState.VESSEL_SHIELD, GameState.VESSEL_LASER,
|
||||||
|
GameState.VESSEL_LASER]))
|
||||||
var skill: Node = main.get_node("LaserController")
|
var skill: Node = main.get_node("LaserController")
|
||||||
skill._process(0.0)
|
skill._process(0.0)
|
||||||
# Show both per-ship states in one frame: one laser ship rearming, the
|
# Show both per-ship states in one frame: one laser ship rearming, the
|
||||||
# other ready and supplying the shot.
|
# other ready and supplying the shot.
|
||||||
var rearming: Node = fleet.ships[2].get_component("laser")
|
var rearming: Node = fleet.ships[1].get_component("laser")
|
||||||
rearming.consume_shot()
|
rearming.consume_shot()
|
||||||
rearming.tick(rearming.cooldown_duration * 0.5)
|
rearming.tick(rearming.cooldown_duration * 0.5)
|
||||||
main.get_node("Camera2D").zoom = Vector2(1.8, 1.8)
|
main.get_node("Camera2D").zoom = Vector2(1.8, 1.8)
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,17 @@ func _process(delta: float) -> bool:
|
||||||
return _fail_now("aiming did not zoom out")
|
return _fail_now("aiming did not zoom out")
|
||||||
print("aiming zoomed the camera out")
|
print("aiming zoomed the camera out")
|
||||||
|
|
||||||
|
# #203: far enough out to show the WHOLE beam. The drag above is
|
||||||
|
# horizontal, so the screen's narrow half is the binding one.
|
||||||
|
var half_screen: Vector2 = skill.get_viewport_rect().size * 0.5 \
|
||||||
|
- Vector2(54.0, 54.0)
|
||||||
|
var framed: float = (2900.0 + BEAM_HALF_WIDTH) \
|
||||||
|
* main.get_node("Camera2D").zoom.x
|
||||||
|
if framed > half_screen.x + 1.0:
|
||||||
|
return _fail_now("aiming framed %.0fpx of beam on %.0fpx of screen" \
|
||||||
|
% [framed, half_screen.x])
|
||||||
|
print("aiming framed the beam's full range")
|
||||||
|
|
||||||
# --- B. targets: two on the line, one off it, one past range ---
|
# --- B. targets: two on the line, one off it, one past range ---
|
||||||
for offset in [Vector2(200.0, 0.0), Vector2(500.0, 0.0)]:
|
for offset in [Vector2(200.0, 0.0), Vector2(500.0, 0.0)]:
|
||||||
var enemy: Node = load("res://enemies/Enemy.tscn").instantiate()
|
var enemy: Node = load("res://enemies/Enemy.tscn").instantiate()
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ script = ExtResource("mouse_ctrl")
|
||||||
|
|
||||||
[node name="LaserController" type="Node2D" parent="."]
|
[node name="LaserController" type="Node2D" parent="."]
|
||||||
script = ExtResource("30")
|
script = ExtResource("30")
|
||||||
|
minimum_zoom = 0.1
|
||||||
|
|
||||||
[node name="DashController" type="Node2D" parent="."]
|
[node name="DashController" type="Node2D" parent="."]
|
||||||
script = ExtResource("dash_ctrl")
|
script = ExtResource("dash_ctrl")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue