some shake

This commit is contained in:
2026-01-20 15:27:59 +01:00
parent 8d1e7ebb4f
commit c1ca0bf27b
143 changed files with 5704 additions and 6 deletions

View File

@@ -0,0 +1,92 @@
extends Node
func shake_by_preset(preset:ShakerPresetBase, node:Node, duration:float, speed:float=1.0, intensity:float=1.0, fade_in:float=0.25, fade_out:float=2.0):
if (preset is ShakerPreset2D && not node is Node2D): assert(false, "ShakerPreset2D only works for Node2D type")
if (preset is ShakerPreset3D && not node is Node3D): assert(false, "ShakerPreset3D only works for Node3D type")
if preset is ShakerPreset3D:
var component:ShakerComponent3D = ShakerComponent3D.new()
add_child(component)
component.name = "TEMP_ShakerComponent3D"
component.custom_target = true
component.intensity = intensity
component.Targets.append(node)
component.duration = duration
component.shake_speed = speed
component.fade_in = fade_in
component.fade_out = fade_out
component.shakerPreset = preset
component.shake_finished.connect(_on_shake_finished.bind(component))
component.play_shake()
elif preset is ShakerPreset2D:
var component:ShakerComponent2D = ShakerComponent2D.new()
component.name = "ShakerComponent2D"
add_child(component)
component.custom_target = true
component.Targets.append(node)
component.duration = duration
component.intensity = intensity
component.shake_speed = speed
component.fade_in = fade_in
component.fade_out = fade_out
component.shakerPreset = preset
component.shake_finished.connect(_on_shake_finished.bind(component))
component.play_shake()
func shake_property(property:ShakerProperty, node:Node, duration:float, speed:float=1.0, intensity:float=1.0, fade_in:float=0.25, fade_out:float=2.0) -> ShakerComponent:
var component:ShakerComponent = ShakerComponent.new()
component.name = "ShakerComponent"
add_child(component)
component.custom_target = true
component.Targets.append(node)
component.duration = duration
component.shake_speed = speed
component.intensity = intensity
component.fade_in = fade_in
component.fade_out = fade_out
component.shakerProperty.append(property)
component.shake_finished.connect(_on_shake_finished.bind(component))
component.play_shake()
return component
func shake_emit_3d(position:Vector3,preset:ShakerPreset3D, max_distance:float, duration:float, distance_attenuation:float=0.5, speed:float=1.0, fade_in:float=0.25, fade_out:float=2.0) -> ShakerEmitter3D:
var component:ShakerEmitter3D = ShakerEmitter3D.new()
component.name = "ShakerEmitter3D"
add_child(component)
component.max_distance = max_distance
component.global_position = position
component.duration = duration
component.shake_speed = speed
component.fade_in = fade_in
component.fade_out = fade_out
component.shakerPreset = preset
component.collision.shape = SphereShape3D.new()
component.collision.shape.radius = max_distance
component.shake_finished.connect(_on_shake_finished.bind(component))
component.play_shake()
return component
func shake_emit_2d(position:Vector2,preset:ShakerPreset2D, max_distance:float, duration:float, distance_attenuation:float=0.5, speed:float=1.0, fade_in:float=0.25, fade_out:float=2.0) -> ShakerEmitter2D:
var component:ShakerEmitter2D = ShakerEmitter2D.new()
component.name = "ShakerEmitter2D"
add_child(component)
component.max_distance = max_distance
component.global_position = position
component.duration = duration
component.shake_speed = speed
component.fade_in = fade_in
component.fade_out = fade_out
component.shakerPreset = preset
component.collision.shape = CircleShape2D.new()
component.collision.shape.radius = max_distance
component.shake_finished.connect(_on_shake_finished.bind(component))
component.play_shake()
return component
func _on_shake_finished(shaker_component) -> void:
shaker_component.queue_free()

View File

@@ -0,0 +1 @@
uid://c7flmumgr5w3u

View File

@@ -0,0 +1,90 @@
@tool
extends Node2D
# Shake intensity
@export_range(0.0, 1.0, 0.001, "or_greater") var intensity: float = 1.0:
set = set_intensity,
get = get_intensity
# Shake duration
@export var duration: float = 0.00:
set = set_duration,
get = get_duration
# Shake speed
@export_range(0.0, 1.0, 0.001, "or_greater") var shake_speed: float = 1.0:
set = set_shake_speed,
get = get_shake_speed
# Fade-in easing
@export_exp_easing var fade_in: float = 0.25:
set = set_fade_in,
get = get_fade_in
# Fade-out easing
@export_exp_easing("attenuation") var fade_out: float = 0.25:
set = set_fade_out,
get = get_fade_out
# Shaker preset
@export var shakerPreset:ShakerPreset2D:
set = set_shaker_preset,
get = get_shaker_preset
# Timer for shake progress
var timer: float = 0.0:
set = _on_timeline_progress
# SIGNALS
signal timeline_progress(progress: float)
signal shake_started
signal shake_finished
signal shake_fading_out
func set_intensity(value: float) -> void:
intensity = max(value, 0.0)
func get_intensity() -> float:
return intensity
func set_duration(value: float) -> void:
duration = max(value, 0.0)
if shakerPreset != null:
shakerPreset.component_duration = duration
notify_property_list_changed()
func get_duration() -> float:
return duration
func set_shake_speed(value: float) -> void:
shake_speed = max(value, 0.001)
notify_property_list_changed()
func get_shake_speed() -> float:
return shake_speed
func set_fade_in(value: float) -> void:
fade_in = value
func get_fade_in() -> float:
return fade_in
func set_fade_out(value: float) -> void:
fade_out = value
func get_fade_out() -> float:
return fade_out
func set_shaker_preset(value: ShakerPreset2D) -> void:
shakerPreset = value
if shakerPreset != null:
shakerPreset.parent = self
shakerPreset.component_duration = duration
func get_shaker_preset() -> ShakerPreset2D:
return shakerPreset
# Handles timeline progress
func _on_timeline_progress(value: float) -> void:
timer = value
timeline_progress.emit(timer)

View File

@@ -0,0 +1 @@
uid://c0tuadw5ygn6m

View File

@@ -0,0 +1,172 @@
@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

View File

@@ -0,0 +1 @@
uid://ddpgk78xe5y4g

View File

@@ -0,0 +1,148 @@
@icon("res://addons/shaker/assets/ShakerReceiver2D.svg")
@tool
class_name ShakerReceiver2D
extends Node2D
## Transmits values from ShakerEmitter2D to ShakerComponent2D
# 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 area2D: Area2D
var position_offset: Vector2 = Vector2.ZERO
var rotation_offset:float = 0.0
var scale_offset: Vector2 = Vector2.ZERO
var emitter_list: Array[EmitterData]
# Called when the node enters the scene tree for the first time
func _ready() -> void:
_setup_area2D()
if !Engine.is_editor_hint():
add_to_group("ShakerReceiver")
_connect_signals()
# Sets up the Area2D node
func _setup_area2D() -> void:
for child in get_children():
if child is Area2D:
area2D = child
return
area2D = Area2D.new()
var 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 = 0
area2D.collision_mask = 1 << 9
# Connects signals
func _connect_signals() -> void:
area2D.area_entered.connect(on_area_entered)
area2D.area_exited.connect(on_area_exited)
# Called every frame
func _process(delta: float) -> void:
position_offset = Vector2.ZERO
rotation_offset = 0.0
scale_offset = Vector2.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 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[Vector2]:
return [position_offset, Vector2(rotation_offset, 0.0), scale_offset]
# Returns configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
if not get_parent() is ShakerComponent2D:
return ["Parent must be ShakerComponent2D"]
var _ex:bool = false
for i in get_children():
if i is Area2D:
_ex = true
break
if !_ex:
return ["ShakerReceiver2D needs Area2D to work"]
return []
# Called when an area enters
func on_area_entered(area: Area2D) -> void:
var node = area.get_parent()
if node is ShakerEmitter2D:
var data = EmitterData.new(node)
emitter_list.append(data)
# Called when an area exits
func on_area_exited(area: Area2D) -> void:
var node = area.get_parent()
if node is ShakerEmitter2D:
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: ShakerEmitter2D
var timer: float = 0.0
var fade_out_timer: float = 0.0
var ease_out_intensity:float = 1.0
func _init(_emitter: ShakerEmitter2D) -> void:
self.emitter = _emitter
func is_playing() -> bool:
for i:EmitterData in emitter_list:
return i.emitter.emitting
return false

View File

@@ -0,0 +1 @@
uid://ca4ybjjck0t0a

View File

@@ -0,0 +1,287 @@
@tool
@icon("res://addons/shaker/assets/Shaker2D.svg")
class_name ShakerComponent2D
extends "res://addons/shaker/src/Vector2/ShakerBase2D.gd"
## Allows you to apply shake effect to any 2D node according to position, rotation, scale
enum ShakeAddMode {
add,
override
}
# Custom target flag
@export var custom_target: bool = false:
set = set_custom_target,
get = get_custom_target
# Array of target Node2D objects
@export var Targets: Array[Node2D]
# Randomization flag
@export var randomize: bool = false:
set = set_randomize,
get = get_randomize
# Playing state
@export var is_playing: bool = false
@export var AutoPlay:bool = false
# Private variables
var _last_position_shake: Array[Vector2] = [Vector2.ZERO]
var _last_scale_shake: Array[Vector2] = [Vector2.ZERO]
var _last_rotation_shake: Array[float] = [0]
var _timer_offset: float = 0.0
var _fading_out: bool = false
var _seed: float = 203445
var _external_shakes:Array[ExternalShake]
# Called when the node enters the scene tree for the first time
func _ready() -> void:
set_process_input(false)
set_process_internal(false)
set_process_shortcut_input(false)
set_process_unhandled_input(false)
set_physics_process(false)
set_physics_process_internal(false)
add_to_group("ShakerComponent")
if !Engine.is_editor_hint():
if AutoPlay:
play_shake()
# Resets the shaker to its initial state
func _reset() -> void:
_last_position_shake = [Vector2.ZERO]
_last_scale_shake = [Vector2.ZERO]
_last_rotation_shake = [0]
_external_shakes.clear()
_initalize_prev_positions()
is_playing = false
_initialize_timer_offset()
_fading_out = false
_initalize_target()
# Initializes previous positions for randomized shaking
func _initalize_prev_positions() -> void:
_last_position_shake.resize(Targets.size())
_last_position_shake.fill(Vector2.ZERO)
_last_scale_shake.resize(Targets.size())
_last_scale_shake.fill(Vector2.ZERO)
_last_rotation_shake.resize(Targets.size())
_last_rotation_shake.fill(0)
# Called every frame
func _process(delta: float) -> void:
if is_playing:
if shakerPreset != null || _external_shakes.size() > 0 || is_receiving_from_emitters():
if timer <= duration || 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 && !_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 (!(duration > 0) || _fading_out) && is_playing:
if _ease_out <= get_process_delta_time():
force_stop_shake()
var _shake_position: Array[Vector2] = []
var _shake_rotation: Array[float] = []
var _shake_scale: Array[Vector2] = []
var _count:int =(Targets.size() if randomize else 1)
_shake_position.resize(_count)
_shake_position.fill(Vector2.ZERO)
_shake_rotation.resize(_count)
_shake_rotation.fill(0)
_shake_scale.resize(_count)
_shake_scale.fill(Vector2.ZERO)
for _index in _count:
var _randomized: float = (_seed * (float(_index+1) / Targets.size())) if randomize else 0.0
if _last_position_shake.size() != _count: _initalize_prev_positions()
# Shaker Preset
if shakerPreset != null:
var _value:float = timer + _randomized
var _strength:float = intensity * _ease_in * _ease_out
_shake_position[_index] += (shakerPreset.get_value(_value, ShakerPreset2D.Categories.POSITION) * _strength)
_shake_rotation[_index] += (shakerPreset.get_value(_value, ShakerPreset2D.Categories.ROTATION) * _strength * (PI/2.0))
_shake_scale[_index] += (shakerPreset.get_value(_value, ShakerPreset2D.Categories.SCALE) * _strength)
# External Shake Addition
for external_shake:ExternalShake in _external_shakes:
var _real_time:float = min(timer-external_shake.start_time, external_shake.duration)
_ease_in = ease(_real_time/external_shake.duration, external_shake.fade_in)
_ease_out = ease(1.0-(_real_time/external_shake.duration), external_shake.fade_out)
var _value:float = (_real_time*external_shake.speed) + _randomized
var _strength:float = external_shake.intensity * intensity * _ease_in * _ease_out
var _mode_value:Array[Vector2] = [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO]
match external_shake.mode:
ShakeAddMode.add:
_mode_value = [_shake_position[_index], Vector2(_shake_rotation[_index], 0.0), _shake_scale[_index] ]
_shake_position[_index] = _mode_value[0] + (external_shake.preset.get_value(_value, ShakerPreset2D.Categories.POSITION) * _strength)
_shake_rotation[_index] = _mode_value[1].x +(external_shake.preset.get_value(_value, ShakerPreset2D.Categories.ROTATION).x * _strength * (PI/2.0))
_shake_scale[_index] = _mode_value[2] + (external_shake.preset.get_value(_value, ShakerPreset2D.Categories.SCALE) * _strength)
if _real_time >= external_shake.duration:
_external_shakes.erase(external_shake)
# Shake Emitter
for _child in get_children():
if _child is ShakerReceiver2D:
_shake_position[_index] += _child.position_offset
_shake_rotation[_index] += _child.rotation_offset
_shake_scale[_index] += _child.scale_offset
for index: int in Targets.size():
var target: Node2D = Targets[index]
if !is_instance_valid(target):
Targets.remove_at(index)
index-=1
if Targets.size() <= 0:
shake_finished.emit()
break
var _i:int = fmod(index, _shake_position.size())
target.position += -_last_position_shake[_i] + _shake_position[_i]
target.rotation += -_last_rotation_shake[_i] + _shake_rotation[_i]
target.scale += -_last_scale_shake[_i] + _shake_scale[_i]
_last_position_shake = _shake_position
_last_rotation_shake = _shake_rotation
_last_scale_shake = _shake_scale
# Stops the shake effect with a fade-out
func stop_shake() -> void:
if !_fading_out:
_timer_offset = timer
_fading_out = true
shake_fading_out.emit()
# Immediately stops the shake effect
func force_stop_shake() -> void:
if is_playing || _fading_out:
set_progress(0.0)
_reset()
shake_finished.emit()
# Starts the shake effect
func play_shake() -> void:
_initalize_target()
randomize_shake()
is_playing = !is_playing if Engine.is_editor_hint() else true
_fading_out = false
_initialize_timer_offset()
shake_started.emit()
func randomize_shake() -> void:
_seed = randf_range(10000, 99999)
func _initalize_target() -> void:
if !custom_target:
Targets.clear()
if get_parent() is Node2D:
Targets.append(get_parent())
# Placeholder for shake function
func shake(shaker_preset:ShakerPreset2D, _mode:ShakeAddMode=ShakeAddMode.add, duration:float=1.0, speed:float=1.0, intensity:float=1.0, fade_in:float=.25, fade_out:float=.25) -> void:
var external_shake:ExternalShake = ExternalShake.new()
external_shake.preset = shaker_preset
external_shake.duration = duration
external_shake.speed = speed
external_shake.intensity = intensity
external_shake.start_time = timer
external_shake.fade_in = fade_in
external_shake.fade_out = fade_out
external_shake.mode = _mode
_external_shakes.append(external_shake)
if Targets.is_empty():
_initalize_target()
is_playing = true
# Validates property visibility
func _validate_property(property: Dictionary) -> void:
if property.name == "Targets" || property.name == "randomize":
if !custom_target:
property.usage = PROPERTY_USAGE_NONE
if property.name == "fade_time":
if duration > 0:
property.usage = PROPERTY_USAGE_NONE
# Get configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
if !custom_target:
if not get_parent() is Node2D:
return ["Parent must be Node2D"]
return []
# Sets the shake progress
func set_progress(value: float) -> void:
timer = value
_progress_shake()
# Custom setter and getter functions for @export variables
func set_custom_target(value: bool) -> void:
custom_target = value
notify_property_list_changed()
func get_custom_target() -> bool:
return custom_target
func set_randomize(value: bool) -> void:
if custom_target && Targets.size() > 1:
for index: int in Targets.size():
var target: Node2D = Targets[index]
var i = fmod(index, _last_position_shake.size())
target.position += -_last_position_shake[i]
target.rotation += -_last_rotation_shake[i]
target.scale += -_last_scale_shake[i]
_last_position_shake.fill(_last_position_shake[0])
_last_rotation_shake.fill(_last_rotation_shake[0])
_last_scale_shake.fill(_last_scale_shake[0])
randomize = value
randomize_shake()
func get_randomize() -> bool:
return randomize
class ExternalShake:
var preset:ShakerPreset2D
var duration:float = 1.0
var speed:float = 1.0
var intensity:float = 1.0
var start_time:float = 0.0
var fade_in:float = 0.25
var fade_out:float = 0.25
var mode:ShakeAddMode=ShakeAddMode.add
func _initialize_timer_offset() -> void:
if !(duration > 0): _timer_offset = 0x80000
else: _timer_offset = 0.0
func is_receiving_from_emitters() -> bool:
for _child in get_children():
if _child is ShakerReceiver2D:
return _child.is_playing()
return false

View File

@@ -0,0 +1 @@
uid://b78swtk5maey2

View File

@@ -0,0 +1,91 @@
@tool
#class_name ShakerBase3D
extends Node3D
# Shake intensity
@export_range(0.0, 1.0, 0.001, "or_greater") var intensity: float = 1.0:
set = set_intensity,
get = get_intensity
# Shake duration
@export var duration: float = 0.00:
set = set_duration,
get = get_duration
# Shake speed
@export_range(0.0, 1.0, 0.001, "or_greater") var shake_speed: float = 1.0:
set = set_shake_speed,
get = get_shake_speed
# Fade-in easing
@export_exp_easing var fade_in: float = 0.25:
set = set_fade_in,
get = get_fade_in
# Fade-out easing
@export_exp_easing("attenuation") var fade_out: float = 0.25:
set = set_fade_out,
get = get_fade_out
# Shaker preset
@export var shakerPreset:ShakerPreset3D:
set = set_shaker_preset,
get = get_shaker_preset
# Timer for shake progress
var timer: float = 0.0:
set = _on_timeline_progress
# SIGNALS
signal timeline_progress(progress: float)
signal shake_started
signal shake_finished
signal shake_fading_out
func set_intensity(value: float) -> void:
intensity = max(value, 0.0)
func get_intensity() -> float:
return intensity
func set_duration(value: float) -> void:
duration = max(value, 0.0)
if shakerPreset != null:
shakerPreset.component_duration = duration
notify_property_list_changed()
func get_duration() -> float:
return duration
func set_shake_speed(value: float) -> void:
shake_speed = max(value, 0.001)
notify_property_list_changed()
func get_shake_speed() -> float:
return shake_speed
func set_fade_in(value: float) -> void:
fade_in = value
func get_fade_in() -> float:
return fade_in
func set_fade_out(value: float) -> void:
fade_out = value
func get_fade_out() -> float:
return fade_out
func set_shaker_preset(value: ShakerPreset3D) -> void:
shakerPreset = value
if shakerPreset != null:
shakerPreset.parent = self
shakerPreset.component_duration = duration
func get_shaker_preset() -> ShakerPreset3D:
return shakerPreset
# Handles timeline progress
func _on_timeline_progress(value: float) -> void:
timer = value
timeline_progress.emit(timer)

View File

@@ -0,0 +1 @@
uid://cna76yluotcdw

View File

@@ -0,0 +1,183 @@
@icon("res://addons/shaker/assets/ShakerEmitter3D.svg")
@tool
class_name ShakerEmitter3D
extends "res://addons/shaker/src/Vector3/ShakerBase3D.gd"
## It emits shake values and is received by ShakeEmitter3D.
# 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: Vector3 = Vector3.ZERO
var shake_offset_rotation: Vector3 = Vector3.ZERO
var shake_offset_scale: Vector3 = Vector3.ZERO
var area3d: Area3D
var collision:CollisionShape3D
# 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 Area3D:
area3d = child
if not area3d:
_create_area3d()
set_emit(emit)
# Creates an Area3D child node if one doesn't exist
func _create_area3d() -> void:
area3d = Area3D.new()
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 = 1 << 9
area3d.collision_mask = 0
# Called every frame
func _process(delta: float) -> void:
if !Engine.is_editor_hint():
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:
if !Engine.is_editor_hint():
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: Vector3 = Vector3.ZERO
var _shake_rotation: Vector3 = Vector3.ZERO
var _shake_scale: Vector3 = Vector3.ZERO
if shakerPreset != null:
var _value: float = timer
var _strength: float = intensity * _ease_in * _ease_out
_shake_position += (shakerPreset.get_value(_value, ShakerPreset3D.Categories.POSITION) * _strength)
_shake_rotation += (shakerPreset.get_value(_value, ShakerPreset3D.Categories.ROTATION) * _strength * (PI/2.0))
_shake_scale += (shakerPreset.get_value(_value, ShakerPreset3D.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 !Engine.is_editor_hint():
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
# Updates the gizmo
#func update_gizmo() -> void:
#if _gizmo:
#_gizmo._redraw()
# Stops the shake effect with a fade-out
func stop_shake() -> void:
if !Engine.is_editor_hint():
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 Area3D):
return ["First child must be Area3D"]
return []
# Sets the shake progress
func set_progress(value: float) -> void:
if !Engine.is_editor_hint():
timer = value
_progress_shake()
# Setter for emit property
func set_emit(value: bool) -> void:
emit = value
if !Engine.is_editor_hint():
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

View File

@@ -0,0 +1 @@
uid://vo46i2pfq0uf

View File

@@ -0,0 +1,159 @@
@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

View File

@@ -0,0 +1 @@
uid://csr4nkcrmm0bq

View File

@@ -0,0 +1,284 @@
@tool
@icon("res://addons/shaker/assets/Shaker3D.svg")
class_name ShakerComponent3D
extends "res://addons/shaker/src/Vector3/ShakerBase3D.gd"
## Allows you to apply shake effect to any 3D node according to position, rotation, scale
enum ShakeAddMode {
add,
override
}
# Custom target flag
@export var custom_target: bool = false:
set = set_custom_target,
get = get_custom_target
# Array of target Node3D objects
@export var Targets: Array[Node3D]
# Randomization flag
@export var randomize: bool = false:
set = set_randomize,
get = get_randomize
# Playing state
@export var is_playing: bool = false
@export var AutoPlay:bool = false
# Private variables
var _last_position_shake: Array[Vector3] = [Vector3.ZERO]
var _last_scale_shake: Array[Vector3] = [Vector3.ZERO]
var _last_rotation_shake: Array[Vector3] = [Vector3.ZERO]
var _timer_offset: float = 0.0
var _fading_out: bool = false
var _seed: float = 203445
var _external_shakes:Array[ExternalShake]
# Called when the node enters the scene tree for the first time
func _ready() -> void:
set_process_input(false)
set_process_internal(false)
set_process_shortcut_input(false)
set_process_unhandled_input(false)
set_physics_process(false)
set_physics_process_internal(false)
add_to_group("ShakerComponent")
if !Engine.is_editor_hint():
if AutoPlay:
play_shake()
# Resets the shaker to its initial state
func _reset() -> void:
_last_position_shake = [Vector3.ZERO]
_last_scale_shake = [Vector3.ZERO]
_last_rotation_shake = [Vector3.ZERO]
_external_shakes.clear()
_initalize_prev_positions()
is_playing = false
_initialize_timer_offset()
_fading_out = false
_initalize_target()
# Initializes previous positions for randomized shaking
func _initalize_prev_positions() -> void:
_last_position_shake.resize(Targets.size())
_last_position_shake.fill(Vector3.ZERO)
_last_scale_shake.resize(Targets.size())
_last_scale_shake.fill(Vector3.ZERO)
_last_rotation_shake.resize(Targets.size())
_last_rotation_shake.fill(Vector3.ZERO)
# Called every frame
func _process(delta: float) -> void:
if is_playing:
if shakerPreset != null || _external_shakes.size() > 0 || is_receiving_from_emitters():
if timer <= duration || 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 && !_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 (!(duration > 0) || _fading_out) && is_playing:
if _ease_out <= get_process_delta_time():
force_stop_shake()
var _shake_position: Array[Vector3] = []
var _shake_rotation: Array[Vector3] = []
var _shake_scale: Array[Vector3] = []
var _count:int =(Targets.size() if randomize else 1)
_shake_position.resize(_count)
_shake_position.fill(Vector3.ZERO)
_shake_rotation.resize(_count)
_shake_rotation.fill(Vector3.ZERO)
_shake_scale.resize(_count)
_shake_scale.fill(Vector3.ZERO)
for _index in _count:
var _randomized: float = (_seed * (float(_index+1) / Targets.size())) if randomize else 0.0
if _last_position_shake.size() != _count: _initalize_prev_positions()
# Shaker Preset
if shakerPreset != null:
var _value:float = timer + _randomized
var _strength:float = intensity * _ease_in * _ease_out
_shake_position[_index] += (shakerPreset.get_value(_value, ShakerPreset3D.Categories.POSITION) * _strength)
_shake_rotation[_index] += (shakerPreset.get_value(_value, ShakerPreset3D.Categories.ROTATION) * _strength * (PI/2.0))
_shake_scale[_index] += (shakerPreset.get_value(_value, ShakerPreset3D.Categories.SCALE) * _strength)
# External Shake Addition
for external_shake:ExternalShake in _external_shakes:
var _real_time:float = min(timer-external_shake.start_time, external_shake.duration)
_ease_in = ease(_real_time/external_shake.duration, external_shake.fade_in)
_ease_out = ease(1.0-(_real_time/external_shake.duration), external_shake.fade_out)
var _value:float = (_real_time*external_shake.speed) + _randomized
var _strength:float = external_shake.intensity * intensity * _ease_in * _ease_out
var _mode_value:Array[Vector3] = [Vector3.ZERO, Vector3.ZERO, Vector3.ZERO]
match external_shake.mode:
ShakeAddMode.add:
_mode_value = [_shake_position[_index], _shake_rotation[_index], _shake_scale[_index] ]
_shake_position[_index] = _mode_value[0] + (external_shake.preset.get_value(_value, ShakerPreset3D.Categories.POSITION) * _strength)
_shake_rotation[_index] = _mode_value[1] +(external_shake.preset.get_value(_value, ShakerPreset3D.Categories.ROTATION) * _strength * (PI/2.0))
_shake_scale[_index] = _mode_value[2] + (external_shake.preset.get_value(_value, ShakerPreset3D.Categories.SCALE) * _strength)
if _real_time >= external_shake.duration:
_external_shakes.erase(external_shake)
# Shake Emitter
for _child in get_children():
if _child is ShakerReceiver3D:
_shake_position[_index] += _child.position_offset
_shake_rotation[_index] += _child.rotation_offset
_shake_scale[_index] += _child.scale_offset
for index: int in Targets.size():
var target: Node3D = Targets[index]
if !is_instance_valid(target):
Targets.remove_at(index)
index-=1
if Targets.size() <= 0:
shake_finished.emit()
break
var _i: int = fmod(index, _shake_position.size())
target.position += -_last_position_shake[_i] + _shake_position[_i]
target.rotation += -_last_rotation_shake[_i] + _shake_rotation[_i]
target.scale += -_last_scale_shake[_i] + _shake_scale[_i]
_last_position_shake = _shake_position
_last_rotation_shake = _shake_rotation
_last_scale_shake = _shake_scale
# Stops the shake effect with a fade-out
func stop_shake() -> void:
if !_fading_out:
_timer_offset = timer
_fading_out = true
shake_fading_out.emit()
# Immediately stops the shake effect
func force_stop_shake() -> void:
if is_playing || _fading_out:
set_progress(0.0)
_reset()
shake_finished.emit()
# Starts the shake effect
func play_shake() -> void:
_initalize_target()
randomize_shake()
is_playing = !is_playing if Engine.is_editor_hint() else true
_fading_out = false
_initialize_timer_offset()
shake_started.emit()
func randomize_shake() -> void:
_seed = randf_range(10000, 99999)
func _initialize_timer_offset() -> void:
if !(duration > 0): _timer_offset = 0x80000
else: _timer_offset = 0.0
func _initalize_target() -> void:
if !custom_target:
Targets.clear()
if get_parent() is Node3D:
Targets.append(get_parent())
# Placeholder for shake function
func shake(shaker_preset:ShakerPreset3D, _mode:ShakeAddMode=ShakeAddMode.add, duration:float=1.0, speed:float=1.0, intensity:float=1.0, fade_in:float=.25, fade_out:float=.25) -> void:
var external_shake:ExternalShake = ExternalShake.new()
external_shake.preset = shaker_preset
external_shake.duration = duration
external_shake.speed = speed
external_shake.intensity = intensity
external_shake.start_time = timer
external_shake.fade_in = fade_in
external_shake.fade_out = fade_out
external_shake.mode = _mode
_external_shakes.append(external_shake)
if Targets.is_empty():
_initalize_target()
is_playing = true
# Validates property visibility
func _validate_property(property: Dictionary) -> void:
if property.name == "Targets" || property.name == "randomize":
if !custom_target:
property.usage = PROPERTY_USAGE_NONE
if property.name == "fade_time":
if duration > 0:
property.usage = PROPERTY_USAGE_NONE
# Get configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
if !custom_target:
if not get_parent() is Node3D:
return ["Parent must be Node3D"]
return []
# Sets the shake progress
func set_progress(value: float) -> void:
timer = value
_progress_shake()
# Custom setter and getter functions for @export variables
func set_custom_target(value: bool) -> void:
custom_target = value
notify_property_list_changed()
func get_custom_target() -> bool:
return custom_target
func set_randomize(value: bool) -> void:
if custom_target && Targets.size() > 1:
for index: int in Targets.size():
var target: Node3D = Targets[index]
var i = fmod(index, _last_position_shake.size())
target.position += -_last_position_shake[i]
target.rotation += -_last_rotation_shake[i]
target.scale += -_last_scale_shake[i]
_last_position_shake.fill(_last_position_shake[0])
_last_rotation_shake.fill(_last_rotation_shake[0])
_last_scale_shake.fill(_last_scale_shake[0])
randomize = value
randomize_shake()
func get_randomize() -> bool:
return randomize
class ExternalShake:
var preset:ShakerPreset3D
var duration:float = 1.0
var speed:float = 1.0
var intensity:float = 1.0
var start_time:float = 0.0
var fade_in:float = 0.25
var fade_out:float = 0.25
var mode:ShakeAddMode=ShakeAddMode.add
func is_receiving_from_emitters() -> bool:
for _child in get_children():
if _child is ShakerReceiver3D:
return _child.is_playing()
return false

View File

@@ -0,0 +1 @@
uid://dnlxsrumw6ygp

View File

@@ -0,0 +1,347 @@
@tool
@icon("res://addons/shaker/assets/Shaker.svg")
class_name ShakerComponent
extends Node
# Allows you to apply a shake effect to any variable of a node of any type
# Custom target flag
@export var custom_target: bool = false:
set = set_custom_target,
get = get_custom_target
# Array of target Node objects
@export var Targets: Array[Node]
# Randomization flag
@export var randomize: bool = false:
set = set_randomize,
get = get_randomize
@export var AutoPlay:bool = false
# Playing state
@export var play:bool = false:
set(value):
play = value
if value:
play_shake()
elif timer > 0:
force_stop_shake()
var is_playing: bool = false
# Shake intensity
@export_range(0.0, 1.0, 0.001, "or_greater") var intensity: float = 1.0:
set = set_intensity,
get = get_intensity
# Shake duration
@export var duration: float = 0.00:
set = set_duration,
get = get_duration
# Shake speed
@export_range(0.0, 1.0, 0.001, "or_greater") var shake_speed: float = 1.0:
set = set_shake_speed,
get = get_shake_speed
# Fade-in easing
@export_exp_easing var fade_in: float = 0.25:
set = set_fade_in,
get = get_fade_in
# Fade-out easing
@export_exp_easing("attenuation") var fade_out: float = 0.25:
set = set_fade_out,
get = get_fade_out
# Shaker preset
@export var shakerProperty:Array[ShakerProperty]:
set = set_shaker_property,
get = get_shaker_property
# Timer for shake progress
var timer: float = 0.0:
set = _on_timeline_progress
# SIGNALS
signal timeline_progress(progress: float)
signal shake_started
signal shake_finished
signal shake_fading_out
func set_intensity(value: float) -> void:
intensity = max(value, 0.0)
func get_intensity() -> float:
return intensity
func set_duration(value: float) -> void:
duration = max(value, 0.0)
notify_property_list_changed()
func get_duration() -> float:
return duration
func set_shake_speed(value: float) -> void:
shake_speed = max(value, 0.001)
notify_property_list_changed()
func get_shake_speed() -> float:
return shake_speed
func set_fade_in(value: float) -> void:
fade_in = value
func get_fade_in() -> float:
return fade_in
func set_fade_out(value: float) -> void:
fade_out = value
func get_fade_out() -> float:
return fade_out
# Handles timeline progress
func _on_timeline_progress(value: float) -> void:
timer = value
timeline_progress.emit(timer)
# Private variables
var _timer_offset: float = 0.0
var _fading_out: bool = false
var _seed: float = 203445
var _last_values:Array[Dictionary]
# Called when the node enters the scene tree for the first time
func _ready() -> void:
set_process_input(false)
set_process_internal(false)
set_process_shortcut_input(false)
set_process_unhandled_input(false)
set_physics_process(false)
set_physics_process_internal(false)
add_to_group("ShakerComponent")
if !Engine.is_editor_hint():
if AutoPlay:
play_shake()
# Resets the shaker to its initial state
func _reset() -> void:
_last_values = []
is_playing = false
_initialize_timer_offset()
_fading_out = false
_initalize_target()
# Called every frame
func _process(delta: float) -> void:
if is_playing:
if shakerProperty != null:
if timer <= duration || 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 && !_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)
# Check all targets
for i:int in Targets.size():
var target:Node = Targets[i]
if !is_instance_valid(target):
Targets.remove_at(i)
i -= 1
if Targets.size() <= 0:
shake_finished.emit()
break
if (!(duration > 0) || _fading_out) && is_playing:
if _ease_out <= get_process_delta_time():
force_stop_shake()
var _count:int = Targets.size() # if randomize else 1)
var _value_temp:Array[Dictionary] = []
for i in _count:
_value_temp.append({})
for _index:int in _count:
var _randomized:float = (_seed * (float(_index+1) / Targets.size())) if randomize else 0.0
var target:Node = Targets[_index]
if _index > _last_values.size()-1:
_last_values.append({})
# Shaker Preset
for shake:ShakerProperty in shakerProperty:
if shake:
if !shake.property_name.is_empty() && shake.shake_type:
var _value:float = timer + _randomized
var _add_value = (shake.get_value(_value))
var current_value = target.get(shake.property_name)
if typeof(current_value) == typeof(current_value):
if !(_last_values[_index].has(shake.property_name)):
_last_values[_index][shake.property_name] = {}
_last_values[_index][shake.property_name]["value"] = _add_value * 0.0
var _prev_temp = (_add_value * 0.0) if !(_value_temp[_index].has(shake.property_name)) else _value_temp[_index][shake.property_name]["value"]
_value_temp[_index][shake.property_name] = {}
_value_temp[_index][shake.property_name]["value"] = _prev_temp + _add_value
_value_temp[_index][shake.property_name]["blend_mode"] = shake.shake_type.BlendingMode
else:
push_error("Variable value type is %s but Shake type is %s" % [type_string(current_value), type_string(_add_value)])
var _strength:float = intensity * _ease_in * _ease_out
for index:int in Targets.size():
var target:Node = Targets[index]
var i:int = fmod(index, _value_temp.size())
var property = _value_temp[i]
for property_index:int in property.size():
var property_name:StringName = property.keys()[property_index]
if !property_name.is_empty():
var value = property[property_name]["value"]
var blend_mode = property[property_name]["blend_mode"]
var current_value = target.get(property_name)-_last_values[i][property_name]["value"]
var default_value = current_value
match blend_mode:
ShakerTypeBase.BlendingModes.Add:
current_value += value
ShakerTypeBase.BlendingModes.Multiply:
current_value *= value
ShakerTypeBase.BlendingModes.Subtract:
current_value -= value
ShakerTypeBase.BlendingModes.Max:
if typeof(current_value) == TYPE_VECTOR2 || typeof(current_value) == TYPE_VECTOR2I:
current_value.x = max(current_value.x, value.x)
current_value.y = max(current_value.y, value.y)
elif typeof(current_value) == TYPE_VECTOR3 || typeof(current_value) == TYPE_VECTOR3I:
current_value.x = max(current_value.x, value.x)
current_value.y = max(current_value.y, value.y)
current_value.z = max(current_value.z, value.z)
else:
current_value = max(current_value, value)
ShakerTypeBase.BlendingModes.Min:
if typeof(current_value) == TYPE_VECTOR2 || typeof(current_value) == TYPE_VECTOR2I:
current_value.x = min(current_value.x, value.x)
current_value.y = min(current_value.y, value.y)
elif typeof(current_value) == TYPE_VECTOR3 || typeof(current_value) == TYPE_VECTOR3I:
current_value.x = min(current_value.x, value.x)
current_value.y = min(current_value.y, value.y)
current_value.z = min(current_value.z, value.z)
else:
current_value = min(current_value, value)
ShakerTypeBase.BlendingModes.Average:
current_value = (current_value + value) * 0.5
ShakerTypeBase.BlendingModes.Override:
current_value = value
if current_value != null:
var _added_value = (current_value - default_value) * _strength
target.set(property_name, default_value + _added_value )
_value_temp[i][property_name]["value"] = _added_value
else:
push_error(name," Variable Error: ",target," has no variable named \"",property_name,"\"")
_last_values = _value_temp
# Stops the shake effect with a fade-out
func stop_shake() -> void:
if !_fading_out:
_timer_offset = timer
_fading_out = true
shake_fading_out.emit()
# Immediately stops the shake effect
func force_stop_shake() -> void:
if is_playing || _fading_out:
set_progress(0.0)
is_playing = false
_fading_out = false
_last_values.clear()
shake_finished.emit()
func set_shaker_property(value:Array[ShakerProperty]) -> void:
shakerProperty = value
func get_shaker_property() -> Array[ShakerProperty]:
return shakerProperty
# Starts the shake effect
func play_shake() -> void:
if shakerProperty != null:
_initalize_target()
randomize_shake()
is_playing = !is_playing if Engine.is_editor_hint() else true
_fading_out = false
_initialize_timer_offset()
shake_started.emit()
func randomize_shake() -> void:
_seed = randf_range(10000, 99999)
func _initalize_target() -> void:
if !custom_target:
Targets.clear()
if get_parent() is Node:
Targets.append(get_parent())
# Validates property visibility
func _validate_property(property: Dictionary) -> void:
if property.name == "Targets" || property.name == "randomize":
if !custom_target:
property.usage = PROPERTY_USAGE_NONE
if property.name == "fade_time":
if duration > 0:
property.usage = PROPERTY_USAGE_NONE
# Get configuration warnings
func _get_configuration_warnings() -> PackedStringArray:
if !custom_target:
if not get_parent() is Node:
return ["Parent must be Node"]
return []
# Sets the shake progress
func set_progress(value: float) -> void:
timer = value
_progress_shake()
# Custom setter and getter functions for @export variables
func set_custom_target(value: bool) -> void:
custom_target = value
notify_property_list_changed()
func get_custom_target() -> bool:
return custom_target
func set_randomize(value: bool) -> void:
if custom_target && Targets.size() > 1:
if _last_values.size() > 0:
for index: int in Targets.size():
var target:Node = Targets[index]
var i:int = fmod(index, _last_values.size())
for shake:ShakerProperty in shakerProperty:
var current_value = target.get(shake.property_name)
target.set(shake.property_name, current_value - _last_values[i][shake.property_name]["value"])
_last_values.clear()
randomize = value
randomize_shake()
func _initialize_timer_offset() -> void:
if !(duration > 0): _timer_offset = 0x80000
else: _timer_offset = 0.0
func get_randomize() -> bool:
return randomize

View File

@@ -0,0 +1 @@
uid://dqvqij8tcek5

View File

@@ -0,0 +1,265 @@
@tool
extends Panel
const TIME_LINE_SCRIPT: GDScript = preload("res://addons/shaker/src/shaker_timeline.gd")
const ShakerBase3d = preload("res://addons/shaker/src/Vector3/ShakerBase3D.gd")
const ShakerBase2d = preload("res://addons/shaker/src/Vector2/ShakerBase2D.gd")
# Graph properties
var graph_points:Array[PackedFloat32Array]
var y_scale: float = 32.0
var width: float = 2.0
var shake: Resource
var graph_offset: Vector2 = Vector2(15.0, 0.0)
# Private variables
var _graph_min: float = -1.0
var _graph_max: float = 1.0
var _graph_max_total: float = 1.0
var layout_box: HBoxContainer = HBoxContainer.new()
var axis_button: OptionButton = OptionButton.new()
var fit_button: Button = Button.new()
var category_button: OptionButton
var time_line: Control
var flip_y: bool = true
var selected_category: int = 0
var graph_pressing: bool = false
var graph_middle_pressing: bool = false
var point_color_by_axis:Array[Color] = [Color.RED, Color.GREEN, Color.DEEP_SKY_BLUE]
var _unselected_opacity:float = .10
# Graph time offset property
var graph_time_offset: float = 0.0:
set = set_graph_time_offset,
get = get_graph_time_offset
# Called when the node enters the scene tree for the first timeX
func _ready() -> void:
shake.property_changed.connect(on_property_changed)
clip_contents = true
_setup_timeline()
_setup_layout_box()
_setup_fit_button()
_setup_category_button()
_setup_axis_button()
_update_graph()
_on_fit_button_clicked()
# Sets up the timeline
func _setup_timeline() -> void:
if shake is ShakerPresetBase:
time_line = TIME_LINE_SCRIPT.new()
time_line.GRAPH = self
add_child(time_line)
# Sets up the layout box
func _setup_layout_box() -> void:
add_child(layout_box)
layout_box.set_anchors_preset(Control.PRESET_TOP_WIDE)
layout_box.custom_minimum_size.y = 16
layout_box.alignment = BoxContainer.ALIGNMENT_END
# Sets up the fit button
func _setup_fit_button() -> void:
layout_box.add_child(fit_button)
fit_button.text = "Fit"
fit_button.pressed.connect(_on_fit_button_clicked)
# Sets up the category button
func _setup_category_button() -> void:
if shake is ShakerPresetBase:
category_button = OptionButton.new()
category_button.item_selected.connect(_on_category_selected)
layout_box.add_child(category_button)
category_button.custom_minimum_size = Vector2(16, 16)
for _category_index in shake.Categories.size():
var _category_name: StringName = shake.Categories.keys()[_category_index]
var _category_value: int = shake.Categories.values()[_category_index]
category_button.add_item(_category_name, _category_value)
if shake.Categories.size() > 0: category_button.select(0)
# Sets up the axis button
func _setup_axis_button() -> void:
if axis_button.get_parent() != layout_box:
layout_box.add_child(axis_button)
axis_button.position = Vector2(-32, 4)
axis_button.custom_minimum_size = Vector2(16, 16)
axis_button.item_selected.connect(_axis_selected)
axis_button.add_theme_color_override("background_color", Color.GREEN)
var selected:int = max(axis_button.get_selected_id(), 0)
axis_button.clear()
var Axis:Array = []
if shake is ShakerTypeBase:
Axis = shake.GraphAxis.keys()
elif shake is ShakerPresetBase:
var shakes:Array = shake.get_shakes_by_category(category_button.selected)
if shakes.size() > 0 && shakes[0]:
Axis = shakes[0].get_script().GraphAxis.keys()
for axis in Axis:
axis_button.add_item(axis)
selected = min(selected, Axis.size())
if Axis.size() > 0:
axis_button.select(selected)
# Updates the graph
func _update_graph() -> void:
graph_points.clear()
if not axis_button.get_selected_id() < 0:
var _baked: float = round(shake.bake_internal)
_graph_min = 0.0
_graph_max = 0.0
graph_points.resize(axis_button.item_count)
for axis_index in axis_button.item_count:
for i in _baked + 1:
var _args: Array = [graph_time_offset + (i / _baked)]
if shake is ShakerPresetBase:
_args.append(selected_category)
var _val = shake.callv("get_value", _args)
if typeof(_val) != TYPE_FLOAT: _val = _val[axis_index]
var _result: float = _val * (-1 if flip_y else 1)
graph_points[axis_index].append(_result)
_graph_min = min(_graph_min, _result)
_graph_max = max(_graph_max, _result)
_graph_max_total = max(abs(_graph_max), abs(_graph_min))
# Draws the graph
func _draw() -> void:
_draw_zero_line()
_draw_min_max()
_draw_graph_points()
_draw_graph_info()
# Draws the zero line
func _draw_zero_line() -> void:
var font_size: int = 8
draw_line(Vector2(0, size.y * 0.5), Vector2(size.x, size.y * 0.5), Color.DIM_GRAY, 1, false)
draw_string(ThemeDB.fallback_font, Vector2(5.0, size.y * 0.5 + font_size * 0.5), "0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.DIM_GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
# Draws the min/max values
func _draw_min_max() -> void:
var font_size: int = 8
var _view_percent: float = (y_scale * _graph_max_total) / (size.y * 0.5)
if (_view_percent * _graph_max_total) > 0.25:
var _padding: int = 10
var _up_offset: float = max((-size.y * 0.5) * min(_view_percent, 1.0), -size.y * 0.5 + _padding)
var _down_offset: float = min((size.y * 0.5) * min(_view_percent, 1.0), size.y * 0.5 - _padding)
var _min_max_percent: float = 1.0 / max(_view_percent, 1.0)
draw_string(ThemeDB.fallback_font, Vector2(5.0, size.y * 0.5 + font_size * 0.5 + _up_offset), "%.2f" % (1.0 * _graph_max_total * _min_max_percent), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.DIM_GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
draw_string(ThemeDB.fallback_font, Vector2(5.0, size.y * 0.5 + font_size * 0.5 + _down_offset), "%.2f" % (1.0 * _graph_min * _min_max_percent), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color.DIM_GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
# Draws the graph points
func _draw_graph_points() -> void:
for axis_index in graph_points.size():
var _point_length:int = graph_points[axis_index].size()
var _point_color:Color = point_color_by_axis[fmod(axis_index, point_color_by_axis.size())]
var alpha:float = _unselected_opacity if axis_index != axis_button.get_selected_id() else 1.0
var _final_size: Vector2 = size - graph_offset
var _offset: Vector2 = Vector2(1, y_scale)
for point_index in _point_length:
var _size_offset = Vector2((_final_size.x / (_point_length)) * point_index, _final_size.y * 0.5)
var point:float = graph_points[axis_index][point_index]
if point_index < _point_length - 1:
var _size_offset_next = Vector2((_final_size.x / (_point_length)) * (point_index + 1), _final_size.y * 0.5)
var point_next:float = graph_points[axis_index][point_index + 1]
var _final_point_1: Vector2 = graph_offset + _size_offset + Vector2(0.0, point) * _offset
var _final_point_2: Vector2 = graph_offset + _size_offset_next + Vector2(0.0, point_next) * _offset
draw_line(_final_point_1, _final_point_2, _point_color * Color(1,1,1, alpha), width, false)
# Draws the graph info
func _draw_graph_info() -> void:
var font_size: int = 8
var _text = "Zoom: %.2f | Zoom IN / OUT with mouse scroll" % (y_scale / (size.y * 0.5 / _graph_max_total))
var _text_size: float = _text.length() * font_size * 0.5
draw_string(ThemeDB.fallback_font, Vector2(size.x - _text_size, size.y - 8.0), _text, HORIZONTAL_ALIGNMENT_RIGHT, -1, font_size, Color.GRAY, TextServer.JUSTIFICATION_NONE, TextServer.DIRECTION_AUTO, TextServer.ORIENTATION_HORIZONTAL)
# Handles GUI input
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
_handle_mouse_button(event)
if event is InputEventMouseMotion:
_handle_mouse_motion(event)
# Handles mouse button input
func _handle_mouse_button(event: InputEventMouseButton) -> void:
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
y_scale -= 1.0 / _graph_max_total
accept_event()
elif event.button_index == MOUSE_BUTTON_WHEEL_UP:
y_scale += 1.0 / _graph_max_total
accept_event()
elif event.button_index == MOUSE_BUTTON_LEFT:
if shake is ShakerPresetBase:
graph_pressing = event.pressed
update_timeline_position(event.position)
if event.button_index == MOUSE_BUTTON_MIDDLE:
if shake is ShakerPresetBase:
graph_middle_pressing = event.pressed
y_scale = max(y_scale, 1)
queue_redraw()
# Handles mouse motion input
func _handle_mouse_motion(event: InputEventMouseMotion) -> void:
if graph_pressing:
update_timeline_position(event.position)
if graph_middle_pressing:
graph_time_offset += -(event.relative.x / size.x)
graph_time_offset = max(graph_time_offset, 0.0)
# Updates the timeline position
func update_timeline_position(pos: Vector2) -> void:
if shake is ShakerPresetBase:
var _press_percent: float = max(((pos.x) - graph_offset.x) / (size.x - graph_offset.x), 0.0)
if shake.parent is ShakerBase3d || shake.parent is ShakerBase2d:
var _shaker_component = shake.parent
if _shaker_component != null:
_shaker_component.set_progress(_press_percent + graph_time_offset)
# Called when a property changes
func on_property_changed(_name: StringName) -> void:
_setup_axis_button()
_update_graph()
queue_redraw()
if is_inf(y_scale) or is_nan(y_scale):
_on_fit_button_clicked()
# Called when an axis is selected
func _axis_selected(item: int) -> void:
_update_graph()
_on_fit_button_clicked()
# Called when the fit button is clicked
func _on_fit_button_clicked() -> void:
y_scale = (size.y * 0.5) / _graph_max_total
graph_time_offset = 0.0
queue_redraw()
# Called when a category is selected
func _on_category_selected(item: int) -> void:
selected_category = item
_setup_axis_button()
_update_graph()
_on_fit_button_clicked()
# Setter for graph_time_offset
func set_graph_time_offset(value: float) -> void:
graph_time_offset = value
if time_line != null:
time_line.queue_redraw()
_update_graph()
queue_redraw()
# Getter for graph_time_offset
func get_graph_time_offset() -> float:
return graph_time_offset
func select_axis(index:int) -> void:
if axis_button.has_selectable_items():
axis_button.select(index)
func select_category(index:int) -> void:
if category_button.has_selectable_items():
category_button.select(index)

View File

@@ -0,0 +1 @@
uid://bihgv1h2lc26e

View File

@@ -0,0 +1,50 @@
extends EditorInspectorPlugin
const GRAPH_SCRIPT = preload("res://addons/shaker/src/shaker_graph.gd")
const SHAKER_PANEL = preload("res://addons/shaker/src/shaker_panel.gd")
func _can_handle(object: Object) -> bool:
return (object is ShakerTypeBase || object is ShakerPresetBase || object is ShakerComponent3D || object is ShakerComponent2D )
func _parse_group(object: Object, group: String) -> void:
if object is ShakerTypeBase:
if group == "Live Shake Graph":
add_graph(object)
func _parse_category(object: Object, category: String) -> void:
pass
func _parse_begin(object: Object) -> void:
if object is ShakerComponent3D || object is ShakerComponent2D:
var _panel:MarginContainer = SHAKER_PANEL.new()
_panel.Target = object;
_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
add_custom_control(_panel)
func _parse_end(object: Object) -> void:
pass
func add_graph(_object:Object) -> Panel:
var property_control:Panel = Panel.new()
property_control.set_script(GRAPH_SCRIPT)
property_control.shake = _object;
add_custom_control(property_control)
property_control.custom_minimum_size.y = 128;
property_control.set_anchors_preset(Control.PRESET_FULL_RECT)
return property_control;
func _parse_property(object: Object, type: Variant.Type, name: String, hint_type: PropertyHint, hint_string: String, usage_flags: int, wide: bool) -> bool:
if object is ShakerTypeBase:
if name == "_temp_graph":
return true;
if object is ShakerPresetBase:
if name == "bake_internal":
object.Graph = add_graph(object)
if object is ShakerComponent3D:
if name == "is_playing":
#var _panel:Panel = SHAKER_PANEL.new()
#_panel.Target = object;
#_panel.set_anchors_preset(Control.PRESET_FULL_RECT)
#add_custom_control(_panel)
return true;
return false;

View File

@@ -0,0 +1 @@
uid://bj0ltv8atp1sn

View File

@@ -0,0 +1,63 @@
@tool
extends MarginContainer
var _texture_button_play:Button = Button.new()
var _texture_button_stop:Button = Button.new()
var hbox:HBoxContainer = HBoxContainer.new()
const play_texture:CompressedTexture2D = preload("res://addons/shaker/assets/Play.svg")
const pause_texture:CompressedTexture2D = preload("res://addons/shaker/assets/Pause.svg")
const stop_texture:CompressedTexture2D = preload("res://addons/shaker/assets/Stop.svg")
var Target;
var button_width:float = 96;
func _ready() -> void:
custom_minimum_size.y = 32;
add_theme_constant_override("margin_left",5)
add_theme_constant_override("margin_right",5)
add_theme_constant_override("margin_bottom",5)
add_theme_constant_override("margin_top",5)
add_child(hbox)
hbox.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT, Control.PRESET_MODE_KEEP_HEIGHT, 5)
hbox.alignment = BoxContainer.ALIGNMENT_CENTER;
hbox.add_child(_texture_button_play)
_texture_button_play.custom_minimum_size.x = button_width;
_texture_button_play.expand_icon = true;
_update_buttons()
hbox.add_child(_texture_button_stop)
_texture_button_stop.text = "Stop"
_texture_button_stop.icon = stop_texture;
_texture_button_stop.custom_minimum_size.x = button_width;
_texture_button_stop.expand_icon = true;
_texture_button_play.pressed.connect(_on_play_pressed)
_texture_button_stop.pressed.connect(_on_stop_pressed)
if Target != null:
Target.timeline_progress.connect(func(progress:float):
_update_buttons()
)
Target.shake_finished.connect(func():
_update_buttons()
)
func _on_play_pressed() -> void:
Target.play_shake()
_update_buttons()
func _update_buttons() -> void:
_texture_button_play.text = "Play" if (Target.timer == 0.0 || !Target.is_playing) else "Pause"
_texture_button_play.icon = play_texture if (Target.timer == 0.0 || !Target.is_playing) else pause_texture;
_texture_button_stop.text = "Stop" if (!Target._fading_out) else "Force Stop"
_texture_button_stop.modulate = Color.WHITE if (!Target._fading_out) else Color.INDIAN_RED;
func _on_stop_pressed() -> void:
if !Target._fading_out:
Target.stop_shake()
else:
Target.force_stop_shake()
#_update_buttons()

View File

@@ -0,0 +1 @@
uid://biww5l4smr7f0

View File

@@ -0,0 +1,14 @@
@tool
extends EditorPlugin
var SHAKER_INSPECTOR_PLUGIN:EditorInspectorPlugin = preload("res://addons/shaker/src/shaker_inspector.gd").new()
func _enter_tree() -> void:
# Initialization of the plugin goes here.
add_inspector_plugin(SHAKER_INSPECTOR_PLUGIN)
add_autoload_singleton("Shaker", "res://addons/shaker/src/Shaker.gd")
func _exit_tree() -> void:
# Clean-up of the plugin goes here.
remove_inspector_plugin(SHAKER_INSPECTOR_PLUGIN)
remove_autoload_singleton("Shaker")

View File

@@ -0,0 +1 @@
uid://xqg7s8xnwljh

View File

@@ -0,0 +1,41 @@
@tool
extends Control
const ShakerBase3d = preload("res://addons/shaker/src/Vector3/ShakerBase3D.gd")
const ShakerBase2d = preload("res://addons/shaker/src/Vector2/ShakerBase2D.gd")
var GRAPH:Panel:
set=_initalize_graph
var timer:float = 0.0;
var playing:bool = false;
var _shaker_component;
func _ready() -> void:
mouse_filter = MOUSE_FILTER_IGNORE;
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT, Control.PRESET_MODE_KEEP_SIZE)
func _initalize_graph(value:Panel) -> void:
GRAPH = value;
if GRAPH != null:
if GRAPH.shake is ShakerPresetBase:
_shaker_component = GRAPH.shake.parent;
if _shaker_component != null:
_shaker_component.timeline_progress.connect(update_timeline)
_shaker_component.shake_finished.connect(func():
if GRAPH.shake is ShakerPresetBase:
GRAPH.graph_time_offset = 0.0;
)
func update_timeline(value=1) -> void:
timer = value;
if GRAPH.shake is ShakerPresetBase && _shaker_component != null:
if GRAPH.shake.__follow_timeline && _shaker_component.is_playing:
GRAPH.graph_time_offset = max(timer - .5, 0.0)
queue_redraw()
func _draw() -> void:
# Playing Timeline
var _final_size:Vector2 = size - GRAPH.graph_offset;
var _timeline_offset:Vector2 = Vector2(-GRAPH.graph_time_offset * _final_size.x, 0.0)
draw_line(GRAPH.graph_offset+_timeline_offset+Vector2(timer*_final_size.x, 0.0), GRAPH.graph_offset+_timeline_offset+Vector2(timer*_final_size.x, _final_size.y), Color.ORANGE, 1, false)

View File

@@ -0,0 +1 @@
uid://dk3dikejci4sq