Files
MovementTests/addons/shaker/src/Vector3/ShakerReceiver3D.gd
2026-01-20 15:27:59 +01:00

160 lines
4.7 KiB
GDScript

@icon("res://addons/shaker/assets/ShakerReceiver3D.svg")
@tool
class_name ShakerReceiver3D
extends Node3D
## Transmits values from ShakerEmitter3D to ShakerComponent3D
# Fade-in easing
@export_exp_easing var enter_fade_in: float = 0.1:
set = set_fade_in,
get = get_fade_in
# Fade-out easing
@export_exp_easing("attenuation") var exit_fade_out: float = 3.0:
set = set_fade_out,
get = get_fade_out
# Private variables
var area3d: Area3D
var position_offset: Vector3 = Vector3.ZERO
var rotation_offset: Vector3 = Vector3.ZERO
var scale_offset: Vector3 = Vector3.ZERO
var emitter_list: Array[EmitterData]
# Called when the node enters the scene tree for the first time
func _ready() -> void:
_setup_area3d()
if !Engine.is_editor_hint():
add_to_group("ShakerReceiver")
_connect_signals()
# Sets up the Area3D node
func _setup_area3d() -> void:
for child in get_children():
if child is Area3D:
area3d = child
return
area3d = Area3D.new()
var collision = CollisionShape3D.new()
add_child(area3d)
area3d.add_child(collision)
area3d.set_owner(get_tree().edited_scene_root)
collision.set_owner(get_tree().edited_scene_root)
area3d.name = "Area3D"
collision.name = "CollisionShape3D"
area3d.collision_layer = 0
area3d.collision_mask = 1 << 9
# Connects signals
func _connect_signals() -> void:
area3d.area_entered.connect(on_area_entered)
area3d.area_exited.connect(on_area_exited)
# Called every frame
func _process(delta: float) -> void:
if !Engine.is_editor_hint():
position_offset = Vector3.ZERO
rotation_offset = Vector3.ZERO
scale_offset = Vector3.ZERO
if emitter_list.size() > 0:
for emitter_data in emitter_list:
_process_emitter(emitter_data, delta)
# Processes each emitter
func _process_emitter(emitter_data: EmitterData, delta: float) -> void:
if !Engine.is_editor_hint():
if is_instance_valid(emitter_data.emitter):
var ease_in: float = ease(emitter_data.timer, enter_fade_in)
var ease_out: float = ease(1.0 - (emitter_data.timer - emitter_data.fade_out_timer), exit_fade_out) if emitter_data.fade_out_timer != 0.0 else 1.0
emitter_data.ease_out_intensity = move_toward(emitter_data.ease_out_intensity, ease_out, delta)
ease_out = emitter_data.ease_out_intensity
var max_distance: float = emitter_data.emitter.max_distance
var distance: float = min(emitter_data.emitter.global_position.distance_to(global_position), max_distance) / max(max_distance, 0.001)
var attenuation: float = ease(1.0 - distance, emitter_data.emitter.distance_attenuation)
position_offset += emitter_data.emitter.shake_offset_position * ease_in * ease_out * attenuation
rotation_offset += emitter_data.emitter.shake_offset_rotation * ease_in * ease_out * attenuation
scale_offset += emitter_data.emitter.shake_offset_scale * ease_in * ease_out * attenuation
emitter_data.timer += delta
if ease_out <= delta:
emitter_list.erase(emitter_data)
else:
emitter_list.erase(emitter_data)
# Returns the current shake values
func get_value() -> Array[Vector3]:
return [position_offset, rotation_offset, scale_offset]
# Returns configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
if not get_parent() is ShakerComponent3D:
return ["Parent must be ShakerComponent3D"]
var _ex:bool = false
for i in get_children():
if i is Area3D:
_ex = true
break
if !_ex:
return ["ShakerReceiver3D needs Area3D to work"]
return []
# Called when an area enters
func on_area_entered(area: Area3D) -> void:
var node = area.get_parent()
if node is ShakerEmitter3D:
var _exists = null
for index in emitter_list.size():
var data = emitter_list[index]
if data.emitter == node: _exists = data
if !_exists:
var data = EmitterData.new(node)
emitter_list.append(data)
else:
_exists.fade_out_timer = 0.0
# Called when an area exits
func on_area_exited(area: Area3D) -> void:
var node = area.get_parent()
if node is ShakerEmitter3D:
for index in emitter_list.size():
var data = emitter_list[index]
if data.emitter == node:
data.fade_out_timer = data.timer
break
# Setter for enter_fade_in
func set_fade_in(value: float) -> void:
enter_fade_in = value
# Getter for enter_fade_in
func get_fade_in() -> float:
return enter_fade_in
# Setter for exit_fade_out
func set_fade_out(value: float) -> void:
exit_fade_out = value
# Getter for exit_fade_out
func get_fade_out() -> float:
return exit_fade_out
# EmitterData inner class
class EmitterData:
var emitter: ShakerEmitter3D
var timer: float = 0.0
var fade_out_timer: float = 0.0
var ease_out_intensity:float = 1.0
func _init(_emitter: ShakerEmitter3D) -> void:
self.emitter = _emitter
func is_playing() -> bool:
for i:EmitterData in emitter_list:
return i.emitter.emitting
return false