285 lines
9.0 KiB
GDScript
285 lines
9.0 KiB
GDScript
@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
|