diff --git a/addons/shaker/assets/Pause.svg b/addons/shaker/assets/Pause.svg
new file mode 100644
index 00000000..5b720e01
--- /dev/null
+++ b/addons/shaker/assets/Pause.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/Pause.svg.import b/addons/shaker/assets/Pause.svg.import
new file mode 100644
index 00000000..a563f3c2
--- /dev/null
+++ b/addons/shaker/assets/Pause.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://gdl502i1v8r0"
+path="res://.godot/imported/Pause.svg-bf8a05fc34af72ad423e24582ebdfe77.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/Pause.svg"
+dest_files=["res://.godot/imported/Pause.svg-bf8a05fc34af72ad423e24582ebdfe77.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/Play.svg b/addons/shaker/assets/Play.svg
new file mode 100644
index 00000000..c0148f3c
--- /dev/null
+++ b/addons/shaker/assets/Play.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/Play.svg.import b/addons/shaker/assets/Play.svg.import
new file mode 100644
index 00000000..bc11ba59
--- /dev/null
+++ b/addons/shaker/assets/Play.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bkiomqdsxl5am"
+path="res://.godot/imported/Play.svg-7dc254fbb0920cfd077cd3ee6c205a32.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/Play.svg"
+dest_files=["res://.godot/imported/Play.svg-7dc254fbb0920cfd077cd3ee6c205a32.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShaderPreset.svg.import b/addons/shaker/assets/ShaderPreset.svg.import
new file mode 100644
index 00000000..af1974b2
--- /dev/null
+++ b/addons/shaker/assets/ShaderPreset.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dhcslgdhamqy2"
+path="res://.godot/imported/ShaderPreset.svg-baad8a708c9f1500bbd83c441bef7504.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShaderPreset.svg"
+dest_files=["res://.godot/imported/ShaderPreset.svg-baad8a708c9f1500bbd83c441bef7504.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakeTypeBase.svg.import b/addons/shaker/assets/ShakeTypeBase.svg.import
new file mode 100644
index 00000000..e31e7072
--- /dev/null
+++ b/addons/shaker/assets/ShakeTypeBase.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ds3kdjxwpmo1g"
+path="res://.godot/imported/ShakeTypeBase.svg-a9a71023b460c004e6d4f7703e2899c5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakeTypeBase.svg"
+dest_files=["res://.godot/imported/ShakeTypeBase.svg-a9a71023b460c004e6d4f7703e2899c5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=0.12
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/Shaker.svg b/addons/shaker/assets/Shaker.svg
new file mode 100644
index 00000000..6e5ba215
--- /dev/null
+++ b/addons/shaker/assets/Shaker.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/Shaker.svg.import b/addons/shaker/assets/Shaker.svg.import
new file mode 100644
index 00000000..8a7a3d54
--- /dev/null
+++ b/addons/shaker/assets/Shaker.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dbffv1avjrgvs"
+path="res://.godot/imported/Shaker.svg-c2d9686bdc2ea03ed737291e31637bc9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/Shaker.svg"
+dest_files=["res://.godot/imported/Shaker.svg-c2d9686bdc2ea03ed737291e31637bc9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=4.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/Shaker2D.svg b/addons/shaker/assets/Shaker2D.svg
new file mode 100644
index 00000000..20ba9f9b
--- /dev/null
+++ b/addons/shaker/assets/Shaker2D.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/Shaker2D.svg.import b/addons/shaker/assets/Shaker2D.svg.import
new file mode 100644
index 00000000..7367279d
--- /dev/null
+++ b/addons/shaker/assets/Shaker2D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dn8xxp7r67as3"
+path="res://.godot/imported/Shaker2D.svg-a5c6b73315dff06dec7bf743af8bf0a9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/Shaker2D.svg"
+dest_files=["res://.godot/imported/Shaker2D.svg-a5c6b73315dff06dec7bf743af8bf0a9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/Shaker3D.svg b/addons/shaker/assets/Shaker3D.svg
new file mode 100644
index 00000000..fcfa230d
--- /dev/null
+++ b/addons/shaker/assets/Shaker3D.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/Shaker3D.svg.import b/addons/shaker/assets/Shaker3D.svg.import
new file mode 100644
index 00000000..0b634a3b
--- /dev/null
+++ b/addons/shaker/assets/Shaker3D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dm4yc7w77q1u8"
+path="res://.godot/imported/Shaker3D.svg-ed425183c69b0c043ce3ce5088c6ff49.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/Shaker3D.svg"
+dest_files=["res://.godot/imported/Shaker3D.svg-ed425183c69b0c043ce3ce5088c6ff49.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerEmitter.svg b/addons/shaker/assets/ShakerEmitter.svg
new file mode 100644
index 00000000..466356b4
--- /dev/null
+++ b/addons/shaker/assets/ShakerEmitter.svg
@@ -0,0 +1,28 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerEmitter.svg.import b/addons/shaker/assets/ShakerEmitter.svg.import
new file mode 100644
index 00000000..42b31289
--- /dev/null
+++ b/addons/shaker/assets/ShakerEmitter.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://do6o8aob6hi6v"
+path="res://.godot/imported/ShakerEmitter.svg-99409f1c9ef4b7c7de1c60cac5619af4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerEmitter.svg"
+dest_files=["res://.godot/imported/ShakerEmitter.svg-99409f1c9ef4b7c7de1c60cac5619af4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=4.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerEmitter2D.svg b/addons/shaker/assets/ShakerEmitter2D.svg
new file mode 100644
index 00000000..62f7f5fd
--- /dev/null
+++ b/addons/shaker/assets/ShakerEmitter2D.svg
@@ -0,0 +1,28 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerEmitter2D.svg.import b/addons/shaker/assets/ShakerEmitter2D.svg.import
new file mode 100644
index 00000000..bbade203
--- /dev/null
+++ b/addons/shaker/assets/ShakerEmitter2D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://blvoltt74gide"
+path="res://.godot/imported/ShakerEmitter2D.svg-8c956d5aa03f14ce38dd1438d858114a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerEmitter2D.svg"
+dest_files=["res://.godot/imported/ShakerEmitter2D.svg-8c956d5aa03f14ce38dd1438d858114a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerEmitter3D.svg b/addons/shaker/assets/ShakerEmitter3D.svg
new file mode 100644
index 00000000..73359d51
--- /dev/null
+++ b/addons/shaker/assets/ShakerEmitter3D.svg
@@ -0,0 +1,35 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerEmitter3D.svg.import b/addons/shaker/assets/ShakerEmitter3D.svg.import
new file mode 100644
index 00000000..bbbf5eda
--- /dev/null
+++ b/addons/shaker/assets/ShakerEmitter3D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dbu31ogcp75bh"
+path="res://.godot/imported/ShakerEmitter3D.svg-b7aef419023b93692d18599c57d2046e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerEmitter3D.svg"
+dest_files=["res://.godot/imported/ShakerEmitter3D.svg-b7aef419023b93692d18599c57d2046e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerPreset.svg b/addons/shaker/assets/ShakerPreset.svg
new file mode 100644
index 00000000..43c82936
--- /dev/null
+++ b/addons/shaker/assets/ShakerPreset.svg
@@ -0,0 +1,74 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerPreset.svg.import b/addons/shaker/assets/ShakerPreset.svg.import
new file mode 100644
index 00000000..23bda4ac
--- /dev/null
+++ b/addons/shaker/assets/ShakerPreset.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://24qdtwt14uiu"
+path="res://.godot/imported/ShakerPreset.svg-e4c67cf03c32289efa8dece68a179055.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerPreset.svg"
+dest_files=["res://.godot/imported/ShakerPreset.svg-e4c67cf03c32289efa8dece68a179055.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerPreset2D.svg b/addons/shaker/assets/ShakerPreset2D.svg
new file mode 100644
index 00000000..090a93a9
--- /dev/null
+++ b/addons/shaker/assets/ShakerPreset2D.svg
@@ -0,0 +1,74 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerPreset2D.svg.import b/addons/shaker/assets/ShakerPreset2D.svg.import
new file mode 100644
index 00000000..403f3e29
--- /dev/null
+++ b/addons/shaker/assets/ShakerPreset2D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dkyto2qkh0a2h"
+path="res://.godot/imported/ShakerPreset2D.svg-dda8f976490b38216c486a3c325a6e1c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerPreset2D.svg"
+dest_files=["res://.godot/imported/ShakerPreset2D.svg-dda8f976490b38216c486a3c325a6e1c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerPreset3D.svg b/addons/shaker/assets/ShakerPreset3D.svg
new file mode 100644
index 00000000..39fbda6a
--- /dev/null
+++ b/addons/shaker/assets/ShakerPreset3D.svg
@@ -0,0 +1,87 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerPreset3D.svg.import b/addons/shaker/assets/ShakerPreset3D.svg.import
new file mode 100644
index 00000000..22ca476b
--- /dev/null
+++ b/addons/shaker/assets/ShakerPreset3D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://3461bedpoktj"
+path="res://.godot/imported/ShakerPreset3D.svg-ac15877b6c78545ef46f5ca5bf99cede.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerPreset3D.svg"
+dest_files=["res://.godot/imported/ShakerPreset3D.svg-ac15877b6c78545ef46f5ca5bf99cede.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerReceiver.svg b/addons/shaker/assets/ShakerReceiver.svg
new file mode 100644
index 00000000..b4029d4c
--- /dev/null
+++ b/addons/shaker/assets/ShakerReceiver.svg
@@ -0,0 +1,50 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerReceiver.svg.import b/addons/shaker/assets/ShakerReceiver.svg.import
new file mode 100644
index 00000000..a25a0020
--- /dev/null
+++ b/addons/shaker/assets/ShakerReceiver.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://5ubwmsna8qv3"
+path="res://.godot/imported/ShakerReceiver.svg-8fa75e085c3c726d592ed5236376746f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerReceiver.svg"
+dest_files=["res://.godot/imported/ShakerReceiver.svg-8fa75e085c3c726d592ed5236376746f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerReceiver2D.svg b/addons/shaker/assets/ShakerReceiver2D.svg
new file mode 100644
index 00000000..e20c9b0b
--- /dev/null
+++ b/addons/shaker/assets/ShakerReceiver2D.svg
@@ -0,0 +1,53 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerReceiver2D.svg.import b/addons/shaker/assets/ShakerReceiver2D.svg.import
new file mode 100644
index 00000000..9f51f6c0
--- /dev/null
+++ b/addons/shaker/assets/ShakerReceiver2D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bu1om2nm428ec"
+path="res://.godot/imported/ShakerReceiver2D.svg-99072f9033b4ac51b75a8e8f089f2f8c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerReceiver2D.svg"
+dest_files=["res://.godot/imported/ShakerReceiver2D.svg-99072f9033b4ac51b75a8e8f089f2f8c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerReceiver3D.svg b/addons/shaker/assets/ShakerReceiver3D.svg
new file mode 100644
index 00000000..82c325aa
--- /dev/null
+++ b/addons/shaker/assets/ShakerReceiver3D.svg
@@ -0,0 +1,56 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerReceiver3D.svg.import b/addons/shaker/assets/ShakerReceiver3D.svg.import
new file mode 100644
index 00000000..1289fd32
--- /dev/null
+++ b/addons/shaker/assets/ShakerReceiver3D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bfwyj7gf187kd"
+path="res://.godot/imported/ShakerReceiver3D.svg-1cf90100ef9bf3dbbc6653627fdab7fc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerReceiver3D.svg"
+dest_files=["res://.godot/imported/ShakerReceiver3D.svg-1cf90100ef9bf3dbbc6653627fdab7fc.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerType.svg b/addons/shaker/assets/ShakerType.svg
new file mode 100644
index 00000000..a172e84d
--- /dev/null
+++ b/addons/shaker/assets/ShakerType.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerType.svg.import b/addons/shaker/assets/ShakerType.svg.import
new file mode 100644
index 00000000..e7342339
--- /dev/null
+++ b/addons/shaker/assets/ShakerType.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ch5mp344tqj3w"
+path="res://.godot/imported/ShakerType.svg-0e40c87944ca8f10dcb4da67f37ccfdf.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerType.svg"
+dest_files=["res://.godot/imported/ShakerType.svg-0e40c87944ca8f10dcb4da67f37ccfdf.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerType2D.svg b/addons/shaker/assets/ShakerType2D.svg
new file mode 100644
index 00000000..01b9deb5
--- /dev/null
+++ b/addons/shaker/assets/ShakerType2D.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerType2D.svg.import b/addons/shaker/assets/ShakerType2D.svg.import
new file mode 100644
index 00000000..ddcab397
--- /dev/null
+++ b/addons/shaker/assets/ShakerType2D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://caqg2nfs1y64r"
+path="res://.godot/imported/ShakerType2D.svg-a5c5aa77a1e3263e925140c95a5a30c3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerType2D.svg"
+dest_files=["res://.godot/imported/ShakerType2D.svg-a5c5aa77a1e3263e925140c95a5a30c3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/ShakerType3D.svg b/addons/shaker/assets/ShakerType3D.svg
new file mode 100644
index 00000000..99b7eb16
--- /dev/null
+++ b/addons/shaker/assets/ShakerType3D.svg
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/ShakerType3D.svg.import b/addons/shaker/assets/ShakerType3D.svg.import
new file mode 100644
index 00000000..c605f013
--- /dev/null
+++ b/addons/shaker/assets/ShakerType3D.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dd7q87w4eoudf"
+path="res://.godot/imported/ShakerType3D.svg-1bc2b24943aa01bcdaa956f9b08b254c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/ShakerType3D.svg"
+dest_files=["res://.godot/imported/ShakerType3D.svg-1bc2b24943aa01bcdaa956f9b08b254c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/Stop.svg b/addons/shaker/assets/Stop.svg
new file mode 100644
index 00000000..82c898cf
--- /dev/null
+++ b/addons/shaker/assets/Stop.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/addons/shaker/assets/Stop.svg.import b/addons/shaker/assets/Stop.svg.import
new file mode 100644
index 00000000..f0f73795
--- /dev/null
+++ b/addons/shaker/assets/Stop.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://m45uspfna0ol"
+path="res://.godot/imported/Stop.svg-ae1c54e343f70b570e088fac0e2ac232.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/Stop.svg"
+dest_files=["res://.godot/imported/Stop.svg-ae1c54e343f70b570e088fac0e2ac232.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/shaker/assets/icon.png b/addons/shaker/assets/icon.png
new file mode 100644
index 00000000..7ae824c0
--- /dev/null
+++ b/addons/shaker/assets/icon.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b2633a59c816940b80d69d61b07a5568e764e464e80e7d9b735e5cdb08bd37f3
+size 19416
diff --git a/addons/shaker/assets/icon.png.import b/addons/shaker/assets/icon.png.import
new file mode 100644
index 00000000..b6c8d3f9
--- /dev/null
+++ b/addons/shaker/assets/icon.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://8m3nobyk16wm"
+path="res://.godot/imported/icon.png-0661645ebcd1f259541e43f0afd1f280.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/shaker/assets/icon.png"
+dest_files=["res://.godot/imported/icon.png-0661645ebcd1f259541e43f0afd1f280.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/shaker/data/BaseShakerType.gd b/addons/shaker/data/BaseShakerType.gd
new file mode 100644
index 00000000..336c9cfe
--- /dev/null
+++ b/addons/shaker/data/BaseShakerType.gd
@@ -0,0 +1,112 @@
+@icon("res://addons/shaker/assets/ShakerType.svg")
+@tool
+class_name ShakerTypeBase
+extends Resource
+
+# Enumerations for blending modes and graph axes
+enum BlendingModes {
+ Add,
+ Override,
+ Multiply,
+ Subtract,
+ Average,
+ Max,
+ Min
+}
+
+# Shake Properties
+@export_group("Shake Properties")
+@export var BlendingMode: BlendingModes = BlendingModes.Add:
+ set = set_blending_mode,
+ get = get_blending_mode
+
+@export_exp_easing var fade_in: float = 0.0:
+ set = set_fade_in,
+ get = get_fade_in
+
+@export_exp_easing("attenuation") var fade_out: float = 0.0:
+ set = set_fade_out,
+ get = get_fade_out
+
+@export_range(0.0, 1.0) var start_percent: float = 0.0:
+ set = set_start_percent,
+ get = get_start_percent
+
+@export_range(0.0, 1.0) var end_percent: float = 1.0:
+ set = set_end_percent,
+ get = get_end_percent
+
+# Live Shake Graph
+@export_group("Live Shake Graph")
+@export var _temp_graph: bool = false
+
+@export_range(16, 96) var bake_internal: int = 64:
+ set = set_bake_internal,
+ get = get_bake_internal
+
+var duration = 0.0:
+ set = set_duration,
+ get = get_duration
+
+func _init(blending_mode:BlendingModes=BlendingModes.Add, fade_in:float=self.fade_in, fade_out:float=self.fade_out, start_percent:float=self.start_percent, end_percent:float=self.end_percent) -> void:
+ self.BlendingMode = blending_mode
+ self.fade_in = fade_in
+ self.fade_out = fade_out
+ self.start_percent = start_percent
+ self.end_percent = end_percent
+
+# Signals
+signal property_changed(name: StringName)
+
+# Custom setter and getter functions
+func set_blending_mode(value: BlendingModes) -> void:
+ BlendingMode = value
+ _on_property_changed("BlendingMode")
+
+func get_blending_mode() -> BlendingModes:
+ return BlendingMode
+
+func set_fade_in(value: float) -> void:
+ fade_in = value
+ _on_property_changed("fade_in")
+
+func get_fade_in() -> float:
+ return fade_in
+
+func set_fade_out(value: float) -> void:
+ fade_out = value
+ _on_property_changed("fade_out")
+
+func get_fade_out() -> float:
+ return fade_out
+
+func set_start_percent(value: float) -> void:
+ start_percent = min(value, end_percent)
+ _on_property_changed("start_percent")
+
+func get_start_percent() -> float:
+ return start_percent
+
+func set_end_percent(value: float) -> void:
+ end_percent = max(value, start_percent)
+ _on_property_changed("end_percent")
+
+func get_end_percent() -> float:
+ return end_percent
+
+func set_bake_internal(value: int) -> void:
+ bake_internal = clamp(value, 16, 96)
+ _on_property_changed("bake_internal")
+
+func get_bake_internal() -> int:
+ return bake_internal
+
+func set_duration(value: float = 0.0) -> void:
+ duration = value
+
+func get_duration() -> float:
+ return duration
+
+# Handle property changes
+func _on_property_changed(property_name: StringName) -> void:
+ property_changed.emit(property_name)
diff --git a/addons/shaker/data/BaseShakerType.gd.uid b/addons/shaker/data/BaseShakerType.gd.uid
new file mode 100644
index 00000000..cb993e13
--- /dev/null
+++ b/addons/shaker/data/BaseShakerType.gd.uid
@@ -0,0 +1 @@
+uid://d2i321te0ny64
diff --git a/addons/shaker/data/ShakerPresetBase.gd b/addons/shaker/data/ShakerPresetBase.gd
new file mode 100644
index 00000000..3444beec
--- /dev/null
+++ b/addons/shaker/data/ShakerPresetBase.gd
@@ -0,0 +1,53 @@
+@tool
+@icon("res://addons/shaker/assets/ShakerPreset.svg")
+class_name ShakerPresetBase
+extends Resource
+
+# Enum for shake categories
+enum Categories {
+ POSITION,
+ ROTATION,
+ SCALE
+}
+
+# Graph panel reference
+var Graph: Panel
+
+# Bake internal setting
+@export_range(16, 96) var bake_internal: int = 64:
+ set = set_bake_internal,
+ get = get_bake_internal
+
+# Follow timeline flag
+@export var __follow_timeline: bool = false:
+ set = set_follow_timeline,
+ get = get_follow_timeline
+
+# Component duration and parent node
+var component_duration: float = 0.0
+var parent: Node
+
+# Signal for property changes
+signal property_changed(name: StringName)
+
+func set_bake_internal(value: int) -> void:
+ bake_internal = clamp(value, 16, 96)
+ _on_property_changed("bake_internal")
+
+func get_bake_internal() -> int:
+ return bake_internal
+
+func set_follow_timeline(value: bool) -> void:
+ __follow_timeline = value
+ _on_property_changed("__follow_timeline")
+
+func get_follow_timeline() -> bool:
+ return __follow_timeline
+
+# Handle property changes
+func _on_property_changed(property_name: StringName) -> void:
+ property_changed.emit(property_name)
+
+# Calculate the difference between two arrays
+func _array_difference(a: Array, b: Array) -> Array:
+ return b.filter(func(item): return not a.has(item))
diff --git a/addons/shaker/data/ShakerPresetBase.gd.uid b/addons/shaker/data/ShakerPresetBase.gd.uid
new file mode 100644
index 00000000..01390867
--- /dev/null
+++ b/addons/shaker/data/ShakerPresetBase.gd.uid
@@ -0,0 +1 @@
+uid://cveqkhn5t8vng
diff --git a/addons/shaker/data/ShakerProperty.gd b/addons/shaker/data/ShakerProperty.gd
new file mode 100644
index 00000000..221dabca
--- /dev/null
+++ b/addons/shaker/data/ShakerProperty.gd
@@ -0,0 +1,24 @@
+@tool
+@icon("res://addons/shaker/assets/ShakerPreset.svg")
+class_name ShakerProperty
+extends Resource
+
+@export var property_name:String
+# Properties
+@export var shake_type:ShakerTypeBase
+
+# Signal for property changes
+signal property_changed(name: StringName)
+
+func _init(property_name:String="", shake_type:ShakerTypeBase=null) -> void:
+ self.property_name = property_name
+ self.shake_type = shake_type
+
+# Handle property changes
+func _on_property_changed(property_name: StringName) -> void:
+ property_changed.emit(property_name)
+
+func get_value(_t:float) -> Variant:
+ if shake_type:
+ return shake_type.get_value(_t)
+ return 0.0
diff --git a/addons/shaker/data/ShakerProperty.gd.uid b/addons/shaker/data/ShakerProperty.gd.uid
new file mode 100644
index 00000000..22ec96d8
--- /dev/null
+++ b/addons/shaker/data/ShakerProperty.gd.uid
@@ -0,0 +1 @@
+uid://bd8eceas8jvyl
diff --git a/addons/shaker/data/Single/BaseShakerType1D.gd b/addons/shaker/data/Single/BaseShakerType1D.gd
new file mode 100644
index 00000000..2d4be567
--- /dev/null
+++ b/addons/shaker/data/Single/BaseShakerType1D.gd
@@ -0,0 +1,46 @@
+@icon("res://addons/shaker/assets/ShakerType.svg")
+@tool
+class_name ShakerTypeBase1D
+extends ShakerTypeBase
+
+enum GraphAxis {
+ X,
+}
+
+@export var amplitude:float = 1.0:
+ set = set_amplitude,
+ get = get_amplitude
+
+@export var offset:float = 0.0:
+ set = set_offset,
+ get = get_offset
+
+func set_amplitude(value: float) -> void:
+ amplitude = value
+ _on_property_changed("amplitude")
+
+func get_amplitude() -> float:
+ return amplitude
+
+func set_offset(value: float) -> void:
+ offset = value
+ _on_property_changed("offset")
+
+func get_offset() -> float:
+ return offset
+
+# Get the shake value at a given time
+func get_value(t: float) -> float:
+ var result:float = 0.0;
+ return _calc_value(fmod(t, 1.0), result)
+
+# Calculate the shake value
+func _calc_value(t: float, result: float) -> float:
+ if duration > 0:
+ t /= duration
+ if (start_percent != 0 && start_percent > t) || (end_percent != 1 && end_percent < t):
+ result = 0.0;
+ else:
+ result = result * amplitude + offset
+ result *= (ease(t, fade_in) if fade_in > 0.0001 else 1.0) * (ease(1.0 - t, fade_out) if fade_out > 0.0001 else 1.0)
+ return result;
diff --git a/addons/shaker/data/Single/BaseShakerType1D.gd.uid b/addons/shaker/data/Single/BaseShakerType1D.gd.uid
new file mode 100644
index 00000000..8e6e5bcf
--- /dev/null
+++ b/addons/shaker/data/Single/BaseShakerType1D.gd.uid
@@ -0,0 +1 @@
+uid://bhqlklbhcxfgf
diff --git a/addons/shaker/data/Single/ShakerTypeAudioBus1D.gd b/addons/shaker/data/Single/ShakerTypeAudioBus1D.gd
new file mode 100644
index 00000000..4620c993
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeAudioBus1D.gd
@@ -0,0 +1,43 @@
+@tool
+class_name ShakerTypeAudioBus1D
+extends ShakerTypeBase1D
+
+@export var bus_name:String = "Master":
+ set = set_bus_name,
+ get = get_bus_name
+@export_range(20, 20000) var min_frequence:float = 20
+@export_range(20, 20000) var max_frequence:float = 20000
+
+var bus_index:int = 0
+var effect:AudioEffectSpectrumAnalyzerInstance
+
+## Calculates the value of the square wave at time t.
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ if effect:
+ var mag:Vector2 = effect.get_magnitude_for_frequency_range(min_frequence, max_frequence, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX)
+ result = mag.length()
+ return _calc_value(t, result)
+
+func set_bus_name(value: String) -> void:
+ bus_name = value
+ _update_bus_index()
+ _on_property_changed("bus_name")
+
+func get_bus_name() -> String:
+ return bus_name
+
+func _update_bus_index() -> void:
+ bus_index = AudioServer.get_bus_index(bus_name)
+ if bus_index > -1:
+ for e in AudioServer.get_bus_effect_count(bus_index):
+ var _effect:AudioEffect = AudioServer.get_bus_effect(bus_index, e)
+ if _effect is AudioEffectSpectrumAnalyzer:
+ effect = AudioServer.get_bus_effect_instance(bus_index, e, 0)
+ break;
+ if effect == null:
+ AudioServer.add_bus_effect(bus_index, AudioEffectSpectrumAnalyzer.new(), 0)
+ effect = AudioServer.get_bus_effect_instance(bus_index, 0)
+ else:
+ push_error("Error: Bus '" + bus_name + "' not found!")
+ effect = null
diff --git a/addons/shaker/data/Single/ShakerTypeAudioBus1D.gd.uid b/addons/shaker/data/Single/ShakerTypeAudioBus1D.gd.uid
new file mode 100644
index 00000000..200f8d62
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeAudioBus1D.gd.uid
@@ -0,0 +1 @@
+uid://dshti5xd41w07
diff --git a/addons/shaker/data/Single/ShakerTypeBrownianShake1D.gd b/addons/shaker/data/Single/ShakerTypeBrownianShake1D.gd
new file mode 100644
index 00000000..504dca3e
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeBrownianShake1D.gd
@@ -0,0 +1,42 @@
+@tool
+class_name ShakerTypeBrownianShake1D
+extends ShakerTypeBase1D
+
+@export var roughness:float = 1.0:
+ set = set_roughness,
+ get = get_roughness
+
+@export var persistence:float = 0.5:
+ set = set_persistence,
+ get = get_persistence
+
+var _generator: RandomNumberGenerator = RandomNumberGenerator.new()
+var _last_pos:float = 0.0
+
+func _init() -> void:
+ property_changed.connect(_property_changed)
+
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ result = (_last_pos + _generator.randf_range(-roughness, roughness))
+ result = _calc_value(t, result)
+
+ _last_pos = lerpf(_last_pos, result, 1.0 - persistence)
+ return _last_pos
+
+func _property_changed(name: StringName) -> void:
+ _last_pos = 0.0
+
+func set_roughness(value: float) -> void:
+ roughness = value
+ _on_property_changed("roughness")
+
+func get_roughness() -> float:
+ return roughness
+
+func set_persistence(value: float) -> void:
+ persistence = clamp(persistence,0, 1)
+ _on_property_changed("persistence")
+
+func get_persistence() -> float:
+ return persistence
diff --git a/addons/shaker/data/Single/ShakerTypeBrownianShake1D.gd.uid b/addons/shaker/data/Single/ShakerTypeBrownianShake1D.gd.uid
new file mode 100644
index 00000000..418ea2dd
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeBrownianShake1D.gd.uid
@@ -0,0 +1 @@
+uid://62yumjskyeow
diff --git a/addons/shaker/data/Single/ShakerTypeCurve1D.gd b/addons/shaker/data/Single/ShakerTypeCurve1D.gd
new file mode 100644
index 00000000..ea914745
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeCurve1D.gd
@@ -0,0 +1,41 @@
+@tool
+class_name ShakerTypeCurve1D
+extends ShakerTypeBase1D
+
+@export var curve: Curve:
+ set = set_curve,
+ get = get_curve
+
+@export var loop: bool = true:
+ set = set_loop,
+ get = get_loop
+
+func _curve_changed() -> void:
+ _on_property_changed("curve")
+
+func get_value(t: float) -> float:
+ var result: float = 0.0
+ if loop && t > 1.0:
+ t = fmod(t, 1.0)
+ if curve:
+ result = curve.sample(t)
+
+ return _calc_value(t, result)
+
+func set_curve(value: Curve) -> void:
+ if curve:
+ curve.changed.disconnect(_curve_changed)
+ curve = value
+ if curve:
+ curve.changed.connect(_curve_changed)
+ else:
+ _curve_changed()
+func get_curve() -> Curve:
+ return curve
+
+func set_loop(value: bool) -> void:
+ loop = value
+ _on_property_changed("loop")
+
+func get_loop() -> bool:
+ return loop
diff --git a/addons/shaker/data/Single/ShakerTypeCurve1D.gd.uid b/addons/shaker/data/Single/ShakerTypeCurve1D.gd.uid
new file mode 100644
index 00000000..04399af2
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeCurve1D.gd.uid
@@ -0,0 +1 @@
+uid://dadkdm3tvmuqq
diff --git a/addons/shaker/data/Single/ShakerTypeNoiseShake1D.gd b/addons/shaker/data/Single/ShakerTypeNoiseShake1D.gd
new file mode 100644
index 00000000..758f44aa
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeNoiseShake1D.gd
@@ -0,0 +1,32 @@
+@tool
+class_name ShakerTypeNoiseShake1D
+extends ShakerTypeBase1D
+
+@export var noise_texture: NoiseTexture2D = NoiseTexture2D.new():
+ set = set_noise_texture,
+ get = get_noise_texture
+
+func _init() -> void:
+ noise_texture.changed.connect(_on_noise_changed)
+
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ if noise_texture && noise_texture.noise:
+ var noise_size:Vector2 = Vector2(noise_texture.width, noise_texture.height)
+ var noise_offset:float = t * noise_size.x
+ result = noise_texture.noise.get_noise_1d(noise_offset)
+ result *= 2.0
+ return _calc_value(t, result)
+
+func _on_noise_changed() -> void:
+ _on_property_changed("noise_texture")
+
+func set_noise_texture(value: NoiseTexture2D) -> void:
+ if noise_texture:
+ noise_texture.changed.disconnect(_on_noise_changed)
+ noise_texture = value
+ if noise_texture:
+ noise_texture.changed.connect(_on_noise_changed)
+
+func get_noise_texture() -> NoiseTexture2D:
+ return noise_texture
diff --git a/addons/shaker/data/Single/ShakerTypeNoiseShake1D.gd.uid b/addons/shaker/data/Single/ShakerTypeNoiseShake1D.gd.uid
new file mode 100644
index 00000000..9328b4a4
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeNoiseShake1D.gd.uid
@@ -0,0 +1 @@
+uid://bdrk4rkut1y3l
diff --git a/addons/shaker/data/Single/ShakerTypeRandom1D.gd b/addons/shaker/data/Single/ShakerTypeRandom1D.gd
new file mode 100644
index 00000000..727965f2
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeRandom1D.gd
@@ -0,0 +1,30 @@
+@tool
+class_name ShakerTypeRandom1D
+extends ShakerTypeBase1D
+
+## The seed for the random number generator.
+@export var seed: int = 0:
+ set = set_seed
+
+## The random number generator instance.
+var _generator: RandomNumberGenerator = RandomNumberGenerator.new()
+
+## Initializes the shake type with the given seed.
+func _init() -> void:
+ set_seed(seed)
+
+## Calculates a random value for each axis at time t.
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ result = _generator.randf_range(-1.0, 1.0)
+ return _calc_value(t, result)
+
+## Sets the seed for the random number generator.
+func set_seed(value: int) -> void:
+ seed = value
+ _generator.seed = seed
+ _on_property_changed("seed")
+
+## Gets the current seed of the random number generator.
+func get_seed() -> int:
+ return seed
diff --git a/addons/shaker/data/Single/ShakerTypeRandom1D.gd.uid b/addons/shaker/data/Single/ShakerTypeRandom1D.gd.uid
new file mode 100644
index 00000000..3112efa7
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeRandom1D.gd.uid
@@ -0,0 +1 @@
+uid://buor2ix0dmwk2
diff --git a/addons/shaker/data/Single/ShakerTypeSawtoothWave1D.gd b/addons/shaker/data/Single/ShakerTypeSawtoothWave1D.gd
new file mode 100644
index 00000000..abb0be9c
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeSawtoothWave1D.gd
@@ -0,0 +1,41 @@
+@tool
+class_name ShakerTypeSawtoothWave1D
+extends ShakerTypeBase1D
+
+## The frequency of the sawtooth wave for each axis.
+@export var frequency:float = 5.0:
+ set = set_frequency
+
+## The asymmetry of the sawtooth wave for each axis (0 to 1).
+@export var asymmetry:float = 0.5:
+ set = set_asymmetry
+
+## Sets the frequency of the sawtooth wave.
+func set_frequency(value: float) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+## Gets the frequency of the sawtooth wave.
+func get_frequency() -> float:
+ return frequency
+
+## Sets the asymmetry of the sawtooth wave.
+func set_asymmetry(value: float) -> void:
+ asymmetry = clamp(value, 0.0, 0.0)
+ _on_property_changed("asymmetry")
+
+## Gets the asymmetry of the sawtooth wave.
+func get_asymmetry() -> float:
+ return asymmetry
+
+## Calculates the value of the sawtooth wave at time t.
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ var _real_time:float = fmod(t, 1.0) if t > 1.0 else t
+ var wave:float = fmod(_real_time * frequency, 1.0)
+
+ wave = wave / asymmetry if wave < asymmetry else (1.0 - wave) / (1.0 - asymmetry)
+ result = wave
+ result = _calc_value(t, result)
+ result = (result - amplitude * 0.5) * 2.0
+ return result
diff --git a/addons/shaker/data/Single/ShakerTypeSawtoothWave1D.gd.uid b/addons/shaker/data/Single/ShakerTypeSawtoothWave1D.gd.uid
new file mode 100644
index 00000000..c64e3385
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeSawtoothWave1D.gd.uid
@@ -0,0 +1 @@
+uid://tftoajmajyfg
diff --git a/addons/shaker/data/Single/ShakerTypeSineWave1D.gd b/addons/shaker/data/Single/ShakerTypeSineWave1D.gd
new file mode 100644
index 00000000..ddc5f389
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeSineWave1D.gd
@@ -0,0 +1,32 @@
+@tool
+class_name ShakerTypeSineWave1D
+extends ShakerTypeBase1D
+
+@export_group("Sinewave Properties")
+@export var frequency:float = 1.0:
+ set = set_frequency,
+ get = get_frequency
+
+@export var phase:float = 0.0:
+ set = set_phase,
+ get = get_phase
+
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ var _real_time: float = fmod(t, 1.0) if t > 1.0 else t
+ result = sin(t * frequency * TAU + phase)
+ return _calc_value(_real_time, result)
+
+func set_frequency(value: float) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+func get_frequency() -> float:
+ return frequency
+
+func set_phase(value: float) -> void:
+ phase = value
+ _on_property_changed("phase")
+
+func get_phase() -> float:
+ return phase
diff --git a/addons/shaker/data/Single/ShakerTypeSineWave1D.gd.uid b/addons/shaker/data/Single/ShakerTypeSineWave1D.gd.uid
new file mode 100644
index 00000000..ffa9e241
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeSineWave1D.gd.uid
@@ -0,0 +1 @@
+uid://vvwsdrpmw7w4
diff --git a/addons/shaker/data/Single/ShakerTypeSquareWave1D.gd b/addons/shaker/data/Single/ShakerTypeSquareWave1D.gd
new file mode 100644
index 00000000..9f2f440e
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeSquareWave1D.gd
@@ -0,0 +1,35 @@
+@tool
+class_name ShakerTypeSquareWave1D
+extends ShakerTypeBase1D
+
+## The frequency of the square wave for each axis.
+@export var frequency:float = 5.0:
+ set = set_frequency
+
+## The duty cycle of the square wave for each axis (0 to 1).
+@export var duty_cycle:float = 0.5:
+ set = set_duty_cycle
+
+## Sets the frequency of the square wave.
+func set_frequency(value:float) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+## Gets the frequency of the square wave.
+func get_frequency() -> float:
+ return frequency
+
+## Sets the duty cycle of the square wave.
+func set_duty_cycle(value:float) -> void:
+ duty_cycle = clamp(value, 0.0, 0.0)
+ _on_property_changed("duty_cycle")
+
+## Gets the duty cycle of the square wave.
+func get_duty_cycle() -> float:
+ return duty_cycle
+
+## Calculates the value of the square wave at time t.
+func get_value(t: float) -> float:
+ var result:float = 0.0
+ result = 1.0 if fmod(t * frequency, 1.0) < duty_cycle else -1.0
+ return _calc_value(t, result)
diff --git a/addons/shaker/data/Single/ShakerTypeSquareWave1D.gd.uid b/addons/shaker/data/Single/ShakerTypeSquareWave1D.gd.uid
new file mode 100644
index 00000000..fe004800
--- /dev/null
+++ b/addons/shaker/data/Single/ShakerTypeSquareWave1D.gd.uid
@@ -0,0 +1 @@
+uid://bwuxn8yt7jiwy
diff --git a/addons/shaker/data/Vector2/BaseShakerType2D.gd b/addons/shaker/data/Vector2/BaseShakerType2D.gd
new file mode 100644
index 00000000..d27815b6
--- /dev/null
+++ b/addons/shaker/data/Vector2/BaseShakerType2D.gd
@@ -0,0 +1,47 @@
+@icon("res://addons/shaker/assets/ShakerType2D.svg")
+@tool
+class_name ShakerTypeBase2D
+extends ShakerTypeBase
+
+enum GraphAxis {
+ X,
+ Y,
+}
+
+@export var amplitude: Vector2 = Vector2.ONE:
+ set = set_amplitude,
+ get = get_amplitude
+
+@export var offset: Vector2 = Vector2.ZERO:
+ set = set_offset,
+ get = get_offset
+
+func set_amplitude(value: Vector2) -> void:
+ amplitude = value
+ _on_property_changed("amplitude")
+
+func get_amplitude() -> Vector2:
+ return amplitude
+
+func set_offset(value: Vector2) -> void:
+ offset = value
+ _on_property_changed("offset")
+
+func get_offset() -> Vector2:
+ return offset
+
+# Get the shake value at a given time
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ return _calc_value(fmod(t, 1.0), result)
+
+# Calculate the shake value
+func _calc_value(t: float, result: Vector2) -> Vector2:
+ if duration > 0:
+ t /= duration
+ if (start_percent != 0 && start_percent > t) || (end_percent != 1 && end_percent < t):
+ result = Vector2.ZERO
+ else:
+ result = result * amplitude + offset
+ result *= (ease(t, fade_in) if fade_in > 0.0001 else 1.0) * (ease(1.0 - t, fade_out) if fade_out > 0.0001 else 1.0)
+ return result
diff --git a/addons/shaker/data/Vector2/BaseShakerType2D.gd.uid b/addons/shaker/data/Vector2/BaseShakerType2D.gd.uid
new file mode 100644
index 00000000..21135260
--- /dev/null
+++ b/addons/shaker/data/Vector2/BaseShakerType2D.gd.uid
@@ -0,0 +1 @@
+uid://chp1jfq2fdtbm
diff --git a/addons/shaker/data/Vector2/ShakerPreset2D.gd b/addons/shaker/data/Vector2/ShakerPreset2D.gd
new file mode 100644
index 00000000..4d23cd3a
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerPreset2D.gd
@@ -0,0 +1,94 @@
+@tool
+@icon("res://addons/shaker/assets/ShakerPreset2D.svg")
+class_name ShakerPreset2D
+extends ShakerPresetBase
+
+
+# Shake type arrays for each category
+@export var PositionShake: Array[ShakerTypeBase2D]:
+ set = set_position_shake,
+ get = get_position_shake
+
+@export var RotationShake: Array[ShakerTypeBase1D]:
+ set = set_rotation_shake,
+ get = get_rotation_shake
+
+@export var ScaleShake: Array[ShakerTypeBase2D]:
+ set = set_scale_shake,
+ get = get_scale_shake
+
+# Custom setter and getter functions
+func set_position_shake(value: Array[ShakerTypeBase2D]) -> void:
+ for _shake_type in _array_difference(PositionShake, value):
+ if _shake_type != null:
+ _shake_type.property_changed.connect(_on_property_changed)
+ PositionShake = value
+ _on_property_changed("PositionShake")
+ if Graph != null:
+ Graph._on_fit_button_clicked()
+
+func get_position_shake() -> Array[ShakerTypeBase2D]:
+ return PositionShake
+
+func set_rotation_shake(value: Array[ShakerTypeBase1D]) -> void:
+ for _shake_type in _array_difference(RotationShake, value):
+ if _shake_type != null:
+ _shake_type.property_changed.connect(_on_property_changed)
+ RotationShake = value
+ _on_property_changed("RotationShake")
+ if Graph != null:
+ Graph._on_fit_button_clicked()
+
+func get_rotation_shake() -> Array[ShakerTypeBase1D]:
+ return RotationShake
+
+func get_shakes_by_category(category:Categories) -> Array:
+ if category == Categories.POSITION:
+ return PositionShake
+ elif category == Categories.ROTATION:
+ return RotationShake
+ elif category == Categories.SCALE:
+ return ScaleShake
+ return [null]
+
+func set_scale_shake(value: Array[ShakerTypeBase2D]) -> void:
+ for _shake_type in _array_difference(ScaleShake, value):
+ if _shake_type != null:
+ _shake_type.property_changed.connect(_on_property_changed)
+ ScaleShake = value
+ _on_property_changed("ScaleShake")
+ if Graph != null:
+ Graph._on_fit_button_clicked()
+
+func get_scale_shake() -> Array[ShakerTypeBase2D]:
+ return ScaleShake
+
+# Get the shake value for a given time and category
+func get_value(t: float, _category: Categories = Categories.POSITION):
+ var result
+ if _category == Categories.ROTATION:
+ result = 0.0
+ else:
+ result = Vector2.ZERO
+ for shake_type in [PositionShake, RotationShake, ScaleShake][_category]:
+ if shake_type != null:
+ shake_type.duration = component_duration
+ var _shake_result = shake_type.get_value(t)
+ match shake_type.BlendingMode:
+ shake_type.BlendingModes.Add:
+ result += _shake_result
+ shake_type.BlendingModes.Multiply:
+ result *= _shake_result
+ shake_type.BlendingModes.Subtract:
+ result -= _shake_result
+ shake_type.BlendingModes.Max:
+ result.x = max(result.x, _shake_result.x)
+ result.y = max(result.y, _shake_result.y)
+ shake_type.BlendingModes.Min:
+ result.x = min(result.x, _shake_result.x)
+ result.y = min(result.y, _shake_result.y)
+ shake_type.BlendingModes.Average:
+ result = (result + _shake_result) * 0.5
+ shake_type.BlendingModes.Override:
+ result = _shake_result
+ return result
diff --git a/addons/shaker/data/Vector2/ShakerPreset2D.gd.uid b/addons/shaker/data/Vector2/ShakerPreset2D.gd.uid
new file mode 100644
index 00000000..2e41a547
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerPreset2D.gd.uid
@@ -0,0 +1 @@
+uid://ieocwh32dyx1
diff --git a/addons/shaker/data/Vector2/ShakerTypeAudioBus2D.gd b/addons/shaker/data/Vector2/ShakerTypeAudioBus2D.gd
new file mode 100644
index 00000000..5509fd18
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeAudioBus2D.gd
@@ -0,0 +1,45 @@
+@tool
+class_name ShakerTypeAudioBus2D
+extends ShakerTypeBase2D
+
+@export var bus_name:String = "Master":
+ set = set_bus_name,
+ get = get_bus_name
+@export var min_frequence:Vector2 = Vector2(20, 20)
+@export var max_frequence:Vector2 = Vector2(20000, 20000)
+
+var bus_index:int = 0
+var effect:AudioEffectSpectrumAnalyzerInstance
+
+## Calculates the value of the square wave at time t.
+func get_value(t: float) -> Vector2:
+ var result:Vector2 = Vector2.ZERO
+ if effect:
+ var mag_x:Vector2 = effect.get_magnitude_for_frequency_range(min_frequence.x, max_frequence.x, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX)
+ var mag_y:Vector2 = effect.get_magnitude_for_frequency_range(min_frequence.y, max_frequence.y, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX)
+ result.x = mag_x.length()
+ result.y = mag_y.length()
+ return _calc_value(t, result)
+
+func set_bus_name(value: String) -> void:
+ bus_name = value
+ _update_bus_index()
+ _on_property_changed("bus_name")
+
+func get_bus_name() -> String:
+ return bus_name
+
+func _update_bus_index() -> void:
+ bus_index = AudioServer.get_bus_index(bus_name)
+ if bus_index > -1:
+ for e in AudioServer.get_bus_effect_count(bus_index):
+ var _effect:AudioEffect = AudioServer.get_bus_effect(bus_index, e)
+ if _effect is AudioEffectSpectrumAnalyzer:
+ effect = AudioServer.get_bus_effect_instance(bus_index, e, 0)
+ break;
+ if effect == null:
+ AudioServer.add_bus_effect(bus_index, AudioEffectSpectrumAnalyzer.new(), 0)
+ effect = AudioServer.get_bus_effect_instance(bus_index, 0)
+ else:
+ push_error("Error: Bus '" + bus_name + "' not found!")
+ effect = null
diff --git a/addons/shaker/data/Vector2/ShakerTypeAudioBus2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeAudioBus2D.gd.uid
new file mode 100644
index 00000000..3833d851
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeAudioBus2D.gd.uid
@@ -0,0 +1 @@
+uid://4msbbvpnv85g
diff --git a/addons/shaker/data/Vector2/ShakerTypeBrownianShake2D.gd b/addons/shaker/data/Vector2/ShakerTypeBrownianShake2D.gd
new file mode 100644
index 00000000..97a52a30
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeBrownianShake2D.gd
@@ -0,0 +1,44 @@
+@tool
+class_name ShakerTypeBrownianShake2D
+extends ShakerTypeBase2D
+
+@export var roughness: Vector2 = Vector2.ONE * 1.0:
+ set = set_roughness,
+ get = get_roughness
+
+@export var persistence: Vector2 = Vector2.ONE * 0.5:
+ set = set_persistence,
+ get = get_persistence
+
+var _generator: RandomNumberGenerator = RandomNumberGenerator.new()
+var _last_pos: Vector2 = Vector2.ZERO
+
+func _init() -> void:
+ property_changed.connect(_property_changed)
+
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ result.x = (_last_pos.x + _generator.randf_range(-roughness.x, roughness.x))
+ result.y = (_last_pos.y + _generator.randf_range(-roughness.y, roughness.y))
+ result = _calc_value(t, result)
+
+ _last_pos.x = lerpf(_last_pos.x, result.x, 1.0 - persistence.x)
+ _last_pos.y = lerpf(_last_pos.y, result.y, 1.0 - persistence.y)
+ return _last_pos
+
+func _property_changed(name: StringName) -> void:
+ _last_pos = Vector2.ZERO
+
+func set_roughness(value: Vector2) -> void:
+ roughness = value
+ _on_property_changed("roughness")
+
+func get_roughness() -> Vector2:
+ return roughness
+
+func set_persistence(value: Vector2) -> void:
+ persistence = value.clamp(Vector2(0,0),Vector2(1,1))
+ _on_property_changed("persistence")
+
+func get_persistence() -> Vector2:
+ return persistence
diff --git a/addons/shaker/data/Vector2/ShakerTypeBrownianShake2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeBrownianShake2D.gd.uid
new file mode 100644
index 00000000..481696c9
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeBrownianShake2D.gd.uid
@@ -0,0 +1 @@
+uid://c2p5nhqgceojd
diff --git a/addons/shaker/data/Vector2/ShakerTypeCurve2D.gd b/addons/shaker/data/Vector2/ShakerTypeCurve2D.gd
new file mode 100644
index 00000000..1baebd71
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeCurve2D.gd
@@ -0,0 +1,60 @@
+@tool
+class_name ShakerTypeCurve2D
+extends ShakerTypeBase2D
+
+@export var curve_x: Curve:
+ set = set_curve_x,
+ get = get_curve_x
+
+@export var curve_y: Curve:
+ set = set_curve_y,
+ get = get_curve_y
+
+@export var loop: bool = true:
+ set = set_loop,
+ get = get_loop
+
+func _curve_changed() -> void:
+ _on_property_changed("curve_")
+
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ if loop && t > 1.0:
+ t = fmod(t, 1.0)
+ if curve_x:
+ result.x = curve_x.sample(t)
+ if curve_y:
+ result.y = curve_y.sample(t)
+
+ return _calc_value(t, result)
+
+func set_curve_x(value: Curve) -> void:
+ if curve_x:
+ curve_x.changed.disconnect(_curve_changed)
+ curve_x = value
+ if curve_x:
+ curve_x.changed.connect(_curve_changed)
+ else:
+ _curve_changed()
+
+func get_curve_x() -> Curve:
+ return curve_x
+
+func set_curve_y(value: Curve) -> void:
+ if curve_y:
+ curve_y.changed.disconnect(_curve_changed)
+ curve_y = value
+ if curve_y:
+ curve_y.changed.connect(_curve_changed)
+ else:
+ _curve_changed()
+
+func get_curve_y() -> Curve:
+ return curve_y
+
+func set_loop(value: bool) -> void:
+ loop = value
+ _on_property_changed("loop")
+
+func get_loop() -> bool:
+ return loop
diff --git a/addons/shaker/data/Vector2/ShakerTypeCurve2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeCurve2D.gd.uid
new file mode 100644
index 00000000..6de51741
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeCurve2D.gd.uid
@@ -0,0 +1 @@
+uid://dd6hr5hjnra0i
diff --git a/addons/shaker/data/Vector2/ShakerTypeNoiseShake2D.gd b/addons/shaker/data/Vector2/ShakerTypeNoiseShake2D.gd
new file mode 100644
index 00000000..c1f0dc5d
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeNoiseShake2D.gd
@@ -0,0 +1,33 @@
+@tool
+class_name ShakerTypeNoiseShake2D
+extends ShakerTypeBase2D
+
+@export var noise_texture: NoiseTexture2D = NoiseTexture2D.new():
+ set = set_noise_texture,
+ get = get_noise_texture
+
+func _init() -> void:
+ noise_texture.changed.connect(_on_noise_changed)
+
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ if noise_texture && noise_texture.noise:
+ var noise_size: Vector2 = Vector2(noise_texture.width, noise_texture.height)
+ var noise_offset: Vector2 = t * noise_size
+ result.x = noise_texture.noise.get_noise_2d(noise_offset.x, 0.0)
+ result.y = noise_texture.noise.get_noise_2d(0.0, noise_offset.y)
+ result *= 2.0
+ return _calc_value(t, result)
+
+func _on_noise_changed() -> void:
+ _on_property_changed("noise_texture")
+
+func set_noise_texture(value: NoiseTexture2D) -> void:
+ if noise_texture:
+ noise_texture.changed.disconnect(_on_noise_changed)
+ noise_texture = value
+ if noise_texture:
+ noise_texture.changed.connect(_on_noise_changed)
+
+func get_noise_texture() -> NoiseTexture2D:
+ return noise_texture
diff --git a/addons/shaker/data/Vector2/ShakerTypeNoiseShake2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeNoiseShake2D.gd.uid
new file mode 100644
index 00000000..2abe4ef8
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeNoiseShake2D.gd.uid
@@ -0,0 +1 @@
+uid://rytryaspugdk
diff --git a/addons/shaker/data/Vector2/ShakerTypeRandom2D.gd b/addons/shaker/data/Vector2/ShakerTypeRandom2D.gd
new file mode 100644
index 00000000..3a23abe5
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeRandom2D.gd
@@ -0,0 +1,31 @@
+@tool
+class_name ShakerTypeRandom2D
+extends ShakerTypeBase2D
+
+## The seed for the random number generator.
+@export var seed: int = 0:
+ set = set_seed
+
+## The random number generator instance.
+var _generator: RandomNumberGenerator = RandomNumberGenerator.new()
+
+## Initializes the shake type with the given seed.
+func _init() -> void:
+ set_seed(seed)
+
+## Calculates a random value for each axis at time t.
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ result.x = _generator.randf_range(-1.0, 1.0)
+ result.y = _generator.randf_range(-1.0, 1.0)
+ return _calc_value(t, result)
+
+## Sets the seed for the random number generator.
+func set_seed(value: int) -> void:
+ seed = value
+ _generator.seed = seed
+ _on_property_changed("seed")
+
+## Gets the current seed of the random number generator.
+func get_seed() -> int:
+ return seed
diff --git a/addons/shaker/data/Vector2/ShakerTypeRandom2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeRandom2D.gd.uid
new file mode 100644
index 00000000..659f7133
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeRandom2D.gd.uid
@@ -0,0 +1 @@
+uid://c7jr8ltct1k17
diff --git a/addons/shaker/data/Vector2/ShakerTypeSawtoothWave2D.gd b/addons/shaker/data/Vector2/ShakerTypeSawtoothWave2D.gd
new file mode 100644
index 00000000..54bc2f43
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeSawtoothWave2D.gd
@@ -0,0 +1,42 @@
+@tool
+class_name ShakerTypeSawtoothWave2D
+extends ShakerTypeBase2D
+
+## The frequency of the sawtooth wave for each axis.
+@export var frequency: Vector2 = Vector2.ONE * 5.0:
+ set = set_frequency
+
+## The asymmetry of the sawtooth wave for each axis (0 to 1).
+@export var asymmetry: Vector2 = Vector2.ONE * 0.5:
+ set = set_asymmetry
+
+## Sets the frequency of the sawtooth wave.
+func set_frequency(value: Vector2) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+## Gets the frequency of the sawtooth wave.
+func get_frequency() -> Vector2:
+ return frequency
+
+## Sets the asymmetry of the sawtooth wave.
+func set_asymmetry(value: Vector2) -> void:
+ asymmetry = value.clamp(Vector2.ZERO, Vector2.ONE)
+ _on_property_changed("asymmetry")
+
+## Gets the asymmetry of the sawtooth wave.
+func get_asymmetry() -> Vector2:
+ return asymmetry
+
+## Calculates the value of the sawtooth wave at time t.
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ var _real_time: float = fmod(t, 1.0) if t > 1.0 else t
+ var wave: Vector2 = (_real_time * frequency).posmod(1.0)
+
+ wave.x = wave.x / asymmetry.x if wave.x < asymmetry.x else (1.0 - wave.x) / (1.0 - asymmetry.x)
+ wave.y = wave.y / asymmetry.y if wave.y < asymmetry.y else (1.0 - wave.y) / (1.0 - asymmetry.y)
+ result = wave
+ result = _calc_value(t, result)
+ result = (result - amplitude * 0.5) * 2.0
+ return result
diff --git a/addons/shaker/data/Vector2/ShakerTypeSawtoothWave2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeSawtoothWave2D.gd.uid
new file mode 100644
index 00000000..331eb73f
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeSawtoothWave2D.gd.uid
@@ -0,0 +1 @@
+uid://x16d6et41iut
diff --git a/addons/shaker/data/Vector2/ShakerTypeSineWave2D.gd b/addons/shaker/data/Vector2/ShakerTypeSineWave2D.gd
new file mode 100644
index 00000000..e3c4b822
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeSineWave2D.gd
@@ -0,0 +1,33 @@
+@tool
+class_name ShakerTypeSineWave2D
+extends ShakerTypeBase2D
+
+@export_group("Sinewave Properties")
+@export var frequency: Vector2 = Vector2.ONE:
+ set = set_frequency,
+ get = get_frequency
+
+@export var phase: Vector2 = Vector2.ONE:
+ set = set_phase,
+ get = get_phase
+
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ var _real_time: float = fmod(t, 1.0) if t > 1.0 else t
+ result.x = sin(t * frequency.x * TAU + phase.x)
+ result.y = sin(t * frequency.y * TAU + phase.y + PI/2)
+ return _calc_value(_real_time, result)
+
+func set_frequency(value: Vector2) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+func get_frequency() -> Vector2:
+ return frequency
+
+func set_phase(value: Vector2) -> void:
+ phase = value
+ _on_property_changed("phase")
+
+func get_phase() -> Vector2:
+ return phase
diff --git a/addons/shaker/data/Vector2/ShakerTypeSineWave2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeSineWave2D.gd.uid
new file mode 100644
index 00000000..563657d4
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeSineWave2D.gd.uid
@@ -0,0 +1 @@
+uid://drm7drd6f4t4x
diff --git a/addons/shaker/data/Vector2/ShakerTypeSquareWave2D.gd b/addons/shaker/data/Vector2/ShakerTypeSquareWave2D.gd
new file mode 100644
index 00000000..788e55b4
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeSquareWave2D.gd
@@ -0,0 +1,36 @@
+@tool
+class_name ShakerTypeSquareWave2D
+extends ShakerTypeBase2D
+
+## The frequency of the square wave for each axis.
+@export var frequency: Vector2 = Vector2.ONE * 5.0:
+ set = set_frequency
+
+## The duty cycle of the square wave for each axis (0 to 1).
+@export var duty_cycle: Vector2 = Vector2.ONE * 0.5:
+ set = set_duty_cycle
+
+## Sets the frequency of the square wave.
+func set_frequency(value: Vector2) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+## Gets the frequency of the square wave.
+func get_frequency() -> Vector2:
+ return frequency
+
+## Sets the duty cycle of the square wave.
+func set_duty_cycle(value: Vector2) -> void:
+ duty_cycle = value.clamp(Vector2.ZERO, Vector2.ONE)
+ _on_property_changed("duty_cycle")
+
+## Gets the duty cycle of the square wave.
+func get_duty_cycle() -> Vector2:
+ return duty_cycle
+
+## Calculates the value of the square wave at time t.
+func get_value(t: float) -> Vector2:
+ var result: Vector2 = Vector2.ZERO
+ result.x = 1.0 if fmod(t * frequency.x, 1.0) < duty_cycle.x else -1.0
+ result.y = 1.0 if fmod(t * frequency.y, 1.0) < duty_cycle.y else -1.0
+ return _calc_value(t, result)
diff --git a/addons/shaker/data/Vector2/ShakerTypeSquareWave2D.gd.uid b/addons/shaker/data/Vector2/ShakerTypeSquareWave2D.gd.uid
new file mode 100644
index 00000000..a8789f41
--- /dev/null
+++ b/addons/shaker/data/Vector2/ShakerTypeSquareWave2D.gd.uid
@@ -0,0 +1 @@
+uid://btxrpjbvdcl1f
diff --git a/addons/shaker/data/Vector3/BaseShakerType3D.gd b/addons/shaker/data/Vector3/BaseShakerType3D.gd
new file mode 100644
index 00000000..9255d376
--- /dev/null
+++ b/addons/shaker/data/Vector3/BaseShakerType3D.gd
@@ -0,0 +1,48 @@
+@icon("res://addons/shaker/assets/ShakerType3D.svg")
+@tool
+class_name ShakerTypeBase3D
+extends ShakerTypeBase
+
+enum GraphAxis {
+ X,
+ Y,
+ Z
+}
+
+@export var amplitude: Vector3 = Vector3.ONE:
+ set = set_amplitude,
+ get = get_amplitude
+
+@export var offset: Vector3 = Vector3.ZERO:
+ set = set_offset,
+ get = get_offset
+
+func set_amplitude(value: Vector3) -> void:
+ amplitude = value
+ _on_property_changed("amplitude")
+
+func get_amplitude() -> Vector3:
+ return amplitude
+
+func set_offset(value: Vector3) -> void:
+ offset = value
+ _on_property_changed("offset")
+
+func get_offset() -> Vector3:
+ return offset
+
+# Get the shake value at a given time
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ return _calc_value(fmod(t, 1.0), result)
+
+# Calculate the shake value
+func _calc_value(t: float, result: Vector3) -> Vector3:
+ if duration > 0:
+ t /= duration
+ if (start_percent != 0 && start_percent > t) || (end_percent != 1 && end_percent < t):
+ result = Vector3.ZERO
+ else:
+ result = result * amplitude + offset
+ result *= (ease(t, fade_in) if abs(fade_in) > 0.0001 else 1.0) * (ease(1.0 - t, fade_out) if abs(fade_out) > 0.0001 else 1.0)
+ return result
diff --git a/addons/shaker/data/Vector3/BaseShakerType3D.gd.uid b/addons/shaker/data/Vector3/BaseShakerType3D.gd.uid
new file mode 100644
index 00000000..1950fb48
--- /dev/null
+++ b/addons/shaker/data/Vector3/BaseShakerType3D.gd.uid
@@ -0,0 +1 @@
+uid://0tu2q57qqu4s
diff --git a/addons/shaker/data/Vector3/ShakerPreset3D.gd b/addons/shaker/data/Vector3/ShakerPreset3D.gd
new file mode 100644
index 00000000..1059613c
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerPreset3D.gd
@@ -0,0 +1,103 @@
+@tool
+@icon("res://addons/shaker/assets/ShakerPreset3D.svg")
+class_name ShakerPreset3D
+extends ShakerPresetBase
+
+# Shake type arrays for each category
+@export var PositionShake: Array[ShakerTypeBase3D]:
+ set = set_position_shake,
+ get = get_position_shake
+
+@export var RotationShake: Array[ShakerTypeBase3D]:
+ set = set_rotation_shake,
+ get = get_rotation_shake
+
+@export var ScaleShake: Array[ShakerTypeBase3D]:
+ set = set_scale_shake,
+ get = get_scale_shake
+
+# Custom setter and getter functions
+func set_position_shake(value: Array[ShakerTypeBase3D]) -> void:
+ for _shake_type in _array_difference(PositionShake, value):
+ if _shake_type != null:
+ _shake_type.property_changed.connect(_on_property_changed)
+ _shake_type.property_changed.connect(_change_graph_category.bind(0))
+ PositionShake = value
+ if Graph != null:
+ Graph.select_category(0)
+ Graph._on_fit_button_clicked()
+ _on_property_changed("PositionShake")
+
+func get_position_shake() -> Array[ShakerTypeBase3D]:
+ return PositionShake
+
+func set_rotation_shake(value: Array[ShakerTypeBase3D]) -> void:
+ for _shake_type in _array_difference(RotationShake, value):
+ if _shake_type != null:
+ _shake_type.property_changed.connect(_on_property_changed)
+ _shake_type.property_changed.connect(_change_graph_category.bind(1))
+ RotationShake = value
+ if Graph != null:
+ Graph.select_category(1)
+ Graph._on_fit_button_clicked()
+ _on_property_changed("RotationShake")
+
+func get_rotation_shake() -> Array[ShakerTypeBase3D]:
+ return RotationShake
+
+func set_scale_shake(value: Array[ShakerTypeBase3D]) -> void:
+ for _shake_type in _array_difference(ScaleShake, value):
+ if _shake_type != null:
+ _shake_type.property_changed.connect(_on_property_changed)
+ _shake_type.property_changed.connect(_change_graph_category.bind(2))
+ ScaleShake = value
+ if Graph != null:
+ Graph.select_category(2)
+ Graph._on_fit_button_clicked()
+ _on_property_changed("ScaleShake")
+
+func get_scale_shake() -> Array[ShakerTypeBase3D]:
+ return ScaleShake
+
+# Get the shake value for a given time and category
+func get_value(t: float, _category: Categories = Categories.POSITION) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ var selected_category: Array[ShakerTypeBase3D] = [PositionShake, RotationShake, ScaleShake][_category]
+ for shake_type in selected_category:
+ if shake_type != null:
+ shake_type.duration = component_duration
+ var _shake_result: Vector3 = shake_type.get_value(t)
+ match shake_type.BlendingMode:
+ shake_type.BlendingModes.Add:
+ result += _shake_result
+ shake_type.BlendingModes.Multiply:
+ result *= _shake_result
+ shake_type.BlendingModes.Subtract:
+ result -= _shake_result
+ shake_type.BlendingModes.Max:
+ result.x = max(result.x, _shake_result.x)
+ result.y = max(result.y, _shake_result.y)
+ result.z = max(result.z, _shake_result.z)
+ shake_type.BlendingModes.Min:
+ result.x = min(result.x, _shake_result.x)
+ result.y = min(result.y, _shake_result.y)
+ result.z = min(result.z, _shake_result.z)
+ shake_type.BlendingModes.Average:
+ result = (result + _shake_result) * 0.5
+ shake_type.BlendingModes.Override:
+ result = _shake_result
+ return result
+
+func _change_graph_category(_name:String, _category_index:int) -> void:
+ if Graph:
+ Graph.category_button.select(_category_index)
+ Graph.category_button.item_selected.emit(_category_index)
+
+func get_shakes_by_category(category:Categories) -> Array:
+ if category == Categories.POSITION:
+ return PositionShake
+ elif category == Categories.ROTATION:
+ return RotationShake
+ elif category == Categories.SCALE:
+ return ScaleShake
+ return [null]
diff --git a/addons/shaker/data/Vector3/ShakerPreset3D.gd.uid b/addons/shaker/data/Vector3/ShakerPreset3D.gd.uid
new file mode 100644
index 00000000..f0edd1e9
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerPreset3D.gd.uid
@@ -0,0 +1 @@
+uid://mlwdmecg12xd
diff --git a/addons/shaker/data/Vector3/ShakerTypeAudioBus3D.gd b/addons/shaker/data/Vector3/ShakerTypeAudioBus3D.gd
new file mode 100644
index 00000000..d8330368
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeAudioBus3D.gd
@@ -0,0 +1,49 @@
+@tool
+class_name ShakerTypeAudioBus3D
+extends ShakerTypeBase3D
+
+@export var bus_name:String = "Master":
+ set = set_bus_name,
+ get = get_bus_name
+@export var min_frequence:Vector3 = Vector3(20, 20, 20)
+@export var max_frequence:Vector3 = Vector3(20000, 20000, 20000)
+
+var bus_index:int = 0
+var effect:AudioEffectSpectrumAnalyzerInstance
+
+func _init():
+ _update_bus_index()
+## Calculates the value of the square wave at time t.
+func get_value(t: float) -> Vector3:
+ var result:Vector3 = Vector3.ZERO
+ if effect:
+ var mag_x:Vector2 = effect.get_magnitude_for_frequency_range(min_frequence.x, max_frequence.x, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX)
+ var mag_y:Vector2 = effect.get_magnitude_for_frequency_range(min_frequence.y, max_frequence.y, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX)
+ var mag_z:Vector2 = effect.get_magnitude_for_frequency_range(min_frequence.z, max_frequence.z, AudioEffectSpectrumAnalyzerInstance.MAGNITUDE_MAX)
+ result.x = mag_x.length()
+ result.y = mag_y.length()
+ result.z = mag_y.length()
+ return _calc_value(t, result)
+
+func set_bus_name(value: String) -> void:
+ bus_name = value
+ _update_bus_index()
+ _on_property_changed("bus_name")
+
+func get_bus_name() -> String:
+ return bus_name
+
+func _update_bus_index() -> void:
+ bus_index = AudioServer.get_bus_index(bus_name)
+ if bus_index > -1:
+ for e in AudioServer.get_bus_effect_count(bus_index):
+ var _effect:AudioEffect = AudioServer.get_bus_effect(bus_index, e)
+ if _effect is AudioEffectSpectrumAnalyzer:
+ effect = AudioServer.get_bus_effect_instance(bus_index, e, 0)
+ break;
+ if effect == null:
+ AudioServer.add_bus_effect(bus_index, AudioEffectSpectrumAnalyzer.new(), 0)
+ effect = AudioServer.get_bus_effect_instance(bus_index, 0)
+ else:
+ push_error("Error: Bus '" + bus_name + "' not found!")
+ effect = null
diff --git a/addons/shaker/data/Vector3/ShakerTypeAudioBus3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeAudioBus3D.gd.uid
new file mode 100644
index 00000000..a2aa063c
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeAudioBus3D.gd.uid
@@ -0,0 +1 @@
+uid://cdne0sspxhssj
diff --git a/addons/shaker/data/Vector3/ShakerTypeBrownianShake3D.gd b/addons/shaker/data/Vector3/ShakerTypeBrownianShake3D.gd
new file mode 100644
index 00000000..f51b34f6
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeBrownianShake3D.gd
@@ -0,0 +1,46 @@
+@tool
+class_name ShakerTypeBrownianShake3D
+extends ShakerTypeBase3D
+
+@export var roughness: Vector3 = Vector3.ONE * 1.0:
+ set = set_roughness,
+ get = get_roughness
+
+@export var persistence: Vector3 = Vector3.ONE * 0.5:
+ set = set_persistence,
+ get = get_persistence
+
+var _generator: RandomNumberGenerator = RandomNumberGenerator.new()
+var _last_pos: Vector3 = Vector3.ZERO
+
+func _init() -> void:
+ property_changed.connect(_property_changed)
+
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ result.x = (_last_pos.x + _generator.randf_range(-roughness.x, roughness.x))
+ result.y = (_last_pos.y + _generator.randf_range(-roughness.y, roughness.y))
+ result.z = (_last_pos.z + _generator.randf_range(-roughness.z, roughness.z))
+ result = _calc_value(t, result)
+
+ _last_pos.x = lerpf(_last_pos.x, result.x, 1.0 - persistence.x)
+ _last_pos.y = lerpf(_last_pos.y, result.y, 1.0 - persistence.y)
+ _last_pos.z = lerpf(_last_pos.z, result.z, 1.0 - persistence.z)
+ return _last_pos
+
+func _property_changed(name: StringName) -> void:
+ _last_pos = Vector3.ZERO
+
+func set_roughness(value: Vector3) -> void:
+ roughness = value
+ _on_property_changed("roughness")
+
+func get_roughness() -> Vector3:
+ return roughness
+
+func set_persistence(value: Vector3) -> void:
+ persistence = value.clamp(Vector3.ZERO, Vector3.ONE)
+ _on_property_changed("persistence")
+
+func get_persistence() -> Vector3:
+ return persistence
diff --git a/addons/shaker/data/Vector3/ShakerTypeBrownianShake3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeBrownianShake3D.gd.uid
new file mode 100644
index 00000000..8a0c185d
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeBrownianShake3D.gd.uid
@@ -0,0 +1 @@
+uid://ddk408cg8156q
diff --git a/addons/shaker/data/Vector3/ShakerTypeCurve3D.gd b/addons/shaker/data/Vector3/ShakerTypeCurve3D.gd
new file mode 100644
index 00000000..5325b98d
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeCurve3D.gd
@@ -0,0 +1,78 @@
+@tool
+class_name ShakerTypeCurve3D
+extends ShakerTypeBase3D
+
+@export var curve_x: Curve:
+ set = set_curve_x,
+ get = get_curve_x
+
+@export var curve_y: Curve:
+ set = set_curve_y,
+ get = get_curve_y
+
+@export var curve_z: Curve:
+ set = set_curve_z,
+ get = get_curve_z
+
+@export var loop: bool = true:
+ set = set_loop,
+ get = get_loop
+
+func _curve_changed() -> void:
+ _on_property_changed("curve_")
+
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ if loop && t > 1.0:
+ t = fmod(t, 1.0)
+ if curve_x:
+ result.x = curve_x.sample(t)
+ if curve_y:
+ result.y = curve_y.sample(t)
+ if curve_z:
+ result.z = curve_z.sample(t)
+
+ return _calc_value(t, result)
+
+func set_curve_x(value: Curve) -> void:
+ if curve_x:
+ curve_x.changed.disconnect(_curve_changed)
+ curve_x = value
+ if curve_x:
+ curve_x.changed.connect(_curve_changed)
+ else:
+ _curve_changed()
+
+func get_curve_x() -> Curve:
+ return curve_x
+
+func set_curve_y(value: Curve) -> void:
+ if curve_y:
+ curve_y.changed.disconnect(_curve_changed)
+ curve_y = value
+ if curve_y:
+ curve_y.changed.connect(_curve_changed)
+ else:
+ _curve_changed()
+
+func get_curve_y() -> Curve:
+ return curve_y
+
+func set_curve_z(value: Curve) -> void:
+ if curve_z:
+ curve_z.changed.disconnect(_curve_changed)
+ curve_z = value
+ if curve_z:
+ curve_z.changed.connect(_curve_changed)
+ else:
+ _curve_changed()
+
+func get_curve_z() -> Curve:
+ return curve_z
+
+func set_loop(value: bool) -> void:
+ loop = value
+ _on_property_changed("loop")
+
+func get_loop() -> bool:
+ return loop
diff --git a/addons/shaker/data/Vector3/ShakerTypeCurve3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeCurve3D.gd.uid
new file mode 100644
index 00000000..4e6f0ec1
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeCurve3D.gd.uid
@@ -0,0 +1 @@
+uid://biqfuixncau4j
diff --git a/addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd b/addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd
new file mode 100644
index 00000000..e9fcb465
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd
@@ -0,0 +1,34 @@
+@tool
+class_name ShakerTypeNoiseShake3D
+extends ShakerTypeBase3D
+
+@export var noise_texture: NoiseTexture3D = NoiseTexture3D.new():
+ set = set_noise_texture,
+ get = get_noise_texture
+
+func _init() -> void:
+ noise_texture.changed.connect(_on_noise_changed)
+
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ if noise_texture && noise_texture.noise:
+ var noise_size: Vector3 = Vector3(noise_texture.width, noise_texture.height, noise_texture.depth)
+ var noise_offset: Vector3 = t * noise_size
+ result.x = noise_texture.noise.get_noise_3d(noise_offset.x, 0.0, 0.0)
+ result.y = noise_texture.noise.get_noise_3d(0.0, noise_offset.y, 0.0)
+ result.z = noise_texture.noise.get_noise_3d(0.0, 0.0, noise_offset.z)
+ result *= 2.0
+ return _calc_value(t, result)
+
+func _on_noise_changed() -> void:
+ _on_property_changed("noise_texture")
+
+func set_noise_texture(value: NoiseTexture3D) -> void:
+ if noise_texture:
+ noise_texture.changed.disconnect(_on_noise_changed)
+ noise_texture = value
+ if noise_texture:
+ noise_texture.changed.connect(_on_noise_changed)
+
+func get_noise_texture() -> NoiseTexture3D:
+ return noise_texture
diff --git a/addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd.uid
new file mode 100644
index 00000000..a2d0e965
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd.uid
@@ -0,0 +1 @@
+uid://ptaespkh1sk2
diff --git a/addons/shaker/data/Vector3/ShakerTypeRandom3D.gd b/addons/shaker/data/Vector3/ShakerTypeRandom3D.gd
new file mode 100644
index 00000000..b5ac3a79
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeRandom3D.gd
@@ -0,0 +1,32 @@
+@tool
+class_name ShakerTypeRandom3D
+extends ShakerTypeBase3D
+
+## The seed for the random number generator.
+@export var seed: int = 0:
+ set = set_seed
+
+## The random number generator instance.
+var _generator: RandomNumberGenerator = RandomNumberGenerator.new()
+
+## Initializes the shake type with the given seed.
+func _init() -> void:
+ set_seed(seed)
+
+## Calculates a random value for each axis at time t.
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ result.x = _generator.randf_range(-1.0, 1.0)
+ result.y = _generator.randf_range(-1.0, 1.0)
+ result.z = _generator.randf_range(-1.0, 1.0)
+ return _calc_value(t, result)
+
+## Sets the seed for the random number generator.
+func set_seed(value: int) -> void:
+ seed = value
+ _generator.seed = seed
+ _on_property_changed("seed")
+
+## Gets the current seed of the random number generator.
+func get_seed() -> int:
+ return seed
diff --git a/addons/shaker/data/Vector3/ShakerTypeRandom3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeRandom3D.gd.uid
new file mode 100644
index 00000000..77f9008b
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeRandom3D.gd.uid
@@ -0,0 +1 @@
+uid://jyrxhux22cdm
diff --git a/addons/shaker/data/Vector3/ShakerTypeSawtoothWave3D.gd b/addons/shaker/data/Vector3/ShakerTypeSawtoothWave3D.gd
new file mode 100644
index 00000000..1a384223
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeSawtoothWave3D.gd
@@ -0,0 +1,43 @@
+@tool
+class_name ShakerTypeSawtoothWave3D
+extends ShakerTypeBase3D
+
+## The frequency of the sawtooth wave for each axis.
+@export var frequency: Vector3 = Vector3.ONE * 5.0:
+ set = set_frequency
+
+## The asymmetry of the sawtooth wave for each axis (0 to 1).
+@export var asymmetry: Vector3 = Vector3.ONE * 0.5:
+ set = set_asymmetry
+
+## Sets the frequency of the sawtooth wave.
+func set_frequency(value: Vector3) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+## Gets the frequency of the sawtooth wave.
+func get_frequency() -> Vector3:
+ return frequency
+
+## Sets the asymmetry of the sawtooth wave.
+func set_asymmetry(value: Vector3) -> void:
+ asymmetry = value.clamp(Vector3.ZERO, Vector3.ONE)
+ _on_property_changed("asymmetry")
+
+## Gets the asymmetry of the sawtooth wave.
+func get_asymmetry() -> Vector3:
+ return asymmetry
+
+## Calculates the value of the sawtooth wave at time t.
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ var _real_time: float = fmod(t, 1.0) if t > 1.0 else t
+ var wave: Vector3 = (_real_time * frequency).posmod(1.0)
+
+ wave.x = wave.x / asymmetry.x if wave.x < asymmetry.x else (1.0 - wave.x) / (1.0 - asymmetry.x)
+ wave.y = wave.y / asymmetry.y if wave.y < asymmetry.y else (1.0 - wave.y) / (1.0 - asymmetry.y)
+ wave.z = wave.z / asymmetry.z if wave.z < asymmetry.z else (1.0 - wave.z) / (1.0 - asymmetry.z)
+ result = wave
+ result = _calc_value(t, result)
+ result = (result - amplitude * 0.5) * 2.0
+ return result
diff --git a/addons/shaker/data/Vector3/ShakerTypeSawtoothWave3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeSawtoothWave3D.gd.uid
new file mode 100644
index 00000000..f8f8801c
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeSawtoothWave3D.gd.uid
@@ -0,0 +1 @@
+uid://dpoggf3psdkci
diff --git a/addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd b/addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd
new file mode 100644
index 00000000..d3334c11
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd
@@ -0,0 +1,34 @@
+@tool
+class_name ShakerTypeSineWave3D
+extends ShakerTypeBase3D
+
+@export_group("Sinewave Properties")
+@export var frequency: Vector3 = Vector3.ONE:
+ set = set_frequency,
+ get = get_frequency
+
+@export var phase: Vector3 = Vector3.ONE:
+ set = set_phase,
+ get = get_phase
+
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ var _real_time: float = fmod(t, 1.0) if t > 1.0 else t
+ result.x = sin(t * frequency.x * TAU + phase.x)
+ result.y = sin(t * frequency.y * TAU + phase.y + PI/2)
+ result.z = sin(t * frequency.z * TAU + phase.z + PI/4)
+ return _calc_value(_real_time, result)
+
+func set_frequency(value: Vector3) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+func get_frequency() -> Vector3:
+ return frequency
+
+func set_phase(value: Vector3) -> void:
+ phase = value
+ _on_property_changed("phase")
+
+func get_phase() -> Vector3:
+ return phase
diff --git a/addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd.uid
new file mode 100644
index 00000000..b280d1a1
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd.uid
@@ -0,0 +1 @@
+uid://sih7vd0uinav
diff --git a/addons/shaker/data/Vector3/ShakerTypeSquareWave3D.gd b/addons/shaker/data/Vector3/ShakerTypeSquareWave3D.gd
new file mode 100644
index 00000000..68699388
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeSquareWave3D.gd
@@ -0,0 +1,37 @@
+@tool
+class_name ShakerTypeSquareWave3D
+extends ShakerTypeBase3D
+
+## The frequency of the square wave for each axis.
+@export var frequency: Vector3 = Vector3.ONE * 5.0:
+ set = set_frequency
+
+## The duty cycle of the square wave for each axis (0 to 1).
+@export var duty_cycle: Vector3 = Vector3.ONE * 0.5:
+ set = set_duty_cycle
+
+## Sets the frequency of the square wave.
+func set_frequency(value: Vector3) -> void:
+ frequency = value
+ _on_property_changed("frequency")
+
+## Gets the frequency of the square wave.
+func get_frequency() -> Vector3:
+ return frequency
+
+## Sets the duty cycle of the square wave.
+func set_duty_cycle(value: Vector3) -> void:
+ duty_cycle = value.clamp(Vector3.ZERO, Vector3.ONE)
+ _on_property_changed("duty_cycle")
+
+## Gets the duty cycle of the square wave.
+func get_duty_cycle() -> Vector3:
+ return duty_cycle
+
+## Calculates the value of the square wave at time t.
+func get_value(t: float) -> Vector3:
+ var result: Vector3 = Vector3.ZERO
+ result.x = 1.0 if fmod(t * frequency.x, 1.0) < duty_cycle.x else -1.0
+ result.y = 1.0 if fmod(t * frequency.y, 1.0) < duty_cycle.y else -1.0
+ result.z = 1.0 if fmod(t * frequency.z, 1.0) < duty_cycle.z else -1.0
+ return _calc_value(t, result)
diff --git a/addons/shaker/data/Vector3/ShakerTypeSquareWave3D.gd.uid b/addons/shaker/data/Vector3/ShakerTypeSquareWave3D.gd.uid
new file mode 100644
index 00000000..8ff70c81
--- /dev/null
+++ b/addons/shaker/data/Vector3/ShakerTypeSquareWave3D.gd.uid
@@ -0,0 +1 @@
+uid://drpiei33e2en
diff --git a/addons/shaker/data/resources/box_opening3D.tres b/addons/shaker/data/resources/box_opening3D.tres
new file mode 100644
index 00000000..a287537c
--- /dev/null
+++ b/addons/shaker/data/resources/box_opening3D.tres
@@ -0,0 +1,90 @@
+[gd_resource type="Resource" script_class="ShakerPreset3D" load_steps=13 format=3 uid="uid://cqfe3012jylqs"]
+
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerTypeCurve3D.gd" id="2_020y4"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerTypeRandom3D.gd" id="3_6s2h2"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerPreset3D.gd" id="4_ko4ur"]
+
+[sub_resource type="Curve" id="Curve_csfne"]
+_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.429245, 0), 0.0, 0.0, 0, 0, Vector2(0.457547, 1), 0.0, 0.0, 0, 0, Vector2(0.787736, 1), 0.0, 0.0, 0, 0, Vector2(0.985849, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
+point_count = 6
+
+[sub_resource type="Resource" id="Resource_n7d86"]
+script = ExtResource("2_020y4")
+curve_y = SubResource("Curve_csfne")
+loop = true
+amplitude = Vector3(1, 1, 1)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Resource" id="Resource_c2tfw"]
+script = ExtResource("3_6s2h2")
+seed = 0
+amplitude = Vector3(0.25, 0.25, 0.25)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 5.42787
+start_percent = 0.0
+end_percent = 0.59
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Curve" id="Curve_ioyrx"]
+_data = [Vector2(0.501572, 0), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0]
+point_count = 2
+
+[sub_resource type="Resource" id="Resource_1e44d"]
+script = ExtResource("2_020y4")
+curve_y = SubResource("Curve_ioyrx")
+loop = true
+amplitude = Vector3(2, 2, 2)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Curve" id="Curve_01cbm"]
+_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.0613208, 0.20904), 0.0, 0.0, 0, 0, Vector2(0.143082, 0), 0.0, 0.0, 0, 0, Vector2(0.215409, 0.20904), 0.0, 0.0, 0, 0, Vector2(0.275157, 0), 0.0, 0.0, 0, 0, Vector2(0.429245, 0), 0.0, 0.0, 0, 0, Vector2(0.463836, 1), 0.0, 0.0, 0, 0, Vector2(0.905204, 1), 0.35252, 0.35252, 0, 0, Vector2(0.957249, 0.482796), 0.0, 0.0, 0, 0, Vector2(0.99999, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
+point_count = 11
+
+[sub_resource type="Curve" id="Curve_uayq1"]
+_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.0613208, 0.20904), 0.0, 0.0, 0, 0, Vector2(0.143082, 0), 0.0, 0.0, 0, 0, Vector2(0.215409, 0.20904), 0.0, 0.0, 0, 0, Vector2(0.275157, 0), 0.0, 0.0, 0, 0, Vector2(0.429245, 0), 0.0, 0.0, 0, 0, Vector2(0.463836, 1), 0.0, 0.0, 0, 0, Vector2(0.905204, 1), 0.35252, 0.35252, 0, 0, Vector2(0.957249, 0.482796), 0.0, 0.0, 0, 0, Vector2(0.99999, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
+point_count = 11
+
+[sub_resource type="Curve" id="Curve_b6gtf"]
+_data = [Vector2(0, 0), 0.0, 0.0, 0, 0, Vector2(0.0613208, 0.20904), 0.0, 0.0, 0, 0, Vector2(0.143082, 0), 0.0, 0.0, 0, 0, Vector2(0.215409, 0.20904), 0.0, 0.0, 0, 0, Vector2(0.275157, 0), 0.0, 0.0, 0, 0, Vector2(0.429245, 0), 0.0, 0.0, 0, 0, Vector2(0.463836, 1), 0.0, 0.0, 0, 0, Vector2(0.905204, 1), 0.35252, 0.35252, 0, 0, Vector2(0.957249, 0.482796), 0.0, 0.0, 0, 0, Vector2(0.99999, 1), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0, 0.0, 0, 0]
+point_count = 11
+
+[sub_resource type="Resource" id="Resource_011mv"]
+script = ExtResource("2_020y4")
+curve_x = SubResource("Curve_01cbm")
+curve_y = SubResource("Curve_uayq1")
+curve_z = SubResource("Curve_b6gtf")
+loop = true
+amplitude = Vector3(0.5, 0.5, 0.5)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[resource]
+script = ExtResource("4_ko4ur")
+PositionShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_n7d86")])
+RotationShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_c2tfw"), SubResource("Resource_1e44d")])
+ScaleShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_011mv")])
+bake_internal = 64
+__follow_timeline = true
diff --git a/addons/shaker/data/resources/shaker_earthquake3D.tres b/addons/shaker/data/resources/shaker_earthquake3D.tres
new file mode 100644
index 00000000..6ab68c8f
--- /dev/null
+++ b/addons/shaker/data/resources/shaker_earthquake3D.tres
@@ -0,0 +1,55 @@
+[gd_resource type="Resource" script_class="ShakerPreset3D" load_steps=8 format=3 uid="uid://cvklw5ika1tao"]
+
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/BaseShakerType3D.gd" id="1_d2tua"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd" id="2_pfinc"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerTypeRandom3D.gd" id="3_ghpi8"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerPreset3D.gd" id="4_trx4m"]
+
+[sub_resource type="Resource" id="Resource_pq027"]
+script = ExtResource("2_pfinc")
+frequency = Vector3(10, 10, 1)
+phase = Vector3(1, 1, 1)
+amplitude = Vector3(0.25, 0.2, 0)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 1e-05
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Resource" id="Resource_won02"]
+script = ExtResource("3_ghpi8")
+seed = 0
+amplitude = Vector3(0.25, 0.25, 0)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.633041
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Resource" id="Resource_twjh4"]
+script = ExtResource("2_pfinc")
+frequency = Vector3(1, 1, 1)
+phase = Vector3(1, 1, 1)
+amplitude = Vector3(0.025, 0, 0.025)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[resource]
+script = ExtResource("4_trx4m")
+PositionShake = Array[ExtResource("1_d2tua")]([SubResource("Resource_pq027"), SubResource("Resource_won02")])
+RotationShake = Array[ExtResource("1_d2tua")]([SubResource("Resource_twjh4")])
+ScaleShake = Array[ExtResource("1_d2tua")]([])
+bake_internal = 64
+__follow_timeline = false
diff --git a/addons/shaker/data/resources/shaker_head_blob3D.tres b/addons/shaker/data/resources/shaker_head_blob3D.tres
new file mode 100644
index 00000000..8c0d5a69
--- /dev/null
+++ b/addons/shaker/data/resources/shaker_head_blob3D.tres
@@ -0,0 +1,55 @@
+[gd_resource type="Resource" script_class="ShakerPreset3D" load_steps=7 format=3 uid="uid://bgwupdyecxqic"]
+
+[ext_resource type="Resource" uid="uid://cqhisip66ngbl" path="res://ShakerDemoScenes/HeadBobWalking.tres" id="2_abfix"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd" id="2_lj1xu"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerPreset3D.gd" id="3_1llgx"]
+
+[sub_resource type="Resource" id="Resource_hui0g"]
+script = ExtResource("2_lj1xu")
+frequency = Vector3(0.75, 1, 1)
+phase = Vector3(1, 1, 1)
+amplitude = Vector3(0.04, 0.08, 0)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Resource" id="Resource_usaqk"]
+script = ExtResource("2_lj1xu")
+frequency = Vector3(1, 1, 1)
+phase = Vector3(1.5, 1, 1)
+amplitude = Vector3(0.02, 0, 0)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Resource" id="Resource_sxvgt"]
+script = ExtResource("2_lj1xu")
+frequency = Vector3(0.5, 1, 0.25)
+phase = Vector3(1, 1, 1)
+amplitude = Vector3(0.02, 0, 0.008)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 0.0
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[resource]
+script = ExtResource("3_1llgx")
+PositionShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_hui0g"), SubResource("Resource_usaqk")])
+RotationShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_sxvgt"), ExtResource("2_abfix")])
+ScaleShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([])
+bake_internal = 64
+__follow_timeline = false
diff --git a/addons/shaker/data/resources/strong_shake3D.tres b/addons/shaker/data/resources/strong_shake3D.tres
new file mode 100644
index 00000000..bb500beb
--- /dev/null
+++ b/addons/shaker/data/resources/strong_shake3D.tres
@@ -0,0 +1,40 @@
+[gd_resource type="Resource" script_class="ShakerPreset3D" load_steps=5 format=3 uid="uid://dakufr1eoowxf"]
+
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerTypeSineWave3D.gd" id="2_dydew"]
+[ext_resource type="Script" path="res://addons/shaker/data/Vector3/ShakerPreset3D.gd" id="3_1oh1n"]
+
+[sub_resource type="Resource" id="Resource_rqq58"]
+script = ExtResource("2_dydew")
+frequency = Vector3(7.065, 1, 1)
+phase = Vector3(1, 1, 1)
+amplitude = Vector3(0.5, 0.5, 0)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 2.36259
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[sub_resource type="Resource" id="Resource_4yuof"]
+script = ExtResource("2_dydew")
+frequency = Vector3(2.94, 1.125, 1)
+phase = Vector3(1, 1, 1)
+amplitude = Vector3(0.053, 0.05, 0.1)
+offset = Vector3(0, 0, 0)
+BlendingMode = 0
+fade_in = 0.0
+fade_out = 2.36259
+start_percent = 0.0
+end_percent = 1.0
+_temp_graph = false
+bake_internal = 64
+
+[resource]
+script = ExtResource("3_1oh1n")
+PositionShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_rqq58")])
+RotationShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([SubResource("Resource_4yuof")])
+ScaleShake = Array[Resource("res://addons/shaker/data/Vector3/BaseShakerType3D.gd")]([])
+bake_internal = 64
+__follow_timeline = false
diff --git a/addons/shaker/plugin.cfg b/addons/shaker/plugin.cfg
new file mode 100644
index 00000000..bd9db9cd
--- /dev/null
+++ b/addons/shaker/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Shaker"
+description=""
+author="Eneskp"
+version="1.0.7"
+script="src/shaker_plugin.gd"
diff --git a/addons/shaker/src/Shaker.gd b/addons/shaker/src/Shaker.gd
new file mode 100644
index 00000000..a8e476af
--- /dev/null
+++ b/addons/shaker/src/Shaker.gd
@@ -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()
diff --git a/addons/shaker/src/Shaker.gd.uid b/addons/shaker/src/Shaker.gd.uid
new file mode 100644
index 00000000..373ef131
--- /dev/null
+++ b/addons/shaker/src/Shaker.gd.uid
@@ -0,0 +1 @@
+uid://c7flmumgr5w3u
diff --git a/addons/shaker/src/Vector2/ShakerBase2D.gd b/addons/shaker/src/Vector2/ShakerBase2D.gd
new file mode 100644
index 00000000..d4a9fc73
--- /dev/null
+++ b/addons/shaker/src/Vector2/ShakerBase2D.gd
@@ -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)
diff --git a/addons/shaker/src/Vector2/ShakerBase2D.gd.uid b/addons/shaker/src/Vector2/ShakerBase2D.gd.uid
new file mode 100644
index 00000000..d31ecbbe
--- /dev/null
+++ b/addons/shaker/src/Vector2/ShakerBase2D.gd.uid
@@ -0,0 +1 @@
+uid://c0tuadw5ygn6m
diff --git a/addons/shaker/src/Vector2/ShakerEmitter2D.gd b/addons/shaker/src/Vector2/ShakerEmitter2D.gd
new file mode 100644
index 00000000..690fb0a2
--- /dev/null
+++ b/addons/shaker/src/Vector2/ShakerEmitter2D.gd
@@ -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
diff --git a/addons/shaker/src/Vector2/ShakerEmitter2D.gd.uid b/addons/shaker/src/Vector2/ShakerEmitter2D.gd.uid
new file mode 100644
index 00000000..3597b2d2
--- /dev/null
+++ b/addons/shaker/src/Vector2/ShakerEmitter2D.gd.uid
@@ -0,0 +1 @@
+uid://ddpgk78xe5y4g
diff --git a/addons/shaker/src/Vector2/ShakerReceiver2D.gd b/addons/shaker/src/Vector2/ShakerReceiver2D.gd
new file mode 100644
index 00000000..522bc881
--- /dev/null
+++ b/addons/shaker/src/Vector2/ShakerReceiver2D.gd
@@ -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
+
diff --git a/addons/shaker/src/Vector2/ShakerReceiver2D.gd.uid b/addons/shaker/src/Vector2/ShakerReceiver2D.gd.uid
new file mode 100644
index 00000000..5b2c9ae8
--- /dev/null
+++ b/addons/shaker/src/Vector2/ShakerReceiver2D.gd.uid
@@ -0,0 +1 @@
+uid://ca4ybjjck0t0a
diff --git a/addons/shaker/src/Vector2/shaker_component2D.gd b/addons/shaker/src/Vector2/shaker_component2D.gd
new file mode 100644
index 00000000..dca2fece
--- /dev/null
+++ b/addons/shaker/src/Vector2/shaker_component2D.gd
@@ -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
diff --git a/addons/shaker/src/Vector2/shaker_component2D.gd.uid b/addons/shaker/src/Vector2/shaker_component2D.gd.uid
new file mode 100644
index 00000000..eb57ed90
--- /dev/null
+++ b/addons/shaker/src/Vector2/shaker_component2D.gd.uid
@@ -0,0 +1 @@
+uid://b78swtk5maey2
diff --git a/addons/shaker/src/Vector3/ShakerBase3D.gd b/addons/shaker/src/Vector3/ShakerBase3D.gd
new file mode 100644
index 00000000..f8f01009
--- /dev/null
+++ b/addons/shaker/src/Vector3/ShakerBase3D.gd
@@ -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)
diff --git a/addons/shaker/src/Vector3/ShakerBase3D.gd.uid b/addons/shaker/src/Vector3/ShakerBase3D.gd.uid
new file mode 100644
index 00000000..df835625
--- /dev/null
+++ b/addons/shaker/src/Vector3/ShakerBase3D.gd.uid
@@ -0,0 +1 @@
+uid://cna76yluotcdw
diff --git a/addons/shaker/src/Vector3/ShakerEmitter3D.gd b/addons/shaker/src/Vector3/ShakerEmitter3D.gd
new file mode 100644
index 00000000..4ee775df
--- /dev/null
+++ b/addons/shaker/src/Vector3/ShakerEmitter3D.gd
@@ -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
diff --git a/addons/shaker/src/Vector3/ShakerEmitter3D.gd.uid b/addons/shaker/src/Vector3/ShakerEmitter3D.gd.uid
new file mode 100644
index 00000000..22b35b48
--- /dev/null
+++ b/addons/shaker/src/Vector3/ShakerEmitter3D.gd.uid
@@ -0,0 +1 @@
+uid://vo46i2pfq0uf
diff --git a/addons/shaker/src/Vector3/ShakerReceiver3D.gd b/addons/shaker/src/Vector3/ShakerReceiver3D.gd
new file mode 100644
index 00000000..490b983a
--- /dev/null
+++ b/addons/shaker/src/Vector3/ShakerReceiver3D.gd
@@ -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
diff --git a/addons/shaker/src/Vector3/ShakerReceiver3D.gd.uid b/addons/shaker/src/Vector3/ShakerReceiver3D.gd.uid
new file mode 100644
index 00000000..409bf595
--- /dev/null
+++ b/addons/shaker/src/Vector3/ShakerReceiver3D.gd.uid
@@ -0,0 +1 @@
+uid://csr4nkcrmm0bq
diff --git a/addons/shaker/src/Vector3/shaker_component3D.gd b/addons/shaker/src/Vector3/shaker_component3D.gd
new file mode 100644
index 00000000..3d044b03
--- /dev/null
+++ b/addons/shaker/src/Vector3/shaker_component3D.gd
@@ -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
diff --git a/addons/shaker/src/Vector3/shaker_component3D.gd.uid b/addons/shaker/src/Vector3/shaker_component3D.gd.uid
new file mode 100644
index 00000000..fc418910
--- /dev/null
+++ b/addons/shaker/src/Vector3/shaker_component3D.gd.uid
@@ -0,0 +1 @@
+uid://dnlxsrumw6ygp
diff --git a/addons/shaker/src/shaker_component.gd b/addons/shaker/src/shaker_component.gd
new file mode 100644
index 00000000..72461baf
--- /dev/null
+++ b/addons/shaker/src/shaker_component.gd
@@ -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
diff --git a/addons/shaker/src/shaker_component.gd.uid b/addons/shaker/src/shaker_component.gd.uid
new file mode 100644
index 00000000..4e35c637
--- /dev/null
+++ b/addons/shaker/src/shaker_component.gd.uid
@@ -0,0 +1 @@
+uid://dqvqij8tcek5
diff --git a/addons/shaker/src/shaker_graph.gd b/addons/shaker/src/shaker_graph.gd
new file mode 100644
index 00000000..cab2c3b9
--- /dev/null
+++ b/addons/shaker/src/shaker_graph.gd
@@ -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)
diff --git a/addons/shaker/src/shaker_graph.gd.uid b/addons/shaker/src/shaker_graph.gd.uid
new file mode 100644
index 00000000..16e757f2
--- /dev/null
+++ b/addons/shaker/src/shaker_graph.gd.uid
@@ -0,0 +1 @@
+uid://bihgv1h2lc26e
diff --git a/addons/shaker/src/shaker_inspector.gd b/addons/shaker/src/shaker_inspector.gd
new file mode 100644
index 00000000..de767336
--- /dev/null
+++ b/addons/shaker/src/shaker_inspector.gd
@@ -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;
diff --git a/addons/shaker/src/shaker_inspector.gd.uid b/addons/shaker/src/shaker_inspector.gd.uid
new file mode 100644
index 00000000..a016ea59
--- /dev/null
+++ b/addons/shaker/src/shaker_inspector.gd.uid
@@ -0,0 +1 @@
+uid://bj0ltv8atp1sn
diff --git a/addons/shaker/src/shaker_panel.gd b/addons/shaker/src/shaker_panel.gd
new file mode 100644
index 00000000..67c2b3ef
--- /dev/null
+++ b/addons/shaker/src/shaker_panel.gd
@@ -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()
diff --git a/addons/shaker/src/shaker_panel.gd.uid b/addons/shaker/src/shaker_panel.gd.uid
new file mode 100644
index 00000000..867ce05f
--- /dev/null
+++ b/addons/shaker/src/shaker_panel.gd.uid
@@ -0,0 +1 @@
+uid://biww5l4smr7f0
diff --git a/addons/shaker/src/shaker_plugin.gd b/addons/shaker/src/shaker_plugin.gd
new file mode 100644
index 00000000..aa44b32b
--- /dev/null
+++ b/addons/shaker/src/shaker_plugin.gd
@@ -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")
diff --git a/addons/shaker/src/shaker_plugin.gd.uid b/addons/shaker/src/shaker_plugin.gd.uid
new file mode 100644
index 00000000..1b920aed
--- /dev/null
+++ b/addons/shaker/src/shaker_plugin.gd.uid
@@ -0,0 +1 @@
+uid://xqg7s8xnwljh
diff --git a/addons/shaker/src/shaker_timeline.gd b/addons/shaker/src/shaker_timeline.gd
new file mode 100644
index 00000000..6d6f16ed
--- /dev/null
+++ b/addons/shaker/src/shaker_timeline.gd
@@ -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)
diff --git a/addons/shaker/src/shaker_timeline.gd.uid b/addons/shaker/src/shaker_timeline.gd.uid
new file mode 100644
index 00000000..3c04e87e
--- /dev/null
+++ b/addons/shaker/src/shaker_timeline.gd.uid
@@ -0,0 +1 @@
+uid://dk3dikejci4sq
diff --git a/player_controller/Scripts/PlayerController.cs b/player_controller/Scripts/PlayerController.cs
index 30aa2c6c..51ed8a7b 100644
--- a/player_controller/Scripts/PlayerController.cs
+++ b/player_controller/Scripts/PlayerController.cs
@@ -1787,13 +1787,19 @@ public partial class PlayerController : CharacterBody3D,
var finalDamage = CDamageable.TakeDamage(damageRecord);
DamageTaken?.Invoke(this, finalDamage);
+ HeadSystem.OnGetHit();
TriggerHitstop();
-
- _isInvincible = true;
- _invincibilityTimer.Start();
+ OnHitInvincibility();
return finalDamage;
}
+
+ public void OnHitInvincibility()
+ {
+ _isInvincible = true;
+ _invincibilityTimer.Start();
+ }
+
public void OnInputHitPressed()
{
if (_aiming.Active && WeaponSystem.InHandState.Active)
@@ -1842,6 +1848,8 @@ public partial class PlayerController : CharacterBody3D,
foreach (var damageable in _hitEnemies)
damageable.TakeDamage(new DamageRecord(this, RDamage));
_hitEnemies.Clear();
+
+ HeadSystem.OnHitTarget();
TriggerHitstop();
}
diff --git a/project.godot b/project.godot
index e592f32d..aebea61e 100644
--- a/project.godot
+++ b/project.godot
@@ -26,6 +26,7 @@ AppConfig="*res://addons/maaacks_game_template/base/scenes/autoloads/app_config.
SceneLoader="*res://addons/maaacks_game_template/base/scenes/autoloads/scene_loader.tscn"
ProjectMusicController="*res://addons/maaacks_game_template/base/scenes/autoloads/project_music_controller.tscn"
ProjectUISoundController="*res://addons/maaacks_game_template/base/scenes/autoloads/project_ui_sound_controller.tscn"
+Shaker="*res://addons/shaker/src/Shaker.gd"
[display]
@@ -38,7 +39,7 @@ project/assembly_name="Movement tests"
[editor_plugins]
-enabled=PackedStringArray("res://addons/godot_state_charts/plugin.cfg", "res://addons/guide/plugin.cfg", "res://addons/maaacks_game_template/plugin.cfg")
+enabled=PackedStringArray("res://addons/godot_state_charts/plugin.cfg", "res://addons/guide/plugin.cfg", "res://addons/maaacks_game_template/plugin.cfg", "res://addons/shaker/plugin.cfg")
[gui]
diff --git a/systems/head/HeadSystem.cs b/systems/head/HeadSystem.cs
index b8635c3e..c9833092 100644
--- a/systems/head/HeadSystem.cs
+++ b/systems/head/HeadSystem.cs
@@ -10,6 +10,11 @@ public partial class HeadSystem : Node3D
public delegate void HitboxActivatedEventHandler();
[Signal]
public delegate void HitboxDeactivatedEventHandler();
+
+ [Signal]
+ public delegate void HitTargetEventHandler();
+ [Signal]
+ public delegate void GotHitEventHandler();
public record CameraParameters(
double Delta,
@@ -121,6 +126,15 @@ public partial class HeadSystem : Node3D
_animationTree.Set("parameters/OnHit/request", (int) AnimationNodeOneShot.OneShotRequest.Fire);
}
+ public void OnHitTarget()
+ {
+ EmitSignalHitTarget();
+ }
+ public void OnGetHit()
+ {
+ EmitSignalGotHit();
+ }
+
public void OnHitboxActivated()
{
EmitSignalHitboxActivated();
diff --git a/systems/head/head_system.tscn b/systems/head/head_system.tscn
index 646112e1..6b907411 100644
--- a/systems/head/head_system.tscn
+++ b/systems/head/head_system.tscn
@@ -1,8 +1,75 @@
-[gd_scene load_steps=11 format=3 uid="uid://0ysqmqphq6mq"]
+[gd_scene load_steps=29 format=3 uid="uid://0ysqmqphq6mq"]
[ext_resource type="Script" uid="uid://dtkdrnsmlwm67" path="res://systems/head/HeadSystem.cs" id="1_8abgy"]
[ext_resource type="ArrayMesh" uid="uid://ckr26s4e3fj1m" path="res://assets/swords/resources/fp_sword23.tres" id="2_c5qep"]
[ext_resource type="AnimationNodeBlendTree" uid="uid://c26yvcyyyj811" path="res://systems/head/fp_blend_tree.tres" id="3_r0h40"]
+[ext_resource type="Script" uid="uid://dnlxsrumw6ygp" path="res://addons/shaker/src/Vector3/shaker_component3D.gd" id="3_ubhf8"]
+[ext_resource type="Script" uid="uid://0tu2q57qqu4s" path="res://addons/shaker/data/Vector3/BaseShakerType3D.gd" id="4_1ay6d"]
+[ext_resource type="Script" uid="uid://ptaespkh1sk2" path="res://addons/shaker/data/Vector3/ShakerTypeNoiseShake3D.gd" id="5_sdjj3"]
+[ext_resource type="Script" uid="uid://mlwdmecg12xd" path="res://addons/shaker/data/Vector3/ShakerPreset3D.gd" id="6_ll12k"]
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_ubhf8"]
+frequency = 0.15
+fractal_octaves = 1
+
+[sub_resource type="NoiseTexture3D" id="NoiseTexture3D_1ay6d"]
+noise = SubResource("FastNoiseLite_ubhf8")
+
+[sub_resource type="Resource" id="Resource_sdjj3"]
+script = ExtResource("5_sdjj3")
+noise_texture = SubResource("NoiseTexture3D_1ay6d")
+amplitude = Vector3(0.1, 0.1, 0.1)
+metadata/_custom_type_script = "uid://ptaespkh1sk2"
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_sdjj3"]
+frequency = 0.1
+
+[sub_resource type="NoiseTexture3D" id="NoiseTexture3D_ll12k"]
+noise = SubResource("FastNoiseLite_sdjj3")
+
+[sub_resource type="Resource" id="Resource_dvot2"]
+script = ExtResource("5_sdjj3")
+noise_texture = SubResource("NoiseTexture3D_ll12k")
+amplitude = Vector3(0.02, 0.02, 0.02)
+metadata/_custom_type_script = "uid://ptaespkh1sk2"
+
+[sub_resource type="Resource" id="Resource_60ouj"]
+script = ExtResource("6_ll12k")
+PositionShake = Array[ExtResource("4_1ay6d")]([SubResource("Resource_sdjj3")])
+RotationShake = Array[ExtResource("4_1ay6d")]([SubResource("Resource_dvot2")])
+metadata/_custom_type_script = "uid://mlwdmecg12xd"
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_1ay6d"]
+frequency = 0.02
+fractal_octaves = 1
+
+[sub_resource type="NoiseTexture3D" id="NoiseTexture3D_sdjj3"]
+noise = SubResource("FastNoiseLite_1ay6d")
+
+[sub_resource type="Resource" id="Resource_ll12k"]
+script = ExtResource("5_sdjj3")
+noise_texture = SubResource("NoiseTexture3D_sdjj3")
+amplitude = Vector3(0.2, 0, 0)
+metadata/_custom_type_script = "uid://ptaespkh1sk2"
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_ll12k"]
+frequency = 0.02
+fractal_octaves = 1
+
+[sub_resource type="NoiseTexture3D" id="NoiseTexture3D_se3kf"]
+noise = SubResource("FastNoiseLite_ll12k")
+
+[sub_resource type="Resource" id="Resource_0k6hv"]
+script = ExtResource("5_sdjj3")
+noise_texture = SubResource("NoiseTexture3D_se3kf")
+amplitude = Vector3(0.1, 0.2, 0.1)
+metadata/_custom_type_script = "uid://ptaespkh1sk2"
+
+[sub_resource type="Resource" id="Resource_se3kf"]
+script = ExtResource("6_ll12k")
+PositionShake = Array[ExtResource("4_1ay6d")]([SubResource("Resource_ll12k")])
+RotationShake = Array[ExtResource("4_1ay6d")]([SubResource("Resource_0k6hv")])
+metadata/_custom_type_script = "uid://mlwdmecg12xd"
[sub_resource type="Animation" id="Animation_urko7"]
length = 0.001
@@ -268,10 +335,29 @@ mesh = ExtResource("2_c5qep")
transform = Transform3D(1, -0.00011616431, 0, 0.00011616433, 0.9999998, 0, 0, 0, 0.9999998, 0, 0, 0)
[node name="Camera3D" type="Camera3D" parent="CameraSmooth"]
-transform = Transform3D(1, 0, 0, 0, 0.99999994, 0, 0, 0, 0.99999994, 0, 0, 0)
+transform = Transform3D(1, -7.508787e-09, -1.4551914e-08, 7.508788e-09, 0.99999994, -1.5046679e-08, 1.4551915e-08, 1.5046679e-08, 0.99999994, -0.04610423, -0.02960227, -0.053528003)
current = true
fov = 90.0
+[node name="OnGetHitShaker" type="Node3D" parent="CameraSmooth/Camera3D"]
+transform = Transform3D(1, 0, 0, -7.275958e-12, 0.99999976, 0, 0, 0, 0.99999976, 0, 1.8626451e-09, 3.7252903e-09)
+script = ExtResource("3_ubhf8")
+intensity = 1.2
+duration = 0.5
+fade_in = 0.06470415
+fade_out = 0.46651623
+shakerPreset = SubResource("Resource_60ouj")
+metadata/_custom_type_script = "uid://dnlxsrumw6ygp"
+
+[node name="OnHitShaker" type="Node3D" parent="CameraSmooth/Camera3D"]
+transform = Transform3D(1, 0, 0, -7.275958e-12, 0.99999976, 0, 0, 0, 0.99999976, 0, 1.8626451e-09, 3.7252903e-09)
+script = ExtResource("3_ubhf8")
+duration = 0.3
+fade_in = 0.041234624
+fade_out = 0.5547845
+shakerPreset = SubResource("Resource_se3kf")
+metadata/_custom_type_script = "uid://dnlxsrumw6ygp"
+
[node name="CameraAnchor" type="Marker3D" parent="."]
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
@@ -296,3 +382,6 @@ parameters/OnJumpEnd/request = 0
parameters/OnMantle/active = false
parameters/OnMantle/internal_active = false
parameters/OnMantle/request = 0
+
+[connection signal="GotHit" from="." to="CameraSmooth/Camera3D/OnGetHitShaker" method="play_shake"]
+[connection signal="HitTarget" from="." to="CameraSmooth/Camera3D/OnHitShaker" method="play_shake"]