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

173 lines
4.6 KiB
GDScript

@icon("res://addons/shaker/assets/ShakerEmitter2D.svg")
@tool
class_name ShakerEmitter2D
extends "res://addons/shaker/src/Vector2/ShakerBase2D.gd"
## It emits shake values and is received by ShakeEmitter2D.
# Exported variables
@export var emit: bool:
set = set_emit,
get = get_emit
@export var max_distance: float = 0.0:
set = set_max_distance,
get = get_max_distance
@export_exp_easing("attenuation") var distance_attenuation: float = 0.5:
set = set_distance_attenuation,
get = get_distance_attenuation
# Private variables
var emitting: bool = false
var _timer_offset: float = 0.0
var _fading_out: bool = false
var shake_offset_position: Vector2 = Vector2.ZERO
var shake_offset_rotation:float = 0.0
var shake_offset_scale: Vector2 = Vector2.ZERO
var area2d: Area2D
var collision:CollisionShape2D
# Called when the node enters the scene tree for the first time
func _ready() -> void:
add_to_group("ShakerEmitter")
for child in get_children():
if child is Area2D:
area2d = child
if not area2d:
_create_Area2D()
set_emit(emit)
# Creates an Area2D child node if one doesn't exist
func _create_Area2D() -> void:
area2d = Area2D.new()
collision = CollisionShape2D.new()
add_child(area2d)
area2d.add_child(collision)
area2d.set_owner(get_tree().edited_scene_root)
collision.set_owner(get_tree().edited_scene_root)
area2d.name = "Area2D"
collision.name = "CollisionShape2D"
area2d.collision_layer = 1 << 9
area2d.collision_mask = 0
# Called every frame
func _process(delta: float) -> void:
if emitting:
if shakerPreset != null:
if timer <= duration or duration == 0.0:
_progress_shake()
timer += delta * shake_speed
else:
force_stop_shake()
else:
if timer > 0:
force_stop_shake()
# Progresses the shake effect
func _progress_shake() -> void:
var _ease_in: float = 1.0
var _ease_out: float = 1.0
var _final_duration: float = duration if (duration > 0 and not _fading_out) else 1.0
_ease_in = ease(timer/_final_duration, fade_in)
_ease_out = ease(1.0 - (max((timer - _timer_offset), 0.0))/_final_duration, fade_out)
if not (duration > 0) or _fading_out:
if _ease_out <= get_process_delta_time():
force_stop_shake()
var _shake_position: Vector2 = Vector2.ZERO
var _shake_rotation:float = 0.0
var _shake_scale: Vector2 = Vector2.ZERO
if shakerPreset != null:
var _value: float = timer
var _strength: float = intensity * _ease_in * _ease_out
_shake_position += (shakerPreset.get_value(_value, ShakerPreset2D.Categories.POSITION) * _strength)
_shake_rotation += (shakerPreset.get_value(_value, ShakerPreset2D.Categories.ROTATION) * _strength * (PI/2.0))
_shake_scale += (shakerPreset.get_value(_value, ShakerPreset2D.Categories.SCALE) * _strength)
shake_offset_position = _shake_position
shake_offset_rotation = _shake_rotation
shake_offset_scale = _shake_scale
# Starts the shake effect
func play_shake() -> void:
if shakerPreset != null:
emitting = true
_fading_out = false
_initialize_timer_offset()
shake_started.emit()
func _initialize_timer_offset() -> void:
if !(duration > 0): _timer_offset = 0x80000
else: _timer_offset = 0.0
# Stops the shake effect with a fade-out
func stop_shake() -> void:
if not _fading_out:
_timer_offset = timer
_fading_out = true
shake_fading_out.emit()
# Immediately stops the shake effect
func force_stop_shake() -> void:
if emitting:
if emit: emit = false
_fading_out = false
emitting = false
set_progress(0.0)
shake_finished.emit()
# Returns configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
if not get_children().any(func(child): return child is Area2D):
return ["First child must be Area2D"]
return []
# Sets the shake progress
func set_progress(value: float) -> void:
timer = value
_progress_shake()
# Setter for emit property
func set_emit(value: bool) -> void:
emit = value
if value:
play_shake()
elif timer > 0:
force_stop_shake()
# Getter for emit property
func get_emit() -> bool:
return emit
# Setter for max_distance property
func set_max_distance(value: float) -> void:
max_distance = value
notify_property_list_changed()
# Getter for max_distance property
func get_max_distance() -> float:
return max_distance
# Setter for distance_attenuation property
func set_distance_attenuation(value: float) -> void:
distance_attenuation = value
# Getter for distance_attenuation property
func get_distance_attenuation() -> float:
return distance_attenuation
# Validates properties
func _validate_property(property: Dictionary) -> void:
if property.name == "distance_attenuation":
if not (max_distance > 0):
property.usage = PROPERTY_USAGE_NONE