Set up framework for more advance pickup behaviours.
This commit is contained in:
parent
a0df0be862
commit
935b61bd3b
16 changed files with 485 additions and 20 deletions
14
resources/pickups/drift_diagonal.tres
Normal file
14
resources/pickups/drift_diagonal.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[gd_resource type="Resource" script_class="PickupDriftBehavior" format=3 uid="uid://cdiglwsw46g4y"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://b4lar4f3526b1" path="res://resources/pickups/pickup_drift_behavior.gd" id="1_drift"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_drift")
|
||||||
|
drift_type = 3
|
||||||
|
wave_amplitude = 100.0
|
||||||
|
wave_frequency = 2.0
|
||||||
|
drift_speed = 30.0
|
||||||
|
diagonal_forward = true
|
||||||
|
drift_along_x = true
|
||||||
|
reverse_direction = false
|
||||||
|
randomize_origin = false
|
||||||
14
resources/pickups/drift_leaf_fall.tres
Normal file
14
resources/pickups/drift_leaf_fall.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[gd_resource type="Resource" script_class="PickupDriftBehavior" format=3 uid="uid://pickup_drift_leaf"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://pickup_drift_uid" path="res://resources/pickups/pickup_drift_behavior.gd" id="1_drift"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_drift")
|
||||||
|
drift_type = 2
|
||||||
|
drift_speed = 30.0
|
||||||
|
fall_speed = 15.0
|
||||||
|
wave_amplitude = 40.0
|
||||||
|
wave_frequency = 1.0
|
||||||
|
drift_along_x = true
|
||||||
|
reverse_direction = false
|
||||||
|
randomize_origin = false
|
||||||
13
resources/pickups/drift_sine_wave.tres
Normal file
13
resources/pickups/drift_sine_wave.tres
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[gd_resource type="Resource" script_class="PickupDriftBehavior" format=3 uid="uid://cr7lye10sx1q6"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://b4lar4f3526b1" path="res://resources/pickups/pickup_drift_behavior.gd" id="1_drift"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_drift")
|
||||||
|
drift_type = 1
|
||||||
|
drift_speed = 30.0
|
||||||
|
wave_amplitude = 40.0
|
||||||
|
wave_frequency = 1.0
|
||||||
|
drift_along_x = true
|
||||||
|
reverse_direction = false
|
||||||
|
randomize_origin = false
|
||||||
43
resources/pickups/pickup_behavior.gd
Normal file
43
resources/pickups/pickup_behavior.gd
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
class_name PickupBehavior extends Resource
|
||||||
|
|
||||||
|
## Base resource that defines how a pickup moves and looks on screen.
|
||||||
|
## Assign to [member pickup_behavior] on a PickupBehaviorController.
|
||||||
|
|
||||||
|
@export_group("Animation")
|
||||||
|
## Whether the pickup bobs up and down in place.
|
||||||
|
@export var bob_enabled: bool = true
|
||||||
|
## Bob oscillation frequency in cycles per second.
|
||||||
|
@export var bob_frequency: float = 2.0
|
||||||
|
## Bob vertical amplitude in pixels (peak-to-center).
|
||||||
|
@export var bob_amplitude: float = 4.0
|
||||||
|
|
||||||
|
## Whether the pickup subtly shakes (jitter) in place.
|
||||||
|
@export var shake_enabled: bool = false
|
||||||
|
## Shake frequency in Hz (how fast the jitter oscillates).
|
||||||
|
@export var shake_frequency: float = 8.0
|
||||||
|
## Shake amplitude in pixels (peak-to-center).
|
||||||
|
@export var shake_amplitude: float = 1.5
|
||||||
|
|
||||||
|
## Optional fade-in duration (0 = no fade).
|
||||||
|
@export var fade_in_duration: float = 0.0
|
||||||
|
## Optional fade-out duration when the pickup is about to be collected
|
||||||
|
## (used by controller; 0 = no fade).
|
||||||
|
@export var fade_out_duration: float = 0.0
|
||||||
|
|
||||||
|
var _elapsed: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
func start() -> void:
|
||||||
|
_elapsed = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
func process(delta: float) -> float:
|
||||||
|
# Returns the current opacity factor (0..1) based on fade timers.
|
||||||
|
_elapsed += delta
|
||||||
|
|
||||||
|
var target_alpha: float = 1.0
|
||||||
|
|
||||||
|
if fade_in_duration > 0 and _elapsed < fade_in_duration:
|
||||||
|
target_alpha = clampf(_elapsed / fade_in_duration, 0.0, 1.0)
|
||||||
|
|
||||||
|
return target_alpha
|
||||||
1
resources/pickups/pickup_behavior.gd.uid
Normal file
1
resources/pickups/pickup_behavior.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://d0l8e2ymk0lx1
|
||||||
47
resources/pickups/pickup_drift_behavior.gd
Normal file
47
resources/pickups/pickup_drift_behavior.gd
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
class_name PickupDriftBehavior extends Resource
|
||||||
|
|
||||||
|
## Base resource that defines the global travel pattern of a pickup across
|
||||||
|
## the screen. Assign to [member drift_behavior] on a PickupBehaviorController.
|
||||||
|
|
||||||
|
enum DriftType {
|
||||||
|
STATIONARY, ## Does not move (aside from bob/shake).
|
||||||
|
SINE_WAVE, ## Travels in a sine curve.
|
||||||
|
LEAF_FALL, ## Drifts downward with gentle horizontal sway.
|
||||||
|
DIAGONAL, ## Moves diagonally in one direction.
|
||||||
|
}
|
||||||
|
|
||||||
|
@export_group("Drift")
|
||||||
|
## The drift pattern to apply.
|
||||||
|
@export var drift_type: DriftType = DriftType.STATIONARY
|
||||||
|
|
||||||
|
## Horizontal speed in pixels per second (applies to SINE_WAVE, LEAF_FALL).
|
||||||
|
@export var drift_speed: float = 30.0
|
||||||
|
## Vertical fall speed in pixels per second (applies to LEAF_FALL).
|
||||||
|
@export var fall_speed: float = 15.0
|
||||||
|
|
||||||
|
## Horizontal wave amplitude in pixels (applies to SINE_WAVE, LEAF_FALL).
|
||||||
|
@export var wave_amplitude: float = 40.0
|
||||||
|
## Wave frequency in cycles per second (applies to SINE_WAVE, LEAF_FALL).
|
||||||
|
@export var wave_frequency: float = 1.0
|
||||||
|
|
||||||
|
## Diagonal direction: +X forward, -X backward (applies to DIAGONAL).
|
||||||
|
@export var diagonal_forward: bool = true
|
||||||
|
|
||||||
|
## Main drift axis for SINE_WAVE and LEAF_FALL.
|
||||||
|
## true = horizontal travel (x), false = vertical travel (y).
|
||||||
|
@export var drift_along_x: bool = true
|
||||||
|
|
||||||
|
## Reverse the primary direction (left/right or up/down).
|
||||||
|
@export var reverse_direction: bool = false
|
||||||
|
|
||||||
|
## Randomize the perpendicular axis origin (y if horizontal, x if vertical).
|
||||||
|
@export var randomize_origin: bool = false
|
||||||
|
|
||||||
|
## Phase offset in radians — randomize per-instance for variety.
|
||||||
|
@export var phase_offset: float = 0.0
|
||||||
|
|
||||||
|
var _initial_x: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
func reset(initial_position: Vector2) -> void:
|
||||||
|
_initial_x = initial_position.x
|
||||||
1
resources/pickups/pickup_drift_behavior.gd.uid
Normal file
1
resources/pickups/pickup_drift_behavior.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b4lar4f3526b1
|
||||||
13
resources/pickups/rare_powerup.tres
Normal file
13
resources/pickups/rare_powerup.tres
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[gd_resource type="Resource" script_class="PickupBehavior" format=3 uid="uid://pickup_rare_powerup"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://pickup_behavior_uid" path="res://resources/pickups/pickup_behavior.gd" id="1_behavior"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_behavior")
|
||||||
|
bob_enabled = true
|
||||||
|
bob_frequency = 3.5
|
||||||
|
bob_amplitude = 6.0
|
||||||
|
shake_enabled = true
|
||||||
|
shake_frequency = 12.0
|
||||||
|
shake_amplitude = 1.5
|
||||||
|
fade_in_duration = 0.5
|
||||||
8
resources/pickups/std_powerup.tres
Normal file
8
resources/pickups/std_powerup.tres
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[gd_resource type="Resource" script_class="PickupBehavior" format=3 uid="uid://dflj31qle75ol"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://d0l8e2ymk0lx1" path="res://resources/pickups/pickup_behavior.gd" id="1_behavior"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1_behavior")
|
||||||
|
shake_enabled = true
|
||||||
|
fade_in_duration = 0.3
|
||||||
|
|
@ -5,7 +5,7 @@ class_name Player extends Area2D
|
||||||
@onready var movement_component: MovementComponent = %MovementComponent
|
@onready var movement_component: MovementComponent = %MovementComponent
|
||||||
@onready var shoot_component: ShootComponent = %ShootComponent
|
@onready var shoot_component: ShootComponent = %ShootComponent
|
||||||
@onready var weapon_component: WeaponComponent = %WeaponComponent
|
@onready var weapon_component: WeaponComponent = %WeaponComponent
|
||||||
@onready var powerup_spawner := _find_powerup_spawner()
|
@onready var powerup_spawner: PowerUpSpawner = _find_powerup_spawner()
|
||||||
|
|
||||||
# Get the viewport size for positioning
|
# Get the viewport size for positioning
|
||||||
@onready var screensize = get_viewport().content_scale_size
|
@onready var screensize = get_viewport().content_scale_size
|
||||||
|
|
@ -63,7 +63,8 @@ func _on_help_box_area_entered(area: Area2D) -> void:
|
||||||
return
|
return
|
||||||
var weapon_shot: WeaponShot = area.get_meta("weapon_shot")
|
var weapon_shot: WeaponShot = area.get_meta("weapon_shot")
|
||||||
weapon_component.collect_weapon(weapon_shot)
|
weapon_component.collect_weapon(weapon_shot)
|
||||||
powerup_spawner.pickup_collected()
|
if powerup_spawner != null:
|
||||||
|
powerup_spawner.pickup_collected()
|
||||||
area.queue_free()
|
area.queue_free()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,9 @@ size = Vector2(6, 5.75)
|
||||||
|
|
||||||
[node name="Player" type="Area2D" unique_id=652131079]
|
[node name="Player" type="Area2D" unique_id=652131079]
|
||||||
script = ExtResource("1_ur7pv")
|
script = ExtResource("1_ur7pv")
|
||||||
|
can_shoot = null
|
||||||
|
is_shooting = null
|
||||||
|
muzzle_flash = null
|
||||||
|
|
||||||
[node name="Ship" type="Sprite2D" parent="." unique_id=1155866924]
|
[node name="Ship" type="Sprite2D" parent="." unique_id=1155866924]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,21 @@
|
||||||
[ext_resource type="Resource" uid="uid://ds53sryglmf27" path="res://resources/player_weapon_resources/spread.tres" id="6_mwa7c"]
|
[ext_resource type="Resource" uid="uid://ds53sryglmf27" path="res://resources/player_weapon_resources/spread.tres" id="6_mwa7c"]
|
||||||
[ext_resource type="Resource" uid="uid://bhc6aja38vyr" path="res://resources/player_weapon_resources/vanguard.tres" id="7_vphcr"]
|
[ext_resource type="Resource" uid="uid://bhc6aja38vyr" path="res://resources/player_weapon_resources/vanguard.tres" id="7_vphcr"]
|
||||||
[ext_resource type="Resource" uid="uid://c41aoyka7nfr8" path="res://resources/player_weapon_resources/vanguard+.tres" id="8_g67dp"]
|
[ext_resource type="Resource" uid="uid://c41aoyka7nfr8" path="res://resources/player_weapon_resources/vanguard+.tres" id="8_g67dp"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_std_powerup" path="res://resources/pickups/std_powerup.tres" id="9_std"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_rare_powerup" path="res://resources/pickups/rare_powerup.tres" id="10_rare"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_drift_sine" path="res://resources/pickups/drift_sine_wave.tres" id="11_sine"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_drift_leaf" path="res://resources/pickups/drift_leaf_fall.tres" id="12_leaf"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_drift_diagonal" path="res://resources/pickups/drift_diagonal.tres" id="13_diag"]
|
||||||
|
|
||||||
[node name="PowerUpSpawner" type="Node2D" unique_id=1191463626]
|
[node name="PowerUpSpawner" type="Node2D" unique_id=1191463626]
|
||||||
script = ExtResource("2_9m3n7")
|
script = ExtResource("2_9m3n7")
|
||||||
pickup_scene = ExtResource("1_xk8p2")
|
pickup_scene = ExtResource("1_xk8p2")
|
||||||
|
pickup_behaviors = Array[ExtResource("3_07x44")](
|
||||||
|
[ExtResource("9_std"), ExtResource("10_rare")]
|
||||||
|
)
|
||||||
|
drift_behaviors = Array[ExtResource("3_07x44")](
|
||||||
|
[ExtResource("11_sine"), ExtResource("12_leaf"), ExtResource("13_diag")]
|
||||||
|
)
|
||||||
all_weapon_shots = Array[ExtResource("3_07x44")](
|
all_weapon_shots = Array[ExtResource("3_07x44")](
|
||||||
[ExtResource("4_0eev3"), ExtResource("5_qqjui"), ExtResource("6_mwa7c"), ExtResource("7_vphcr"), ExtResource("8_g67dp")]
|
[ExtResource("4_0eev3"), ExtResource("5_qqjui"), ExtResource("6_mwa7c"), ExtResource("7_vphcr"), ExtResource("8_g67dp")]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,16 @@
|
||||||
[gd_scene format=3 uid="uid://dsqfuv86suu17"]
|
[gd_scene format=3 uid="uid://dsqfuv86suu17"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://pickup_controller_uid" path="res://scripts/pickups/pickup_behavior_controller.gd" id="1_ctrl"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_behavior_uid" path="res://resources/pickups/std_powerup.tres" id="2_std"]
|
||||||
|
[ext_resource type="Resource" uid="uid://pickup_drift_uid" path="res://resources/pickups/drift_sine_wave.tres" id="3_drift"]
|
||||||
|
|
||||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_mmgg1"]
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_mmgg1"]
|
||||||
size = Vector2(14, 14)
|
size = Vector2(14, 14)
|
||||||
|
|
||||||
[node name="PowerUpPickup" type="Area2D" unique_id=478708932]
|
[node name="PowerUpPickup" type="Area2D" unique_id=478708932]
|
||||||
|
script = ExtResource("1_ctrl")
|
||||||
|
behavior = ExtResource("2_std")
|
||||||
|
drift_behavior = ExtResource("3_drift")
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=347643413]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="." unique_id=347643413]
|
||||||
|
|
|
||||||
132
scripts/pickups/pickup_behavior_controller.gd
Normal file
132
scripts/pickups/pickup_behavior_controller.gd
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
class_name PickupBehaviorController extends Area2D
|
||||||
|
|
||||||
|
## Controls visual movement (bob, shake) and drift of a pickup.
|
||||||
|
## Attach to the root Area2D node of your pickup scene.
|
||||||
|
|
||||||
|
@export var behavior: PickupBehavior
|
||||||
|
@export var drift_behavior: PickupDriftBehavior
|
||||||
|
|
||||||
|
|
||||||
|
var _icon_node: TextureRect
|
||||||
|
var _spawn_position: Vector2 = Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
# Find the runtime-added icon node.
|
||||||
|
for child in get_children():
|
||||||
|
if child is TextureRect:
|
||||||
|
_icon_node = child as TextureRect
|
||||||
|
break
|
||||||
|
# Capture the initial spawn position.
|
||||||
|
_spawn_position = global_position
|
||||||
|
|
||||||
|
if behavior != null:
|
||||||
|
behavior.start()
|
||||||
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if behavior == null or drift_behavior == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var alpha: float = behavior.process(delta)
|
||||||
|
|
||||||
|
if _icon_node != null:
|
||||||
|
_icon_node.visible = alpha > 0.01
|
||||||
|
if behavior != null and behavior.fade_in_duration > 0 and _icon_node is CanvasItem:
|
||||||
|
_icon_node.modulate.a = alpha
|
||||||
|
|
||||||
|
var offset := _compute_animation_offset()
|
||||||
|
var drift_offset := _compute_drift_offset()
|
||||||
|
|
||||||
|
position = _spawn_position + Vector2(drift_offset.x, drift_offset.y)
|
||||||
|
|
||||||
|
if _icon_node != null:
|
||||||
|
_icon_node.position = offset
|
||||||
|
|
||||||
|
|
||||||
|
func _compute_animation_offset() -> Vector2:
|
||||||
|
# Returns the bob + shake offset applied to the icon node.
|
||||||
|
var result := Vector2.ZERO
|
||||||
|
|
||||||
|
if behavior == null:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Bob (vertical oscillation).
|
||||||
|
if behavior.bob_enabled:
|
||||||
|
var bob_angle := _elapsed_time() * TAU * behavior.bob_frequency
|
||||||
|
result.y += sin(bob_angle) * behavior.bob_amplitude
|
||||||
|
|
||||||
|
# Shake (subtle jitter).
|
||||||
|
if behavior.shake_enabled:
|
||||||
|
var shake_angle := _elapsed_time() * TAU * behavior.shake_frequency
|
||||||
|
result.x += sin(shake_angle) * behavior.shake_amplitude
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
func _compute_drift_offset() -> Vector2:
|
||||||
|
# Returns the global drift position offset from initial spawn position.
|
||||||
|
if drift_behavior == null:
|
||||||
|
return Vector2.ZERO
|
||||||
|
|
||||||
|
match drift_behavior.drift_type:
|
||||||
|
PickupDriftBehavior.DriftType.STATIONARY:
|
||||||
|
return Vector2.ZERO
|
||||||
|
|
||||||
|
PickupDriftBehavior.DriftType.SINE_WAVE:
|
||||||
|
var t := _elapsed_time() * drift_behavior.wave_frequency
|
||||||
|
var travel_speed: float = drift_behavior.drift_speed
|
||||||
|
if drift_behavior.reverse_direction:
|
||||||
|
travel_speed *= -1.0
|
||||||
|
|
||||||
|
var offset := Vector2.ZERO
|
||||||
|
if drift_behavior.drift_along_x:
|
||||||
|
# Travels along X; waves on Y.
|
||||||
|
offset.x = travel_speed * _elapsed_time()
|
||||||
|
offset.y = sin(t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude
|
||||||
|
else:
|
||||||
|
# Travels along Y; waves on X.
|
||||||
|
offset.y = travel_speed * _elapsed_time()
|
||||||
|
offset.x = sin(t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude
|
||||||
|
return offset
|
||||||
|
|
||||||
|
PickupDriftBehavior.DriftType.LEAF_FALL:
|
||||||
|
var leaf_t := _elapsed_time() * drift_behavior.wave_frequency
|
||||||
|
var travel_speed: float = drift_behavior.fall_speed
|
||||||
|
if drift_behavior.reverse_direction:
|
||||||
|
travel_speed *= -1.0
|
||||||
|
|
||||||
|
var offset := Vector2.ZERO
|
||||||
|
if drift_behavior.drift_along_x:
|
||||||
|
# Travels along X; waves on Y.
|
||||||
|
offset.x = travel_speed * _elapsed_time()
|
||||||
|
offset.y = sin(leaf_t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude
|
||||||
|
else:
|
||||||
|
# Travels along Y; waves on X.
|
||||||
|
offset.y = travel_speed * _elapsed_time()
|
||||||
|
offset.x = sin(leaf_t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude
|
||||||
|
return offset
|
||||||
|
|
||||||
|
PickupDriftBehavior.DriftType.DIAGONAL:
|
||||||
|
var dir: float = 1.0 if drift_behavior.diagonal_forward else -1.0
|
||||||
|
if drift_behavior.reverse_direction:
|
||||||
|
dir *= -1.0
|
||||||
|
|
||||||
|
var offset := Vector2.ZERO
|
||||||
|
if drift_behavior.drift_along_x:
|
||||||
|
# Travels along X; secondary on Y.
|
||||||
|
offset.x = dir * drift_behavior.drift_speed * _elapsed_time()
|
||||||
|
offset.y = dir * (drift_behavior.drift_speed * 0.5) * _elapsed_time()
|
||||||
|
else:
|
||||||
|
# Travels along Y; secondary on X.
|
||||||
|
offset.y = dir * drift_behavior.drift_speed * _elapsed_time()
|
||||||
|
offset.x = dir * (drift_behavior.drift_speed * 0.5) * _elapsed_time()
|
||||||
|
return offset
|
||||||
|
|
||||||
|
return Vector2.ZERO
|
||||||
|
|
||||||
|
|
||||||
|
func _elapsed_time() -> float:
|
||||||
|
if behavior == null:
|
||||||
|
return 0.0
|
||||||
|
return behavior._elapsed
|
||||||
1
scripts/pickups/pickup_behavior_controller.gd.uid
Normal file
1
scripts/pickups/pickup_behavior_controller.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c4a7husvaq2eo
|
||||||
|
|
@ -9,18 +9,44 @@ class_name PowerUpSpawner extends Node2D
|
||||||
@onready var screensize: Vector2 = get_viewport().content_scale_size
|
@onready var screensize: Vector2 = get_viewport().content_scale_size
|
||||||
@onready var spawn_timer: Timer = $SpawnTimer
|
@onready var spawn_timer: Timer = $SpawnTimer
|
||||||
|
|
||||||
|
# Optional behavior resources to assign randomly on spawn.
|
||||||
|
@export var pickup_behaviors: Array[PickupBehavior] = []
|
||||||
|
|
||||||
# All weapon resources to spawn as pickups.
|
# All weapon resources to spawn as pickups.
|
||||||
# Populate in the editor with your weapon_shot resources.
|
# Populate in the editor with your weapon_shot resources.
|
||||||
@export var all_weapon_shots: Array[WeaponShot] = []
|
@export var all_weapon_shots: Array[WeaponShot] = []
|
||||||
|
|
||||||
|
# Optional drift behaviors to assign randomly on spawn.
|
||||||
|
# Leave empty to use whatever is baked into the pickup scene.
|
||||||
|
@export var drift_behaviors: Array[PickupDriftBehavior] = []
|
||||||
|
|
||||||
|
# How long a pickup can linger off-screen before being cleaned up.
|
||||||
|
@export var offscreen_timeout: float = 5.0
|
||||||
|
|
||||||
|
# How long to wait after a pickup is collected before spawning the next one.
|
||||||
|
@export var cooldown_after_collection: float = 5.0
|
||||||
|
|
||||||
var _pickups_spawned: int = 0
|
var _pickups_spawned: int = 0
|
||||||
var _pickups_remaining: int = 0
|
var _pickups_remaining: int = 0
|
||||||
|
|
||||||
|
# Tracks active pickups so we can check off-screen timeouts.
|
||||||
|
var _active_pickups: Array[Area2D] = []
|
||||||
|
|
||||||
|
# Cooldown state for post-collection pause.
|
||||||
|
var _cooldown_timer: Timer
|
||||||
|
var _in_cooldown: bool = false
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if spawn_timer == null:
|
if spawn_timer == null:
|
||||||
return
|
return
|
||||||
spawn_timer.wait_time = spawn_interval
|
spawn_timer.wait_time = spawn_interval
|
||||||
|
|
||||||
|
# Create a one-shot cooldown timer.
|
||||||
|
_cooldown_timer = Timer.new()
|
||||||
|
_cooldown_timer.one_shot = true
|
||||||
|
_cooldown_timer.timeout.connect(_on_cooldown_finished)
|
||||||
|
add_child(_cooldown_timer)
|
||||||
|
|
||||||
|
|
||||||
func _on_spawn_timer_timeout() -> void:
|
func _on_spawn_timer_timeout() -> void:
|
||||||
if test_mode:
|
if test_mode:
|
||||||
|
|
@ -29,6 +55,12 @@ func _on_spawn_timer_timeout() -> void:
|
||||||
_spawn_normal_pickup()
|
_spawn_normal_pickup()
|
||||||
|
|
||||||
|
|
||||||
|
func _on_cooldown_finished() -> void:
|
||||||
|
_in_cooldown = false
|
||||||
|
if spawn_timer != null and test_mode:
|
||||||
|
spawn_timer.start()
|
||||||
|
|
||||||
|
|
||||||
func _spawn_normal_pickup() -> void:
|
func _spawn_normal_pickup() -> void:
|
||||||
# Spawn a single random pickup at a position near the player.
|
# Spawn a single random pickup at a position near the player.
|
||||||
if all_weapon_shots.is_empty():
|
if all_weapon_shots.is_empty():
|
||||||
|
|
@ -59,26 +91,51 @@ func _spawn_normal_pickup() -> void:
|
||||||
|
|
||||||
|
|
||||||
func _spawn_test_pickups() -> void:
|
func _spawn_test_pickups() -> void:
|
||||||
for weapon: WeaponShot in all_weapon_shots:
|
# In test mode: pick ONE random weapon and spawn a single pickup.
|
||||||
if weapon == null or weapon.shot_name.is_empty():
|
if all_weapon_shots.is_empty():
|
||||||
continue
|
return
|
||||||
|
|
||||||
var pickup := _spawn_pickup(weapon)
|
var chosen_weapon: WeaponShot = all_weapon_shots[randi() % all_weapon_shots.size()]
|
||||||
if not is_instance_valid(pickup):
|
if chosen_weapon == null or chosen_weapon.shot_name.is_empty():
|
||||||
continue # skipped — no valid icon
|
return
|
||||||
|
|
||||||
# Random position within viewport, with edge padding.
|
var pickup := _spawn_pickup(chosen_weapon)
|
||||||
var margin: float = 15.0
|
if not is_instance_valid(pickup):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Random position within viewport, with edge padding.
|
||||||
|
var margin: float = 15.0
|
||||||
|
if _should_randomize_origin(pickup):
|
||||||
|
# Randomize the perpendicular axis so pickups don't all start at one edge.
|
||||||
|
var drift := _get_pickup_drift(pickup)
|
||||||
|
if drift != null:
|
||||||
|
var offset := Vector2.ZERO
|
||||||
|
if drift.drift_along_x:
|
||||||
|
# Travels along X; randomize Y origin.
|
||||||
|
offset.x = randf_range(margin, screensize.x - margin)
|
||||||
|
offset.y = 0.0
|
||||||
|
else:
|
||||||
|
# Travels along Y; randomize X origin.
|
||||||
|
offset.x = 0.0
|
||||||
|
offset.y = randf_range(margin, screensize.y - margin)
|
||||||
|
pickup.position = offset
|
||||||
|
else:
|
||||||
|
pickup.position = Vector2(
|
||||||
|
randf_range(margin, screensize.x - margin),
|
||||||
|
randf_range(margin, screensize.y - margin)
|
||||||
|
)
|
||||||
|
else:
|
||||||
pickup.position = Vector2(
|
pickup.position = Vector2(
|
||||||
randf_range(margin, screensize.x - margin),
|
randf_range(margin, screensize.x - margin),
|
||||||
randf_range(margin, screensize.y - margin)
|
randf_range(margin, screensize.y - margin)
|
||||||
)
|
)
|
||||||
|
|
||||||
add_child(pickup)
|
add_child(pickup)
|
||||||
_pickups_spawned += 1
|
_active_pickups.append(pickup)
|
||||||
_pickups_remaining = _pickups_spawned
|
_pickups_spawned += 1
|
||||||
|
_pickups_remaining = _pickups_spawned
|
||||||
|
|
||||||
# Pause the timer while pickups are on screen; resume when all gone.
|
# Pause the timer while pickup is on screen; resume when gone or expired.
|
||||||
if spawn_timer != null:
|
if spawn_timer != null:
|
||||||
spawn_timer.stop()
|
spawn_timer.stop()
|
||||||
|
|
||||||
|
|
@ -92,12 +149,16 @@ func pickup_collected() -> void:
|
||||||
var wc: WeaponComponent = level.get_node_or_null("WeaponComponent")
|
var wc: WeaponComponent = level.get_node_or_null("WeaponComponent")
|
||||||
if wc != null:
|
if wc != null:
|
||||||
wc.test_mode = test_mode
|
wc.test_mode = test_mode
|
||||||
# Decrement counter; resume timer once all pickups are gone.
|
# Remove from active list and start cooldown.
|
||||||
_pickups_remaining = max(0, _pickups_remaining - 1)
|
_active_pickups.clear()
|
||||||
if _pickups_remaining <= 0:
|
if spawn_timer != null:
|
||||||
if spawn_timer != null:
|
spawn_timer.stop()
|
||||||
spawn_timer.start()
|
_start_cooldown()
|
||||||
_pickups_spawned = 0
|
|
||||||
|
|
||||||
|
func _process(delta: float) -> void:
|
||||||
|
if test_mode:
|
||||||
|
_update_active_pickups(delta)
|
||||||
|
|
||||||
|
|
||||||
func _spawn_pickup(weapon: WeaponShot) -> Area2D:
|
func _spawn_pickup(weapon: WeaponShot) -> Area2D:
|
||||||
|
|
@ -123,4 +184,99 @@ func _spawn_pickup(weapon: WeaponShot) -> Area2D:
|
||||||
icon.size = Vector2(16, 16)
|
icon.size = Vector2(16, 16)
|
||||||
pickup.add_child(icon)
|
pickup.add_child(icon)
|
||||||
|
|
||||||
|
# Assign a random behavior for visual variety.
|
||||||
|
_apply_random_behavior(pickup)
|
||||||
|
|
||||||
|
# Assign a random drift behavior for visual variety.
|
||||||
|
_apply_random_drift(pickup)
|
||||||
|
|
||||||
return pickup
|
return pickup
|
||||||
|
|
||||||
|
|
||||||
|
func _cleanup_pickup(pickup: Area2D) -> void:
|
||||||
|
# Remove from active list if present.
|
||||||
|
for i in _active_pickups.size():
|
||||||
|
if _active_pickups[i] == pickup:
|
||||||
|
_active_pickups.remove_at(i)
|
||||||
|
break
|
||||||
|
pickup.queue_free()
|
||||||
|
# Trigger cooldown/respawn when a pickup drifts off-screen.
|
||||||
|
_start_cooldown()
|
||||||
|
|
||||||
|
func _update_active_pickups(_delta: float) -> void:
|
||||||
|
# Check each active pickup for off-screen timeout.
|
||||||
|
var viewport_rect := get_viewport_rect().size
|
||||||
|
var margin: float = 50.0 # how far off-screen counts as "gone"
|
||||||
|
var to_remove: Array[Area2D] = []
|
||||||
|
|
||||||
|
for pickup in _active_pickups:
|
||||||
|
if not is_instance_valid(pickup):
|
||||||
|
continue
|
||||||
|
# Simple off-screen check: outside viewport + margin.
|
||||||
|
if (pickup.position.x < -margin
|
||||||
|
or pickup.position.x > viewport_rect.x + margin
|
||||||
|
or pickup.position.y < -margin
|
||||||
|
or pickup.position.y > viewport_rect.y + margin):
|
||||||
|
to_remove.append(pickup)
|
||||||
|
|
||||||
|
for bad_pickup in to_remove:
|
||||||
|
_cleanup_pickup(bad_pickup)
|
||||||
|
|
||||||
|
|
||||||
|
func _start_cooldown() -> void:
|
||||||
|
if offscreen_timeout <= 0 or cooldown_after_collection <= 0:
|
||||||
|
return
|
||||||
|
_in_cooldown = true
|
||||||
|
_cooldown_timer.wait_time = cooldown_after_collection
|
||||||
|
_cooldown_timer.start()
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_random_behavior(pickup: Area2D) -> void:
|
||||||
|
if pickup_behaviors.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var controller := pickup.get_node_or_null("PickupBehaviorController")
|
||||||
|
if controller == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var chosen: PickupBehavior = pickup_behaviors[randi() % pickup_behaviors.size()].duplicate() as PickupBehavior
|
||||||
|
if chosen != null:
|
||||||
|
controller.behavior = chosen
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_random_drift(pickup: Area2D) -> void:
|
||||||
|
# Randomly assign a drift behavior to the spawned pickup instance.
|
||||||
|
if drift_behaviors.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
var controller := pickup.get_node_or_null("PickupBehaviorController")
|
||||||
|
if controller == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
var chosen: PickupDriftBehavior = drift_behaviors[randi() % drift_behaviors.size()].duplicate() as PickupDriftBehavior
|
||||||
|
if chosen == null:
|
||||||
|
return
|
||||||
|
# Randomize phase offset for per-instance variety.
|
||||||
|
chosen.phase_offset = randf_range(0.0, TAU)
|
||||||
|
# Randomize axis (horizontal vs vertical travel).
|
||||||
|
chosen.drift_along_x = randf() > 0.5
|
||||||
|
# Randomize direction (forward vs reverse).
|
||||||
|
chosen.reverse_direction = randf() > 0.5
|
||||||
|
# Randomize perpendicular origin.
|
||||||
|
chosen.randomize_origin = randf() > 0.5
|
||||||
|
# When randomizing origin, zero the phase so the wave starts at center (0 offset).
|
||||||
|
if chosen.randomize_origin:
|
||||||
|
chosen.phase_offset = 0.0
|
||||||
|
controller.drift_behavior = chosen
|
||||||
|
|
||||||
|
|
||||||
|
func _get_pickup_drift(pickup: Area2D) -> PickupDriftBehavior:
|
||||||
|
var controller := pickup.get_node_or_null("PickupBehaviorController")
|
||||||
|
if controller == null:
|
||||||
|
return null
|
||||||
|
return controller.drift_behavior
|
||||||
|
|
||||||
|
|
||||||
|
func _should_randomize_origin(pickup: Area2D) -> bool:
|
||||||
|
var drift := _get_pickup_drift(pickup)
|
||||||
|
return drift != null and drift.randomize_origin
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue