diff --git a/README.md b/README.md
deleted file mode 100644
index 34bef07..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# randomgeon
diff --git a/addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png
new file mode 100644
index 0000000..3082789
Binary files /dev/null and b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png differ
diff --git a/addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png.import b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png.import
new file mode 100644
index 0000000..ad3a214
--- /dev/null
+++ b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://censw3w53gldn"
+path="res://.godot/imported/PhantomCameraBtnPrimaryDefault.png-fcf3696b583a82b1078609a5bfd648f5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png"
+dest_files=["res://.godot/imported/PhantomCameraBtnPrimaryDefault.png-fcf3696b583a82b1078609a5bfd648f5.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
diff --git a/addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png
new file mode 100644
index 0000000..1e0c31a
Binary files /dev/null and b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png differ
diff --git a/addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png.import b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png.import
new file mode 100644
index 0000000..dd6f1a4
--- /dev/null
+++ b/addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pvr8mbvl1onm"
+path="res://.godot/imported/PhantomCameraBtnPrimaryHover.png-3d2e4d225f6a86ce8a9c981ee7926a16.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png"
+dest_files=["res://.godot/imported/PhantomCameraBtnPrimaryHover.png-3d2e4d225f6a86ce8a9c981ee7926a16.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
diff --git a/addons/phantom_camera/examples/credits.txt b/addons/phantom_camera/examples/credits.txt
new file mode 100644
index 0000000..d5e949a
--- /dev/null
+++ b/addons/phantom_camera/examples/credits.txt
@@ -0,0 +1,7 @@
+#####################
+EXAMPLE ASSET CREDITS
+#####################
+
+# level_spritesheet
+https://opengameart.org/content/a-platformer-in-the-forest
+https://opengameart.org/users/buch
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_example_scene.tscn
new file mode 100644
index 0000000..ec0608d
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_example_scene.tscn
@@ -0,0 +1,248 @@
+[gd_scene load_steps=12 format=4 uid="uid://ohwjxojqcj63"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_foq54"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_kmt5y"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_1cmgi"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="4_4dx73"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="5_gcww2"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="6_i3g4f"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="7_j2i8l"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_ytjsp"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="9_o4c4h"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_yx3lp"]
+texture = ExtResource("1_foq54")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_nawqc"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_yx3lp")
+
+[node name="Root" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="Pillar" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAAAPr/AAAKAAIAAAAAAPv/AAAKAAMAAAAAAPz/AAAKAAMAAAAAAP3/AAAKAAMAAAAAAP7/AAAKAAMAAAAAAP//AAAKAAMAAAABAPr/AAALAAIAAAABAPv/AAALAAEAAAABAPz/AAALAAEAAAABAP3/AAALAAEAAAABAP7/AAALAAEAAAABAP//AAALAAEAAAACAPr/AAAMAAIAAAACAPv/AAAMAAMAAAACAPz/AAAMAAMAAAACAP3/AAAMAAMAAAACAP7/AAAMAAMAAAACAP//AAAMAAMAAAA=")
+tile_set = SubResource("TileSet_nawqc")
+collision_enabled = false
+navigation_enabled = false
+
+[node name="Terrain" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAAJAAYAAAAMAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAAHAAEAAAABAAUAAAALAAEAAAABAAYAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAACAAYAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAADAAYAAAALAAEAAAAEAAEAAAAHAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAEAAYAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAAHAAEAAAAFAAUAAAALAAEAAAAFAAYAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAGAAYAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAHAAYAAAAHAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAAHAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAIAAYAAAALAAEAAAAKAAEAAAAIAAUAAAALAAEAAAAIAAUAAAAMAAEAAAAIAAUAAAANAAEAAAAIAAUAAAAKAAIAAAAIAAYAAAALAAIAAAAIAAYAAAAMAAIAAAAIAAYAAAANAAIAAAAIAAYAAAAKAAMAAAALAAEAAAAKAAQAAAALAAEAAAALAAMAAAALAAEAAAALAAQAAAALAAEAAAAMAAMAAAALAAEAAAAMAAQAAAALAAEAAAANAAMAAAALAAEAAAANAAQAAAALAAEAAAAOAAEAAAAIAAUAAAAPAAEAAAAIAAUAAAAQAAEAAAAIAAUAAAAOAAIAAAAIAAYAAAAPAAIAAAAIAAYAAAAQAAIAAAAIAAYAAAAOAAMAAAALAAEAAAAPAAMAAAALAAEAAAAPAAQAAAALAAEAAAAQAAQAAAALAAEAAAAQAAMAAAALAAEAAAAOAAQAAAALAAEAAAARAAAAAAALAAQAAAARAAEAAAALAAUAAAARAAIAAAALAAUAAAARAAMAAAALAAUAAAARAAQAAAALAAUAAAASAAAAAAAMAAQAAAATAAAAAAAMAAQAAAAUAAAAAAAMAAQAAAAVAAAAAAAMAAQAAAAWAAAAAAAMAAQAAAAXAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAATAAEAAAAMAAUAAAATAAIAAAAJAAYAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAKAAYAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAARAAUAAAALAAUAAAARAAYAAAALAAUAAAASAAUAAAAJAAYAAAASAAYAAAAMAAUAAAATAAUAAAAMAAUAAAATAAYAAAAMAAUAAAAUAAUAAAAMAAUAAAAUAAYAAAAMAAUAAAAVAAUAAAAMAAUAAAAVAAYAAAAMAAUAAAAWAAUAAAAMAAUAAAAWAAYAAAAMAAUAAAAXAAUAAAAMAAUAAAAXAAYAAAAMAAUAAAAKAP7/AAALAAQAAAALAP7/AAAMAAQAAAAMAP7/AAAMAAQAAAAKAP//AAALAAYAAAALAP//AAAMAAYAAAAMAP//AAAMAAYAAAAQAP7/AAANAAQAAAAQAP//AAANAAYAAAANAP7/AAAMAAQAAAAOAP7/AAAMAAQAAAAPAP7/AAAMAAQAAAANAP//AAAMAAYAAAAOAP//AAAMAAYAAAAPAP//AAAMAAYAAAAMAP3/AAAOAAAAAAADAP//AAAOAAIAAAAEAP//AAAPAAIAAAAFAP//AAAQAAIAAAAGAP//AAAOAAIAAAAHAP//AAAPAAIAAAAIAP//AAAQAAIAAAD//wAAAAAKAAAAAAD//wEAAAAKAAEAAAD//wIAAAAKAAEAAAD//wMAAAAKAAEAAAD//wQAAAAKAAEAAAD//wUAAAAKAAEAAAD//wYAAAAKAAEAAAD//wcAAAAKAAEAAAD//wgAAAAKAAEAAAAAAAAAAAALAAAAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAAAAAYAAAALAAEAAAAAAAcAAAALAAEAAAAAAAgAAAALAAEAAAABAAgAAAALAAEAAAACAAgAAAALAAEAAAADAAgAAAALAAEAAAAEAAgAAAALAAEAAAAFAAgAAAALAAEAAAAGAAgAAAALAAEAAAAHAAgAAAALAAEAAAAIAAgAAAALAAEAAAAJAAgAAAAMAAEAAAAJAAcAAAAMAAEAAAAIAAcAAAALAAEAAAAHAAcAAAALAAEAAAAGAAcAAAAHAAEAAAAFAAcAAAALAAEAAAAEAAcAAAALAAEAAAADAAcAAAALAAEAAAACAAcAAAALAAEAAAABAAcAAAALAAEAAAD///v/AAANAAQAAAD///z/AAANAAUAAAD///3/AAANAAUAAAD///7/AAANAAUAAAD/////AAANAAYAAAD+//v/AAAMAAQAAAD9//v/AAAMAAQAAAD+//z/AAAJAAYAAAD9//z/AAAMAAUAAAD6////AAAMAAUAAAD7////AAAMAAUAAAD8////AAAMAAUAAAD9////AAAMAAUAAAD+////AAAMAAUAAAD+//7/AAAMAAUAAAD+//3/AAAMAAUAAAD9//3/AAAMAAUAAAD9//7/AAAKAAYAAAD+/wAAAAANAAUAAAD+/wEAAAANAAUAAAD+/wIAAAANAAUAAAD+/wMAAAANAAUAAAD+/wQAAAANAAUAAAD+/wUAAAANAAUAAAD+/wYAAAANAAUAAAD9/wAAAAAMAAUAAAD8/wAAAAAMAAUAAAD7/wAAAAAMAAUAAAD6/wAAAAAMAAUAAAD5/wAAAAALAAUAAAD6/wEAAAAMAAUAAAD6/wIAAAAMAAUAAAD6/wMAAAAMAAUAAAD7/wMAAAAMAAUAAAD7/wQAAAAMAAUAAAD8/wEAAAAMAAUAAAD9/wEAAAAMAAUAAAD9/wIAAAAMAAUAAAD9/wMAAAAMAAUAAAD9/wQAAAAMAAUAAAD9/wUAAAAMAAUAAAD9/wYAAAAMAAUAAAD8/wUAAAAMAAUAAAD7/wUAAAAMAAUAAAD8/wYAAAAMAAUAAAD8/wQAAAAKAAYAAAD8/wMAAAAMAAUAAAD8/wIAAAAMAAUAAAD7/wEAAAAMAAUAAAD7/wIAAAAJAAYAAAD7/wYAAAAMAAUAAAD6/wYAAAAMAAUAAAD6/wUAAAAMAAUAAAD6/wQAAAAMAAUAAAD5////AAALAAUAAAD5/wEAAAALAAUAAAD5/wIAAAALAAUAAAD5/wMAAAALAAUAAAD5/wQAAAALAAUAAAD5/wUAAAALAAUAAAD5/wYAAAALAAUAAAD8//r/AAALAAMAAAAOAP3/AAALAAMAAAALAP3/AAALAAMAAAASAP//AAALAAMAAAAUAP//AAALAAMAAAD6//r/AAAQAAUAAAD7//r/AAALAAMAAAANAP3/AAAOAAYAAAAWAP//AAAPAAYAAAD9//r/AAAPAAUAAAAXAP//AAAQAAUAAAD5//v/AAALAAQAAAD5//z/AAALAAUAAAD5//3/AAALAAUAAAD5//7/AAALAAUAAAD6//v/AAAMAAQAAAD6//z/AAAKAAYAAAD6//3/AAAMAAUAAAD6//7/AAAMAAUAAAD7//v/AAAMAAQAAAD7//z/AAAMAAUAAAD7//3/AAAMAAUAAAD7//7/AAAMAAUAAAD8//v/AAAMAAQAAAD8//z/AAAMAAUAAAD8//3/AAAMAAUAAAD8//7/AAAMAAUAAAARAAcAAAALAAUAAAARAAgAAAALAAYAAAAXAAcAAAAMAAUAAAAWAAcAAAAMAAUAAAAVAAcAAAAMAAUAAAAUAAcAAAAMAAUAAAATAAcAAAAMAAUAAAASAAcAAAAMAAUAAAASAAgAAAAMAAYAAAATAAgAAAAMAAYAAAAUAAgAAAAMAAYAAAAVAAgAAAAMAAYAAAAWAAgAAAAMAAYAAAAXAAgAAAAMAAYAAAAKAAUAAAALAAEAAAAKAAYAAAALAAEAAAAKAAcAAAALAAEAAAAKAAgAAAALAAEAAAALAAUAAAALAAEAAAALAAYAAAALAAEAAAALAAcAAAALAAEAAAALAAgAAAALAAEAAAAMAAUAAAALAAEAAAAMAAYAAAALAAEAAAAMAAcAAAALAAEAAAAMAAgAAAALAAEAAAANAAUAAAALAAEAAAANAAYAAAALAAEAAAANAAcAAAALAAEAAAANAAgAAAALAAEAAAAOAAUAAAALAAEAAAAOAAYAAAALAAEAAAAOAAcAAAALAAEAAAAOAAgAAAALAAEAAAAPAAUAAAALAAEAAAAPAAYAAAALAAEAAAAPAAcAAAALAAEAAAAPAAgAAAALAAEAAAAQAAUAAAALAAEAAAAQAAYAAAALAAEAAAAQAAcAAAALAAEAAAAQAAgAAAALAAEAAAAdAAAAAAANAAQAAAAdAAEAAAANAAUAAAAdAAIAAAANAAUAAAAdAAMAAAANAAUAAAAdAAQAAAANAAUAAAAdAAUAAAANAAUAAAAdAAYAAAANAAUAAAAdAAcAAAANAAUAAAAdAAgAAAANAAYAAAAZAP//AAAOAAQAAAAYAAAAAAAMAAQAAAAZAAAAAAAMAAQAAAAaAAAAAAAMAAQAAAAbAAAAAAAMAAQAAAAcAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAZAAEAAAAMAAUAAAAaAAEAAAAMAAUAAAAbAAEAAAAMAAUAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAJAAYAAAAbAAIAAAAMAAUAAAAaAAIAAAAMAAUAAAAZAAIAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAYAAYAAAAMAAUAAAAYAAcAAAAMAAUAAAAYAAgAAAAMAAYAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAZAAYAAAAMAAUAAAAZAAcAAAAMAAUAAAAZAAgAAAAMAAYAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAaAAYAAAAKAAYAAAAaAAcAAAAMAAUAAAAaAAgAAAAMAAYAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAbAAYAAAAMAAUAAAAbAAcAAAAMAAUAAAAbAAgAAAAMAAYAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAcAAYAAAAMAAUAAAAcAAcAAAAMAAUAAAAcAAgAAAAMAAYAAAAPAP3/AAAQAAYAAAAiAPr/AAAQAAYAAAAfAPr/AAAOAAYAAAAkAPr/AAAPAAYAAAAgAPr/AAAPAAUAAAAbAP//AAALAAMAAAAaAP//AAALAAMAAAAjAPr/AAALAAMAAAAhAPr/AAALAAMAAAATAP//AAALAAMAAAAVAP//AAALAAMAAAAeAPv/AAALAAQAAAAeAPz/AAALAAUAAAAeAP3/AAALAAUAAAAeAP7/AAALAAUAAAAeAP//AAALAAUAAAAmAP//AAANAAUAAAAmAP7/AAANAAUAAAAmAP3/AAANAAUAAAAmAPv/AAANAAQAAAAfAPv/AAAMAAQAAAAgAPv/AAAMAAQAAAAhAPv/AAAMAAQAAAAiAPv/AAAMAAQAAAAjAPv/AAAMAAQAAAAkAPv/AAAMAAQAAAAlAPv/AAAMAAQAAAAmAPz/AAANAAUAAAAlAP//AAAMAAUAAAAlAP7/AAAMAAUAAAAlAP3/AAAMAAUAAAAlAPz/AAAMAAUAAAAkAPz/AAAMAAUAAAAjAPz/AAAMAAUAAAAiAPz/AAAMAAUAAAAhAPz/AAAMAAUAAAAgAPz/AAAMAAUAAAAfAPz/AAAMAAUAAAAfAP3/AAAKAAYAAAAfAP7/AAAMAAUAAAAfAP//AAAMAAUAAAAkAP//AAAKAAYAAAAkAP7/AAAJAAYAAAAkAP3/AAAMAAUAAAAjAP3/AAAMAAUAAAAiAP3/AAAMAAUAAAAhAP3/AAAMAAUAAAAgAP3/AAAMAAUAAAAgAP7/AAAJAAYAAAAgAP//AAAMAAUAAAAjAP//AAAMAAUAAAAjAP7/AAAMAAUAAAAiAP7/AAAMAAUAAAAhAP7/AAAMAAUAAAAhAP//AAAMAAUAAAAiAP//AAAMAAUAAAAeAAgAAAALAAYAAAAeAAcAAAALAAUAAAAeAAYAAAALAAUAAAAeAAUAAAALAAUAAAAeAAQAAAALAAUAAAAeAAMAAAALAAUAAAAeAAIAAAALAAUAAAAeAAEAAAALAAUAAAAeAAAAAAALAAUAAAAfAAgAAAAMAAYAAAAgAAgAAAAMAAYAAAAhAAgAAAAMAAYAAAAiAAgAAAAMAAYAAAAjAAgAAAAMAAYAAAAkAAgAAAAMAAYAAAAlAAgAAAAMAAYAAAAmAAgAAAANAAYAAAAmAAAAAAANAAUAAAAmAAEAAAANAAUAAAAmAAIAAAANAAUAAAAmAAMAAAANAAUAAAAmAAQAAAANAAUAAAAmAAUAAAANAAUAAAAmAAYAAAANAAUAAAAmAAcAAAANAAUAAAAfAAAAAAAMAAUAAAAfAAEAAAAMAAUAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAfAAYAAAAKAAYAAAAfAAcAAAAMAAUAAAAgAAAAAAAMAAUAAAAgAAEAAAAMAAUAAAAgAAIAAAAMAAUAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAgAAYAAAAMAAUAAAAgAAcAAAAMAAUAAAAhAAAAAAAMAAUAAAAhAAEAAAAMAAUAAAAhAAIAAAAKAAYAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAhAAYAAAAMAAUAAAAhAAcAAAAMAAUAAAAiAAAAAAAMAAUAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAKAAYAAAAiAAUAAAAKAAYAAAAiAAYAAAAMAAUAAAAiAAcAAAAMAAUAAAAjAAAAAAAMAAUAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAjAAYAAAAMAAUAAAAjAAcAAAAMAAUAAAAkAAAAAAAKAAYAAAAkAAEAAAAMAAUAAAAkAAIAAAAMAAUAAAAkAAMAAAAMAAUAAAAkAAQAAAAMAAUAAAAkAAUAAAAMAAUAAAAkAAYAAAAMAAUAAAAkAAcAAAAKAAYAAAAlAAAAAAAMAAUAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAlAAYAAAAMAAUAAAAlAAcAAAAMAAUAAAD6/wcAAAAMAAUAAAD7/wcAAAAMAAUAAAD8/wcAAAAMAAUAAAD9/wcAAAAMAAUAAAD5/wcAAAALAAUAAAD+/wcAAAANAAUAAAD5/wgAAAALAAYAAAD6/wgAAAAMAAYAAAD7/wgAAAAMAAYAAAD8/wgAAAAMAAYAAAD9/wgAAAAMAAYAAAD+/wgAAAANAAYAAAA=")
+tile_set = SubResource("TileSet_nawqc")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_kmt5y")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_1cmgi")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Controls" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("4_4dx73")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(227, -28)
+zoom = Vector2(1.5, 1.5)
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("5_gcww2")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+process_priority = -1
+top_level = true
+position = Vector2(227, -28)
+script = ExtResource("6_i3g4f")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D")
+zoom = Vector2(1.5, 1.5)
+frame_preview = false
+tween_resource = ExtResource("7_j2i8l")
+tween_on_load = false
+follow_damping = true
+draw_limits = true
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("8_ytjsp")]
+position = Vector2(227, -28)
+script = ExtResource("9_o4c4h")
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_framed_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_framed_example_scene.tscn
new file mode 100644
index 0000000..9b9cc88
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_framed_example_scene.tscn
@@ -0,0 +1,251 @@
+[gd_scene load_steps=12 format=4 uid="uid://dg1tuoxd3b4tw"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_nf5bo"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_5oggv"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_aku7q"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_j3ux0"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="5_uwr6r"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="6_4l0c3"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="7_briql"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_i4m1d"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="9_m3lnd"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_dpuou"]
+texture = ExtResource("1_nf5bo")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kxirl"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_dpuou")
+
+[node name="Root" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -345.0
+offset_top = -143.0
+offset_right = 947.0
+offset_bottom = 578.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="Pillar" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAAAPr/AAAKAAIAAAAAAPv/AAAKAAMAAAAAAPz/AAAKAAMAAAAAAP3/AAAKAAMAAAAAAP7/AAAKAAMAAAAAAP//AAAKAAMAAAABAPr/AAALAAIAAAABAPv/AAALAAEAAAABAPz/AAALAAEAAAABAP3/AAALAAEAAAABAP7/AAALAAEAAAABAP//AAALAAEAAAACAPr/AAAMAAIAAAACAPv/AAAMAAMAAAACAPz/AAAMAAMAAAACAP3/AAAMAAMAAAACAP7/AAAMAAMAAAACAP//AAAMAAMAAAA=")
+tile_set = SubResource("TileSet_kxirl")
+collision_enabled = false
+navigation_enabled = false
+
+[node name="Terrain" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAAJAAYAAAAMAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAAHAAEAAAABAAUAAAALAAEAAAABAAYAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAACAAYAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAADAAYAAAALAAEAAAAEAAEAAAAHAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAEAAYAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAAHAAEAAAAFAAUAAAALAAEAAAAFAAYAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAGAAYAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAHAAYAAAAHAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAAHAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAIAAYAAAALAAEAAAAKAAEAAAAIAAUAAAALAAEAAAAIAAUAAAAMAAEAAAAIAAUAAAANAAEAAAAIAAUAAAAKAAIAAAAIAAYAAAALAAIAAAAIAAYAAAAMAAIAAAAIAAYAAAANAAIAAAAIAAYAAAAKAAMAAAALAAEAAAAKAAQAAAALAAEAAAALAAMAAAALAAEAAAALAAQAAAALAAEAAAAMAAMAAAALAAEAAAAMAAQAAAALAAEAAAANAAMAAAALAAEAAAANAAQAAAALAAEAAAAOAAEAAAAIAAUAAAAPAAEAAAAIAAUAAAAQAAEAAAAIAAUAAAAOAAIAAAAIAAYAAAAPAAIAAAAIAAYAAAAQAAIAAAAIAAYAAAAOAAMAAAALAAEAAAAPAAMAAAALAAEAAAAPAAQAAAALAAEAAAAQAAQAAAALAAEAAAAQAAMAAAALAAEAAAAOAAQAAAALAAEAAAARAAAAAAALAAQAAAARAAEAAAALAAUAAAARAAIAAAALAAUAAAARAAMAAAALAAUAAAARAAQAAAALAAUAAAASAAAAAAAMAAQAAAATAAAAAAAMAAQAAAAUAAAAAAAMAAQAAAAVAAAAAAAMAAQAAAAWAAAAAAAMAAQAAAAXAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAATAAEAAAAMAAUAAAATAAIAAAAJAAYAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAKAAYAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAARAAUAAAALAAUAAAARAAYAAAALAAUAAAASAAUAAAAJAAYAAAASAAYAAAAMAAUAAAATAAUAAAAMAAUAAAATAAYAAAAMAAUAAAAUAAUAAAAMAAUAAAAUAAYAAAAMAAUAAAAVAAUAAAAMAAUAAAAVAAYAAAAMAAUAAAAWAAUAAAAMAAUAAAAWAAYAAAAMAAUAAAAXAAUAAAAMAAUAAAAXAAYAAAAMAAUAAAAKAP7/AAALAAQAAAALAP7/AAAMAAQAAAAMAP7/AAAMAAQAAAAKAP//AAALAAYAAAALAP//AAAMAAYAAAAMAP//AAAMAAYAAAAQAP7/AAANAAQAAAAQAP//AAANAAYAAAANAP7/AAAMAAQAAAAOAP7/AAAMAAQAAAAPAP7/AAAMAAQAAAANAP//AAAMAAYAAAAOAP//AAAMAAYAAAAPAP//AAAMAAYAAAAMAP3/AAAOAAAAAAADAP//AAAOAAIAAAAEAP//AAAPAAIAAAAFAP//AAAQAAIAAAAGAP//AAAOAAIAAAAHAP//AAAPAAIAAAAIAP//AAAQAAIAAAD//wAAAAAKAAAAAAD//wEAAAAKAAEAAAD//wIAAAAKAAEAAAD//wMAAAAKAAEAAAD//wQAAAAKAAEAAAD//wUAAAAKAAEAAAD//wYAAAAKAAEAAAD//wcAAAAKAAEAAAD//wgAAAAKAAEAAAAAAAAAAAALAAAAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAAAAAYAAAALAAEAAAAAAAcAAAALAAEAAAAAAAgAAAALAAEAAAABAAgAAAALAAEAAAACAAgAAAALAAEAAAADAAgAAAALAAEAAAAEAAgAAAALAAEAAAAFAAgAAAALAAEAAAAGAAgAAAALAAEAAAAHAAgAAAALAAEAAAAIAAgAAAALAAEAAAAJAAgAAAAMAAEAAAAJAAcAAAAMAAEAAAAIAAcAAAALAAEAAAAHAAcAAAALAAEAAAAGAAcAAAAHAAEAAAAFAAcAAAALAAEAAAAEAAcAAAALAAEAAAADAAcAAAALAAEAAAACAAcAAAALAAEAAAABAAcAAAALAAEAAAD///v/AAANAAQAAAD///z/AAANAAUAAAD///3/AAANAAUAAAD///7/AAANAAUAAAD/////AAANAAYAAAD+//v/AAAMAAQAAAD9//v/AAAMAAQAAAD+//z/AAAJAAYAAAD9//z/AAAMAAUAAAD6////AAAMAAUAAAD7////AAAMAAUAAAD8////AAAMAAUAAAD9////AAAMAAUAAAD+////AAAMAAUAAAD+//7/AAAMAAUAAAD+//3/AAAMAAUAAAD9//3/AAAMAAUAAAD9//7/AAAKAAYAAAD+/wAAAAANAAUAAAD+/wEAAAANAAUAAAD+/wIAAAANAAUAAAD+/wMAAAANAAUAAAD+/wQAAAANAAUAAAD+/wUAAAANAAUAAAD+/wYAAAANAAUAAAD9/wAAAAAMAAUAAAD8/wAAAAAMAAUAAAD7/wAAAAAMAAUAAAD6/wAAAAAMAAUAAAD5/wAAAAALAAUAAAD6/wEAAAAMAAUAAAD6/wIAAAAMAAUAAAD6/wMAAAAMAAUAAAD7/wMAAAAMAAUAAAD7/wQAAAAMAAUAAAD8/wEAAAAMAAUAAAD9/wEAAAAMAAUAAAD9/wIAAAAMAAUAAAD9/wMAAAAMAAUAAAD9/wQAAAAMAAUAAAD9/wUAAAAMAAUAAAD9/wYAAAAMAAUAAAD8/wUAAAAMAAUAAAD7/wUAAAAMAAUAAAD8/wYAAAAMAAUAAAD8/wQAAAAKAAYAAAD8/wMAAAAMAAUAAAD8/wIAAAAMAAUAAAD7/wEAAAAMAAUAAAD7/wIAAAAJAAYAAAD7/wYAAAAMAAUAAAD6/wYAAAAMAAUAAAD6/wUAAAAMAAUAAAD6/wQAAAAMAAUAAAD5////AAALAAUAAAD5/wEAAAALAAUAAAD5/wIAAAALAAUAAAD5/wMAAAALAAUAAAD5/wQAAAALAAUAAAD5/wUAAAALAAUAAAD5/wYAAAALAAUAAAD8//r/AAALAAMAAAAOAP3/AAALAAMAAAALAP3/AAALAAMAAAASAP//AAALAAMAAAAUAP//AAALAAMAAAD6//r/AAAQAAUAAAD7//r/AAALAAMAAAANAP3/AAAOAAYAAAAWAP//AAAPAAYAAAD9//r/AAAPAAUAAAAXAP//AAAQAAUAAAD5//v/AAALAAQAAAD5//z/AAALAAUAAAD5//3/AAALAAUAAAD5//7/AAALAAUAAAD6//v/AAAMAAQAAAD6//z/AAAKAAYAAAD6//3/AAAMAAUAAAD6//7/AAAMAAUAAAD7//v/AAAMAAQAAAD7//z/AAAMAAUAAAD7//3/AAAMAAUAAAD7//7/AAAMAAUAAAD8//v/AAAMAAQAAAD8//z/AAAMAAUAAAD8//3/AAAMAAUAAAD8//7/AAAMAAUAAAARAAcAAAALAAUAAAARAAgAAAALAAYAAAAXAAcAAAAMAAUAAAAWAAcAAAAMAAUAAAAVAAcAAAAMAAUAAAAUAAcAAAAMAAUAAAATAAcAAAAMAAUAAAASAAcAAAAMAAUAAAASAAgAAAAMAAYAAAATAAgAAAAMAAYAAAAUAAgAAAAMAAYAAAAVAAgAAAAMAAYAAAAWAAgAAAAMAAYAAAAXAAgAAAAMAAYAAAAKAAUAAAALAAEAAAAKAAYAAAALAAEAAAAKAAcAAAALAAEAAAAKAAgAAAALAAEAAAALAAUAAAALAAEAAAALAAYAAAALAAEAAAALAAcAAAALAAEAAAALAAgAAAALAAEAAAAMAAUAAAALAAEAAAAMAAYAAAALAAEAAAAMAAcAAAALAAEAAAAMAAgAAAALAAEAAAANAAUAAAALAAEAAAANAAYAAAALAAEAAAANAAcAAAALAAEAAAANAAgAAAALAAEAAAAOAAUAAAALAAEAAAAOAAYAAAALAAEAAAAOAAcAAAALAAEAAAAOAAgAAAALAAEAAAAPAAUAAAALAAEAAAAPAAYAAAALAAEAAAAPAAcAAAALAAEAAAAPAAgAAAALAAEAAAAQAAUAAAALAAEAAAAQAAYAAAALAAEAAAAQAAcAAAALAAEAAAAQAAgAAAALAAEAAAAdAAAAAAANAAQAAAAdAAEAAAANAAUAAAAdAAIAAAANAAUAAAAdAAMAAAANAAUAAAAdAAQAAAANAAUAAAAdAAUAAAANAAUAAAAdAAYAAAANAAUAAAAdAAcAAAANAAUAAAAdAAgAAAANAAYAAAAZAP//AAAOAAQAAAAYAAAAAAAMAAQAAAAZAAAAAAAMAAQAAAAaAAAAAAAMAAQAAAAbAAAAAAAMAAQAAAAcAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAZAAEAAAAMAAUAAAAaAAEAAAAMAAUAAAAbAAEAAAAMAAUAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAJAAYAAAAbAAIAAAAMAAUAAAAaAAIAAAAMAAUAAAAZAAIAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAYAAYAAAAMAAUAAAAYAAcAAAAMAAUAAAAYAAgAAAAMAAYAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAZAAYAAAAMAAUAAAAZAAcAAAAMAAUAAAAZAAgAAAAMAAYAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAaAAYAAAAKAAYAAAAaAAcAAAAMAAUAAAAaAAgAAAAMAAYAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAbAAYAAAAMAAUAAAAbAAcAAAAMAAUAAAAbAAgAAAAMAAYAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAcAAYAAAAMAAUAAAAcAAcAAAAMAAUAAAAcAAgAAAAMAAYAAAAPAP3/AAAQAAYAAAAiAPr/AAAQAAYAAAAfAPr/AAAOAAYAAAAkAPr/AAAPAAYAAAAgAPr/AAAPAAUAAAAbAP//AAALAAMAAAAaAP//AAALAAMAAAAjAPr/AAALAAMAAAAhAPr/AAALAAMAAAATAP//AAALAAMAAAAVAP//AAALAAMAAAAeAPv/AAALAAQAAAAeAPz/AAALAAUAAAAeAP3/AAALAAUAAAAeAP7/AAALAAUAAAAeAP//AAALAAUAAAAmAP//AAANAAUAAAAmAP7/AAANAAUAAAAmAP3/AAANAAUAAAAmAPv/AAANAAQAAAAfAPv/AAAMAAQAAAAgAPv/AAAMAAQAAAAhAPv/AAAMAAQAAAAiAPv/AAAMAAQAAAAjAPv/AAAMAAQAAAAkAPv/AAAMAAQAAAAlAPv/AAAMAAQAAAAmAPz/AAANAAUAAAAlAP//AAAMAAUAAAAlAP7/AAAMAAUAAAAlAP3/AAAMAAUAAAAlAPz/AAAMAAUAAAAkAPz/AAAMAAUAAAAjAPz/AAAMAAUAAAAiAPz/AAAMAAUAAAAhAPz/AAAMAAUAAAAgAPz/AAAMAAUAAAAfAPz/AAAMAAUAAAAfAP3/AAAKAAYAAAAfAP7/AAAMAAUAAAAfAP//AAAMAAUAAAAkAP//AAAKAAYAAAAkAP7/AAAJAAYAAAAkAP3/AAAMAAUAAAAjAP3/AAAMAAUAAAAiAP3/AAAMAAUAAAAhAP3/AAAMAAUAAAAgAP3/AAAMAAUAAAAgAP7/AAAJAAYAAAAgAP//AAAMAAUAAAAjAP//AAAMAAUAAAAjAP7/AAAMAAUAAAAiAP7/AAAMAAUAAAAhAP7/AAAMAAUAAAAhAP//AAAMAAUAAAAiAP//AAAMAAUAAAAeAAgAAAALAAYAAAAeAAcAAAALAAUAAAAeAAYAAAALAAUAAAAeAAUAAAALAAUAAAAeAAQAAAALAAUAAAAeAAMAAAALAAUAAAAeAAIAAAALAAUAAAAeAAEAAAALAAUAAAAeAAAAAAALAAUAAAAfAAgAAAAMAAYAAAAgAAgAAAAMAAYAAAAhAAgAAAAMAAYAAAAiAAgAAAAMAAYAAAAjAAgAAAAMAAYAAAAkAAgAAAAMAAYAAAAlAAgAAAAMAAYAAAAmAAgAAAANAAYAAAAmAAAAAAANAAUAAAAmAAEAAAANAAUAAAAmAAIAAAANAAUAAAAmAAMAAAANAAUAAAAmAAQAAAANAAUAAAAmAAUAAAANAAUAAAAmAAYAAAANAAUAAAAmAAcAAAANAAUAAAAfAAAAAAAMAAUAAAAfAAEAAAAMAAUAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAfAAYAAAAKAAYAAAAfAAcAAAAMAAUAAAAgAAAAAAAMAAUAAAAgAAEAAAAMAAUAAAAgAAIAAAAMAAUAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAgAAYAAAAMAAUAAAAgAAcAAAAMAAUAAAAhAAAAAAAMAAUAAAAhAAEAAAAMAAUAAAAhAAIAAAAKAAYAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAhAAYAAAAMAAUAAAAhAAcAAAAMAAUAAAAiAAAAAAAMAAUAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAKAAYAAAAiAAUAAAAKAAYAAAAiAAYAAAAMAAUAAAAiAAcAAAAMAAUAAAAjAAAAAAAMAAUAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAjAAYAAAAMAAUAAAAjAAcAAAAMAAUAAAAkAAAAAAAKAAYAAAAkAAEAAAAMAAUAAAAkAAIAAAAMAAUAAAAkAAMAAAAMAAUAAAAkAAQAAAAMAAUAAAAkAAUAAAAMAAUAAAAkAAYAAAAMAAUAAAAkAAcAAAAKAAYAAAAlAAAAAAAMAAUAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAlAAYAAAAMAAUAAAAlAAcAAAAMAAUAAAD6/wcAAAAMAAUAAAD7/wcAAAAMAAUAAAD8/wcAAAAMAAUAAAD9/wcAAAAMAAUAAAD5/wcAAAALAAUAAAD+/wcAAAANAAUAAAD5/wgAAAALAAYAAAD6/wgAAAAMAAYAAAD7/wgAAAAMAAYAAAD8/wgAAAAMAAYAAAD9/wgAAAAMAAYAAAD+/wgAAAANAAYAAAA=")
+tile_set = SubResource("TileSet_kxirl")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_5oggv")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_aku7q")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(215, -73)
+zoom = Vector2(2, 2)
+process_callback = 0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_j3ux0")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(215, -73)
+script = ExtResource("5_uwr6r")
+priority = 5
+follow_mode = 5
+follow_target = NodePath("../CharacterBody2D")
+zoom = Vector2(2, 2)
+tween_resource = ExtResource("6_4l0c3")
+tween_on_load = false
+follow_offset = Vector2(0, -45)
+follow_damping = true
+dead_zone_width = 0.25
+dead_zone_height = 0.8
+show_viewfinder_in_play = true
+draw_limits = true
+
+[node name="Label" type="Label" parent="Player"]
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("7_briql")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("8_i4m1d")]
+position = Vector2(215, -28)
+script = ExtResource("9_m3lnd")
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_group_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_group_example_scene.tscn
new file mode 100644
index 0000000..dd7bcbc
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_group_example_scene.tscn
@@ -0,0 +1,261 @@
+[gd_scene load_steps=14 format=4 uid="uid://bio6mao7gtru2"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_8rflf"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_tafwr"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_37c7w"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_dxiro"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="5_gaaip"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="6_ojk83"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="7_awenl"]
+[ext_resource type="Texture2D" uid="uid://cwep0on2tthn7" path="res://addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png" id="8_ys0m4"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="9_witv0"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="10_aivri"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_3qxnm"]
+texture = ExtResource("1_8rflf")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_14yng"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_3qxnm")
+
+[sub_resource type="Resource" id="Resource_spy00"]
+script = ExtResource("7_awenl")
+duration = 0.3
+transition = 4
+ease = 2
+
+[node name="Root" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="Pillar" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAAAPr/AAAKAAIAAAAAAPv/AAAKAAMAAAAAAPz/AAAKAAMAAAAAAP3/AAAKAAMAAAAAAP7/AAAKAAMAAAAAAP//AAAKAAMAAAABAPr/AAALAAIAAAABAPv/AAALAAEAAAABAPz/AAALAAEAAAABAP3/AAALAAEAAAABAP7/AAALAAEAAAABAP//AAALAAEAAAACAPr/AAAMAAIAAAACAPv/AAAMAAMAAAACAPz/AAAMAAMAAAACAP3/AAAMAAMAAAACAP7/AAAMAAMAAAACAP//AAAMAAMAAAA=")
+tile_set = SubResource("TileSet_14yng")
+collision_enabled = false
+navigation_enabled = false
+
+[node name="Terrain" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAAJAAYAAAAMAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAAHAAEAAAABAAUAAAALAAEAAAABAAYAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAACAAYAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAADAAYAAAALAAEAAAAEAAEAAAAHAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAEAAYAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAAHAAEAAAAFAAUAAAALAAEAAAAFAAYAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAGAAYAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAHAAYAAAAHAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAAHAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAIAAYAAAALAAEAAAAKAAEAAAAIAAUAAAALAAEAAAAIAAUAAAAMAAEAAAAIAAUAAAANAAEAAAAIAAUAAAAKAAIAAAAIAAYAAAALAAIAAAAIAAYAAAAMAAIAAAAIAAYAAAANAAIAAAAIAAYAAAAKAAMAAAALAAEAAAAKAAQAAAALAAEAAAALAAMAAAALAAEAAAALAAQAAAALAAEAAAAMAAMAAAALAAEAAAAMAAQAAAALAAEAAAANAAMAAAALAAEAAAANAAQAAAALAAEAAAAOAAEAAAAIAAUAAAAPAAEAAAAIAAUAAAAQAAEAAAAIAAUAAAAOAAIAAAAIAAYAAAAPAAIAAAAIAAYAAAAQAAIAAAAIAAYAAAAOAAMAAAALAAEAAAAPAAMAAAALAAEAAAAPAAQAAAALAAEAAAAQAAQAAAALAAEAAAAQAAMAAAALAAEAAAAOAAQAAAALAAEAAAARAAAAAAALAAQAAAARAAEAAAALAAUAAAARAAIAAAALAAUAAAARAAMAAAALAAUAAAARAAQAAAALAAUAAAASAAAAAAAMAAQAAAATAAAAAAAMAAQAAAAUAAAAAAAMAAQAAAAVAAAAAAAMAAQAAAAWAAAAAAAMAAQAAAAXAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAATAAEAAAAMAAUAAAATAAIAAAAJAAYAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAKAAYAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAARAAUAAAALAAUAAAARAAYAAAALAAUAAAASAAUAAAAJAAYAAAASAAYAAAAMAAUAAAATAAUAAAAMAAUAAAATAAYAAAAMAAUAAAAUAAUAAAAMAAUAAAAUAAYAAAAMAAUAAAAVAAUAAAAMAAUAAAAVAAYAAAAMAAUAAAAWAAUAAAAMAAUAAAAWAAYAAAAMAAUAAAAXAAUAAAAMAAUAAAAXAAYAAAAMAAUAAAAKAP7/AAALAAQAAAALAP7/AAAMAAQAAAAMAP7/AAAMAAQAAAAKAP//AAALAAYAAAALAP//AAAMAAYAAAAMAP//AAAMAAYAAAAQAP7/AAANAAQAAAAQAP//AAANAAYAAAANAP7/AAAMAAQAAAAOAP7/AAAMAAQAAAAPAP7/AAAMAAQAAAANAP//AAAMAAYAAAAOAP//AAAMAAYAAAAPAP//AAAMAAYAAAAMAP3/AAAOAAAAAAADAP//AAAOAAIAAAAEAP//AAAPAAIAAAAFAP//AAAQAAIAAAAGAP//AAAOAAIAAAAHAP//AAAPAAIAAAAIAP//AAAQAAIAAAD//wAAAAAKAAAAAAD//wEAAAAKAAEAAAD//wIAAAAKAAEAAAD//wMAAAAKAAEAAAD//wQAAAAKAAEAAAD//wUAAAAKAAEAAAD//wYAAAAKAAEAAAD//wcAAAAKAAEAAAD//wgAAAAKAAEAAAAAAAAAAAALAAAAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAAAAAYAAAALAAEAAAAAAAcAAAALAAEAAAAAAAgAAAALAAEAAAABAAgAAAALAAEAAAACAAgAAAALAAEAAAADAAgAAAALAAEAAAAEAAgAAAALAAEAAAAFAAgAAAALAAEAAAAGAAgAAAALAAEAAAAHAAgAAAALAAEAAAAIAAgAAAALAAEAAAAJAAgAAAAMAAEAAAAJAAcAAAAMAAEAAAAIAAcAAAALAAEAAAAHAAcAAAALAAEAAAAGAAcAAAAHAAEAAAAFAAcAAAALAAEAAAAEAAcAAAALAAEAAAADAAcAAAALAAEAAAACAAcAAAALAAEAAAABAAcAAAALAAEAAAD///v/AAANAAQAAAD///z/AAANAAUAAAD///3/AAANAAUAAAD///7/AAANAAUAAAD/////AAANAAYAAAD+//v/AAAMAAQAAAD9//v/AAAMAAQAAAD+//z/AAAJAAYAAAD9//z/AAAMAAUAAAD6////AAAMAAUAAAD7////AAAMAAUAAAD8////AAAMAAUAAAD9////AAAMAAUAAAD+////AAAMAAUAAAD+//7/AAAMAAUAAAD+//3/AAAMAAUAAAD9//3/AAAMAAUAAAD9//7/AAAKAAYAAAD+/wAAAAANAAUAAAD+/wEAAAANAAUAAAD+/wIAAAANAAUAAAD+/wMAAAANAAUAAAD+/wQAAAANAAUAAAD+/wUAAAANAAUAAAD+/wYAAAANAAUAAAD9/wAAAAAMAAUAAAD8/wAAAAAMAAUAAAD7/wAAAAAMAAUAAAD6/wAAAAAMAAUAAAD5/wAAAAALAAUAAAD6/wEAAAAMAAUAAAD6/wIAAAAMAAUAAAD6/wMAAAAMAAUAAAD7/wMAAAAMAAUAAAD7/wQAAAAMAAUAAAD8/wEAAAAMAAUAAAD9/wEAAAAMAAUAAAD9/wIAAAAMAAUAAAD9/wMAAAAMAAUAAAD9/wQAAAAMAAUAAAD9/wUAAAAMAAUAAAD9/wYAAAAMAAUAAAD8/wUAAAAMAAUAAAD7/wUAAAAMAAUAAAD8/wYAAAAMAAUAAAD8/wQAAAAKAAYAAAD8/wMAAAAMAAUAAAD8/wIAAAAMAAUAAAD7/wEAAAAMAAUAAAD7/wIAAAAJAAYAAAD7/wYAAAAMAAUAAAD6/wYAAAAMAAUAAAD6/wUAAAAMAAUAAAD6/wQAAAAMAAUAAAD5////AAALAAUAAAD5/wEAAAALAAUAAAD5/wIAAAALAAUAAAD5/wMAAAALAAUAAAD5/wQAAAALAAUAAAD5/wUAAAALAAUAAAD5/wYAAAALAAUAAAD8//r/AAALAAMAAAAOAP3/AAALAAMAAAALAP3/AAALAAMAAAASAP//AAALAAMAAAAUAP//AAALAAMAAAD6//r/AAAQAAUAAAD7//r/AAALAAMAAAANAP3/AAAOAAYAAAAWAP//AAAPAAYAAAD9//r/AAAPAAUAAAAXAP//AAAQAAUAAAD5//v/AAALAAQAAAD5//z/AAALAAUAAAD5//3/AAALAAUAAAD5//7/AAALAAUAAAD6//v/AAAMAAQAAAD6//z/AAAKAAYAAAD6//3/AAAMAAUAAAD6//7/AAAMAAUAAAD7//v/AAAMAAQAAAD7//z/AAAMAAUAAAD7//3/AAAMAAUAAAD7//7/AAAMAAUAAAD8//v/AAAMAAQAAAD8//z/AAAMAAUAAAD8//3/AAAMAAUAAAD8//7/AAAMAAUAAAARAAcAAAALAAUAAAARAAgAAAALAAYAAAAXAAcAAAAMAAUAAAAWAAcAAAAMAAUAAAAVAAcAAAAMAAUAAAAUAAcAAAAMAAUAAAATAAcAAAAMAAUAAAASAAcAAAAMAAUAAAASAAgAAAAMAAYAAAATAAgAAAAMAAYAAAAUAAgAAAAMAAYAAAAVAAgAAAAMAAYAAAAWAAgAAAAMAAYAAAAXAAgAAAAMAAYAAAAKAAUAAAALAAEAAAAKAAYAAAALAAEAAAAKAAcAAAALAAEAAAAKAAgAAAALAAEAAAALAAUAAAALAAEAAAALAAYAAAALAAEAAAALAAcAAAALAAEAAAALAAgAAAALAAEAAAAMAAUAAAALAAEAAAAMAAYAAAALAAEAAAAMAAcAAAALAAEAAAAMAAgAAAALAAEAAAANAAUAAAALAAEAAAANAAYAAAALAAEAAAANAAcAAAALAAEAAAANAAgAAAALAAEAAAAOAAUAAAALAAEAAAAOAAYAAAALAAEAAAAOAAcAAAALAAEAAAAOAAgAAAALAAEAAAAPAAUAAAALAAEAAAAPAAYAAAALAAEAAAAPAAcAAAALAAEAAAAPAAgAAAALAAEAAAAQAAUAAAALAAEAAAAQAAYAAAALAAEAAAAQAAcAAAALAAEAAAAQAAgAAAALAAEAAAAdAAAAAAANAAQAAAAdAAEAAAANAAUAAAAdAAIAAAANAAUAAAAdAAMAAAANAAUAAAAdAAQAAAANAAUAAAAdAAUAAAANAAUAAAAdAAYAAAANAAUAAAAdAAcAAAANAAUAAAAdAAgAAAANAAYAAAAZAP//AAAOAAQAAAAYAAAAAAAMAAQAAAAZAAAAAAAMAAQAAAAaAAAAAAAMAAQAAAAbAAAAAAAMAAQAAAAcAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAZAAEAAAAMAAUAAAAaAAEAAAAMAAUAAAAbAAEAAAAMAAUAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAJAAYAAAAbAAIAAAAMAAUAAAAaAAIAAAAMAAUAAAAZAAIAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAYAAYAAAAMAAUAAAAYAAcAAAAMAAUAAAAYAAgAAAAMAAYAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAZAAYAAAAMAAUAAAAZAAcAAAAMAAUAAAAZAAgAAAAMAAYAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAaAAYAAAAKAAYAAAAaAAcAAAAMAAUAAAAaAAgAAAAMAAYAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAbAAYAAAAMAAUAAAAbAAcAAAAMAAUAAAAbAAgAAAAMAAYAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAcAAYAAAAMAAUAAAAcAAcAAAAMAAUAAAAcAAgAAAAMAAYAAAAPAP3/AAAQAAYAAAAiAPr/AAAQAAYAAAAfAPr/AAAOAAYAAAAkAPr/AAAPAAYAAAAgAPr/AAAPAAUAAAAbAP//AAALAAMAAAAaAP//AAALAAMAAAAjAPr/AAALAAMAAAAhAPr/AAALAAMAAAATAP//AAALAAMAAAAVAP//AAALAAMAAAAeAPv/AAALAAQAAAAeAPz/AAALAAUAAAAeAP3/AAALAAUAAAAeAP7/AAALAAUAAAAeAP//AAALAAUAAAAmAP//AAANAAUAAAAmAP7/AAANAAUAAAAmAP3/AAANAAUAAAAmAPv/AAANAAQAAAAfAPv/AAAMAAQAAAAgAPv/AAAMAAQAAAAhAPv/AAAMAAQAAAAiAPv/AAAMAAQAAAAjAPv/AAAMAAQAAAAkAPv/AAAMAAQAAAAlAPv/AAAMAAQAAAAmAPz/AAANAAUAAAAlAP//AAAMAAUAAAAlAP7/AAAMAAUAAAAlAP3/AAAMAAUAAAAlAPz/AAAMAAUAAAAkAPz/AAAMAAUAAAAjAPz/AAAMAAUAAAAiAPz/AAAMAAUAAAAhAPz/AAAMAAUAAAAgAPz/AAAMAAUAAAAfAPz/AAAMAAUAAAAfAP3/AAAKAAYAAAAfAP7/AAAMAAUAAAAfAP//AAAMAAUAAAAkAP//AAAKAAYAAAAkAP7/AAAJAAYAAAAkAP3/AAAMAAUAAAAjAP3/AAAMAAUAAAAiAP3/AAAMAAUAAAAhAP3/AAAMAAUAAAAgAP3/AAAMAAUAAAAgAP7/AAAJAAYAAAAgAP//AAAMAAUAAAAjAP//AAAMAAUAAAAjAP7/AAAMAAUAAAAiAP7/AAAMAAUAAAAhAP7/AAAMAAUAAAAhAP//AAAMAAUAAAAiAP//AAAMAAUAAAAeAAgAAAALAAYAAAAeAAcAAAALAAUAAAAeAAYAAAALAAUAAAAeAAUAAAALAAUAAAAeAAQAAAALAAUAAAAeAAMAAAALAAUAAAAeAAIAAAALAAUAAAAeAAEAAAALAAUAAAAeAAAAAAALAAUAAAAfAAgAAAAMAAYAAAAgAAgAAAAMAAYAAAAhAAgAAAAMAAYAAAAiAAgAAAAMAAYAAAAjAAgAAAAMAAYAAAAkAAgAAAAMAAYAAAAlAAgAAAAMAAYAAAAmAAgAAAANAAYAAAAmAAAAAAANAAUAAAAmAAEAAAANAAUAAAAmAAIAAAANAAUAAAAmAAMAAAANAAUAAAAmAAQAAAANAAUAAAAmAAUAAAANAAUAAAAmAAYAAAANAAUAAAAmAAcAAAANAAUAAAAfAAAAAAAMAAUAAAAfAAEAAAAMAAUAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAfAAYAAAAKAAYAAAAfAAcAAAAMAAUAAAAgAAAAAAAMAAUAAAAgAAEAAAAMAAUAAAAgAAIAAAAMAAUAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAgAAYAAAAMAAUAAAAgAAcAAAAMAAUAAAAhAAAAAAAMAAUAAAAhAAEAAAAMAAUAAAAhAAIAAAAKAAYAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAhAAYAAAAMAAUAAAAhAAcAAAAMAAUAAAAiAAAAAAAMAAUAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAKAAYAAAAiAAUAAAAKAAYAAAAiAAYAAAAMAAUAAAAiAAcAAAAMAAUAAAAjAAAAAAAMAAUAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAjAAYAAAAMAAUAAAAjAAcAAAAMAAUAAAAkAAAAAAAKAAYAAAAkAAEAAAAMAAUAAAAkAAIAAAAMAAUAAAAkAAMAAAAMAAUAAAAkAAQAAAAMAAUAAAAkAAUAAAAMAAUAAAAkAAYAAAAMAAUAAAAkAAcAAAAKAAYAAAAlAAAAAAAMAAUAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAlAAYAAAAMAAUAAAAlAAcAAAAMAAUAAAD6/wcAAAAMAAUAAAD7/wcAAAAMAAUAAAD8/wcAAAAMAAUAAAD9/wcAAAAMAAUAAAD5/wcAAAALAAUAAAD+/wcAAAANAAUAAAD5/wgAAAALAAYAAAD6/wgAAAAMAAYAAAD7/wgAAAAMAAYAAAD8/wgAAAAMAAYAAAD9/wgAAAAMAAYAAAD+/wgAAAANAAYAAAA=")
+tile_set = SubResource("TileSet_14yng")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_tafwr")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_37c7w")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(186, -172.5)
+zoom = Vector2(1.5, 1.5)
+process_callback = 0
+position_smoothing_speed = 8.0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_dxiro")
+
+[node name="Label" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -133.0
+offset_right = 332.0
+offset_bottom = -69.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("5_gaaip")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="PhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_targets")]
+top_level = true
+position = Vector2(186, -172.5)
+script = ExtResource("6_ojk83")
+priority = 10
+follow_mode = 3
+follow_targets = [NodePath("../CharacterBody2D"), NodePath("../GroupNPCSprite")]
+zoom = Vector2(1.5, 1.5)
+tween_resource = SubResource("Resource_spy00")
+tween_on_load = false
+follow_damping = true
+auto_zoom = true
+auto_zoom_min = 0.5
+auto_zoom_max = 1.5
+auto_zoom_margin = Vector4(200, 0, 200, 0)
+draw_limits = true
+
+[node name="GroupNPCSprite" type="Sprite2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(107, -316)
+texture = ExtResource("8_ys0m4")
+
+[node name="CharacterBody2D" parent="." instance=ExtResource("9_witv0")]
+position = Vector2(265, -29)
+script = ExtResource("10_aivri")
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_path_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_path_example_scene.tscn
new file mode 100644
index 0000000..60342a2
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_follow_path_example_scene.tscn
@@ -0,0 +1,269 @@
+[gd_scene load_steps=13 format=4 uid="uid://b75giavcvh1mv"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_t003o"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_4ncqd"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_tpji3"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_w0rat"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="5_q77r4"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="6_y6hoa"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="7_wd55r"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_fy81j"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="9_u6ygl"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_nivvc"]
+texture = ExtResource("1_t003o")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_eyojy"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_nivvc")
+
+[sub_resource type="Curve2D" id="Curve2D_usrhf"]
+_data = {
+"points": PackedVector2Array(-96.4111, 42.3785, 0, 0, 222, 0, 0, 0, 0, 0, 1580.53, 0)
+}
+point_count = 2
+
+[node name="Root" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="Pillar" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAAAPr/AAAKAAIAAAAAAPv/AAAKAAMAAAAAAPz/AAAKAAMAAAAAAP3/AAAKAAMAAAAAAP7/AAAKAAMAAAAAAP//AAAKAAMAAAABAPr/AAALAAIAAAABAPv/AAALAAEAAAABAPz/AAALAAEAAAABAP3/AAALAAEAAAABAP7/AAALAAEAAAABAP//AAALAAEAAAACAPr/AAAMAAIAAAACAPv/AAAMAAMAAAACAPz/AAAMAAMAAAACAP3/AAAMAAMAAAACAP7/AAAMAAMAAAACAP//AAAMAAMAAAA=")
+tile_set = SubResource("TileSet_eyojy")
+collision_enabled = false
+navigation_enabled = false
+
+[node name="Terrain" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAAJAAYAAAAMAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAAHAAEAAAABAAUAAAALAAEAAAABAAYAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAACAAYAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAADAAYAAAALAAEAAAAEAAEAAAAHAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAEAAYAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAAHAAEAAAAFAAUAAAALAAEAAAAFAAYAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAGAAYAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAHAAYAAAAHAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAAHAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAIAAYAAAALAAEAAAAKAAEAAAAIAAUAAAALAAEAAAAIAAUAAAAMAAEAAAAIAAUAAAANAAEAAAAIAAUAAAAKAAIAAAAIAAYAAAALAAIAAAAIAAYAAAAMAAIAAAAIAAYAAAANAAIAAAAIAAYAAAAKAAMAAAALAAEAAAAKAAQAAAALAAEAAAALAAMAAAALAAEAAAALAAQAAAALAAEAAAAMAAMAAAALAAEAAAAMAAQAAAALAAEAAAANAAMAAAALAAEAAAANAAQAAAALAAEAAAAOAAEAAAAIAAUAAAAPAAEAAAAIAAUAAAAQAAEAAAAIAAUAAAAOAAIAAAAIAAYAAAAPAAIAAAAIAAYAAAAQAAIAAAAIAAYAAAAOAAMAAAALAAEAAAAPAAMAAAALAAEAAAAPAAQAAAALAAEAAAAQAAQAAAALAAEAAAAQAAMAAAALAAEAAAAOAAQAAAALAAEAAAARAAAAAAALAAQAAAARAAEAAAALAAUAAAARAAIAAAALAAUAAAARAAMAAAALAAUAAAARAAQAAAALAAUAAAASAAAAAAAMAAQAAAATAAAAAAAMAAQAAAAUAAAAAAAMAAQAAAAVAAAAAAAMAAQAAAAWAAAAAAAMAAQAAAAXAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAATAAEAAAAMAAUAAAATAAIAAAAJAAYAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAKAAYAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAARAAUAAAALAAUAAAARAAYAAAALAAUAAAASAAUAAAAJAAYAAAASAAYAAAAMAAUAAAATAAUAAAAMAAUAAAATAAYAAAAMAAUAAAAUAAUAAAAMAAUAAAAUAAYAAAAMAAUAAAAVAAUAAAAMAAUAAAAVAAYAAAAMAAUAAAAWAAUAAAAMAAUAAAAWAAYAAAAMAAUAAAAXAAUAAAAMAAUAAAAXAAYAAAAMAAUAAAAKAP7/AAALAAQAAAALAP7/AAAMAAQAAAAMAP7/AAAMAAQAAAAKAP//AAALAAYAAAALAP//AAAMAAYAAAAMAP//AAAMAAYAAAAQAP7/AAANAAQAAAAQAP//AAANAAYAAAANAP7/AAAMAAQAAAAOAP7/AAAMAAQAAAAPAP7/AAAMAAQAAAANAP//AAAMAAYAAAAOAP//AAAMAAYAAAAPAP//AAAMAAYAAAAMAP3/AAAOAAAAAAADAP//AAAOAAIAAAAEAP//AAAPAAIAAAAFAP//AAAQAAIAAAAGAP//AAAOAAIAAAAHAP//AAAPAAIAAAAIAP//AAAQAAIAAAD//wAAAAAKAAAAAAD//wEAAAAKAAEAAAD//wIAAAAKAAEAAAD//wMAAAAKAAEAAAD//wQAAAAKAAEAAAD//wUAAAAKAAEAAAD//wYAAAAKAAEAAAD//wcAAAAKAAEAAAD//wgAAAAKAAEAAAAAAAAAAAALAAAAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAAAAAYAAAALAAEAAAAAAAcAAAALAAEAAAAAAAgAAAALAAEAAAABAAgAAAALAAEAAAACAAgAAAALAAEAAAADAAgAAAALAAEAAAAEAAgAAAALAAEAAAAFAAgAAAALAAEAAAAGAAgAAAALAAEAAAAHAAgAAAALAAEAAAAIAAgAAAALAAEAAAAJAAgAAAAMAAEAAAAJAAcAAAAMAAEAAAAIAAcAAAALAAEAAAAHAAcAAAALAAEAAAAGAAcAAAAHAAEAAAAFAAcAAAALAAEAAAAEAAcAAAALAAEAAAADAAcAAAALAAEAAAACAAcAAAALAAEAAAABAAcAAAALAAEAAAD///v/AAANAAQAAAD///z/AAANAAUAAAD///3/AAANAAUAAAD///7/AAANAAUAAAD/////AAANAAYAAAD+//v/AAAMAAQAAAD9//v/AAAMAAQAAAD+//z/AAAJAAYAAAD9//z/AAAMAAUAAAD6////AAAMAAUAAAD7////AAAMAAUAAAD8////AAAMAAUAAAD9////AAAMAAUAAAD+////AAAMAAUAAAD+//7/AAAMAAUAAAD+//3/AAAMAAUAAAD9//3/AAAMAAUAAAD9//7/AAAKAAYAAAD+/wAAAAANAAUAAAD+/wEAAAANAAUAAAD+/wIAAAANAAUAAAD+/wMAAAANAAUAAAD+/wQAAAANAAUAAAD+/wUAAAANAAUAAAD+/wYAAAANAAUAAAD9/wAAAAAMAAUAAAD8/wAAAAAMAAUAAAD7/wAAAAAMAAUAAAD6/wAAAAAMAAUAAAD5/wAAAAALAAUAAAD6/wEAAAAMAAUAAAD6/wIAAAAMAAUAAAD6/wMAAAAMAAUAAAD7/wMAAAAMAAUAAAD7/wQAAAAMAAUAAAD8/wEAAAAMAAUAAAD9/wEAAAAMAAUAAAD9/wIAAAAMAAUAAAD9/wMAAAAMAAUAAAD9/wQAAAAMAAUAAAD9/wUAAAAMAAUAAAD9/wYAAAAMAAUAAAD8/wUAAAAMAAUAAAD7/wUAAAAMAAUAAAD8/wYAAAAMAAUAAAD8/wQAAAAKAAYAAAD8/wMAAAAMAAUAAAD8/wIAAAAMAAUAAAD7/wEAAAAMAAUAAAD7/wIAAAAJAAYAAAD7/wYAAAAMAAUAAAD6/wYAAAAMAAUAAAD6/wUAAAAMAAUAAAD6/wQAAAAMAAUAAAD5////AAALAAUAAAD5/wEAAAALAAUAAAD5/wIAAAALAAUAAAD5/wMAAAALAAUAAAD5/wQAAAALAAUAAAD5/wUAAAALAAUAAAD5/wYAAAALAAUAAAD8//r/AAALAAMAAAAOAP3/AAALAAMAAAALAP3/AAALAAMAAAASAP//AAALAAMAAAAUAP//AAALAAMAAAD6//r/AAAQAAUAAAD7//r/AAALAAMAAAANAP3/AAAOAAYAAAAWAP//AAAPAAYAAAD9//r/AAAPAAUAAAAXAP//AAAQAAUAAAD5//v/AAALAAQAAAD5//z/AAALAAUAAAD5//3/AAALAAUAAAD5//7/AAALAAUAAAD6//v/AAAMAAQAAAD6//z/AAAKAAYAAAD6//3/AAAMAAUAAAD6//7/AAAMAAUAAAD7//v/AAAMAAQAAAD7//z/AAAMAAUAAAD7//3/AAAMAAUAAAD7//7/AAAMAAUAAAD8//v/AAAMAAQAAAD8//z/AAAMAAUAAAD8//3/AAAMAAUAAAD8//7/AAAMAAUAAAARAAcAAAALAAUAAAARAAgAAAALAAYAAAAXAAcAAAAMAAUAAAAWAAcAAAAMAAUAAAAVAAcAAAAMAAUAAAAUAAcAAAAMAAUAAAATAAcAAAAMAAUAAAASAAcAAAAMAAUAAAASAAgAAAAMAAYAAAATAAgAAAAMAAYAAAAUAAgAAAAMAAYAAAAVAAgAAAAMAAYAAAAWAAgAAAAMAAYAAAAXAAgAAAAMAAYAAAAKAAUAAAALAAEAAAAKAAYAAAALAAEAAAAKAAcAAAALAAEAAAAKAAgAAAALAAEAAAALAAUAAAALAAEAAAALAAYAAAALAAEAAAALAAcAAAALAAEAAAALAAgAAAALAAEAAAAMAAUAAAALAAEAAAAMAAYAAAALAAEAAAAMAAcAAAALAAEAAAAMAAgAAAALAAEAAAANAAUAAAALAAEAAAANAAYAAAALAAEAAAANAAcAAAALAAEAAAANAAgAAAALAAEAAAAOAAUAAAALAAEAAAAOAAYAAAALAAEAAAAOAAcAAAALAAEAAAAOAAgAAAALAAEAAAAPAAUAAAALAAEAAAAPAAYAAAALAAEAAAAPAAcAAAALAAEAAAAPAAgAAAALAAEAAAAQAAUAAAALAAEAAAAQAAYAAAALAAEAAAAQAAcAAAALAAEAAAAQAAgAAAALAAEAAAAdAAAAAAANAAQAAAAdAAEAAAANAAUAAAAdAAIAAAANAAUAAAAdAAMAAAANAAUAAAAdAAQAAAANAAUAAAAdAAUAAAANAAUAAAAdAAYAAAANAAUAAAAdAAcAAAANAAUAAAAdAAgAAAANAAYAAAAZAP//AAAOAAQAAAAYAAAAAAAMAAQAAAAZAAAAAAAMAAQAAAAaAAAAAAAMAAQAAAAbAAAAAAAMAAQAAAAcAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAZAAEAAAAMAAUAAAAaAAEAAAAMAAUAAAAbAAEAAAAMAAUAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAJAAYAAAAbAAIAAAAMAAUAAAAaAAIAAAAMAAUAAAAZAAIAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAYAAYAAAAMAAUAAAAYAAcAAAAMAAUAAAAYAAgAAAAMAAYAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAZAAYAAAAMAAUAAAAZAAcAAAAMAAUAAAAZAAgAAAAMAAYAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAaAAYAAAAKAAYAAAAaAAcAAAAMAAUAAAAaAAgAAAAMAAYAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAbAAYAAAAMAAUAAAAbAAcAAAAMAAUAAAAbAAgAAAAMAAYAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAcAAYAAAAMAAUAAAAcAAcAAAAMAAUAAAAcAAgAAAAMAAYAAAAPAP3/AAAQAAYAAAAbAP//AAALAAMAAAAaAP//AAALAAMAAAATAP//AAALAAMAAAAVAP//AAALAAMAAAD6/wcAAAAMAAUAAAD7/wcAAAAMAAUAAAD8/wcAAAAMAAUAAAD9/wcAAAAMAAUAAAD5/wcAAAALAAUAAAD+/wcAAAANAAUAAAD5/wgAAAALAAYAAAD6/wgAAAAMAAYAAAD7/wgAAAAMAAYAAAD8/wgAAAAMAAYAAAD9/wgAAAAMAAYAAAD+/wgAAAANAAYAAAAeAP//AAALAAQAAAAeAAAAAAALAAUAAAAeAAEAAAALAAUAAAAeAAIAAAALAAUAAAAeAAMAAAALAAUAAAAeAAQAAAALAAUAAAAeAAUAAAALAAUAAAAeAAYAAAALAAUAAAAeAAcAAAALAAUAAAAeAAgAAAALAAUAAAAeAAkAAAALAAUAAAAeAAoAAAALAAUAAAAeAAsAAAALAAUAAAAeAAwAAAALAAYAAAAfAP//AAAMAAQAAAAfAAAAAAAMAAUAAAAfAAEAAAAKAAYAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAfAAYAAAAMAAUAAAAfAAcAAAAMAAUAAAAfAAgAAAAMAAUAAAAfAAkAAAAMAAUAAAAfAAoAAAAKAAYAAAAfAAsAAAAMAAUAAAAfAAwAAAAMAAYAAAAgAP//AAAMAAQAAAAgAAAAAAAMAAUAAAAgAAEAAAAMAAUAAAAgAAIAAAAJAAYAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAgAAYAAAAMAAUAAAAgAAcAAAAMAAUAAAAgAAgAAAAMAAUAAAAgAAkAAAAMAAUAAAAgAAoAAAAMAAUAAAAgAAsAAAAMAAUAAAAgAAwAAAAMAAYAAAAhAP//AAAMAAQAAAAhAAAAAAAMAAUAAAAhAAEAAAAMAAUAAAAhAAIAAAAMAAUAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAhAAYAAAAKAAYAAAAhAAcAAAAMAAUAAAAhAAgAAAAMAAUAAAAhAAkAAAAMAAUAAAAhAAoAAAAMAAUAAAAhAAsAAAAMAAUAAAAhAAwAAAAMAAYAAAAiAP//AAAMAAQAAAAiAAAAAAAMAAUAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAMAAUAAAAiAAUAAAAMAAUAAAAiAAYAAAAMAAUAAAAiAAcAAAAMAAUAAAAiAAgAAAAKAAYAAAAiAAkAAAAKAAYAAAAiAAoAAAAMAAUAAAAiAAsAAAAMAAUAAAAiAAwAAAAMAAYAAAAjAP//AAAMAAQAAAAjAAAAAAAMAAUAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAjAAYAAAAMAAUAAAAjAAcAAAAMAAUAAAAjAAgAAAAMAAUAAAAjAAkAAAAMAAUAAAAjAAoAAAAMAAUAAAAjAAsAAAAMAAUAAAAjAAwAAAAMAAYAAAAkAP//AAAMAAQAAAAkAAAAAAAMAAUAAAAkAAEAAAAMAAUAAAAkAAIAAAAJAAYAAAAkAAMAAAAKAAYAAAAkAAQAAAAKAAYAAAAkAAUAAAAMAAUAAAAkAAYAAAAMAAUAAAAkAAcAAAAMAAUAAAAkAAgAAAAMAAUAAAAkAAkAAAAMAAUAAAAkAAoAAAAMAAUAAAAkAAsAAAAKAAYAAAAkAAwAAAAMAAYAAAAlAP//AAAMAAQAAAAlAAAAAAAMAAUAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAlAAYAAAAMAAUAAAAlAAcAAAAMAAUAAAAlAAgAAAAMAAUAAAAlAAkAAAAMAAUAAAAlAAoAAAAMAAUAAAAlAAsAAAAMAAUAAAAlAAwAAAAMAAYAAAAmAP//AAANAAQAAAAmAAAAAAANAAUAAAAmAAEAAAANAAUAAAAmAAIAAAANAAUAAAAmAAMAAAANAAUAAAAmAAQAAAANAAUAAAAmAAUAAAANAAUAAAAmAAYAAAANAAUAAAAmAAcAAAANAAUAAAAmAAgAAAANAAUAAAAmAAkAAAANAAUAAAAmAAoAAAANAAUAAAAmAAsAAAANAAUAAAAmAAwAAAANAAYAAAAnAP7/AAALAAQAAAAnAP//AAALAAUAAAAnAAAAAAALAAUAAAAnAAEAAAALAAUAAAAnAAIAAAALAAUAAAAnAAMAAAALAAUAAAAnAAQAAAALAAUAAAAnAAUAAAALAAUAAAAnAAYAAAALAAUAAAAnAAcAAAALAAUAAAAnAAgAAAALAAUAAAAnAAkAAAALAAUAAAAnAAoAAAALAAUAAAAnAAsAAAALAAYAAAAoAP7/AAAMAAQAAAAoAP//AAAMAAUAAAAoAAAAAAAKAAYAAAAoAAEAAAAMAAUAAAAoAAIAAAAMAAUAAAAoAAMAAAAMAAUAAAAoAAQAAAAMAAUAAAAoAAUAAAAMAAUAAAAoAAYAAAAMAAUAAAAoAAcAAAAMAAUAAAAoAAgAAAAMAAUAAAAoAAkAAAAKAAYAAAAoAAoAAAAMAAUAAAAoAAsAAAAMAAYAAAApAP7/AAAMAAQAAAApAP//AAAMAAUAAAApAAAAAAAMAAUAAAApAAEAAAAMAAUAAAApAAIAAAAMAAUAAAApAAMAAAAMAAUAAAApAAQAAAAMAAUAAAApAAUAAAAMAAUAAAApAAYAAAAMAAUAAAApAAcAAAAMAAUAAAApAAgAAAAMAAUAAAApAAkAAAAMAAUAAAApAAoAAAAMAAUAAAApAAsAAAAMAAYAAAAqAP7/AAAMAAQAAAAqAP//AAAMAAUAAAAqAAAAAAAMAAUAAAAqAAEAAAAJAAYAAAAqAAIAAAAKAAYAAAAqAAMAAAAKAAYAAAAqAAQAAAAMAAUAAAAqAAUAAAAMAAUAAAAqAAYAAAAMAAUAAAAqAAcAAAAMAAUAAAAqAAgAAAAMAAUAAAAqAAkAAAAMAAUAAAAqAAoAAAAKAAYAAAAqAAsAAAAMAAYAAAArAPb/AAALAAQAAAArAPf/AAALAAUAAAArAPj/AAALAAUAAAArAPn/AAALAAUAAAArAPr/AAALAAUAAAArAPv/AAALAAUAAAArAPz/AAALAAUAAAArAP3/AAALAAYAAAAsAPb/AAAMAAQAAAAsAPf/AAAMAAUAAAAsAPj/AAAKAAYAAAAsAPn/AAAMAAUAAAAsAPr/AAAMAAUAAAAsAPv/AAAMAAUAAAAsAPz/AAAMAAUAAAAsAP3/AAAMAAYAAAAtAPb/AAAMAAQAAAAtAPf/AAAMAAUAAAAtAPj/AAAMAAUAAAAtAPn/AAAJAAYAAAAtAPr/AAAMAAUAAAAtAPv/AAAMAAUAAAAtAPz/AAAMAAUAAAAtAP3/AAAMAAYAAAAuAPb/AAAMAAQAAAAuAPf/AAAMAAUAAAAuAPj/AAAMAAUAAAAuAPn/AAAMAAUAAAAuAPr/AAAMAAUAAAAuAPv/AAAMAAUAAAAuAPz/AAAMAAUAAAAuAP3/AAAMAAYAAAAvAPb/AAAMAAQAAAAvAPf/AAAMAAUAAAAvAPj/AAAMAAUAAAAvAPn/AAAMAAUAAAAvAPr/AAAMAAUAAAAvAPv/AAAMAAUAAAAvAPz/AAAMAAUAAAAvAP3/AAAMAAYAAAAwAPb/AAAMAAQAAAAwAPf/AAAMAAUAAAAwAPj/AAAMAAUAAAAwAPn/AAAMAAUAAAAwAPr/AAAMAAUAAAAwAPv/AAAMAAUAAAAwAPz/AAAMAAUAAAAwAP3/AAAMAAYAAAAxAPb/AAAMAAQAAAAxAPf/AAAMAAUAAAAxAPj/AAAMAAUAAAAxAPn/AAAMAAUAAAAxAPr/AAAMAAUAAAAxAPv/AAAMAAUAAAAxAPz/AAAMAAUAAAAxAP3/AAAMAAYAAAAyAPb/AAAMAAQAAAAyAPf/AAAMAAUAAAAyAPj/AAAMAAUAAAAyAPn/AAAMAAUAAAAyAPr/AAAMAAUAAAAyAPv/AAAMAAUAAAAyAPz/AAAMAAUAAAAyAP3/AAAMAAUAAAArAP7/AAANAAQAAAArAP//AAANAAUAAAArAAAAAAANAAUAAAArAAEAAAANAAUAAAArAAIAAAANAAUAAAArAAMAAAANAAUAAAArAAQAAAANAAUAAAArAAUAAAANAAUAAAArAAYAAAANAAUAAAArAAcAAAANAAUAAAArAAgAAAANAAUAAAArAAkAAAANAAUAAAArAAoAAAANAAUAAAArAAsAAAANAAYAAAA=")
+tile_set = SubResource("TileSet_eyojy")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_4ncqd")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_tpji3")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(374, -190)
+zoom = Vector2(1.5, 1.5)
+process_callback = 0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_w0rat")
+
+[node name="Label" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -133.0
+offset_right = 332.0
+offset_bottom = -69.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("5_q77r4")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="Player" type="Node" parent="."]
+
+[node name="Label" type="Label" parent="Player"]
+visible = false
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("5_q77r4")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_target", "follow_path")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(374, -190)
+script = ExtResource("6_y6hoa")
+priority = 10
+follow_mode = 4
+follow_target = NodePath("../CharacterBody2D")
+follow_path = NodePath("../Path2D")
+zoom = Vector2(1.5, 1.5)
+tween_resource = ExtResource("7_wd55r")
+tween_on_load = false
+follow_damping = true
+draw_limits = true
+
+[node name="Path2D" type="Path2D" parent="."]
+position = Vector2(152, -190)
+curve = SubResource("Curve2D_usrhf")
+
+[node name="CharacterBody2D" parent="." instance=ExtResource("8_fy81j")]
+position = Vector2(225, -28)
+script = ExtResource("9_u6ygl")
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_limit_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_limit_example_scene.tscn
new file mode 100644
index 0000000..35538eb
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_limit_example_scene.tscn
@@ -0,0 +1,319 @@
+[gd_scene load_steps=17 format=4 uid="uid://0ox7hgdpwpqp"]
+
+[ext_resource type="Script" uid="uid://dtcuvut1eklnd" path="res://addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd" id="1_bwr3f"]
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="2_f03of"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="3_cysy4"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="4_qqut6"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="5_yv8tn"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="6_2n5r1"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="6_68ewj"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="7_ne05h"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="8_hulu3"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("2_f03of")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[sub_resource type="Resource" id="Resource_ct1eh"]
+script = ExtResource("7_ne05h")
+duration = 0.9
+transition = 2
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_xmxri"]
+size = Vector2(790, 410)
+
+[sub_resource type="Resource" id="Resource_exr3j"]
+script = ExtResource("7_ne05h")
+duration = 0.9
+transition = 2
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_wtfjw"]
+size = Vector2(1530, 700)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_daeuh"]
+size = Vector2(1027.5, 610.5)
+
+[node name="ExampleScene2D" type="Node2D"]
+script = ExtResource("1_bwr3f")
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+metadata/_edit_lock_ = true
+
+[node name="StartingTerrain" type="TileMapLayer" parent="."]
+z_index = 1
+use_parent_material = true
+position = Vector2(-97, 0)
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAD9/wAAAAALAAAAAAD+/wAAAAALAAAAAAD//wAAAAALAAAAAAAAAAAAAAALAAAAAAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAD8/wAAAAALAAAAAAD7/wAAAAAKAAAAAAD7/wEAAAAKAAEAAAD7/wIAAAAKAAEAAAD7/wMAAAAKAAEAAAD7/wQAAAAKAAEAAAD7/wUAAAAKAAEAAAD8/wEAAAALAAEAAAD8/wIAAAALAAEAAAD8/wMAAAALAAEAAAD8/wQAAAALAAEAAAD8/wUAAAALAAEAAAD9/wEAAAALAAEAAAD9/wIAAAALAAEAAAD9/wMAAAALAAEAAAD9/wQAAAALAAEAAAD9/wUAAAALAAEAAAD+/wEAAAALAAEAAAD+/wIAAAALAAEAAAD+/wMAAAALAAEAAAD+/wQAAAALAAEAAAD+/wUAAAALAAEAAAD//wEAAAALAAEAAAD//wIAAAALAAEAAAD//wMAAAALAAEAAAD//wQAAAALAAEAAAD//wUAAAALAAEAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAALAAEAAAABAAUAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAAEAAEAAAALAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAALAAEAAAAFAAUAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAALAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAD7//n/AAALAAYAAAD7//j/AAALAAUAAAD7//f/AAALAAUAAAD7//b/AAALAAUAAAD7//X/AAALAAQAAAD8//n/AAAMAAYAAAD9//n/AAAMAAYAAAD+//n/AAAMAAYAAAD///n/AAAMAAYAAAAAAPn/AAAMAAYAAAABAPn/AAAMAAYAAAACAPn/AAAMAAYAAAADAPn/AAAMAAYAAAAEAPn/AAAMAAYAAAAFAPn/AAAMAAYAAAAGAPn/AAAMAAYAAAAHAPn/AAAMAAYAAAAIAPn/AAAMAAYAAAD8//X/AAAMAAQAAAD9//X/AAAMAAQAAAD+//X/AAAMAAQAAAD///X/AAAMAAQAAAAAAPX/AAAMAAQAAAABAPX/AAAMAAQAAAACAPX/AAAMAAQAAAADAPX/AAAMAAQAAAAEAPX/AAAMAAQAAAAFAPX/AAAMAAQAAAAGAPX/AAAMAAQAAAAHAPX/AAAMAAQAAAAIAPX/AAAMAAQAAAAJAPX/AAANAAQAAAAJAPb/AAANAAUAAAAJAPf/AAANAAUAAAAJAPj/AAANAAUAAAAJAPn/AAANAAYAAAD8//b/AAAMAAUAAAD8//f/AAAMAAUAAAD8//j/AAAMAAUAAAD9//b/AAAMAAUAAAD9//f/AAAMAAUAAAD9//j/AAAMAAUAAAD+//b/AAAMAAUAAAD+//f/AAAMAAUAAAD+//j/AAAMAAUAAAD///b/AAAMAAUAAAD///f/AAAMAAUAAAD///j/AAAMAAUAAAAAAPb/AAAMAAUAAAAAAPf/AAAMAAUAAAAAAPj/AAAMAAUAAAABAPb/AAAMAAUAAAABAPf/AAAMAAUAAAABAPj/AAAMAAUAAAACAPb/AAAMAAUAAAACAPf/AAAMAAUAAAACAPj/AAAMAAUAAAADAPb/AAAMAAUAAAADAPf/AAAMAAUAAAADAPj/AAAMAAUAAAAEAPb/AAAMAAUAAAAEAPf/AAAMAAUAAAAEAPj/AAAMAAUAAAAFAPb/AAAMAAUAAAAFAPf/AAAMAAUAAAAFAPj/AAAMAAUAAAAGAPb/AAAMAAUAAAAGAPf/AAAMAAUAAAAGAPj/AAAMAAUAAAAHAPb/AAAMAAUAAAAHAPf/AAAMAAUAAAAHAPj/AAAMAAUAAAAIAPb/AAAMAAUAAAAIAPf/AAAMAAUAAAAIAPj/AAAMAAUAAAA=")
+tile_set = SubResource("TileSet_kf7eg")
+
+[node name="OtherTerrain" type="TileMapLayer" parent="."]
+z_index = 1
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAIAAAAAAAMAAQAAAAJAAAAAAAMAAQAAAAKAAAAAAAMAAQAAAALAAAAAAAMAAQAAAAMAAAAAAAMAAQAAAANAAAAAAAMAAQAAAAOAAAAAAAMAAQAAAD4/wAAAAANAAUAAAD3/wAAAAAMAAUAAAD2/wAAAAAMAAUAAAD1/wAAAAAMAAUAAAD0/wAAAAAMAAUAAADz/wAAAAAMAAUAAAAIAAEAAAAMAAUAAAAIAAIAAAAMAAUAAAAIAAMAAAAMAAUAAAAIAAQAAAAMAAUAAAAJAAEAAAAMAAUAAAAJAAIAAAAMAAUAAAAJAAMAAAAMAAUAAAAJAAQAAAAMAAUAAAAKAAEAAAAMAAUAAAAKAAIAAAAMAAUAAAAKAAMAAAAMAAUAAAAKAAQAAAAMAAUAAAALAAEAAAAMAAUAAAALAAIAAAAMAAUAAAALAAMAAAAMAAUAAAALAAQAAAAMAAUAAAAMAAEAAAAMAAUAAAAMAAIAAAAMAAUAAAAMAAMAAAAMAAUAAAAMAAQAAAAMAAUAAAANAAEAAAAMAAUAAAANAAIAAAAMAAUAAAANAAMAAAAMAAUAAAANAAQAAAAMAAUAAAAOAAEAAAAMAAUAAAAOAAIAAAAMAAUAAAAOAAMAAAAMAAUAAAAOAAQAAAAMAAUAAAAOAAUAAAAMAAUAAAANAAUAAAAMAAUAAAAMAAUAAAAMAAUAAAALAAUAAAAMAAUAAAAKAAUAAAAMAAUAAAAJAAUAAAAMAAUAAAAIAAUAAAAMAAUAAADv//3/AAALAAUAAADv//7/AAALAAUAAADv////AAALAAUAAADv/wAAAAALAAYAAADw//3/AAAMAAUAAADw//7/AAAMAAUAAADw////AAAMAAUAAADw/wAAAAAMAAUAAADx//3/AAAMAAUAAADx//7/AAAMAAUAAADx////AAAMAAUAAADx/wAAAAAMAAUAAADy//3/AAAMAAUAAADy//7/AAAMAAUAAADy////AAAMAAUAAADy/wAAAAAMAAUAAADz//3/AAAMAAUAAADz//7/AAAMAAUAAADz////AAAMAAUAAAD0//3/AAAMAAUAAAD0//7/AAAMAAUAAAD0////AAAMAAUAAAD1//3/AAAMAAUAAAD1//7/AAAMAAUAAAD1////AAAMAAUAAAD2//3/AAAMAAUAAAD2//7/AAAMAAUAAAD2////AAAMAAUAAAD3//3/AAAMAAUAAAD3//7/AAAMAAUAAAD3////AAAMAAUAAAD4//3/AAANAAUAAAD4//7/AAANAAUAAAD4////AAANAAUAAAD4/wEAAAANAAUAAAD4/wIAAAANAAUAAAD4/wMAAAANAAUAAAD4/wQAAAANAAUAAAD4/wUAAAANAAUAAADw/wEAAAAMAAUAAADw/wIAAAAMAAUAAADw/wMAAAAMAAUAAADw/wQAAAAMAAUAAADw/wUAAAAMAAUAAADx/wEAAAAMAAUAAADx/wIAAAAMAAUAAADx/wMAAAAMAAUAAADx/wQAAAAMAAUAAADx/wUAAAAMAAUAAADy/wEAAAAMAAUAAADy/wIAAAAMAAUAAADy/wMAAAAMAAUAAADy/wQAAAAMAAUAAADy/wUAAAAMAAUAAADz/wEAAAAMAAUAAADz/wIAAAAMAAUAAADz/wMAAAAMAAUAAADz/wQAAAAMAAUAAADz/wUAAAAMAAUAAAD0/wEAAAAMAAUAAAD0/wIAAAAMAAUAAAD0/wMAAAAMAAUAAAD0/wQAAAAMAAUAAAD0/wUAAAAMAAUAAAD1/wEAAAAMAAUAAAD1/wIAAAAMAAUAAAD1/wMAAAAMAAUAAAD1/wQAAAAMAAUAAAD1/wUAAAAMAAUAAAD2/wEAAAAMAAUAAAD2/wIAAAAMAAUAAAD2/wMAAAAMAAUAAAD2/wQAAAAMAAUAAAD2/wUAAAAMAAUAAAD3/wEAAAAMAAUAAAD3/wIAAAAMAAUAAAD3/wMAAAAMAAUAAAD3/wQAAAAMAAUAAAD3/wUAAAAMAAUAAAARAAAAAAAMAAQAAAARAAEAAAAMAAUAAAARAAIAAAAMAAUAAAARAAMAAAAMAAUAAAARAAQAAAAMAAUAAAARAAUAAAAMAAUAAAASAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAASAAUAAAAMAAUAAAAPAAAAAAAMAAQAAAAPAAEAAAAMAAUAAAAPAAIAAAAMAAUAAAAPAAMAAAAMAAUAAAAPAAQAAAAMAAUAAAAPAAUAAAAMAAUAAAAQAAAAAAAMAAQAAAAQAAEAAAAMAAUAAAAQAAIAAAAMAAUAAAAQAAMAAAAMAAUAAAAQAAQAAAAMAAUAAAAQAAUAAAAMAAUAAAATAAAAAAAMAAQAAAATAAEAAAAMAAUAAAATAAIAAAAMAAUAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAATAAUAAAAMAAUAAADv//j/AAALAAUAAADv//n/AAALAAUAAADw//j/AAAMAAUAAADw//n/AAAMAAUAAADx//j/AAAMAAUAAADx//n/AAAMAAUAAADy//j/AAAMAAUAAADy//n/AAAMAAUAAADz//j/AAAMAAUAAADz//n/AAAMAAUAAAD0//j/AAAMAAUAAAD0//n/AAAMAAUAAAD1//j/AAAMAAUAAAD1//n/AAAMAAUAAAD2//j/AAAMAAUAAAD2//n/AAAMAAUAAAD3//j/AAAMAAUAAAD3//n/AAAMAAUAAAD4//j/AAANAAUAAAD4//n/AAANAAUAAADv//X/AAALAAQAAADv//b/AAALAAUAAADv//f/AAALAAUAAADw//X/AAAMAAQAAADw//b/AAAMAAUAAADw//f/AAAMAAUAAADx//X/AAAMAAQAAADx//b/AAAMAAUAAADx//f/AAAMAAUAAADy//X/AAAMAAQAAADy//b/AAAMAAUAAADy//f/AAAMAAUAAADz//X/AAAMAAQAAADz//b/AAAMAAUAAADz//f/AAAMAAUAAAD0//X/AAAMAAQAAAD0//b/AAAMAAUAAAD0//f/AAAMAAUAAAD1//X/AAAMAAQAAAD1//b/AAAMAAUAAAD1//f/AAAMAAUAAAD2//X/AAAMAAQAAAD2//b/AAAMAAUAAAD2//f/AAAMAAUAAAD3//X/AAAMAAQAAAD3//b/AAAMAAUAAAD3//f/AAAMAAUAAAD4//X/AAANAAQAAAD4//b/AAANAAUAAAD4//f/AAANAAUAAADv//r/AAALAAUAAADw//r/AAAMAAUAAADx//r/AAAMAAUAAADy//r/AAAMAAUAAADz//r/AAAMAAUAAAD0//r/AAAMAAUAAAD1//r/AAAMAAUAAAD2//r/AAAMAAUAAAD3//r/AAAMAAUAAAD4//r/AAANAAUAAADv//v/AAALAAUAAADw//v/AAAMAAUAAADx//v/AAAMAAUAAADy//v/AAAMAAUAAADz//v/AAAMAAUAAAD0//v/AAAMAAUAAAD1//v/AAAMAAUAAAD2//v/AAAMAAUAAAD3//v/AAAMAAUAAAD4//v/AAANAAUAAADv//z/AAALAAUAAADw//z/AAAMAAUAAADx//z/AAAMAAUAAADy//z/AAAMAAUAAADz//z/AAAMAAUAAAD0//z/AAAMAAUAAAD1//z/AAAMAAUAAAD2//z/AAAMAAUAAAD3//z/AAAMAAUAAAD4//z/AAANAAUAAAAUAAAAAAAMAAQAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAUAAUAAAAMAAUAAAAVAAAAAAAMAAQAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAVAAUAAAAMAAUAAAAWAAAAAAAMAAQAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAMAAUAAAAWAAUAAAAMAAUAAAAXAAAAAAAMAAQAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAAXAAUAAAAMAAUAAAAYAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAZAAAAAAAMAAQAAAAZAAEAAAAMAAUAAAAZAAIAAAAMAAUAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAaAAAAAAAMAAQAAAAaAAEAAAAMAAUAAAAaAAIAAAAMAAUAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAbAAAAAAAMAAQAAAAbAAEAAAAMAAUAAAAbAAIAAAAMAAUAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAmAAAAAAAMAAQAAAAmAAEAAAAMAAUAAAAmAAIAAAAMAAUAAAAmAAMAAAAMAAUAAAAmAAQAAAAMAAUAAAAmAAUAAAAMAAUAAAAnAAAAAAAMAAQAAAAnAAEAAAAMAAUAAAAnAAIAAAAMAAUAAAAnAAMAAAAMAAUAAAAnAAQAAAAMAAUAAAAnAAUAAAAMAAUAAAAoAAAAAAAMAAQAAAAoAAEAAAAMAAUAAAAoAAIAAAAMAAUAAAAoAAMAAAAMAAUAAAAoAAQAAAAMAAUAAAAoAAUAAAAMAAUAAAApAAAAAAAMAAQAAAApAAEAAAAMAAUAAAApAAIAAAAMAAUAAAApAAMAAAAMAAUAAAApAAQAAAAMAAUAAAApAAUAAAAMAAUAAAAqAAAAAAAMAAQAAAAqAAEAAAAMAAUAAAAqAAIAAAAMAAUAAAAqAAMAAAAMAAUAAAAqAAQAAAAMAAUAAAAqAAUAAAAMAAUAAAArAAAAAAAMAAQAAAArAAEAAAAMAAUAAAArAAIAAAAMAAUAAAArAAMAAAAMAAUAAAArAAQAAAAMAAUAAAArAAUAAAAMAAUAAAAsAAAAAAAMAAQAAAAsAAEAAAAMAAUAAAAsAAIAAAAMAAUAAAAsAAMAAAAMAAUAAAAsAAQAAAAMAAUAAAAsAAUAAAAMAAUAAAAtAAAAAAAMAAQAAAAtAAEAAAAMAAUAAAAtAAIAAAAMAAUAAAAtAAMAAAAMAAUAAAAtAAQAAAAMAAUAAAAtAAUAAAAMAAUAAAAuAAAAAAAMAAQAAAAuAAEAAAAMAAUAAAAuAAIAAAAMAAUAAAAuAAMAAAAMAAUAAAAuAAQAAAAMAAUAAAAuAAUAAAAMAAUAAAAvAAAAAAAMAAQAAAAvAAEAAAAMAAUAAAAvAAIAAAAMAAUAAAAvAAMAAAAMAAUAAAAvAAQAAAAMAAUAAAAvAAUAAAAMAAUAAAAwAAAAAAAMAAQAAAAwAAEAAAAMAAUAAAAwAAIAAAAMAAUAAAAwAAMAAAAMAAUAAAAwAAQAAAAMAAUAAAAwAAUAAAAMAAUAAAAxAAAAAAANAAQAAAAxAAEAAAANAAUAAAAxAAIAAAANAAUAAAAxAAMAAAANAAUAAAAxAAQAAAANAAUAAAAxAAUAAAANAAUAAAAyAPf/AAALAAQAAAAyAPj/AAALAAUAAAAyAPn/AAALAAUAAAAyAPr/AAALAAUAAAAyAPv/AAALAAUAAAAyAPz/AAALAAUAAAAyAP3/AAALAAUAAAAyAP7/AAALAAUAAAAyAP//AAALAAUAAAAyAAAAAAALAAUAAAAyAAEAAAALAAUAAAAyAAIAAAALAAUAAAAyAAMAAAALAAUAAAAyAAQAAAALAAUAAAAyAAUAAAALAAUAAAAzAPf/AAAMAAQAAAAzAPj/AAAMAAUAAAAzAPn/AAAMAAUAAAAzAPr/AAAMAAUAAAAzAPv/AAAMAAUAAAAzAPz/AAAMAAUAAAAzAP3/AAAMAAUAAAAzAP7/AAAMAAUAAAAzAP//AAAMAAUAAAAzAAAAAAAMAAUAAAAzAAEAAAAMAAUAAAAzAAIAAAAMAAUAAAAzAAMAAAAMAAUAAAAzAAQAAAAMAAUAAAAzAAUAAAAMAAUAAAA0APf/AAAMAAQAAAA0APj/AAAMAAUAAAA0APn/AAAMAAUAAAA0APr/AAAMAAUAAAA0APv/AAAMAAUAAAA0APz/AAAMAAUAAAA0AP3/AAAMAAUAAAA0AP7/AAAMAAUAAAA0AP//AAAMAAUAAAA0AAAAAAAMAAUAAAA0AAEAAAAMAAUAAAA0AAIAAAAMAAUAAAA0AAMAAAAMAAUAAAA0AAQAAAAMAAUAAAA0AAUAAAAMAAUAAAA1APf/AAAMAAQAAAA1APj/AAAMAAUAAAA1APn/AAAMAAUAAAA1APr/AAAMAAUAAAA1APv/AAAMAAUAAAA1APz/AAAMAAUAAAA1AP3/AAAMAAUAAAA1AP7/AAAMAAUAAAA1AP//AAAMAAUAAAA1AAAAAAAMAAUAAAA1AAEAAAAMAAUAAAA1AAIAAAAMAAUAAAA1AAMAAAAMAAUAAAA1AAQAAAAMAAUAAAA1AAUAAAAMAAUAAAA2APf/AAAMAAQAAAA2APj/AAAMAAUAAAA2APn/AAAMAAUAAAA2APr/AAAMAAUAAAA2APv/AAAMAAUAAAA2APz/AAAMAAUAAAA2AP3/AAAMAAUAAAA2AP7/AAAMAAUAAAA2AP//AAAMAAUAAAA2AAAAAAAMAAUAAAA2AAEAAAAMAAUAAAA2AAIAAAAMAAUAAAA2AAMAAAAMAAUAAAA2AAQAAAAMAAUAAAA2AAUAAAAMAAUAAAA3APf/AAAMAAQAAAA3APj/AAAMAAUAAAA3APn/AAAMAAUAAAA3APr/AAAMAAUAAAA3APv/AAAMAAUAAAA3APz/AAAMAAUAAAA3AP3/AAAMAAUAAAA3AP7/AAAMAAUAAAA3AP//AAAMAAUAAAA3AAAAAAAMAAUAAAA3AAEAAAAMAAUAAAA3AAIAAAAMAAUAAAA3AAMAAAAMAAUAAAA3AAQAAAAMAAUAAAA3AAUAAAAMAAUAAAA4APf/AAAMAAQAAAA4APj/AAAMAAUAAAA4APn/AAAMAAUAAAA4APr/AAAMAAUAAAA4APv/AAAMAAUAAAA4APz/AAAMAAUAAAA4AP3/AAAMAAUAAAA4AP7/AAAMAAUAAAA4AP//AAAMAAUAAAA4AAAAAAAMAAUAAAA4AAEAAAAMAAUAAAA4AAIAAAAMAAUAAAA4AAMAAAAMAAUAAAA4AAQAAAAMAAUAAAA4AAUAAAAMAAUAAAA5APf/AAAMAAQAAAA5APj/AAAMAAUAAAA5APn/AAAMAAUAAAA5APr/AAAMAAUAAAA5APv/AAAMAAUAAAA5APz/AAAMAAUAAAA5AP3/AAAMAAUAAAA5AP7/AAAMAAUAAAA5AP//AAAMAAUAAAA5AAAAAAAMAAUAAAA5AAEAAAAMAAUAAAA5AAIAAAAMAAUAAAA5AAMAAAAMAAUAAAA5AAQAAAAMAAUAAAA5AAUAAAAMAAUAAAA6APf/AAAMAAQAAAA6APj/AAAMAAUAAAA6APn/AAAMAAUAAAA6APr/AAAMAAUAAAA6APv/AAAMAAUAAAA6APz/AAAMAAUAAAA6AP3/AAAMAAUAAAA6AP7/AAAMAAUAAAA6AP//AAAMAAUAAAA6AAAAAAAMAAUAAAA6AAEAAAAMAAUAAAA6AAIAAAAMAAUAAAA6AAMAAAAMAAUAAAA6AAQAAAAMAAUAAAA6AAUAAAAMAAUAAAA7APf/AAANAAQAAAA7APj/AAANAAUAAAA7APn/AAANAAUAAAA7APr/AAANAAUAAAA7APv/AAANAAUAAAA7APz/AAANAAUAAAA7AP3/AAANAAUAAAA7AP7/AAANAAUAAAA7AP//AAANAAUAAAA7AAAAAAANAAUAAAA7AAEAAAANAAUAAAA7AAIAAAANAAUAAAA7AAMAAAANAAUAAAA7AAQAAAANAAUAAAA7AAUAAAANAAUAAAAcAAAAAAAMAAQAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAMAAUAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAdAAAAAAAMAAQAAAAdAAEAAAAMAAUAAAAdAAIAAAAMAAUAAAAdAAMAAAAMAAUAAAAdAAQAAAAMAAUAAAAdAAUAAAAMAAUAAAAeAAAAAAAMAAQAAAAeAAEAAAAMAAUAAAAeAAIAAAAMAAUAAAAeAAMAAAAMAAUAAAAeAAQAAAAMAAUAAAAeAAUAAAAMAAUAAAAfAAAAAAAMAAQAAAAfAAEAAAAMAAUAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAgAAAAAAAMAAQAAAAgAAEAAAAMAAUAAAAgAAIAAAAMAAUAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAhAAAAAAAMAAQAAAAhAAEAAAAMAAUAAAAhAAIAAAAMAAUAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAiAAAAAAAMAAQAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAMAAUAAAAiAAUAAAAMAAUAAAAjAAAAAAAMAAQAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAkAAAAAAAMAAQAAAAkAAEAAAAMAAUAAAAkAAIAAAAMAAUAAAAkAAMAAAAMAAUAAAAkAAQAAAAMAAUAAAAkAAUAAAAMAAUAAAAlAAAAAAAMAAQAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAmAP//AAAOAAMAAAAnAP//AAAPAAMAAAAoAP//AAAPAAMAAAApAP//AAAPAAMAAAAqAP//AAAPAAMAAAArAP//AAAPAAMAAAAsAP//AAAPAAMAAAAtAP//AAAPAAMAAAAuAP//AAAPAAMAAAAvAP//AAAPAAMAAAAwAP//AAAQAAMAAAAxAPf/AAAHAAQAAAAxAPj/AAAHAAUAAAAxAPn/AAAHAAUAAAAxAPr/AAAHAAUAAAAxAPv/AAAHAAUAAAAxAPz/AAAHAAUAAAAxAP3/AAAHAAUAAAAxAP7/AAAHAAUAAAAxAP//AAAHAAYAAAA=")
+tile_set = SubResource("TileSet_kf7eg")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("3_cysy4")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("4_qqut6")]
+unique_name_in_owner = true
+visible = false
+
+[node name="CharacterBody2D" parent="." instance=ExtResource("5_yv8tn")]
+unique_name_in_owner = true
+z_index = 1
+position = Vector2(66, -28)
+script = ExtResource("6_68ewj")
+
+[node name="RoomLeftPhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(141, -91.205)
+script = ExtResource("6_2n5r1")
+priority = 5
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D")
+zoom = Vector2(2, 2)
+tween_resource = SubResource("Resource_ct1eh")
+follow_offset = Vector2(0, -63.205)
+follow_damping = true
+draw_limits = true
+limit_target = NodePath("../StartingTerrain")
+limit_margin = Vector4i(-50, 0, -50, 0)
+
+[node name="RoomLeftArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(117, -174)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomLeftArea2D"]
+position = Vector2(-79, 43)
+shape = SubResource("RectangleShape2D_xmxri")
+debug_color = Color(0, 0.6, 0.701961, 0.0313726)
+
+[node name="RoomCentrePhantomCamera2D" type="Node2D" parent="."]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(1218, -217)
+script = ExtResource("6_2n5r1")
+follow_mode = 2
+zoom = Vector2(1.5, 1.5)
+tween_resource = SubResource("Resource_exr3j")
+follow_damping = true
+draw_limits = true
+limit_target = NodePath("../RoomCentreArea2D/CollisionShape2D")
+
+[node name="RoomCentreArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(755, -179)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomCentreArea2D"]
+position = Vector2(338, -28)
+shape = SubResource("RectangleShape2D_wtfjw")
+debug_color = Color(0, 0.6, 0.701961, 0)
+
+[node name="RoomRightArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(2065, -160)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomRightArea2D"]
+position = Vector2(255.75, -3.25)
+shape = SubResource("RectangleShape2D_daeuh")
+debug_color = Color(0, 0.6, 0.701961, 0)
+
+[node name="RoomRightPhantomCamera2D" type="Node2D" parent="."]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(2347, -156)
+scale = Vector2(1.0024, 1)
+script = ExtResource("6_2n5r1")
+follow_mode = 2
+zoom = Vector2(2, 2)
+tween_resource = SubResource("Resource_exr3j")
+follow_damping = true
+draw_limits = true
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(141, -91.205)
+zoom = Vector2(2, 2)
+process_callback = 0
+limit_left = -147
+limit_top = -528
+limit_right = 673
+limit_bottom = 288
+position_smoothing_speed = 10.0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("8_hulu3")
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_noise_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_noise_example_scene.tscn
new file mode 100644
index 0000000..68e792e
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_noise_example_scene.tscn
@@ -0,0 +1,289 @@
+[gd_scene load_steps=16 format=4 uid="uid://chw6g32u86uve"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_2m0x8"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_4bfy0"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_vdqsb"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="4_w2gh7"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="5_d6fcf"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="6_bdmii"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="7_dpnkg"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_u5o87"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="9_suxld"]
+[ext_resource type="Script" uid="uid://bhd4nuiu23e7l" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd" id="10_p43w0"]
+[ext_resource type="Script" uid="uid://dimvdouy8g0sv" path="res://addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd" id="11_d6abr"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_lvmak"]
+texture = ExtResource("1_2m0x8")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_lvmak")
+
+[sub_resource type="Resource" id="Resource_87ddr"]
+script = ExtResource("11_d6abr")
+amplitude = 30.0
+frequency = 0.5
+randomize_noise_seed = 1
+noise_seed = 0
+positional_noise = true
+rotational_noise = false
+positional_multiplier_x = 1.0
+positional_multiplier_y = 1.0
+rotational_multiplier = 1.0
+
+[sub_resource type="Resource" id="Resource_rmnw1"]
+script = ExtResource("11_d6abr")
+amplitude = 40.0
+frequency = 30.0
+randomize_noise_seed = 1
+noise_seed = 96
+positional_noise = true
+rotational_noise = true
+positional_multiplier_x = 1.0
+positional_multiplier_y = 1.0
+rotational_multiplier = 1.0
+
+[node name="Root" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="Pillar" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAAAPr/AAAKAAIAAAAAAPv/AAAKAAMAAAAAAPz/AAAKAAMAAAAAAP3/AAAKAAMAAAAAAP7/AAAKAAMAAAAAAP//AAAKAAMAAAABAPr/AAALAAIAAAABAPv/AAALAAEAAAABAPz/AAALAAEAAAABAP3/AAALAAEAAAABAP7/AAALAAEAAAABAP//AAALAAEAAAACAPr/AAAMAAIAAAACAPv/AAAMAAMAAAACAPz/AAAMAAMAAAACAP3/AAAMAAMAAAACAP7/AAAMAAMAAAACAP//AAAMAAMAAAA=")
+tile_set = SubResource("TileSet_kf7eg")
+collision_enabled = false
+navigation_enabled = false
+
+[node name="Terrain" type="TileMapLayer" parent="."]
+z_index = 1
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAAJAAYAAAAMAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAAHAAEAAAABAAUAAAALAAEAAAABAAYAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAACAAYAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAADAAYAAAALAAEAAAAEAAEAAAAHAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAEAAYAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAAHAAEAAAAFAAUAAAALAAEAAAAFAAYAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAGAAYAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAHAAYAAAAHAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAAHAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAIAAYAAAALAAEAAAAKAAEAAAAIAAUAAAALAAEAAAAIAAUAAAAMAAEAAAAIAAUAAAANAAEAAAAIAAUAAAAKAAIAAAAIAAYAAAALAAIAAAAIAAYAAAAMAAIAAAAIAAYAAAANAAIAAAAIAAYAAAAKAAMAAAALAAEAAAAKAAQAAAALAAEAAAALAAMAAAALAAEAAAALAAQAAAALAAEAAAAMAAMAAAALAAEAAAAMAAQAAAALAAEAAAANAAMAAAALAAEAAAANAAQAAAALAAEAAAAOAAEAAAAIAAUAAAAPAAEAAAAIAAUAAAAQAAEAAAAIAAUAAAAOAAIAAAAIAAYAAAAPAAIAAAAIAAYAAAAQAAIAAAAIAAYAAAAOAAMAAAALAAEAAAAPAAMAAAALAAEAAAAPAAQAAAALAAEAAAAQAAQAAAALAAEAAAAQAAMAAAALAAEAAAAOAAQAAAALAAEAAAARAAAAAAALAAQAAAARAAEAAAALAAUAAAARAAIAAAALAAUAAAARAAMAAAALAAUAAAARAAQAAAALAAUAAAASAAAAAAAMAAQAAAATAAAAAAAMAAQAAAAUAAAAAAAMAAQAAAAVAAAAAAAMAAQAAAAWAAAAAAAMAAQAAAAXAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAATAAEAAAAMAAUAAAATAAIAAAAJAAYAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAKAAYAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAARAAUAAAALAAUAAAARAAYAAAALAAUAAAASAAUAAAAJAAYAAAASAAYAAAAMAAUAAAATAAUAAAAMAAUAAAATAAYAAAAMAAUAAAAUAAUAAAAMAAUAAAAUAAYAAAAMAAUAAAAVAAUAAAAMAAUAAAAVAAYAAAAMAAUAAAAWAAUAAAAMAAUAAAAWAAYAAAAMAAUAAAAXAAUAAAAMAAUAAAAXAAYAAAAMAAUAAAAKAP7/AAALAAQAAAALAP7/AAAMAAQAAAAMAP7/AAAMAAQAAAAKAP//AAALAAYAAAALAP//AAAMAAYAAAAMAP//AAAMAAYAAAAQAP7/AAANAAQAAAAQAP//AAANAAYAAAANAP7/AAAMAAQAAAAOAP7/AAAMAAQAAAAPAP7/AAAMAAQAAAANAP//AAAMAAYAAAAOAP//AAAMAAYAAAAPAP//AAAMAAYAAAAMAP3/AAAOAAAAAAADAP//AAAOAAIAAAAEAP//AAAPAAIAAAAFAP//AAAQAAIAAAAGAP//AAAOAAIAAAAHAP//AAAPAAIAAAAIAP//AAAQAAIAAAD//wAAAAAKAAAAAAD//wEAAAAKAAEAAAD//wIAAAAKAAEAAAD//wMAAAAKAAEAAAD//wQAAAAKAAEAAAD//wUAAAAKAAEAAAD//wYAAAAKAAEAAAD//wcAAAAKAAEAAAD//wgAAAAKAAEAAAAAAAAAAAALAAAAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAAAAAYAAAALAAEAAAAAAAcAAAALAAEAAAAAAAgAAAALAAEAAAABAAgAAAALAAEAAAACAAgAAAALAAEAAAADAAgAAAALAAEAAAAEAAgAAAALAAEAAAAFAAgAAAALAAEAAAAGAAgAAAALAAEAAAAHAAgAAAALAAEAAAAIAAgAAAALAAEAAAAJAAgAAAAMAAEAAAAJAAcAAAAMAAEAAAAIAAcAAAALAAEAAAAHAAcAAAALAAEAAAAGAAcAAAAHAAEAAAAFAAcAAAALAAEAAAAEAAcAAAALAAEAAAADAAcAAAALAAEAAAACAAcAAAALAAEAAAABAAcAAAALAAEAAAD///v/AAANAAQAAAD///z/AAANAAUAAAD///3/AAANAAUAAAD///7/AAANAAUAAAD/////AAANAAYAAAD+//v/AAAMAAQAAAD9//v/AAAMAAQAAAD+//z/AAAJAAYAAAD9//z/AAAMAAUAAAD6////AAAMAAUAAAD7////AAAMAAUAAAD8////AAAMAAUAAAD9////AAAMAAUAAAD+////AAAMAAUAAAD+//7/AAAMAAUAAAD+//3/AAAMAAUAAAD9//3/AAAMAAUAAAD9//7/AAAKAAYAAAD+/wAAAAANAAUAAAD+/wEAAAANAAUAAAD+/wIAAAANAAUAAAD+/wMAAAANAAUAAAD+/wQAAAANAAUAAAD+/wUAAAANAAUAAAD+/wYAAAANAAUAAAD9/wAAAAAMAAUAAAD8/wAAAAAMAAUAAAD7/wAAAAAMAAUAAAD6/wAAAAAMAAUAAAD5/wAAAAALAAUAAAD6/wEAAAAMAAUAAAD6/wIAAAAMAAUAAAD6/wMAAAAMAAUAAAD7/wMAAAAMAAUAAAD7/wQAAAAMAAUAAAD8/wEAAAAMAAUAAAD9/wEAAAAMAAUAAAD9/wIAAAAMAAUAAAD9/wMAAAAMAAUAAAD9/wQAAAAMAAUAAAD9/wUAAAAMAAUAAAD9/wYAAAAMAAUAAAD8/wUAAAAMAAUAAAD7/wUAAAAMAAUAAAD8/wYAAAAMAAUAAAD8/wQAAAAKAAYAAAD8/wMAAAAMAAUAAAD8/wIAAAAMAAUAAAD7/wEAAAAMAAUAAAD7/wIAAAAJAAYAAAD7/wYAAAAMAAUAAAD6/wYAAAAMAAUAAAD6/wUAAAAMAAUAAAD6/wQAAAAMAAUAAAD5////AAALAAUAAAD5/wEAAAALAAUAAAD5/wIAAAALAAUAAAD5/wMAAAALAAUAAAD5/wQAAAALAAUAAAD5/wUAAAALAAUAAAD5/wYAAAALAAUAAAD8//r/AAALAAMAAAAOAP3/AAALAAMAAAALAP3/AAALAAMAAAASAP//AAALAAMAAAAUAP//AAALAAMAAAD6//r/AAAQAAUAAAD7//r/AAALAAMAAAANAP3/AAAOAAYAAAAWAP//AAAPAAYAAAD9//r/AAAPAAUAAAAXAP//AAAQAAUAAAD5//v/AAALAAQAAAD5//z/AAALAAUAAAD5//3/AAALAAUAAAD5//7/AAALAAUAAAD6//v/AAAMAAQAAAD6//z/AAAKAAYAAAD6//3/AAAMAAUAAAD6//7/AAAMAAUAAAD7//v/AAAMAAQAAAD7//z/AAAMAAUAAAD7//3/AAAMAAUAAAD7//7/AAAMAAUAAAD8//v/AAAMAAQAAAD8//z/AAAMAAUAAAD8//3/AAAMAAUAAAD8//7/AAAMAAUAAAARAAcAAAALAAUAAAARAAgAAAALAAYAAAAXAAcAAAAMAAUAAAAWAAcAAAAMAAUAAAAVAAcAAAAMAAUAAAAUAAcAAAAMAAUAAAATAAcAAAAMAAUAAAASAAcAAAAMAAUAAAASAAgAAAAMAAYAAAATAAgAAAAMAAYAAAAUAAgAAAAMAAYAAAAVAAgAAAAMAAYAAAAWAAgAAAAMAAYAAAAXAAgAAAAMAAYAAAAKAAUAAAALAAEAAAAKAAYAAAALAAEAAAAKAAcAAAALAAEAAAAKAAgAAAALAAEAAAALAAUAAAALAAEAAAALAAYAAAALAAEAAAALAAcAAAALAAEAAAALAAgAAAALAAEAAAAMAAUAAAALAAEAAAAMAAYAAAALAAEAAAAMAAcAAAALAAEAAAAMAAgAAAALAAEAAAANAAUAAAALAAEAAAANAAYAAAALAAEAAAANAAcAAAALAAEAAAANAAgAAAALAAEAAAAOAAUAAAALAAEAAAAOAAYAAAALAAEAAAAOAAcAAAALAAEAAAAOAAgAAAALAAEAAAAPAAUAAAALAAEAAAAPAAYAAAALAAEAAAAPAAcAAAALAAEAAAAPAAgAAAALAAEAAAAQAAUAAAALAAEAAAAQAAYAAAALAAEAAAAQAAcAAAALAAEAAAAQAAgAAAALAAEAAAAdAAAAAAANAAQAAAAdAAEAAAANAAUAAAAdAAIAAAANAAUAAAAdAAMAAAANAAUAAAAdAAQAAAANAAUAAAAdAAUAAAANAAUAAAAdAAYAAAANAAUAAAAdAAcAAAANAAUAAAAdAAgAAAANAAYAAAAZAP//AAAOAAQAAAAYAAAAAAAMAAQAAAAZAAAAAAAMAAQAAAAaAAAAAAAMAAQAAAAbAAAAAAAMAAQAAAAcAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAZAAEAAAAMAAUAAAAaAAEAAAAMAAUAAAAbAAEAAAAMAAUAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAJAAYAAAAbAAIAAAAMAAUAAAAaAAIAAAAMAAUAAAAZAAIAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAYAAYAAAAMAAUAAAAYAAcAAAAMAAUAAAAYAAgAAAAMAAYAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAZAAYAAAAMAAUAAAAZAAcAAAAMAAUAAAAZAAgAAAAMAAYAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAaAAYAAAAKAAYAAAAaAAcAAAAMAAUAAAAaAAgAAAAMAAYAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAbAAYAAAAMAAUAAAAbAAcAAAAMAAUAAAAbAAgAAAAMAAYAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAcAAYAAAAMAAUAAAAcAAcAAAAMAAUAAAAcAAgAAAAMAAYAAAAPAP3/AAAQAAYAAAAiAPr/AAAQAAYAAAAfAPr/AAAOAAYAAAAkAPr/AAAPAAYAAAAgAPr/AAAPAAUAAAAbAP//AAALAAMAAAAaAP//AAALAAMAAAAjAPr/AAALAAMAAAAhAPr/AAALAAMAAAATAP//AAALAAMAAAAVAP//AAALAAMAAAAeAPv/AAALAAQAAAAeAPz/AAALAAUAAAAeAP3/AAALAAUAAAAeAP7/AAALAAUAAAAeAP//AAALAAUAAAAmAP//AAANAAUAAAAmAP7/AAANAAUAAAAmAP3/AAANAAUAAAAmAPv/AAANAAQAAAAfAPv/AAAMAAQAAAAgAPv/AAAMAAQAAAAhAPv/AAAMAAQAAAAiAPv/AAAMAAQAAAAjAPv/AAAMAAQAAAAkAPv/AAAMAAQAAAAlAPv/AAAMAAQAAAAmAPz/AAANAAUAAAAlAP//AAAMAAUAAAAlAP7/AAAMAAUAAAAlAP3/AAAMAAUAAAAlAPz/AAAMAAUAAAAkAPz/AAAMAAUAAAAjAPz/AAAMAAUAAAAiAPz/AAAMAAUAAAAhAPz/AAAMAAUAAAAgAPz/AAAMAAUAAAAfAPz/AAAMAAUAAAAfAP3/AAAKAAYAAAAfAP7/AAAMAAUAAAAfAP//AAAMAAUAAAAkAP//AAAKAAYAAAAkAP7/AAAJAAYAAAAkAP3/AAAMAAUAAAAjAP3/AAAMAAUAAAAiAP3/AAAMAAUAAAAhAP3/AAAMAAUAAAAgAP3/AAAMAAUAAAAgAP7/AAAJAAYAAAAgAP//AAAMAAUAAAAjAP//AAAMAAUAAAAjAP7/AAAMAAUAAAAiAP7/AAAMAAUAAAAhAP7/AAAMAAUAAAAhAP//AAAMAAUAAAAiAP//AAAMAAUAAAAeAAgAAAALAAYAAAAeAAcAAAALAAUAAAAeAAYAAAALAAUAAAAeAAUAAAALAAUAAAAeAAQAAAALAAUAAAAeAAMAAAALAAUAAAAeAAIAAAALAAUAAAAeAAEAAAALAAUAAAAeAAAAAAALAAUAAAAfAAgAAAAMAAYAAAAgAAgAAAAMAAYAAAAhAAgAAAAMAAYAAAAiAAgAAAAMAAYAAAAjAAgAAAAMAAYAAAAkAAgAAAAMAAYAAAAlAAgAAAAMAAYAAAAmAAgAAAANAAYAAAAmAAAAAAANAAUAAAAmAAEAAAANAAUAAAAmAAIAAAANAAUAAAAmAAMAAAANAAUAAAAmAAQAAAANAAUAAAAmAAUAAAANAAUAAAAmAAYAAAANAAUAAAAmAAcAAAANAAUAAAAfAAAAAAAMAAUAAAAfAAEAAAAMAAUAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAfAAYAAAAKAAYAAAAfAAcAAAAMAAUAAAAgAAAAAAAMAAUAAAAgAAEAAAAMAAUAAAAgAAIAAAAMAAUAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAgAAYAAAAMAAUAAAAgAAcAAAAMAAUAAAAhAAAAAAAMAAUAAAAhAAEAAAAMAAUAAAAhAAIAAAAKAAYAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAhAAYAAAAMAAUAAAAhAAcAAAAMAAUAAAAiAAAAAAAMAAUAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAKAAYAAAAiAAUAAAAKAAYAAAAiAAYAAAAMAAUAAAAiAAcAAAAMAAUAAAAjAAAAAAAMAAUAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAjAAYAAAAMAAUAAAAjAAcAAAAMAAUAAAAkAAAAAAAKAAYAAAAkAAEAAAAMAAUAAAAkAAIAAAAMAAUAAAAkAAMAAAAMAAUAAAAkAAQAAAAMAAUAAAAkAAUAAAAMAAUAAAAkAAYAAAAMAAUAAAAkAAcAAAAKAAYAAAAlAAAAAAAMAAUAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAlAAYAAAAMAAUAAAAlAAcAAAAMAAUAAAD6/wcAAAAMAAUAAAD7/wcAAAAMAAUAAAD8/wcAAAAMAAUAAAD9/wcAAAAMAAUAAAD5/wcAAAALAAUAAAD+/wcAAAANAAUAAAD5/wgAAAALAAYAAAD6/wgAAAAMAAYAAAD7/wgAAAAMAAYAAAD8/wgAAAAMAAYAAAD9/wgAAAAMAAYAAAD+/wgAAAANAAYAAAA=")
+tile_set = SubResource("TileSet_kf7eg")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_4bfy0")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_vdqsb")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Controls" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("4_w2gh7")
+text = "[WASD] to move
+[Space] to jump
+[Q] to trigger Noise Emitter"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(227, -28)
+offset = Vector2(2.3068, -7.8485)
+ignore_rotation = false
+zoom = Vector2(1.5, 1.5)
+process_callback = 0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("5_d6fcf")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+process_priority = -1
+top_level = true
+position = Vector2(227, -28)
+script = ExtResource("6_bdmii")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D")
+zoom = Vector2(1.5, 1.5)
+frame_preview = false
+tween_resource = ExtResource("7_dpnkg")
+tween_on_load = false
+follow_damping = true
+draw_limits = true
+noise = SubResource("Resource_87ddr")
+noise_emitter_layer = 1
+
+[node name="PlayerPhantomCameraNoiseEmitter2D" type="Node2D" parent="Player"]
+unique_name_in_owner = true
+script = ExtResource("10_p43w0")
+noise = SubResource("Resource_rmnw1")
+duration = 0.1
+decay_time = 0.1
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("8_u5o87")]
+z_index = 2
+position = Vector2(227, -28)
+script = ExtResource("9_suxld")
diff --git a/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_tweening_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_tweening_example_scene.tscn
new file mode 100644
index 0000000..523b72c
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D-4.3/2d_tweening_example_scene.tscn
@@ -0,0 +1,402 @@
+[gd_scene load_steps=20 format=4 uid="uid://bvpp5na5054jd"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_h1rbo"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_1f2t2"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_o6nri"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_j7670"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="5_gvv7r"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="6_rwobr"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="7_ylx0h"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_ytsgf"]
+[ext_resource type="Script" uid="uid://t8wa4e5y5hcf" path="res://addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd" id="9_3r1pw"]
+[ext_resource type="Script" uid="uid://cnnaky2ns2pn4" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd" id="9_5jy5e"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="10_guf2v"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_cvmao"]
+texture = ExtResource("1_h1rbo")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_na7gm"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_cvmao")
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_tgk1y"]
+size = Vector2(140, 160)
+
+[sub_resource type="Resource" id="Resource_mtp70"]
+script = ExtResource("10_guf2v")
+duration = 0.6
+transition = 1
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_clm0y"]
+size = Vector2(104, 160)
+
+[sub_resource type="Resource" id="Resource_8jg5c"]
+script = ExtResource("10_guf2v")
+duration = 0.3
+transition = 8
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_uka0w"]
+size = Vector2(560, 160)
+
+[sub_resource type="Resource" id="Resource_e4e41"]
+script = ExtResource("10_guf2v")
+duration = 1.2
+transition = 10
+ease = 2
+
+[node name="Root" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="Pillar" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAAAAPr/AAAKAAIAAAAAAPv/AAAKAAMAAAAAAPz/AAAKAAMAAAAAAP3/AAAKAAMAAAAAAP7/AAAKAAMAAAAAAP//AAAKAAMAAAABAPr/AAALAAIAAAABAPv/AAALAAEAAAABAPz/AAALAAEAAAABAP3/AAALAAEAAAABAP7/AAALAAEAAAABAP//AAALAAEAAAACAPr/AAAMAAIAAAACAPv/AAAMAAMAAAACAPz/AAAMAAMAAAACAP3/AAAMAAMAAAACAP7/AAAMAAMAAAACAP//AAAMAAMAAAA=")
+tile_set = SubResource("TileSet_na7gm")
+collision_enabled = false
+navigation_enabled = false
+
+[node name="Terrain" type="TileMapLayer" parent="."]
+use_parent_material = true
+scale = Vector2(3, 3)
+tile_map_data = PackedByteArray("AAABAAAAAAALAAAAAAACAAAAAAALAAAAAAADAAAAAAALAAAAAAAEAAAAAAALAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAHAAAAAAALAAAAAAAIAAAAAAALAAAAAAAJAAAAAAAMAAAAAAAJAAEAAAAMAAEAAAAJAAIAAAAMAAEAAAAJAAMAAAAMAAEAAAAJAAQAAAAMAAEAAAAJAAUAAAAMAAEAAAAJAAYAAAAMAAEAAAABAAEAAAALAAEAAAABAAIAAAALAAEAAAABAAMAAAALAAEAAAABAAQAAAAHAAEAAAABAAUAAAALAAEAAAABAAYAAAALAAEAAAACAAEAAAALAAEAAAACAAIAAAALAAEAAAACAAMAAAALAAEAAAACAAQAAAALAAEAAAACAAUAAAALAAEAAAACAAYAAAALAAEAAAADAAEAAAALAAEAAAADAAIAAAALAAEAAAADAAMAAAALAAEAAAADAAQAAAALAAEAAAADAAUAAAALAAEAAAADAAYAAAALAAEAAAAEAAEAAAAHAAEAAAAEAAIAAAALAAEAAAAEAAMAAAALAAEAAAAEAAQAAAALAAEAAAAEAAUAAAALAAEAAAAEAAYAAAALAAEAAAAFAAEAAAALAAEAAAAFAAIAAAALAAEAAAAFAAMAAAALAAEAAAAFAAQAAAAHAAEAAAAFAAUAAAALAAEAAAAFAAYAAAALAAEAAAAGAAEAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAGAAQAAAALAAEAAAAGAAUAAAALAAEAAAAGAAYAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAHAAMAAAALAAEAAAAHAAQAAAALAAEAAAAHAAUAAAALAAEAAAAHAAYAAAAHAAEAAAAIAAEAAAALAAEAAAAIAAIAAAALAAEAAAAIAAMAAAAHAAEAAAAIAAQAAAALAAEAAAAIAAUAAAALAAEAAAAIAAYAAAALAAEAAAAKAAEAAAAIAAUAAAALAAEAAAAIAAUAAAAMAAEAAAAIAAUAAAANAAEAAAAIAAUAAAAKAAIAAAAIAAYAAAALAAIAAAAIAAYAAAAMAAIAAAAIAAYAAAANAAIAAAAIAAYAAAAKAAMAAAALAAEAAAAKAAQAAAALAAEAAAALAAMAAAALAAEAAAALAAQAAAALAAEAAAAMAAMAAAALAAEAAAAMAAQAAAALAAEAAAANAAMAAAALAAEAAAANAAQAAAALAAEAAAAOAAEAAAAIAAUAAAAPAAEAAAAIAAUAAAAQAAEAAAAIAAUAAAAOAAIAAAAIAAYAAAAPAAIAAAAIAAYAAAAQAAIAAAAIAAYAAAAOAAMAAAALAAEAAAAPAAMAAAALAAEAAAAPAAQAAAALAAEAAAAQAAQAAAALAAEAAAAQAAMAAAALAAEAAAAOAAQAAAALAAEAAAARAAAAAAALAAQAAAARAAEAAAALAAUAAAARAAIAAAALAAUAAAARAAMAAAALAAUAAAARAAQAAAALAAUAAAASAAAAAAAMAAQAAAATAAAAAAAMAAQAAAAUAAAAAAAMAAQAAAAVAAAAAAAMAAQAAAAWAAAAAAAMAAQAAAAXAAAAAAAMAAQAAAASAAEAAAAMAAUAAAASAAIAAAAMAAUAAAASAAMAAAAMAAUAAAASAAQAAAAMAAUAAAATAAEAAAAMAAUAAAATAAIAAAAJAAYAAAATAAMAAAAMAAUAAAATAAQAAAAMAAUAAAAUAAEAAAAMAAUAAAAUAAIAAAAMAAUAAAAUAAMAAAAMAAUAAAAUAAQAAAAMAAUAAAAVAAEAAAAMAAUAAAAVAAIAAAAMAAUAAAAVAAMAAAAMAAUAAAAVAAQAAAAMAAUAAAAWAAEAAAAMAAUAAAAWAAIAAAAMAAUAAAAWAAMAAAAMAAUAAAAWAAQAAAAKAAYAAAAXAAEAAAAMAAUAAAAXAAIAAAAMAAUAAAAXAAMAAAAMAAUAAAAXAAQAAAAMAAUAAAARAAUAAAALAAUAAAARAAYAAAALAAUAAAASAAUAAAAJAAYAAAASAAYAAAAMAAUAAAATAAUAAAAMAAUAAAATAAYAAAAMAAUAAAAUAAUAAAAMAAUAAAAUAAYAAAAMAAUAAAAVAAUAAAAMAAUAAAAVAAYAAAAMAAUAAAAWAAUAAAAMAAUAAAAWAAYAAAAMAAUAAAAXAAUAAAAMAAUAAAAXAAYAAAAMAAUAAAAKAP7/AAALAAQAAAALAP7/AAAMAAQAAAAMAP7/AAAMAAQAAAAKAP//AAALAAYAAAALAP//AAAMAAYAAAAMAP//AAAMAAYAAAAQAP7/AAANAAQAAAAQAP//AAANAAYAAAANAP7/AAAMAAQAAAAOAP7/AAAMAAQAAAAPAP7/AAAMAAQAAAANAP//AAAMAAYAAAAOAP//AAAMAAYAAAAPAP//AAAMAAYAAAADAP//AAAOAAIAAAAEAP//AAAPAAIAAAAFAP//AAAQAAIAAAAGAP//AAAOAAIAAAAHAP//AAAPAAIAAAAIAP//AAAQAAIAAAD//wAAAAAKAAAAAAD//wEAAAAKAAEAAAD//wIAAAAKAAEAAAD//wMAAAAKAAEAAAD//wQAAAAKAAEAAAD//wUAAAAKAAEAAAD//wYAAAAKAAEAAAD//wcAAAAKAAEAAAD//wgAAAAKAAEAAAAAAAAAAAALAAAAAAAAAAEAAAALAAEAAAAAAAIAAAALAAEAAAAAAAMAAAALAAEAAAAAAAQAAAALAAEAAAAAAAUAAAALAAEAAAAAAAYAAAALAAEAAAAAAAcAAAALAAEAAAAAAAgAAAALAAEAAAABAAgAAAALAAEAAAACAAgAAAALAAEAAAADAAgAAAALAAEAAAAEAAgAAAALAAEAAAAFAAgAAAALAAEAAAAGAAgAAAALAAEAAAAHAAgAAAALAAEAAAAIAAgAAAALAAEAAAAJAAgAAAAMAAEAAAAJAAcAAAAMAAEAAAAIAAcAAAALAAEAAAAHAAcAAAALAAEAAAAGAAcAAAAHAAEAAAAFAAcAAAALAAEAAAAEAAcAAAALAAEAAAADAAcAAAALAAEAAAACAAcAAAALAAEAAAABAAcAAAALAAEAAAD///v/AAANAAQAAAD///z/AAANAAUAAAD///3/AAANAAUAAAD///7/AAANAAUAAAD/////AAANAAYAAAD+//v/AAAMAAQAAAD9//v/AAAMAAQAAAD+//z/AAAJAAYAAAD9//z/AAAMAAUAAAD6////AAAMAAUAAAD7////AAAMAAUAAAD8////AAAMAAUAAAD9////AAAMAAUAAAD+////AAAMAAUAAAD+//7/AAAMAAUAAAD+//3/AAAMAAUAAAD9//3/AAAMAAUAAAD9//7/AAAKAAYAAAD+/wAAAAANAAUAAAD+/wEAAAANAAUAAAD+/wIAAAANAAUAAAD+/wMAAAANAAUAAAD+/wQAAAANAAUAAAD+/wUAAAANAAUAAAD+/wYAAAANAAUAAAD9/wAAAAAMAAUAAAD8/wAAAAAMAAUAAAD7/wAAAAAMAAUAAAD6/wAAAAAMAAUAAAD5/wAAAAALAAUAAAD6/wEAAAAMAAUAAAD6/wIAAAAMAAUAAAD6/wMAAAAMAAUAAAD7/wMAAAAMAAUAAAD7/wQAAAAMAAUAAAD8/wEAAAAMAAUAAAD9/wEAAAAMAAUAAAD9/wIAAAAMAAUAAAD9/wMAAAAMAAUAAAD9/wQAAAAMAAUAAAD9/wUAAAAMAAUAAAD9/wYAAAAMAAUAAAD8/wUAAAAMAAUAAAD7/wUAAAAMAAUAAAD8/wYAAAAMAAUAAAD8/wQAAAAKAAYAAAD8/wMAAAAMAAUAAAD8/wIAAAAMAAUAAAD7/wEAAAAMAAUAAAD7/wIAAAAJAAYAAAD7/wYAAAAMAAUAAAD6/wYAAAAMAAUAAAD6/wUAAAAMAAUAAAD6/wQAAAAMAAUAAAD5////AAALAAUAAAD5/wEAAAALAAUAAAD5/wIAAAALAAUAAAD5/wMAAAALAAUAAAD5/wQAAAALAAUAAAD5/wUAAAALAAUAAAD5/wYAAAALAAUAAAD8//r/AAALAAMAAAAOAP3/AAALAAMAAAALAP3/AAALAAMAAAASAP//AAALAAMAAAAUAP//AAALAAMAAAD6//r/AAAQAAUAAAD7//r/AAALAAMAAAANAP3/AAAOAAYAAAAWAP//AAAPAAYAAAD9//r/AAAPAAUAAAAXAP//AAAQAAUAAAD5//v/AAALAAQAAAD5//z/AAALAAUAAAD5//3/AAALAAUAAAD5//7/AAALAAUAAAD6//v/AAAMAAQAAAD6//z/AAAKAAYAAAD6//3/AAAMAAUAAAD6//7/AAAMAAUAAAD7//v/AAAMAAQAAAD7//z/AAAMAAUAAAD7//3/AAAMAAUAAAD7//7/AAAMAAUAAAD8//v/AAAMAAQAAAD8//z/AAAMAAUAAAD8//3/AAAMAAUAAAD8//7/AAAMAAUAAAARAAcAAAALAAUAAAARAAgAAAALAAYAAAAXAAcAAAAMAAUAAAAWAAcAAAAMAAUAAAAVAAcAAAAMAAUAAAAUAAcAAAAMAAUAAAATAAcAAAAMAAUAAAASAAcAAAAMAAUAAAASAAgAAAAMAAYAAAATAAgAAAAMAAYAAAAUAAgAAAAMAAYAAAAVAAgAAAAMAAYAAAAWAAgAAAAMAAYAAAAXAAgAAAAMAAYAAAAKAAUAAAALAAEAAAAKAAYAAAALAAEAAAAKAAcAAAALAAEAAAAKAAgAAAALAAEAAAALAAUAAAALAAEAAAALAAYAAAALAAEAAAALAAcAAAALAAEAAAALAAgAAAALAAEAAAAMAAUAAAALAAEAAAAMAAYAAAALAAEAAAAMAAcAAAALAAEAAAAMAAgAAAALAAEAAAANAAUAAAALAAEAAAANAAYAAAALAAEAAAANAAcAAAALAAEAAAANAAgAAAALAAEAAAAOAAUAAAALAAEAAAAOAAYAAAALAAEAAAAOAAcAAAALAAEAAAAOAAgAAAALAAEAAAAPAAUAAAALAAEAAAAPAAYAAAALAAEAAAAPAAcAAAALAAEAAAAPAAgAAAALAAEAAAAQAAUAAAALAAEAAAAQAAYAAAALAAEAAAAQAAcAAAALAAEAAAAQAAgAAAALAAEAAAAdAAAAAAANAAQAAAAdAAEAAAANAAUAAAAdAAIAAAANAAUAAAAdAAMAAAANAAUAAAAdAAQAAAANAAUAAAAdAAUAAAANAAUAAAAdAAYAAAANAAUAAAAdAAcAAAANAAUAAAAdAAgAAAANAAYAAAAYAAAAAAAMAAQAAAAZAAAAAAAMAAQAAAAaAAAAAAAMAAQAAAAbAAAAAAAMAAQAAAAcAAAAAAAMAAQAAAAYAAEAAAAMAAUAAAAZAAEAAAAMAAUAAAAaAAEAAAAMAAUAAAAbAAEAAAAMAAUAAAAcAAEAAAAMAAUAAAAcAAIAAAAMAAUAAAAcAAMAAAAJAAYAAAAbAAIAAAAMAAUAAAAaAAIAAAAMAAUAAAAZAAIAAAAMAAUAAAAYAAIAAAAMAAUAAAAYAAMAAAAMAAUAAAAYAAQAAAAMAAUAAAAYAAUAAAAMAAUAAAAYAAYAAAAMAAUAAAAYAAcAAAAMAAUAAAAYAAgAAAAMAAYAAAAZAAMAAAAMAAUAAAAZAAQAAAAMAAUAAAAZAAUAAAAMAAUAAAAZAAYAAAAMAAUAAAAZAAcAAAAMAAUAAAAZAAgAAAAMAAYAAAAaAAMAAAAMAAUAAAAaAAQAAAAMAAUAAAAaAAUAAAAMAAUAAAAaAAYAAAAKAAYAAAAaAAcAAAAMAAUAAAAaAAgAAAAMAAYAAAAbAAMAAAAMAAUAAAAbAAQAAAAMAAUAAAAbAAUAAAAMAAUAAAAbAAYAAAAMAAUAAAAbAAcAAAAMAAUAAAAbAAgAAAAMAAYAAAAcAAQAAAAMAAUAAAAcAAUAAAAMAAUAAAAcAAYAAAAMAAUAAAAcAAcAAAAMAAUAAAAcAAgAAAAMAAYAAAAPAP3/AAAQAAYAAAAiAPr/AAAQAAYAAAAfAPr/AAAOAAYAAAAkAPr/AAAPAAYAAAAgAPr/AAAPAAUAAAAbAP//AAALAAMAAAAaAP//AAALAAMAAAAjAPr/AAALAAMAAAAhAPr/AAALAAMAAAATAP//AAALAAMAAAAVAP//AAALAAMAAAAeAPv/AAALAAQAAAAeAPz/AAALAAUAAAAeAP3/AAALAAUAAAAeAP7/AAALAAUAAAAeAP//AAALAAUAAAAmAP//AAANAAUAAAAmAP7/AAANAAUAAAAmAP3/AAANAAUAAAAmAPv/AAANAAQAAAAfAPv/AAAMAAQAAAAgAPv/AAAMAAQAAAAhAPv/AAAMAAQAAAAiAPv/AAAMAAQAAAAjAPv/AAAMAAQAAAAkAPv/AAAMAAQAAAAlAPv/AAAMAAQAAAAmAPz/AAANAAUAAAAlAP//AAAMAAUAAAAlAP7/AAAMAAUAAAAlAP3/AAAMAAUAAAAlAPz/AAAMAAUAAAAkAPz/AAAMAAUAAAAjAPz/AAAMAAUAAAAiAPz/AAAMAAUAAAAhAPz/AAAMAAUAAAAgAPz/AAAMAAUAAAAfAPz/AAAMAAUAAAAfAP3/AAAKAAYAAAAfAP7/AAAMAAUAAAAfAP//AAAMAAUAAAAkAP//AAAKAAYAAAAkAP7/AAAJAAYAAAAkAP3/AAAMAAUAAAAjAP3/AAAMAAUAAAAiAP3/AAAMAAUAAAAhAP3/AAAMAAUAAAAgAP3/AAAMAAUAAAAgAP7/AAAJAAYAAAAgAP//AAAMAAUAAAAjAP//AAAMAAUAAAAjAP7/AAAMAAUAAAAiAP7/AAAMAAUAAAAhAP7/AAAMAAUAAAAhAP//AAAMAAUAAAAiAP//AAAMAAUAAAAeAAgAAAALAAYAAAAeAAcAAAALAAUAAAAeAAYAAAALAAUAAAAeAAUAAAALAAUAAAAeAAQAAAALAAUAAAAeAAMAAAALAAUAAAAeAAIAAAALAAUAAAAeAAEAAAALAAUAAAAeAAAAAAALAAUAAAAfAAgAAAAMAAYAAAAgAAgAAAAMAAYAAAAhAAgAAAAMAAYAAAAiAAgAAAAMAAYAAAAjAAgAAAAMAAYAAAAkAAgAAAAMAAYAAAAlAAgAAAAMAAYAAAAmAAgAAAANAAYAAAAmAAAAAAANAAUAAAAmAAEAAAANAAUAAAAmAAIAAAANAAUAAAAmAAMAAAANAAUAAAAmAAQAAAANAAUAAAAmAAUAAAANAAUAAAAmAAYAAAANAAUAAAAmAAcAAAANAAUAAAAfAAAAAAAMAAUAAAAfAAEAAAAMAAUAAAAfAAIAAAAMAAUAAAAfAAMAAAAMAAUAAAAfAAQAAAAMAAUAAAAfAAUAAAAMAAUAAAAfAAYAAAAKAAYAAAAfAAcAAAAMAAUAAAAgAAAAAAAMAAUAAAAgAAEAAAAMAAUAAAAgAAIAAAAMAAUAAAAgAAMAAAAMAAUAAAAgAAQAAAAMAAUAAAAgAAUAAAAMAAUAAAAgAAYAAAAMAAUAAAAgAAcAAAAMAAUAAAAhAAAAAAAMAAUAAAAhAAEAAAAMAAUAAAAhAAIAAAAKAAYAAAAhAAMAAAAMAAUAAAAhAAQAAAAMAAUAAAAhAAUAAAAMAAUAAAAhAAYAAAAMAAUAAAAhAAcAAAAMAAUAAAAiAAAAAAAMAAUAAAAiAAEAAAAMAAUAAAAiAAIAAAAMAAUAAAAiAAMAAAAMAAUAAAAiAAQAAAAKAAYAAAAiAAUAAAAKAAYAAAAiAAYAAAAMAAUAAAAiAAcAAAAMAAUAAAAjAAAAAAAMAAUAAAAjAAEAAAAMAAUAAAAjAAIAAAAMAAUAAAAjAAMAAAAMAAUAAAAjAAQAAAAMAAUAAAAjAAUAAAAMAAUAAAAjAAYAAAAMAAUAAAAjAAcAAAAMAAUAAAAkAAAAAAAKAAYAAAAkAAEAAAAMAAUAAAAkAAIAAAAMAAUAAAAkAAMAAAAMAAUAAAAkAAQAAAAMAAUAAAAkAAUAAAAMAAUAAAAkAAYAAAAMAAUAAAAkAAcAAAAKAAYAAAAlAAAAAAAMAAUAAAAlAAEAAAAMAAUAAAAlAAIAAAAMAAUAAAAlAAMAAAAMAAUAAAAlAAQAAAAMAAUAAAAlAAUAAAAMAAUAAAAlAAYAAAAMAAUAAAAlAAcAAAAMAAUAAAD6/wcAAAAMAAUAAAD7/wcAAAAMAAUAAAD8/wcAAAAMAAUAAAD9/wcAAAAMAAUAAAD5/wcAAAALAAUAAAD+/wcAAAANAAUAAAD5/wgAAAALAAYAAAD6/wgAAAAMAAYAAAD7/wgAAAAMAAYAAAD8/wgAAAAMAAYAAAD9/wgAAAAMAAYAAAD+/wgAAAANAAYAAAA=")
+tile_set = SubResource("TileSet_na7gm")
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_1f2t2")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_o6nri")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(227, -28)
+process_callback = 0
+position_smoothing_speed = 10.0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_j7670")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(227, -28)
+script = ExtResource("5_gvv7r")
+priority = 5
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D")
+tween_resource = ExtResource("6_rwobr")
+tween_on_load = false
+follow_damping = true
+draw_limits = true
+
+[node name="Label" type="Label" parent="Player"]
+offset_left = 167.0
+offset_top = -132.0
+offset_right = 332.0
+offset_bottom = -68.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("7_ylx0h")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("8_ytsgf")]
+position = Vector2(227, -28)
+script = ExtResource("9_5jy5e")
+
+[node name="WideArea" type="Area2D" parent="." node_paths=PackedStringArray("area_pcam")]
+position = Vector2(393, -40)
+collision_layer = 2
+collision_mask = 2
+script = ExtResource("9_3r1pw")
+area_pcam = NodePath("PhantomCamera2D")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="WideArea"]
+position = Vector2(0, -40)
+shape = SubResource("RectangleShape2D_tgk1y")
+
+[node name="ColorRect" type="ColorRect" parent="WideArea"]
+offset_left = -70.0
+offset_top = -120.0
+offset_right = 70.0
+offset_bottom = 40.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+color = Color(0.556863, 0.447059, 0.545098, 0.698039)
+
+[node name="Label" type="Label" parent="WideArea"]
+offset_left = -77.0
+offset_top = -250.0
+offset_right = 76.0
+offset_bottom = -120.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("7_ylx0h")
+text = "Transition Type:
+Sine
+
+Duration:
+0.6s"
+horizontal_alignment = 1
+
+[node name="PhantomCamera2D" type="Node2D" parent="WideArea"]
+position = Vector2(4, -100)
+script = ExtResource("5_gvv7r")
+zoom = Vector2(0.8, 0.8)
+tween_resource = SubResource("Resource_mtp70")
+draw_limits = true
+
+[node name="UpperZoomArea" type="Area2D" parent="." node_paths=PackedStringArray("area_pcam")]
+position = Vector2(649, -135)
+collision_layer = 2
+collision_mask = 2
+script = ExtResource("9_3r1pw")
+area_pcam = NodePath("PhantomCamera2D")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="UpperZoomArea"]
+position = Vector2(0, -40)
+shape = SubResource("RectangleShape2D_clm0y")
+
+[node name="CollisionShape2D2" type="CollisionShape2D" parent="UpperZoomArea"]
+position = Vector2(0, -40)
+shape = SubResource("RectangleShape2D_clm0y")
+
+[node name="ColorRect" type="ColorRect" parent="UpperZoomArea"]
+offset_left = -52.0
+offset_top = -120.0
+offset_right = 52.0
+offset_bottom = 40.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+color = Color(0.556863, 0.447059, 0.545098, 0.698039)
+
+[node name="Label" type="Label" parent="UpperZoomArea"]
+offset_left = -74.0
+offset_top = -251.0
+offset_right = 79.0
+offset_bottom = -121.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("7_ylx0h")
+text = "Transition Type:
+Circ
+
+Duration:
+0.3s"
+horizontal_alignment = 1
+
+[node name="PhantomCamera2D" type="Node2D" parent="UpperZoomArea"]
+position = Vector2(2, -83)
+script = ExtResource("5_gvv7r")
+zoom = Vector2(2, 2)
+tween_resource = SubResource("Resource_8jg5c")
+draw_limits = true
+
+[node name="ForwardArea" type="Area2D" parent="." node_paths=PackedStringArray("area_pcam")]
+position = Vector2(1136, -38)
+collision_layer = 2
+collision_mask = 2
+script = ExtResource("9_3r1pw")
+area_pcam = NodePath("PhantomCamera2D")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ForwardArea"]
+position = Vector2(0, -42)
+shape = SubResource("RectangleShape2D_uka0w")
+
+[node name="ColorRect" type="ColorRect" parent="ForwardArea"]
+offset_left = -280.0
+offset_top = -122.0
+offset_right = 280.0
+offset_bottom = 38.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+color = Color(0.556863, 0.447059, 0.545098, 0.698039)
+
+[node name="Label" type="Label" parent="ForwardArea"]
+offset_left = -76.0
+offset_top = -252.0
+offset_right = 77.0
+offset_bottom = -122.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("7_ylx0h")
+text = "Transition Type:
+Back
+
+Duration:
+1.2s"
+horizontal_alignment = 1
+
+[node name="PhantomCamera2D" type="Node2D" parent="ForwardArea"]
+position = Vector2(344, -46)
+script = ExtResource("5_gvv7r")
+zoom = Vector2(0.9, 0.9)
+tween_resource = SubResource("Resource_e4e41")
+draw_limits = true
diff --git a/addons/phantom_camera/examples/example_scenes/2D/2d_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D/2d_example_scene.tscn
new file mode 100644
index 0000000..d491411
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/2d_example_scene.tscn
@@ -0,0 +1,245 @@
+[gd_scene load_steps=11 format=3 uid="uid://drvexsp2t0nfy"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_1utlo"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="2_mgsut"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_54fc4"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="6_kqt1v"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="6_pxbym"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="7_62i3t"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="7_fdx1s"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="12_k4p0h"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("1_1utlo")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[node name="ExampleScene2D2" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="TileMap" type="TileMap" parent="."]
+z_index = -1
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_0/tile_data = PackedInt32Array(-393216, 655360, 2, -327680, 655360, 3, -262144, 655360, 3, -196608, 655360, 3, -131072, 655360, 3, -65536, 655360, 3, -393215, 720896, 2, -327679, 720896, 1, -262143, 720896, 1, -196607, 720896, 1, -131071, 720896, 1, -65535, 720896, 1, -393214, 786432, 2, -327678, 786432, 3, -262142, 786432, 3, -196606, 786432, 3, -131070, 786432, 3, -65534, 786432, 3)
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(1, 720896, 0, 2, 720896, 0, 3, 720896, 0, 4, 720896, 0, 5, 720896, 0, 6, 720896, 0, 7, 720896, 0, 8, 720896, 0, 9, 786432, 0, 65545, 786432, 1, 131081, 786432, 1, 196617, 786432, 1, 262153, 786432, 1, 327689, 786432, 1, 393225, 786432, 1, 65537, 720896, 1, 131073, 720896, 1, 196609, 720896, 1, 262145, 458752, 1, 327681, 720896, 1, 393217, 720896, 1, 65538, 720896, 1, 131074, 720896, 1, 196610, 720896, 1, 262146, 720896, 1, 327682, 720896, 1, 393218, 720896, 1, 65539, 720896, 1, 131075, 720896, 1, 196611, 720896, 1, 262147, 720896, 1, 327683, 720896, 1, 393219, 720896, 1, 65540, 458752, 1, 131076, 720896, 1, 196612, 720896, 1, 262148, 720896, 1, 327684, 720896, 1, 393220, 720896, 1, 65541, 720896, 1, 131077, 720896, 1, 196613, 720896, 1, 262149, 458752, 1, 327685, 720896, 1, 393221, 720896, 1, 65542, 720896, 1, 131078, 720896, 1, 196614, 720896, 1, 262150, 720896, 1, 327686, 720896, 1, 393222, 720896, 1, 65543, 720896, 1, 131079, 720896, 1, 196615, 720896, 1, 262151, 720896, 1, 327687, 720896, 1, 393223, 458752, 1, 65544, 720896, 1, 131080, 720896, 1, 196616, 458752, 1, 262152, 720896, 1, 327688, 720896, 1, 393224, 720896, 1, 65546, 524288, 5, 65547, 524288, 5, 65548, 524288, 5, 65549, 524288, 5, 131082, 524288, 6, 131083, 524288, 6, 131084, 524288, 6, 131085, 524288, 6, 196618, 720896, 1, 262154, 720896, 1, 196619, 720896, 1, 262155, 720896, 1, 196620, 720896, 1, 262156, 720896, 1, 196621, 720896, 1, 262157, 720896, 1, 65550, 524288, 5, 65551, 524288, 5, 65552, 524288, 5, 131086, 524288, 6, 131087, 524288, 6, 131088, 524288, 6, 196622, 720896, 1, 196623, 720896, 1, 262159, 720896, 1, 262160, 720896, 1, 196624, 720896, 1, 262158, 720896, 1, 17, 720896, 4, 65553, 720896, 5, 131089, 720896, 5, 196625, 720896, 5, 262161, 720896, 5, 18, 786432, 4, 19, 786432, 4, 20, 786432, 4, 21, 786432, 4, 22, 786432, 4, 23, 786432, 4, 65554, 786432, 5, 131090, 786432, 5, 196626, 786432, 5, 262162, 786432, 5, 65555, 786432, 5, 131091, 589824, 6, 196627, 786432, 5, 262163, 786432, 5, 65556, 786432, 5, 131092, 786432, 5, 196628, 786432, 5, 262164, 786432, 5, 65557, 786432, 5, 131093, 786432, 5, 196629, 786432, 5, 262165, 786432, 5, 65558, 786432, 5, 131094, 786432, 5, 196630, 786432, 5, 262166, 655360, 6, 65559, 786432, 5, 131095, 786432, 5, 196631, 786432, 5, 262167, 786432, 5, 327697, 720896, 5, 393233, 720896, 5, 327698, 589824, 6, 393234, 786432, 5, 327699, 786432, 5, 393235, 786432, 5, 327700, 786432, 5, 393236, 786432, 5, 327701, 786432, 5, 393237, 786432, 5, 327702, 786432, 5, 393238, 786432, 5, 327703, 786432, 5, 393239, 786432, 5, -131062, 720896, 4, -131061, 786432, 4, -131060, 786432, 4, -65526, 720896, 6, -65525, 786432, 6, -65524, 786432, 6, -131056, 851968, 4, -65520, 851968, 6, -131059, 786432, 4, -131058, 786432, 4, -131057, 786432, 4, -65523, 786432, 6, -65522, 786432, 6, -65521, 786432, 6, -65536, 917504, 2, -65535, 983040, 2, -65534, 1048576, 2, -65533, 917504, 2, -65532, 983040, 2, -65531, 1048576, 2, -65530, 917504, 2, -65529, 983040, 2, -65528, 1048576, 2, 65535, 655360, 0, 131071, 655360, 1, 196607, 655360, 1, 262143, 655360, 1, 327679, 655360, 1, 393215, 655360, 1, 458751, 655360, 1, 524287, 655360, 1, 589823, 655360, 1, 0, 720896, 0, 65536, 720896, 1, 131072, 720896, 1, 196608, 720896, 1, 262144, 720896, 1, 327680, 720896, 1, 393216, 720896, 1, 458752, 720896, 1, 524288, 720896, 1, 524289, 720896, 1, 524290, 720896, 1, 524291, 720896, 1, 524292, 720896, 1, 524293, 720896, 1, 524294, 720896, 1, 524295, 720896, 1, 524296, 720896, 1, 524297, 786432, 1, 458761, 786432, 1, 458760, 720896, 1, 458759, 720896, 1, 458758, 458752, 1, 458757, 720896, 1, 458756, 720896, 1, 458755, 720896, 1, 458754, 720896, 1, 458753, 720896, 1, -262145, 851968, 4, -196609, 851968, 5, -131073, 851968, 5, -65537, 851968, 5, -1, 851968, 6, -262146, 786432, 4, -262147, 786432, 4, -196610, 589824, 6, -196611, 786432, 5, -6, 786432, 5, -5, 786432, 5, -4, 786432, 5, -3, 786432, 5, -2, 786432, 5, -65538, 786432, 5, -131074, 786432, 5, -131075, 786432, 5, -65539, 655360, 6, 65534, 851968, 5, 131070, 851968, 5, 196606, 851968, 5, 262142, 851968, 5, 327678, 851968, 5, 393214, 851968, 5, 458750, 851968, 5, 65533, 786432, 5, 65532, 786432, 5, 65531, 786432, 5, 65530, 786432, 5, 65529, 720896, 5, 131066, 786432, 5, 196602, 786432, 5, 262138, 786432, 5, 262139, 786432, 5, 327675, 786432, 5, 131068, 786432, 5, 131069, 786432, 5, 196605, 786432, 5, 262141, 786432, 5, 327677, 786432, 5, 393213, 786432, 5, 458749, 786432, 5, 393212, 786432, 5, 393211, 786432, 5, 458748, 786432, 5, 327676, 655360, 6, 262140, 786432, 5, 196604, 786432, 5, 131067, 786432, 5, 196603, 589824, 6, 458747, 786432, 5, 458746, 786432, 5, 393210, 786432, 5, 327674, 786432, 5, -7, 720896, 5, 131065, 720896, 5, 196601, 720896, 5, 262137, 720896, 5, 327673, 720896, 5, 393209, 720896, 5, 458745, 720896, 5, -327684, 720896, 3, -196594, 720896, 3, -196597, 720896, 3, -65518, 720896, 3, -65516, 720896, 3, -327686, 1048576, 5, -327685, 720896, 3, -196595, 917504, 6, -65514, 983040, 6, -327683, 983040, 5, -65513, 1048576, 5, -262151, 720896, 4, -196615, 720896, 5, -131079, 720896, 5, -65543, 720896, 5, -262150, 786432, 4, -196614, 655360, 6, -131078, 786432, 5, -65542, 786432, 5, -262149, 786432, 4, -196613, 786432, 5, -131077, 786432, 5, -65541, 786432, 5, -262148, 786432, 4, -196612, 786432, 5, -131076, 786432, 5, -65540, 786432, 5, 458769, 720896, 5, 524305, 720896, 6, 458775, 786432, 5, 458774, 786432, 5, 458773, 786432, 5, 458772, 786432, 5, 458771, 786432, 5, 458770, 786432, 5, 524306, 786432, 6, 524307, 786432, 6, 524308, 786432, 6, 524309, 786432, 6, 524310, 786432, 6, 524311, 786432, 6, 327690, 720896, 1, 393226, 720896, 1, 458762, 720896, 1, 524298, 720896, 1, 327691, 720896, 1, 393227, 720896, 1, 458763, 720896, 1, 524299, 720896, 1, 327692, 720896, 1, 393228, 720896, 1, 458764, 720896, 1, 524300, 720896, 1, 327693, 720896, 1, 393229, 720896, 1, 458765, 720896, 1, 524301, 720896, 1, 327694, 720896, 1, 393230, 720896, 1, 458766, 720896, 1, 524302, 720896, 1, 327695, 720896, 1, 393231, 720896, 1, 458767, 720896, 1, 524303, 720896, 1, 327696, 720896, 1, 393232, 720896, 1, 458768, 720896, 1, 524304, 720896, 1, 29, 851968, 4, 65565, 851968, 5, 131101, 851968, 5, 196637, 851968, 5, 262173, 851968, 5, 327709, 851968, 5, 393245, 851968, 5, 458781, 851968, 5, 524317, 851968, 6, -65511, 917504, 4, 24, 786432, 4, 25, 786432, 4, 26, 786432, 4, 27, 786432, 4, 28, 786432, 4, 65560, 786432, 5, 65561, 786432, 5, 65562, 786432, 5, 65563, 786432, 5, 65564, 786432, 5, 131100, 786432, 5, 196636, 589824, 6, 131099, 786432, 5, 131098, 786432, 5, 131097, 786432, 5, 131096, 786432, 5, 196632, 786432, 5, 262168, 786432, 5, 327704, 786432, 5, 393240, 786432, 5, 458776, 786432, 5, 524312, 786432, 6, 196633, 786432, 5, 262169, 786432, 5, 327705, 786432, 5, 393241, 786432, 5, 458777, 786432, 5, 524313, 786432, 6, 196634, 786432, 5, 262170, 786432, 5, 327706, 786432, 5, 393242, 655360, 6, 458778, 786432, 5, 524314, 786432, 6, 196635, 786432, 5, 262171, 786432, 5, 327707, 786432, 5, 393243, 786432, 5, 458779, 786432, 5, 524315, 786432, 6, 262172, 786432, 5, 327708, 786432, 5, 393244, 786432, 5, 458780, 786432, 5, 524316, 786432, 6, -196593, 1048576, 6, -393182, 1048576, 6, -393185, 917504, 6, -393180, 983040, 6, -393184, 983040, 5, -65509, 720896, 3, -65510, 720896, 3, -393181, 720896, 3, -393183, 720896, 3, -65517, 720896, 3, -65515, 720896, 3, -327650, 720896, 4, -262114, 720896, 5, -196578, 720896, 5, -131042, 720896, 5, -65506, 720896, 5, -65498, 851968, 5, -131034, 851968, 5, -196570, 851968, 5, -327642, 851968, 4, -327649, 786432, 4, -327648, 786432, 4, -327647, 786432, 4, -327646, 786432, 4, -327645, 786432, 4, -327644, 786432, 4, -327643, 786432, 4, -262106, 851968, 5, -65499, 786432, 5, -131035, 786432, 5, -196571, 786432, 5, -262107, 786432, 5, -262108, 786432, 5, -262109, 786432, 5, -262110, 786432, 5, -262111, 786432, 5, -262112, 786432, 5, -262113, 786432, 5, -196577, 655360, 6, -131041, 786432, 5, -65505, 786432, 5, -65500, 655360, 6, -131036, 589824, 6, -196572, 786432, 5, -196573, 786432, 5, -196574, 786432, 5, -196575, 786432, 5, -196576, 786432, 5, -131040, 589824, 6, -65504, 786432, 5, -65501, 786432, 5, -131037, 786432, 5, -131038, 786432, 5, -131039, 786432, 5, -65503, 786432, 5, -65502, 786432, 5, 524318, 720896, 6, 458782, 720896, 5, 393246, 720896, 5, 327710, 720896, 5, 262174, 720896, 5, 196638, 720896, 5, 131102, 720896, 5, 65566, 720896, 5, 30, 720896, 5, 524319, 786432, 6, 524320, 786432, 6, 524321, 786432, 6, 524322, 786432, 6, 524323, 786432, 6, 524324, 786432, 6, 524325, 786432, 6, 524326, 851968, 6, 38, 851968, 5, 65574, 851968, 5, 131110, 851968, 5, 196646, 851968, 5, 262182, 851968, 5, 327718, 851968, 5, 393254, 851968, 5, 458790, 851968, 5, 31, 786432, 5, 65567, 786432, 5, 131103, 786432, 5, 196639, 786432, 5, 262175, 786432, 5, 327711, 786432, 5, 393247, 655360, 6, 458783, 786432, 5, 32, 786432, 5, 65568, 786432, 5, 131104, 786432, 5, 196640, 786432, 5, 262176, 786432, 5, 327712, 786432, 5, 393248, 786432, 5, 458784, 786432, 5, 33, 786432, 5, 65569, 786432, 5, 131105, 655360, 6, 196641, 786432, 5, 262177, 786432, 5, 327713, 786432, 5, 393249, 786432, 5, 458785, 786432, 5, 34, 786432, 5, 65570, 786432, 5, 131106, 786432, 5, 196642, 786432, 5, 262178, 655360, 6, 327714, 655360, 6, 393250, 786432, 5, 458786, 786432, 5, 35, 786432, 5, 65571, 786432, 5, 131107, 786432, 5, 196643, 786432, 5, 262179, 786432, 5, 327715, 786432, 5, 393251, 786432, 5, 458787, 786432, 5, 36, 655360, 6, 65572, 786432, 5, 131108, 786432, 5, 196644, 786432, 5, 262180, 786432, 5, 327716, 786432, 5, 393252, 786432, 5, 458788, 655360, 6, 37, 786432, 5, 65573, 786432, 5, 131109, 786432, 5, 196645, 786432, 5, 262181, 786432, 5, 327717, 786432, 5, 393253, 786432, 5, 458789, 786432, 5, 524282, 786432, 5, 524283, 786432, 5, 524284, 786432, 5, 524285, 786432, 5, 524281, 720896, 5, 524286, 851968, 5, 589817, 720896, 6, 589818, 786432, 6, 589819, 786432, 6, 589820, 786432, 6, 589821, 786432, 6, 589822, 851968, 6, -196596, 917504, 0)
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("7_fdx1s")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("6_kqt1v")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Controls" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("12_k4p0h")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="Camera2D" type="Camera2D" parent="."]
+physics_interpolation_mode = 1
+position = Vector2(227, -28)
+zoom = Vector2(1.5, 1.5)
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_54fc4")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+process_priority = -1
+top_level = true
+position = Vector2(227, -28)
+script = ExtResource("2_mgsut")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D/PlayerVisuals")
+zoom = Vector2(1.5, 1.5)
+frame_preview = false
+tween_resource = ExtResource("6_pxbym")
+tween_on_load = false
+follow_damping = true
+draw_limits = true
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("7_62i3t")]
+position = Vector2(227, -28)
+
+[editable path="Player/CharacterBody2D"]
diff --git a/addons/phantom_camera/examples/example_scenes/2D/2d_follow_framed_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D/2d_follow_framed_example_scene.tscn
new file mode 100644
index 0000000..98a54c2
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/2d_follow_framed_example_scene.tscn
@@ -0,0 +1,245 @@
+[gd_scene load_steps=11 format=3 uid="uid://bxtsl6qlpq1ar"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_27o77"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_1tbys"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_1kfnp"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_mylkx"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="5_lwx5e"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="6_tju6r"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="8_bo8m7"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_wlikg"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("1_27o77")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[node name="ExampleScene2D" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -345.0
+offset_top = -143.0
+offset_right = 947.0
+offset_bottom = 578.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="TileMap" type="TileMap" parent="."]
+z_index = -1
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_0/tile_data = PackedInt32Array(-393216, 655360, 2, -327680, 655360, 3, -262144, 655360, 3, -196608, 655360, 3, -131072, 655360, 3, -65536, 655360, 3, -393215, 720896, 2, -327679, 720896, 1, -262143, 720896, 1, -196607, 720896, 1, -131071, 720896, 1, -65535, 720896, 1, -393214, 786432, 2, -327678, 786432, 3, -262142, 786432, 3, -196606, 786432, 3, -131070, 786432, 3, -65534, 786432, 3)
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(1, 720896, 0, 2, 720896, 0, 3, 720896, 0, 4, 720896, 0, 5, 720896, 0, 6, 720896, 0, 7, 720896, 0, 8, 720896, 0, 9, 786432, 0, 65545, 786432, 1, 131081, 786432, 1, 196617, 786432, 1, 262153, 786432, 1, 327689, 786432, 1, 393225, 786432, 1, 65537, 720896, 1, 131073, 720896, 1, 196609, 720896, 1, 262145, 458752, 1, 327681, 720896, 1, 393217, 720896, 1, 65538, 720896, 1, 131074, 720896, 1, 196610, 720896, 1, 262146, 720896, 1, 327682, 720896, 1, 393218, 720896, 1, 65539, 720896, 1, 131075, 720896, 1, 196611, 720896, 1, 262147, 720896, 1, 327683, 720896, 1, 393219, 720896, 1, 65540, 458752, 1, 131076, 720896, 1, 196612, 720896, 1, 262148, 720896, 1, 327684, 720896, 1, 393220, 720896, 1, 65541, 720896, 1, 131077, 720896, 1, 196613, 720896, 1, 262149, 458752, 1, 327685, 720896, 1, 393221, 720896, 1, 65542, 720896, 1, 131078, 720896, 1, 196614, 720896, 1, 262150, 720896, 1, 327686, 720896, 1, 393222, 720896, 1, 65543, 720896, 1, 131079, 720896, 1, 196615, 720896, 1, 262151, 720896, 1, 327687, 720896, 1, 393223, 458752, 1, 65544, 720896, 1, 131080, 720896, 1, 196616, 458752, 1, 262152, 720896, 1, 327688, 720896, 1, 393224, 720896, 1, 65546, 524288, 5, 65547, 524288, 5, 65548, 524288, 5, 65549, 524288, 5, 131082, 524288, 6, 131083, 524288, 6, 131084, 524288, 6, 131085, 524288, 6, 196618, 720896, 1, 262154, 720896, 1, 196619, 720896, 1, 262155, 720896, 1, 196620, 720896, 1, 262156, 720896, 1, 196621, 720896, 1, 262157, 720896, 1, 65550, 524288, 5, 65551, 524288, 5, 65552, 524288, 5, 131086, 524288, 6, 131087, 524288, 6, 131088, 524288, 6, 196622, 720896, 1, 196623, 720896, 1, 262159, 720896, 1, 262160, 720896, 1, 196624, 720896, 1, 262158, 720896, 1, 17, 720896, 4, 65553, 720896, 5, 131089, 720896, 5, 196625, 720896, 5, 262161, 720896, 5, 18, 786432, 4, 19, 786432, 4, 20, 786432, 4, 21, 786432, 4, 22, 786432, 4, 23, 786432, 4, 65554, 786432, 5, 131090, 786432, 5, 196626, 786432, 5, 262162, 786432, 5, 65555, 786432, 5, 131091, 589824, 6, 196627, 786432, 5, 262163, 786432, 5, 65556, 786432, 5, 131092, 786432, 5, 196628, 786432, 5, 262164, 786432, 5, 65557, 786432, 5, 131093, 786432, 5, 196629, 786432, 5, 262165, 786432, 5, 65558, 786432, 5, 131094, 786432, 5, 196630, 786432, 5, 262166, 655360, 6, 65559, 786432, 5, 131095, 786432, 5, 196631, 786432, 5, 262167, 786432, 5, 327697, 720896, 5, 393233, 720896, 5, 327698, 589824, 6, 393234, 786432, 5, 327699, 786432, 5, 393235, 786432, 5, 327700, 786432, 5, 393236, 786432, 5, 327701, 786432, 5, 393237, 786432, 5, 327702, 786432, 5, 393238, 786432, 5, 327703, 786432, 5, 393239, 786432, 5, -131062, 720896, 4, -131061, 786432, 4, -131060, 786432, 4, -65526, 720896, 6, -65525, 786432, 6, -65524, 786432, 6, -131056, 851968, 4, -65520, 851968, 6, -131059, 786432, 4, -131058, 786432, 4, -131057, 786432, 4, -65523, 786432, 6, -65522, 786432, 6, -65521, 786432, 6, -196596, 917504, 0, -65536, 917504, 2, -65535, 983040, 2, -65534, 1048576, 2, -65533, 917504, 2, -65532, 983040, 2, -65531, 1048576, 2, -65530, 917504, 2, -65529, 983040, 2, -65528, 1048576, 2, 65535, 655360, 0, 131071, 655360, 1, 196607, 655360, 1, 262143, 655360, 1, 327679, 655360, 1, 393215, 655360, 1, 458751, 655360, 1, 524287, 655360, 1, 589823, 655360, 1, 0, 720896, 0, 65536, 720896, 1, 131072, 720896, 1, 196608, 720896, 1, 262144, 720896, 1, 327680, 720896, 1, 393216, 720896, 1, 458752, 720896, 1, 524288, 720896, 1, 524289, 720896, 1, 524290, 720896, 1, 524291, 720896, 1, 524292, 720896, 1, 524293, 720896, 1, 524294, 720896, 1, 524295, 720896, 1, 524296, 720896, 1, 524297, 786432, 1, 458761, 786432, 1, 458760, 720896, 1, 458759, 720896, 1, 458758, 458752, 1, 458757, 720896, 1, 458756, 720896, 1, 458755, 720896, 1, 458754, 720896, 1, 458753, 720896, 1, -262145, 851968, 4, -196609, 851968, 5, -131073, 851968, 5, -65537, 851968, 5, -1, 851968, 6, -262146, 786432, 4, -262147, 786432, 4, -196610, 589824, 6, -196611, 786432, 5, -6, 786432, 5, -5, 786432, 5, -4, 786432, 5, -3, 786432, 5, -2, 786432, 5, -65538, 786432, 5, -131074, 786432, 5, -131075, 786432, 5, -65539, 655360, 6, 65534, 851968, 5, 131070, 851968, 5, 196606, 851968, 5, 262142, 851968, 5, 327678, 851968, 5, 393214, 851968, 5, 458750, 851968, 5, 65533, 786432, 5, 65532, 786432, 5, 65531, 786432, 5, 65530, 786432, 5, 65529, 720896, 5, 131066, 786432, 5, 196602, 786432, 5, 262138, 786432, 5, 262139, 786432, 5, 327675, 786432, 5, 131068, 786432, 5, 131069, 786432, 5, 196605, 786432, 5, 262141, 786432, 5, 327677, 786432, 5, 393213, 786432, 5, 458749, 786432, 5, 393212, 786432, 5, 393211, 786432, 5, 458748, 786432, 5, 327676, 655360, 6, 262140, 786432, 5, 196604, 786432, 5, 131067, 786432, 5, 196603, 589824, 6, 458747, 786432, 5, 458746, 786432, 5, 393210, 786432, 5, 327674, 786432, 5, -7, 720896, 5, 131065, 720896, 5, 196601, 720896, 5, 262137, 720896, 5, 327673, 720896, 5, 393209, 720896, 5, 458745, 720896, 5, -327684, 720896, 3, -196594, 720896, 3, -196597, 720896, 3, -65518, 720896, 3, -65516, 720896, 3, -327686, 1048576, 5, -327685, 720896, 3, -196595, 917504, 6, -65514, 983040, 6, -327683, 983040, 5, -65513, 1048576, 5, -262151, 720896, 4, -196615, 720896, 5, -131079, 720896, 5, -65543, 720896, 5, -262150, 786432, 4, -196614, 655360, 6, -131078, 786432, 5, -65542, 786432, 5, -262149, 786432, 4, -196613, 786432, 5, -131077, 786432, 5, -65541, 786432, 5, -262148, 786432, 4, -196612, 786432, 5, -131076, 786432, 5, -65540, 786432, 5, 458769, 720896, 5, 524305, 720896, 6, 458775, 786432, 5, 458774, 786432, 5, 458773, 786432, 5, 458772, 786432, 5, 458771, 786432, 5, 458770, 786432, 5, 524306, 786432, 6, 524307, 786432, 6, 524308, 786432, 6, 524309, 786432, 6, 524310, 786432, 6, 524311, 786432, 6, 327690, 720896, 1, 393226, 720896, 1, 458762, 720896, 1, 524298, 720896, 1, 327691, 720896, 1, 393227, 720896, 1, 458763, 720896, 1, 524299, 720896, 1, 327692, 720896, 1, 393228, 720896, 1, 458764, 720896, 1, 524300, 720896, 1, 327693, 720896, 1, 393229, 720896, 1, 458765, 720896, 1, 524301, 720896, 1, 327694, 720896, 1, 393230, 720896, 1, 458766, 720896, 1, 524302, 720896, 1, 327695, 720896, 1, 393231, 720896, 1, 458767, 720896, 1, 524303, 720896, 1, 327696, 720896, 1, 393232, 720896, 1, 458768, 720896, 1, 524304, 720896, 1, 29, 851968, 4, 65565, 851968, 5, 131101, 851968, 5, 196637, 851968, 5, 262173, 851968, 5, 327709, 851968, 5, 393245, 851968, 5, 458781, 851968, 5, 524317, 851968, 6, -65511, 917504, 4, 24, 786432, 4, 25, 786432, 4, 26, 786432, 4, 27, 786432, 4, 28, 786432, 4, 65560, 786432, 5, 65561, 786432, 5, 65562, 786432, 5, 65563, 786432, 5, 65564, 786432, 5, 131100, 786432, 5, 196636, 589824, 6, 131099, 786432, 5, 131098, 786432, 5, 131097, 786432, 5, 131096, 786432, 5, 196632, 786432, 5, 262168, 786432, 5, 327704, 786432, 5, 393240, 786432, 5, 458776, 786432, 5, 524312, 786432, 6, 196633, 786432, 5, 262169, 786432, 5, 327705, 786432, 5, 393241, 786432, 5, 458777, 786432, 5, 524313, 786432, 6, 196634, 786432, 5, 262170, 786432, 5, 327706, 786432, 5, 393242, 655360, 6, 458778, 786432, 5, 524314, 786432, 6, 196635, 786432, 5, 262171, 786432, 5, 327707, 786432, 5, 393243, 786432, 5, 458779, 786432, 5, 524315, 786432, 6, 262172, 786432, 5, 327708, 786432, 5, 393244, 786432, 5, 458780, 786432, 5, 524316, 786432, 6, -196593, 1048576, 6, -393182, 1048576, 6, -393185, 917504, 6, -393180, 983040, 6, -393184, 983040, 5, -65509, 720896, 3, -65510, 720896, 3, -393181, 720896, 3, -393183, 720896, 3, -65517, 720896, 3, -65515, 720896, 3, -327650, 720896, 4, -262114, 720896, 5, -196578, 720896, 5, -131042, 720896, 5, -65506, 720896, 5, -65498, 851968, 5, -131034, 851968, 5, -196570, 851968, 5, -327642, 851968, 4, -327649, 786432, 4, -327648, 786432, 4, -327647, 786432, 4, -327646, 786432, 4, -327645, 786432, 4, -327644, 786432, 4, -327643, 786432, 4, -262106, 851968, 5, -65499, 786432, 5, -131035, 786432, 5, -196571, 786432, 5, -262107, 786432, 5, -262108, 786432, 5, -262109, 786432, 5, -262110, 786432, 5, -262111, 786432, 5, -262112, 786432, 5, -262113, 786432, 5, -196577, 655360, 6, -131041, 786432, 5, -65505, 786432, 5, -65500, 655360, 6, -131036, 589824, 6, -196572, 786432, 5, -196573, 786432, 5, -196574, 786432, 5, -196575, 786432, 5, -196576, 786432, 5, -131040, 589824, 6, -65504, 786432, 5, -65501, 786432, 5, -131037, 786432, 5, -131038, 786432, 5, -131039, 786432, 5, -65503, 786432, 5, -65502, 786432, 5, 524318, 720896, 6, 458782, 720896, 5, 393246, 720896, 5, 327710, 720896, 5, 262174, 720896, 5, 196638, 720896, 5, 131102, 720896, 5, 65566, 720896, 5, 30, 720896, 5, 524319, 786432, 6, 524320, 786432, 6, 524321, 786432, 6, 524322, 786432, 6, 524323, 786432, 6, 524324, 786432, 6, 524325, 786432, 6, 524326, 851968, 6, 38, 851968, 5, 65574, 851968, 5, 131110, 851968, 5, 196646, 851968, 5, 262182, 851968, 5, 327718, 851968, 5, 393254, 851968, 5, 458790, 851968, 5, 31, 786432, 5, 65567, 786432, 5, 131103, 786432, 5, 196639, 786432, 5, 262175, 786432, 5, 327711, 786432, 5, 393247, 655360, 6, 458783, 786432, 5, 32, 786432, 5, 65568, 786432, 5, 131104, 786432, 5, 196640, 786432, 5, 262176, 786432, 5, 327712, 786432, 5, 393248, 786432, 5, 458784, 786432, 5, 33, 786432, 5, 65569, 786432, 5, 131105, 655360, 6, 196641, 786432, 5, 262177, 786432, 5, 327713, 786432, 5, 393249, 786432, 5, 458785, 786432, 5, 34, 786432, 5, 65570, 786432, 5, 131106, 786432, 5, 196642, 786432, 5, 262178, 655360, 6, 327714, 655360, 6, 393250, 786432, 5, 458786, 786432, 5, 35, 786432, 5, 65571, 786432, 5, 131107, 786432, 5, 196643, 786432, 5, 262179, 786432, 5, 327715, 786432, 5, 393251, 786432, 5, 458787, 786432, 5, 36, 655360, 6, 65572, 786432, 5, 131108, 786432, 5, 196644, 786432, 5, 262180, 786432, 5, 327716, 786432, 5, 393252, 786432, 5, 458788, 655360, 6, 37, 786432, 5, 65573, 786432, 5, 131109, 786432, 5, 196645, 786432, 5, 262181, 786432, 5, 327717, 786432, 5, 393253, 786432, 5, 458789, 786432, 5, 524282, 786432, 5, 524283, 786432, 5, 524284, 786432, 5, 524285, 786432, 5, 524281, 720896, 5, 524286, 851968, 5, 589817, 720896, 6, 589818, 786432, 6, 589819, 786432, 6, 589820, 786432, 6, 589821, 786432, 6, 589822, 851968, 6)
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_1tbys")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_1kfnp")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(282, -29)
+zoom = Vector2(2, 2)
+process_callback = 0
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_mylkx")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(282, -29)
+script = ExtResource("5_lwx5e")
+priority = 5
+follow_mode = 5
+follow_target = NodePath("../CharacterBody2D/PlayerVisuals")
+zoom = Vector2(2, 2)
+tween_resource = ExtResource("6_tju6r")
+tween_on_load = false
+follow_damping = true
+dead_zone_width = 0.4
+dead_zone_height = 0.8
+show_viewfinder_in_play = true
+draw_limits = true
+
+[node name="Label" type="Label" parent="Player"]
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("8_bo8m7")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("8_wlikg")]
+position = Vector2(282, -29)
+
+[editable path="Player/CharacterBody2D"]
diff --git a/addons/phantom_camera/examples/example_scenes/2D/2d_follow_group_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D/2d_follow_group_example_scene.tscn
new file mode 100644
index 0000000..ba1e261
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/2d_follow_group_example_scene.tscn
@@ -0,0 +1,255 @@
+[gd_scene load_steps=13 format=3 uid="uid://brrncnp26lrco"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_5kqbp"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_xmntp"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_8dojy"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_2efwt"]
+[ext_resource type="Texture2D" uid="uid://cwep0on2tthn7" path="res://addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png" id="5_0v2cd"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="6_diuy4"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="7_ybwrw"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="9_wk0p3"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="12_uvcwb"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("1_5kqbp")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[sub_resource type="Resource" id="Resource_spy00"]
+script = ExtResource("9_wk0p3")
+duration = 0.3
+transition = 4
+ease = 2
+
+[node name="ExampleScene2D" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="TileMap" type="TileMap" parent="."]
+z_index = -1
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_0/tile_data = PackedInt32Array(-393216, 655360, 2, -327680, 655360, 3, -262144, 655360, 3, -196608, 655360, 3, -131072, 655360, 3, -65536, 655360, 3, -393215, 720896, 2, -327679, 720896, 1, -262143, 720896, 1, -196607, 720896, 1, -131071, 720896, 1, -65535, 720896, 1, -393214, 786432, 2, -327678, 786432, 3, -262142, 786432, 3, -196606, 786432, 3, -131070, 786432, 3, -65534, 786432, 3)
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(1, 720896, 0, 2, 720896, 0, 3, 720896, 0, 4, 720896, 0, 5, 720896, 0, 6, 720896, 0, 7, 720896, 0, 8, 720896, 0, 9, 786432, 0, 65545, 786432, 1, 131081, 786432, 1, 196617, 786432, 1, 262153, 786432, 1, 327689, 786432, 1, 393225, 786432, 1, 65537, 720896, 1, 131073, 720896, 1, 196609, 720896, 1, 262145, 458752, 1, 327681, 720896, 1, 393217, 720896, 1, 65538, 720896, 1, 131074, 720896, 1, 196610, 720896, 1, 262146, 720896, 1, 327682, 720896, 1, 393218, 720896, 1, 65539, 720896, 1, 131075, 720896, 1, 196611, 720896, 1, 262147, 720896, 1, 327683, 720896, 1, 393219, 720896, 1, 65540, 458752, 1, 131076, 720896, 1, 196612, 720896, 1, 262148, 720896, 1, 327684, 720896, 1, 393220, 720896, 1, 65541, 720896, 1, 131077, 720896, 1, 196613, 720896, 1, 262149, 458752, 1, 327685, 720896, 1, 393221, 720896, 1, 65542, 720896, 1, 131078, 720896, 1, 196614, 720896, 1, 262150, 720896, 1, 327686, 720896, 1, 393222, 720896, 1, 65543, 720896, 1, 131079, 720896, 1, 196615, 720896, 1, 262151, 720896, 1, 327687, 720896, 1, 393223, 458752, 1, 65544, 720896, 1, 131080, 720896, 1, 196616, 458752, 1, 262152, 720896, 1, 327688, 720896, 1, 393224, 720896, 1, 65546, 524288, 5, 65547, 524288, 5, 65548, 524288, 5, 65549, 524288, 5, 131082, 524288, 6, 131083, 524288, 6, 131084, 524288, 6, 131085, 524288, 6, 196618, 720896, 1, 262154, 720896, 1, 196619, 720896, 1, 262155, 720896, 1, 196620, 720896, 1, 262156, 720896, 1, 196621, 720896, 1, 262157, 720896, 1, 65550, 524288, 5, 65551, 524288, 5, 65552, 524288, 5, 131086, 524288, 6, 131087, 524288, 6, 131088, 524288, 6, 196622, 720896, 1, 196623, 720896, 1, 262159, 720896, 1, 262160, 720896, 1, 196624, 720896, 1, 262158, 720896, 1, 17, 720896, 4, 65553, 720896, 5, 131089, 720896, 5, 196625, 720896, 5, 262161, 720896, 5, 18, 786432, 4, 19, 786432, 4, 20, 786432, 4, 21, 786432, 4, 22, 786432, 4, 23, 786432, 4, 65554, 786432, 5, 131090, 786432, 5, 196626, 786432, 5, 262162, 786432, 5, 65555, 786432, 5, 131091, 589824, 6, 196627, 786432, 5, 262163, 786432, 5, 65556, 786432, 5, 131092, 786432, 5, 196628, 786432, 5, 262164, 786432, 5, 65557, 786432, 5, 131093, 786432, 5, 196629, 786432, 5, 262165, 786432, 5, 65558, 786432, 5, 131094, 786432, 5, 196630, 786432, 5, 262166, 655360, 6, 65559, 786432, 5, 131095, 786432, 5, 196631, 786432, 5, 262167, 786432, 5, 327697, 720896, 5, 393233, 720896, 5, 327698, 589824, 6, 393234, 786432, 5, 327699, 786432, 5, 393235, 786432, 5, 327700, 786432, 5, 393236, 786432, 5, 327701, 786432, 5, 393237, 786432, 5, 327702, 786432, 5, 393238, 786432, 5, 327703, 786432, 5, 393239, 786432, 5, -131062, 720896, 4, -131061, 786432, 4, -131060, 786432, 4, -65526, 720896, 6, -65525, 786432, 6, -65524, 786432, 6, -131056, 851968, 4, -65520, 851968, 6, -131059, 786432, 4, -131058, 786432, 4, -131057, 786432, 4, -65523, 786432, 6, -65522, 786432, 6, -65521, 786432, 6, -196596, 917504, 0, -65536, 917504, 2, -65535, 983040, 2, -65534, 1048576, 2, -65533, 917504, 2, -65532, 983040, 2, -65531, 1048576, 2, -65530, 917504, 2, -65529, 983040, 2, -65528, 1048576, 2, 65535, 655360, 0, 131071, 655360, 1, 196607, 655360, 1, 262143, 655360, 1, 327679, 655360, 1, 393215, 655360, 1, 458751, 655360, 1, 524287, 655360, 1, 589823, 655360, 1, 0, 720896, 0, 65536, 720896, 1, 131072, 720896, 1, 196608, 720896, 1, 262144, 720896, 1, 327680, 720896, 1, 393216, 720896, 1, 458752, 720896, 1, 524288, 720896, 1, 524289, 720896, 1, 524290, 720896, 1, 524291, 720896, 1, 524292, 720896, 1, 524293, 720896, 1, 524294, 720896, 1, 524295, 720896, 1, 524296, 720896, 1, 524297, 786432, 1, 458761, 786432, 1, 458760, 720896, 1, 458759, 720896, 1, 458758, 458752, 1, 458757, 720896, 1, 458756, 720896, 1, 458755, 720896, 1, 458754, 720896, 1, 458753, 720896, 1, -262145, 851968, 4, -196609, 851968, 5, -131073, 851968, 5, -65537, 851968, 5, -1, 851968, 6, -262146, 786432, 4, -262147, 786432, 4, -196610, 589824, 6, -196611, 786432, 5, -6, 786432, 5, -5, 786432, 5, -4, 786432, 5, -3, 786432, 5, -2, 786432, 5, -65538, 786432, 5, -131074, 786432, 5, -131075, 786432, 5, -65539, 655360, 6, 65534, 851968, 5, 131070, 851968, 5, 196606, 851968, 5, 262142, 851968, 5, 327678, 851968, 5, 393214, 851968, 5, 458750, 851968, 5, 65533, 786432, 5, 65532, 786432, 5, 65531, 786432, 5, 65530, 786432, 5, 65529, 720896, 5, 131066, 786432, 5, 196602, 786432, 5, 262138, 786432, 5, 262139, 786432, 5, 327675, 786432, 5, 131068, 786432, 5, 131069, 786432, 5, 196605, 786432, 5, 262141, 786432, 5, 327677, 786432, 5, 393213, 786432, 5, 458749, 786432, 5, 393212, 786432, 5, 393211, 786432, 5, 458748, 786432, 5, 327676, 655360, 6, 262140, 786432, 5, 196604, 786432, 5, 131067, 786432, 5, 196603, 589824, 6, 458747, 786432, 5, 458746, 786432, 5, 393210, 786432, 5, 327674, 786432, 5, -7, 720896, 5, 131065, 720896, 5, 196601, 720896, 5, 262137, 720896, 5, 327673, 720896, 5, 393209, 720896, 5, 458745, 720896, 5, -327684, 720896, 3, -196594, 720896, 3, -196597, 720896, 3, -65518, 720896, 3, -65516, 720896, 3, -327686, 1048576, 5, -327685, 720896, 3, -196595, 917504, 6, -65514, 983040, 6, -327683, 983040, 5, -65513, 1048576, 5, -262151, 720896, 4, -196615, 720896, 5, -131079, 720896, 5, -65543, 720896, 5, -262150, 786432, 4, -196614, 655360, 6, -131078, 786432, 5, -65542, 786432, 5, -262149, 786432, 4, -196613, 786432, 5, -131077, 786432, 5, -65541, 786432, 5, -262148, 786432, 4, -196612, 786432, 5, -131076, 786432, 5, -65540, 786432, 5, 458769, 720896, 5, 524305, 720896, 6, 458775, 786432, 5, 458774, 786432, 5, 458773, 786432, 5, 458772, 786432, 5, 458771, 786432, 5, 458770, 786432, 5, 524306, 786432, 6, 524307, 786432, 6, 524308, 786432, 6, 524309, 786432, 6, 524310, 786432, 6, 524311, 786432, 6, 327690, 720896, 1, 393226, 720896, 1, 458762, 720896, 1, 524298, 720896, 1, 327691, 720896, 1, 393227, 720896, 1, 458763, 720896, 1, 524299, 720896, 1, 327692, 720896, 1, 393228, 720896, 1, 458764, 720896, 1, 524300, 720896, 1, 327693, 720896, 1, 393229, 720896, 1, 458765, 720896, 1, 524301, 720896, 1, 327694, 720896, 1, 393230, 720896, 1, 458766, 720896, 1, 524302, 720896, 1, 327695, 720896, 1, 393231, 720896, 1, 458767, 720896, 1, 524303, 720896, 1, 327696, 720896, 1, 393232, 720896, 1, 458768, 720896, 1, 524304, 720896, 1, 29, 851968, 4, 65565, 851968, 5, 131101, 851968, 5, 196637, 851968, 5, 262173, 851968, 5, 327709, 851968, 5, 393245, 851968, 5, 458781, 851968, 5, 524317, 851968, 6, -65511, 917504, 4, 24, 786432, 4, 25, 786432, 4, 26, 786432, 4, 27, 786432, 4, 28, 786432, 4, 65560, 786432, 5, 65561, 786432, 5, 65562, 786432, 5, 65563, 786432, 5, 65564, 786432, 5, 131100, 786432, 5, 196636, 589824, 6, 131099, 786432, 5, 131098, 786432, 5, 131097, 786432, 5, 131096, 786432, 5, 196632, 786432, 5, 262168, 786432, 5, 327704, 786432, 5, 393240, 786432, 5, 458776, 786432, 5, 524312, 786432, 6, 196633, 786432, 5, 262169, 786432, 5, 327705, 786432, 5, 393241, 786432, 5, 458777, 786432, 5, 524313, 786432, 6, 196634, 786432, 5, 262170, 786432, 5, 327706, 786432, 5, 393242, 655360, 6, 458778, 786432, 5, 524314, 786432, 6, 196635, 786432, 5, 262171, 786432, 5, 327707, 786432, 5, 393243, 786432, 5, 458779, 786432, 5, 524315, 786432, 6, 262172, 786432, 5, 327708, 786432, 5, 393244, 786432, 5, 458780, 786432, 5, 524316, 786432, 6, -196593, 1048576, 6, -393182, 1048576, 6, -393185, 917504, 6, -393180, 983040, 6, -393184, 983040, 5, -65509, 720896, 3, -65510, 720896, 3, -393181, 720896, 3, -393183, 720896, 3, -65517, 720896, 3, -65515, 720896, 3, -327650, 720896, 4, -262114, 720896, 5, -196578, 720896, 5, -131042, 720896, 5, -65506, 720896, 5, -65498, 851968, 5, -131034, 851968, 5, -196570, 851968, 5, -327642, 851968, 4, -327649, 786432, 4, -327648, 786432, 4, -327647, 786432, 4, -327646, 786432, 4, -327645, 786432, 4, -327644, 786432, 4, -327643, 786432, 4, -262106, 851968, 5, -65499, 786432, 5, -131035, 786432, 5, -196571, 786432, 5, -262107, 786432, 5, -262108, 786432, 5, -262109, 786432, 5, -262110, 786432, 5, -262111, 786432, 5, -262112, 786432, 5, -262113, 786432, 5, -196577, 655360, 6, -131041, 786432, 5, -65505, 786432, 5, -65500, 655360, 6, -131036, 589824, 6, -196572, 786432, 5, -196573, 786432, 5, -196574, 786432, 5, -196575, 786432, 5, -196576, 786432, 5, -131040, 589824, 6, -65504, 786432, 5, -65501, 786432, 5, -131037, 786432, 5, -131038, 786432, 5, -131039, 786432, 5, -65503, 786432, 5, -65502, 786432, 5, 524318, 720896, 6, 458782, 720896, 5, 393246, 720896, 5, 327710, 720896, 5, 262174, 720896, 5, 196638, 720896, 5, 131102, 720896, 5, 65566, 720896, 5, 30, 720896, 5, 524319, 786432, 6, 524320, 786432, 6, 524321, 786432, 6, 524322, 786432, 6, 524323, 786432, 6, 524324, 786432, 6, 524325, 786432, 6, 524326, 851968, 6, 38, 851968, 5, 65574, 851968, 5, 131110, 851968, 5, 196646, 851968, 5, 262182, 851968, 5, 327718, 851968, 5, 393254, 851968, 5, 458790, 851968, 5, 31, 786432, 5, 65567, 786432, 5, 131103, 786432, 5, 196639, 786432, 5, 262175, 786432, 5, 327711, 786432, 5, 393247, 655360, 6, 458783, 786432, 5, 32, 786432, 5, 65568, 786432, 5, 131104, 786432, 5, 196640, 786432, 5, 262176, 786432, 5, 327712, 786432, 5, 393248, 786432, 5, 458784, 786432, 5, 33, 786432, 5, 65569, 786432, 5, 131105, 655360, 6, 196641, 786432, 5, 262177, 786432, 5, 327713, 786432, 5, 393249, 786432, 5, 458785, 786432, 5, 34, 786432, 5, 65570, 786432, 5, 131106, 786432, 5, 196642, 786432, 5, 262178, 655360, 6, 327714, 655360, 6, 393250, 786432, 5, 458786, 786432, 5, 35, 786432, 5, 65571, 786432, 5, 131107, 786432, 5, 196643, 786432, 5, 262179, 786432, 5, 327715, 786432, 5, 393251, 786432, 5, 458787, 786432, 5, 36, 655360, 6, 65572, 786432, 5, 131108, 786432, 5, 196644, 786432, 5, 262180, 786432, 5, 327716, 786432, 5, 393252, 786432, 5, 458788, 655360, 6, 37, 786432, 5, 65573, 786432, 5, 131109, 786432, 5, 196645, 786432, 5, 262181, 786432, 5, 327717, 786432, 5, 393253, 786432, 5, 458789, 786432, 5)
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_xmntp")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_8dojy")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(186, -172.5)
+zoom = Vector2(1.5, 1.5)
+position_smoothing_speed = 8.0
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_2efwt")
+
+[node name="Label" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -133.0
+offset_right = 332.0
+offset_bottom = -69.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("12_uvcwb")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="PhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_targets")]
+top_level = true
+position = Vector2(186, -172.5)
+script = ExtResource("6_diuy4")
+priority = 10
+follow_mode = 3
+follow_targets = [NodePath("../GroupNPCSprite"), NodePath("../CharacterBody2D/PlayerVisuals")]
+zoom = Vector2(1.5, 1.5)
+tween_resource = SubResource("Resource_spy00")
+tween_on_load = false
+follow_damping = true
+auto_zoom = true
+auto_zoom_min = 0.5
+auto_zoom_max = 1.5
+auto_zoom_margin = Vector4(200, 0, 200, 0)
+draw_limits = true
+
+[node name="GroupNPCSprite" type="Sprite2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(107, -316)
+texture = ExtResource("5_0v2cd")
+
+[node name="CharacterBody2D" parent="." instance=ExtResource("7_ybwrw")]
+position = Vector2(265, -29)
+
+[editable path="CharacterBody2D"]
diff --git a/addons/phantom_camera/examples/example_scenes/2D/2d_follow_path_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D/2d_follow_path_example_scene.tscn
new file mode 100644
index 0000000..b7ebc1c
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/2d_follow_path_example_scene.tscn
@@ -0,0 +1,263 @@
+[gd_scene load_steps=12 format=3 uid="uid://psbaaxnedqmq"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_17ngo"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_whpvu"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_rbo5b"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_yddet"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="5_x25dj"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="6_4vtmp"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="8_6pcaf"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_a2pel"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("1_17ngo")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[sub_resource type="Curve2D" id="Curve2D_usrhf"]
+_data = {
+"points": PackedVector2Array(-96.4111, 42.3785, 0, 0, 222, 0, 0, 0, 0, 0, 1550, 0)
+}
+point_count = 2
+
+[node name="ExampleScene2D" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="TileMap" type="TileMap" parent="."]
+z_index = -1
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_0/tile_data = PackedInt32Array(-393216, 655360, 2, -327680, 655360, 3, -262144, 655360, 3, -196608, 655360, 3, -131072, 655360, 3, -65536, 655360, 3, -393215, 720896, 2, -327679, 720896, 1, -262143, 720896, 1, -196607, 720896, 1, -131071, 720896, 1, -65535, 720896, 1, -393214, 786432, 2, -327678, 786432, 3, -262142, 786432, 3, -196606, 786432, 3, -131070, 786432, 3, -65534, 786432, 3)
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(1, 720896, 0, 2, 720896, 0, 3, 720896, 0, 4, 720896, 0, 5, 720896, 0, 6, 720896, 0, 7, 720896, 0, 8, 720896, 0, 9, 786432, 0, 65545, 786432, 1, 131081, 786432, 1, 196617, 786432, 1, 262153, 786432, 1, 327689, 786432, 1, 393225, 786432, 1, 65537, 720896, 1, 131073, 720896, 1, 196609, 720896, 1, 262145, 458752, 1, 327681, 720896, 1, 393217, 720896, 1, 65538, 720896, 1, 131074, 720896, 1, 196610, 720896, 1, 262146, 720896, 1, 327682, 720896, 1, 393218, 720896, 1, 65539, 720896, 1, 131075, 720896, 1, 196611, 720896, 1, 262147, 720896, 1, 327683, 720896, 1, 393219, 720896, 1, 65540, 458752, 1, 131076, 720896, 1, 196612, 720896, 1, 262148, 720896, 1, 327684, 720896, 1, 393220, 720896, 1, 65541, 720896, 1, 131077, 720896, 1, 196613, 720896, 1, 262149, 458752, 1, 327685, 720896, 1, 393221, 720896, 1, 65542, 720896, 1, 131078, 720896, 1, 196614, 720896, 1, 262150, 720896, 1, 327686, 720896, 1, 393222, 720896, 1, 65543, 720896, 1, 131079, 720896, 1, 196615, 720896, 1, 262151, 720896, 1, 327687, 720896, 1, 393223, 458752, 1, 65544, 720896, 1, 131080, 720896, 1, 196616, 458752, 1, 262152, 720896, 1, 327688, 720896, 1, 393224, 720896, 1, 65546, 524288, 5, 65547, 524288, 5, 65548, 524288, 5, 65549, 524288, 5, 131082, 524288, 6, 131083, 524288, 6, 131084, 524288, 6, 131085, 524288, 6, 196618, 720896, 1, 262154, 720896, 1, 196619, 720896, 1, 262155, 720896, 1, 196620, 720896, 1, 262156, 720896, 1, 196621, 720896, 1, 262157, 720896, 1, 65550, 524288, 5, 65551, 524288, 5, 65552, 524288, 5, 131086, 524288, 6, 131087, 524288, 6, 131088, 524288, 6, 196622, 720896, 1, 196623, 720896, 1, 262159, 720896, 1, 262160, 720896, 1, 196624, 720896, 1, 262158, 720896, 1, 17, 720896, 4, 65553, 720896, 5, 131089, 720896, 5, 196625, 720896, 5, 262161, 720896, 5, 18, 786432, 4, 19, 786432, 4, 20, 786432, 4, 21, 786432, 4, 22, 786432, 4, 23, 786432, 4, 65554, 786432, 5, 131090, 786432, 5, 196626, 786432, 5, 262162, 786432, 5, 65555, 786432, 5, 131091, 589824, 6, 196627, 786432, 5, 262163, 786432, 5, 65556, 786432, 5, 131092, 786432, 5, 196628, 786432, 5, 262164, 786432, 5, 65557, 786432, 5, 131093, 786432, 5, 196629, 786432, 5, 262165, 786432, 5, 65558, 786432, 5, 131094, 786432, 5, 196630, 786432, 5, 262166, 655360, 6, 65559, 786432, 5, 131095, 786432, 5, 196631, 786432, 5, 262167, 786432, 5, 327697, 720896, 5, 393233, 720896, 5, 327698, 589824, 6, 393234, 786432, 5, 327699, 786432, 5, 393235, 786432, 5, 327700, 786432, 5, 393236, 786432, 5, 327701, 786432, 5, 393237, 786432, 5, 327702, 786432, 5, 393238, 786432, 5, 327703, 786432, 5, 393239, 786432, 5, -131062, 720896, 4, -131061, 786432, 4, -131060, 786432, 4, -65526, 720896, 6, -65525, 786432, 6, -65524, 786432, 6, -131056, 851968, 4, -65520, 851968, 6, -131059, 786432, 4, -131058, 786432, 4, -131057, 786432, 4, -65523, 786432, 6, -65522, 786432, 6, -65521, 786432, 6, -65536, 917504, 2, -65535, 983040, 2, -65534, 1048576, 2, -65533, 917504, 2, -65532, 983040, 2, -65531, 1048576, 2, -65530, 917504, 2, -65529, 983040, 2, -65528, 1048576, 2, 65535, 655360, 0, 131071, 655360, 1, 196607, 655360, 1, 262143, 655360, 1, 327679, 655360, 1, 393215, 655360, 1, 458751, 655360, 1, 524287, 655360, 1, 589823, 655360, 1, 0, 720896, 0, 65536, 720896, 1, 131072, 720896, 1, 196608, 720896, 1, 262144, 720896, 1, 327680, 720896, 1, 393216, 720896, 1, 458752, 720896, 1, 524288, 720896, 1, 524289, 720896, 1, 524290, 720896, 1, 524291, 720896, 1, 524292, 720896, 1, 524293, 720896, 1, 524294, 720896, 1, 524295, 720896, 1, 524296, 720896, 1, 524297, 786432, 1, 458761, 786432, 1, 458760, 720896, 1, 458759, 720896, 1, 458758, 458752, 1, 458757, 720896, 1, 458756, 720896, 1, 458755, 720896, 1, 458754, 720896, 1, 458753, 720896, 1, -262145, 851968, 4, -196609, 851968, 5, -131073, 851968, 5, -65537, 851968, 5, -1, 851968, 6, -262146, 786432, 4, -262147, 786432, 4, -196610, 589824, 6, -196611, 786432, 5, -6, 786432, 5, -5, 786432, 5, -4, 786432, 5, -3, 786432, 5, -2, 786432, 5, -65538, 786432, 5, -131074, 786432, 5, -131075, 786432, 5, -65539, 655360, 6, 65534, 851968, 5, 131070, 851968, 5, 196606, 851968, 5, 262142, 851968, 5, 327678, 851968, 5, 393214, 851968, 5, 458750, 851968, 5, 65533, 786432, 5, 65532, 786432, 5, 65531, 786432, 5, 65530, 786432, 5, 65529, 720896, 5, 131066, 786432, 5, 196602, 786432, 5, 262138, 786432, 5, 262139, 786432, 5, 327675, 786432, 5, 131068, 786432, 5, 131069, 786432, 5, 196605, 786432, 5, 262141, 786432, 5, 327677, 786432, 5, 393213, 786432, 5, 458749, 786432, 5, 393212, 786432, 5, 393211, 786432, 5, 458748, 786432, 5, 327676, 655360, 6, 262140, 786432, 5, 196604, 786432, 5, 131067, 786432, 5, 196603, 589824, 6, 458747, 786432, 5, 458746, 786432, 5, 393210, 786432, 5, 327674, 786432, 5, -7, 720896, 5, 131065, 720896, 5, 196601, 720896, 5, 262137, 720896, 5, 327673, 720896, 5, 393209, 720896, 5, 458745, 720896, 5, -327684, 720896, 3, -196594, 720896, 3, -196597, 720896, 3, -65518, 720896, 3, -65516, 720896, 3, -327686, 1048576, 5, -327685, 720896, 3, -196595, 917504, 6, -65514, 983040, 6, -327683, 983040, 5, -65513, 1048576, 5, -262151, 720896, 4, -196615, 720896, 5, -131079, 720896, 5, -65543, 720896, 5, -262150, 786432, 4, -196614, 655360, 6, -131078, 786432, 5, -65542, 786432, 5, -262149, 786432, 4, -196613, 786432, 5, -131077, 786432, 5, -65541, 786432, 5, -262148, 786432, 4, -196612, 786432, 5, -131076, 786432, 5, -65540, 786432, 5, 458769, 720896, 5, 524305, 720896, 6, 458775, 786432, 5, 458774, 786432, 5, 458773, 786432, 5, 458772, 786432, 5, 458771, 786432, 5, 458770, 786432, 5, 524306, 786432, 6, 524307, 786432, 6, 524308, 786432, 6, 524309, 786432, 6, 524310, 786432, 6, 524311, 786432, 6, 327690, 720896, 1, 393226, 720896, 1, 458762, 720896, 1, 524298, 720896, 1, 327691, 720896, 1, 393227, 720896, 1, 458763, 720896, 1, 524299, 720896, 1, 327692, 720896, 1, 393228, 720896, 1, 458764, 720896, 1, 524300, 720896, 1, 327693, 720896, 1, 393229, 720896, 1, 458765, 720896, 1, 524301, 720896, 1, 327694, 720896, 1, 393230, 720896, 1, 458766, 720896, 1, 524302, 720896, 1, 327695, 720896, 1, 393231, 720896, 1, 458767, 720896, 1, 524303, 720896, 1, 327696, 720896, 1, 393232, 720896, 1, 458768, 720896, 1, 524304, 720896, 1, 29, 851968, 4, 65565, 851968, 5, 131101, 851968, 5, 196637, 851968, 5, 262173, 851968, 5, 327709, 851968, 5, 393245, 851968, 5, 458781, 851968, 5, 524317, 851968, 6, 24, 786432, 4, 25, 786432, 4, 26, 786432, 4, 27, 786432, 4, 28, 786432, 4, 65560, 786432, 5, 65561, 786432, 5, 65562, 786432, 5, 65563, 786432, 5, 65564, 786432, 5, 131100, 786432, 5, 196636, 589824, 6, 131099, 786432, 5, 131098, 786432, 5, 131097, 786432, 5, 131096, 786432, 5, 196632, 786432, 5, 262168, 786432, 5, 327704, 786432, 5, 393240, 786432, 5, 458776, 786432, 5, 524312, 786432, 6, 196633, 786432, 5, 262169, 786432, 5, 327705, 786432, 5, 393241, 786432, 5, 458777, 786432, 5, 524313, 786432, 6, 196634, 786432, 5, 262170, 786432, 5, 327706, 786432, 5, 393242, 655360, 6, 458778, 786432, 5, 524314, 786432, 6, 196635, 786432, 5, 262171, 786432, 5, 327707, 786432, 5, 393243, 786432, 5, 458779, 786432, 5, 524315, 786432, 6, 262172, 786432, 5, 327708, 786432, 5, 393244, 786432, 5, 458780, 786432, 5, 524316, 786432, 6, -196593, 1048576, 6, -65509, 720896, 3, -65510, 720896, 3, -65517, 720896, 3, -65515, 720896, 3, 524282, 786432, 5, 524283, 786432, 5, 524284, 786432, 5, 524285, 786432, 5, 524281, 720896, 5, 524286, 851968, 5, 589817, 720896, 6, 589818, 786432, 6, 589819, 786432, 6, 589820, 786432, 6, 589821, 786432, 6, 589822, 851968, 6, -196569, 720896, 4, -131033, 720896, 5, -65497, 720896, 5, 39, 720896, 5, 65575, 720896, 5, 131111, 720896, 5, 196647, 720896, 5, 262183, 720896, 5, 327719, 720896, 5, 393255, 720896, 5, 458791, 720896, 5, 524327, 720896, 5, 589863, 720896, 5, 655399, 720896, 6, -262104, 917504, 6, -196568, 786432, 4, -131032, 786432, 5, -65496, 655360, 6, 40, 786432, 5, 65576, 786432, 5, 131112, 786432, 5, 196648, 786432, 5, 262184, 786432, 5, 327720, 786432, 5, 393256, 786432, 5, 458792, 786432, 5, 524328, 655360, 6, 589864, 786432, 5, 655400, 786432, 6, -262103, 983040, 5, -196567, 786432, 4, -131031, 786432, 5, -65495, 786432, 5, 41, 589824, 6, 65577, 786432, 5, 131113, 786432, 5, 196649, 786432, 5, 262185, 786432, 5, 327721, 786432, 5, 393257, 786432, 5, 458793, 786432, 5, 524329, 786432, 5, 589865, 786432, 5, 655401, 786432, 6, -262102, 720896, 3, -196566, 786432, 4, -131030, 786432, 5, -65494, 786432, 5, 42, 786432, 5, 65578, 786432, 5, 131114, 786432, 5, 196650, 786432, 5, 262186, 655360, 6, 327722, 786432, 5, 393258, 786432, 5, 458794, 786432, 5, 524330, 786432, 5, 589866, 786432, 5, 655402, 786432, 6, -262101, 1048576, 6, -196565, 786432, 4, -131029, 786432, 5, -65493, 786432, 5, 43, 786432, 5, 65579, 786432, 5, 131115, 786432, 5, 196651, 786432, 5, 262187, 786432, 5, 327723, 786432, 5, 393259, 655360, 6, 458795, 655360, 6, 524331, 786432, 5, 589867, 786432, 5, 655403, 786432, 6, -262100, 720896, 3, -196564, 786432, 4, -131028, 786432, 5, -65492, 786432, 5, 44, 786432, 5, 65580, 786432, 5, 131116, 786432, 5, 196652, 786432, 5, 262188, 786432, 5, 327724, 786432, 5, 393260, 786432, 5, 458796, 786432, 5, 524332, 786432, 5, 589868, 786432, 5, 655404, 786432, 6, -262099, 983040, 6, -196563, 786432, 4, -131027, 786432, 5, -65491, 786432, 5, 45, 589824, 6, 65581, 655360, 6, 131117, 655360, 6, 196653, 786432, 5, 262189, 786432, 5, 327725, 786432, 5, 393261, 786432, 5, 458797, 786432, 5, 524333, 786432, 5, 589869, 655360, 6, 655405, 786432, 6, -196562, 786432, 4, -131026, 786432, 5, -65490, 786432, 5, 46, 786432, 5, 65582, 786432, 5, 131118, 786432, 5, 196654, 786432, 5, 262190, 786432, 5, 327726, 786432, 5, 393262, 786432, 5, 458798, 786432, 5, 524334, 786432, 5, 589870, 786432, 5, 655406, 786432, 6, -196561, 851968, 4, -131025, 851968, 5, -65489, 851968, 5, 47, 851968, 5, 65583, 851968, 5, 131119, 851968, 5, 196655, 851968, 5, 262191, 851968, 5, 327727, 851968, 5, 393263, 851968, 5, 458799, 851968, 5, 524335, 851968, 5, 589871, 851968, 5, 655407, 851968, 6, -131042, 720896, 4, -65506, 720896, 5, 30, 720896, 5, 65566, 720896, 5, 131102, 720896, 5, 196638, 720896, 5, 262174, 720896, 5, 327710, 720896, 5, 393246, 720896, 5, 458782, 720896, 5, 524318, 720896, 5, -196577, 917504, 6, -131041, 786432, 4, -65505, 786432, 5, 31, 655360, 6, 65567, 786432, 5, 131103, 786432, 5, 196639, 786432, 5, 262175, 786432, 5, 327711, 786432, 5, 393247, 786432, 5, 458783, 786432, 5, 524319, 786432, 5, -196576, 983040, 5, -131040, 786432, 4, -65504, 786432, 5, 32, 786432, 5, 65568, 589824, 6, 131104, 786432, 5, 196640, 786432, 5, 262176, 786432, 5, 327712, 786432, 5, 393248, 786432, 5, 458784, 786432, 5, 524320, 786432, 5, -196575, 720896, 3, -131039, 786432, 4, -65503, 786432, 5, 33, 786432, 5, 65569, 786432, 5, 131105, 786432, 5, 196641, 786432, 5, 262177, 786432, 5, 327713, 655360, 6, 393249, 786432, 5, 458785, 786432, 5, 524321, 786432, 5, -196574, 1048576, 6, -131038, 786432, 4, -65502, 786432, 5, 34, 786432, 5, 65570, 786432, 5, 131106, 786432, 5, 196642, 786432, 5, 262178, 786432, 5, 327714, 786432, 5, 393250, 786432, 5, 458786, 655360, 6, 524322, 655360, 6, -196573, 720896, 3, -131037, 786432, 4, -65501, 786432, 5, 35, 786432, 5, 65571, 786432, 5, 131107, 786432, 5, 196643, 786432, 5, 262179, 786432, 5, 327715, 786432, 5, 393251, 786432, 5, 458787, 786432, 5, 524323, 786432, 5, -196572, 983040, 6, -131036, 786432, 4, -65500, 786432, 5, 36, 786432, 5, 65572, 589824, 6, 131108, 655360, 6, 196644, 655360, 6, 262180, 786432, 5, 327716, 786432, 5, 393252, 786432, 5, 458788, 786432, 5, 524324, 786432, 5, -131035, 786432, 4, -65499, 786432, 5, 37, 786432, 5, 65573, 786432, 5, 131109, 786432, 5, 196645, 786432, 5, 262181, 786432, 5, 327717, 786432, 5, 393253, 786432, 5, 458789, 786432, 5, 524325, 786432, 5, -131034, 851968, 4, -65498, 851968, 5, 38, 851968, 5, 65574, 851968, 5, 131110, 851968, 5, 196646, 851968, 5, 262182, 851968, 5, 327718, 851968, 5, 393254, 851968, 5, 458790, 851968, 5, 524326, 851968, 5, 589854, 720896, 5, 655390, 720896, 5, 720926, 720896, 6, 589855, 655360, 6, 655391, 786432, 5, 720927, 786432, 6, 589856, 786432, 5, 655392, 786432, 5, 720928, 786432, 6, 589857, 786432, 5, 655393, 786432, 5, 720929, 786432, 6, 589858, 786432, 5, 655394, 786432, 5, 720930, 786432, 6, 589859, 786432, 5, 655395, 786432, 5, 720931, 786432, 6, 589860, 786432, 5, 655396, 655360, 6, 720932, 786432, 6, 589861, 786432, 5, 655397, 786432, 5, 720933, 786432, 6, 589862, 851968, 5, 655398, 851968, 5, 720934, 851968, 6, -458704, 720896, 4, -393168, 720896, 5, -327632, 720896, 5, -262096, 720896, 5, -196560, 720896, 5, -131024, 720896, 5, -65488, 720896, 5, 48, 720896, 5, 65584, 720896, 5, 131120, 720896, 5, 196656, 720896, 5, 262192, 720896, 5, 327728, 720896, 5, 393264, 720896, 6, -524239, 917504, 6, -458703, 786432, 4, -393167, 786432, 5, -327631, 655360, 6, -262095, 786432, 5, -196559, 786432, 5, -131023, 786432, 5, -65487, 786432, 5, 49, 786432, 5, 65585, 786432, 5, 131121, 786432, 5, 196657, 786432, 5, 262193, 655360, 6, 327729, 786432, 5, 393265, 786432, 6, -524238, 983040, 5, -458702, 786432, 4, -393166, 786432, 5, -327630, 786432, 5, -262094, 589824, 6, -196558, 786432, 5, -131022, 786432, 5, -65486, 786432, 5, 50, 786432, 5, 65586, 786432, 5, 131122, 786432, 5, 196658, 786432, 5, 262194, 786432, 5, 327730, 786432, 5, 393266, 786432, 6, -524237, 720896, 3, -458701, 786432, 4, -393165, 786432, 5, -327629, 786432, 5, -262093, 786432, 5, -196557, 786432, 5, -131021, 786432, 5, -65485, 786432, 5, 51, 655360, 6, 65587, 786432, 5, 131123, 786432, 5, 196659, 786432, 5, 262195, 786432, 5, 327731, 786432, 5, 393267, 786432, 6, -524236, 1048576, 6, -458700, 786432, 4, -393164, 786432, 5, -327628, 786432, 5, -262092, 786432, 5, -196556, 786432, 5, -131020, 786432, 5, -65484, 786432, 5, 52, 786432, 5, 65588, 786432, 5, 131124, 655360, 6, 196660, 655360, 6, 262196, 786432, 5, 327732, 786432, 5, 393268, 786432, 6, -524235, 720896, 3, -458699, 786432, 4, -393163, 786432, 5, -327627, 786432, 5, -262091, 786432, 5, -196555, 786432, 5, -131019, 786432, 5, -65483, 786432, 5, 53, 786432, 5, 65589, 786432, 5, 131125, 786432, 5, 196661, 786432, 5, 262197, 786432, 5, 327733, 786432, 5, 393269, 786432, 6, -524234, 983040, 6, -458698, 786432, 4, -393162, 786432, 5, -327626, 786432, 5, -262090, 589824, 6, -196554, 655360, 6, -131018, 655360, 6, -65482, 786432, 5, 54, 786432, 5, 65590, 786432, 5, 131126, 786432, 5, 196662, 786432, 5, 262198, 786432, 5, 327734, 655360, 6, 393270, 786432, 6, -458697, 786432, 4, -393161, 786432, 5, -327625, 786432, 5, -262089, 786432, 5, -196553, 786432, 5, -131017, 786432, 5, -65481, 786432, 5, 55, 786432, 5, 65591, 786432, 5, 131127, 786432, 5, 196663, 786432, 5, 262199, 786432, 5, 327735, 786432, 5, 393271, 786432, 6, -458696, 851968, 4, -393160, 851968, 5, -327624, 851968, 5, -262088, 851968, 5, -196552, 851968, 5, -131016, 851968, 5, -65480, 851968, 5, 56, 851968, 5, 65592, 851968, 5, 131128, 851968, 5, 196664, 851968, 5, 262200, 851968, 5, 327736, 851968, 5, 393272, 851968, 6)
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_whpvu")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_rbo5b")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(374, -216)
+zoom = Vector2(1.5, 1.5)
+process_callback = 0
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_yddet")
+
+[node name="Label" type="Label" parent="."]
+offset_left = 167.0
+offset_top = -133.0
+offset_right = 332.0
+offset_bottom = -69.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("8_6pcaf")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="Player" type="Node" parent="."]
+
+[node name="Label" type="Label" parent="Player"]
+visible = false
+offset_left = 167.0
+offset_top = -145.0
+offset_right = 332.0
+offset_bottom = -81.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("8_6pcaf")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_target", "follow_path")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(374, -216)
+script = ExtResource("5_x25dj")
+priority = 10
+follow_mode = 4
+follow_target = NodePath("../CharacterBody2D/PlayerVisuals")
+follow_path = NodePath("../Path2D")
+zoom = Vector2(1.5, 1.5)
+tween_resource = ExtResource("6_4vtmp")
+tween_on_load = false
+draw_limits = true
+
+[node name="Path2D" type="Path2D" parent="."]
+position = Vector2(152, -216)
+curve = SubResource("Curve2D_usrhf")
+
+[node name="CharacterBody2D" parent="." instance=ExtResource("8_a2pel")]
+position = Vector2(225, -28)
+
+[editable path="CharacterBody2D"]
diff --git a/addons/phantom_camera/examples/example_scenes/2D/2d_limit_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D/2d_limit_example_scene.tscn
new file mode 100644
index 0000000..7735ec6
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/2d_limit_example_scene.tscn
@@ -0,0 +1,324 @@
+[gd_scene load_steps=16 format=3 uid="uid://w20wokw3ohsq"]
+
+[ext_resource type="Script" uid="uid://c5yewe1hewu7j" path="res://addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd" id="1_ijqyv"]
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_wmhqb"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_v5qg2"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_oqmwp"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="4_4b648"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="5_pr2x5"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="9_twplb"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="9_w5e16"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("1_wmhqb")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+5:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[sub_resource type="Resource" id="Resource_ct1eh"]
+script = ExtResource("9_twplb")
+duration = 0.9
+transition = 2
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_xmxri"]
+size = Vector2(790, 410)
+
+[sub_resource type="Resource" id="Resource_exr3j"]
+script = ExtResource("9_twplb")
+duration = 0.9
+transition = 2
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_wtfjw"]
+size = Vector2(1530, 700)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_daeuh"]
+size = Vector2(1027.5, 610.5)
+
+[node name="ExampleScene2D" type="Node2D"]
+script = ExtResource("1_ijqyv")
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+metadata/_edit_lock_ = true
+
+[node name="TileMap" type="TileMap" parent="."]
+z_index = -1
+position = Vector2(-97, 0)
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_0/tile_data = PackedInt32Array(-458743, 851968, 6, -458744, 786432, 6, -458745, 786432, 6, -458746, 786432, 6, -458747, 786432, 6, -458748, 786432, 6, -458749, 786432, 6, -458750, 786432, 6, -458751, 786432, 6, -393218, 786432, 6, -393219, 786432, 6, -393220, 786432, 6, -393221, 720896, 6, -458752, 786432, 6, -393217, 786432, 6, -524279, 851968, 5, -589815, 851968, 5, -655351, 851968, 5, -720887, 851968, 4, -720888, 786432, 4, -720889, 786432, 4, -720890, 786432, 4, -720891, 786432, 4, -720892, 786432, 4, -720893, 786432, 4, -720894, 786432, 4, -720895, 786432, 4, -720896, 786432, 4, -655361, 786432, 4, -655362, 786432, 4, -655363, 786432, 4, -655364, 786432, 4, -655365, 720896, 4, -589829, 720896, 5, -524293, 720896, 5, -458757, 720896, 5, -589828, 786432, 5, -524292, 786432, 5, -458756, 786432, 5, -589827, 786432, 5, -524291, 786432, 5, -458755, 786432, 5, -589826, 786432, 5, -524290, 786432, 5, -458754, 786432, 5, -589825, 786432, 5, -524289, 786432, 5, -458753, 786432, 5, -655360, 786432, 5, -589824, 786432, 5, -524288, 786432, 5, -655359, 786432, 5, -589823, 786432, 5, -524287, 786432, 5, -655358, 786432, 5, -589822, 786432, 5, -524286, 786432, 5, -655357, 786432, 5, -589821, 786432, 5, -524285, 786432, 5, -655356, 786432, 5, -589820, 786432, 5, -524284, 786432, 5, -655355, 786432, 5, -589819, 786432, 5, -524283, 786432, 5, -655354, 786432, 5, -589818, 786432, 5, -524282, 786432, 5, -655353, 786432, 5, -589817, 786432, 5, -524281, 786432, 5, -655352, 786432, 5, -589816, 786432, 5, -524280, 786432, 5)
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(65533, 720896, 0, 65534, 720896, 0, 65535, 720896, 0, 0, 720896, 0, 1, 720896, 0, 2, 720896, 0, 3, 720896, 0, 4, 720896, 0, 5, 720896, 0, 6, 720896, 0, 7, 720896, 0, 8, 720896, 0, 9, 786432, 0, 65532, 720896, 0, 65531, 655360, 0, 131067, 655360, 1, 196603, 655360, 1, 262139, 655360, 1, 327675, 655360, 1, 393211, 655360, 1, 131068, 720896, 1, 196604, 720896, 1, 262140, 720896, 1, 327676, 720896, 1, 393212, 720896, 1, 131069, 720896, 1, 196605, 720896, 1, 262141, 720896, 1, 327677, 720896, 1, 393213, 720896, 1, 131070, 720896, 1, 196606, 720896, 1, 262142, 720896, 1, 327678, 720896, 1, 393214, 720896, 1, 131071, 720896, 1, 196607, 720896, 1, 262143, 720896, 1, 327679, 720896, 1, 393215, 720896, 1, 65536, 720896, 1, 131072, 720896, 1, 196608, 720896, 1, 262144, 720896, 1, 327680, 720896, 1, 65537, 720896, 1, 131073, 720896, 1, 196609, 720896, 1, 262145, 720896, 1, 327681, 720896, 1, 65538, 720896, 1, 131074, 720896, 1, 196610, 720896, 1, 262146, 720896, 1, 327682, 720896, 1, 65539, 720896, 1, 131075, 720896, 1, 196611, 720896, 1, 262147, 720896, 1, 327683, 720896, 1, 65540, 720896, 1, 131076, 720896, 1, 196612, 720896, 1, 262148, 720896, 1, 327684, 720896, 1, 65541, 720896, 1, 131077, 720896, 1, 196613, 720896, 1, 262149, 720896, 1, 327685, 720896, 1, 65542, 720896, 1, 131078, 720896, 1, 196614, 720896, 1, 262150, 720896, 1, 327686, 720896, 1, 65543, 720896, 1, 131079, 720896, 1, 196615, 720896, 1, 262151, 720896, 1, 327687, 720896, 1, 65544, 720896, 1, 131080, 720896, 1, 196616, 720896, 1, 262152, 720896, 1, 327688, 720896, 1, 65545, 786432, 1, 131081, 786432, 1, 196617, 786432, 1, 262153, 786432, 1, 327689, 786432, 1)
+
+[node name="TileMap2" type="TileMap" parent="."]
+z_index = -1
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(8, 786432, 4, 9, 786432, 4, 10, 786432, 4, 11, 786432, 4, 12, 786432, 4, 13, 786432, 4, 14, 786432, 4, 65528, 851968, 5, 65527, 786432, 5, 65526, 786432, 5, 65525, 786432, 5, 65524, 786432, 5, 65523, 786432, 5, 65544, 786432, 5, 131080, 786432, 5, 196616, 786432, 5, 262152, 786432, 5, 65545, 786432, 5, 131081, 786432, 5, 196617, 786432, 5, 262153, 786432, 5, 65546, 786432, 5, 131082, 786432, 5, 196618, 786432, 5, 262154, 786432, 5, 65547, 786432, 5, 131083, 786432, 5, 196619, 786432, 5, 262155, 786432, 5, 65548, 786432, 5, 131084, 786432, 5, 196620, 786432, 5, 262156, 786432, 5, 65549, 786432, 5, 131085, 786432, 5, 196621, 786432, 5, 262157, 786432, 5, 65550, 786432, 5, 131086, 786432, 5, 196622, 786432, 5, 262158, 786432, 5, 327694, 786432, 5, 327693, 786432, 5, 327692, 786432, 5, 327691, 786432, 5, 327690, 786432, 5, 327689, 786432, 5, 327688, 786432, 5, -131089, 720896, 5, -65553, 720896, 5, -17, 720896, 5, 65519, 720896, 6, -131088, 786432, 5, -65552, 786432, 5, -16, 786432, 5, 65520, 786432, 5, -131087, 786432, 5, -65551, 786432, 5, -15, 786432, 5, 65521, 786432, 5, -131086, 786432, 5, -65550, 786432, 5, -14, 786432, 5, 65522, 786432, 5, -131085, 786432, 5, -65549, 786432, 5, -13, 786432, 5, -131084, 786432, 5, -65548, 786432, 5, -12, 786432, 5, -131083, 786432, 5, -65547, 786432, 5, -11, 786432, 5, -131082, 786432, 5, -65546, 786432, 5, -10, 786432, 5, -131081, 786432, 5, -65545, 786432, 5, -9, 786432, 5, -131080, 851968, 5, -65544, 851968, 5, -8, 851968, 5, 131064, 851968, 5, 196600, 851968, 5, 262136, 851968, 5, 327672, 851968, 5, 393208, 851968, 5, 131056, 786432, 5, 196592, 786432, 5, 262128, 786432, 5, 327664, 786432, 5, 393200, 786432, 5, 131057, 786432, 5, 196593, 786432, 5, 262129, 786432, 5, 327665, 786432, 5, 393201, 786432, 5, 131058, 786432, 5, 196594, 786432, 5, 262130, 786432, 5, 327666, 786432, 5, 393202, 786432, 5, 131059, 786432, 5, 196595, 786432, 5, 262131, 786432, 5, 327667, 786432, 5, 393203, 786432, 5, 131060, 786432, 5, 196596, 786432, 5, 262132, 786432, 5, 327668, 786432, 5, 393204, 786432, 5, 131061, 786432, 5, 196597, 786432, 5, 262133, 786432, 5, 327669, 786432, 5, 393205, 786432, 5, 131062, 786432, 5, 196598, 786432, 5, 262134, 786432, 5, 327670, 786432, 5, 393206, 786432, 5, 131063, 786432, 5, 196599, 786432, 5, 262135, 786432, 5, 327671, 786432, 5, 393207, 786432, 5, 17, 786432, 4, 65553, 786432, 5, 131089, 786432, 5, 196625, 786432, 5, 262161, 786432, 5, 327697, 786432, 5, 18, 786432, 4, 65554, 786432, 5, 131090, 786432, 5, 196626, 786432, 5, 262162, 786432, 5, 327698, 786432, 5, 15, 786432, 4, 65551, 786432, 5, 131087, 786432, 5, 196623, 786432, 5, 262159, 786432, 5, 327695, 786432, 5, 16, 786432, 4, 65552, 786432, 5, 131088, 786432, 5, 196624, 786432, 5, 262160, 786432, 5, 327696, 786432, 5, 19, 786432, 4, 65555, 786432, 5, 131091, 786432, 5, 196627, 786432, 5, 262163, 786432, 5, 327699, 786432, 5, -458769, 720896, 5, -393233, 720896, 5, -458768, 786432, 5, -393232, 786432, 5, -458767, 786432, 5, -393231, 786432, 5, -458766, 786432, 5, -393230, 786432, 5, -458765, 786432, 5, -393229, 786432, 5, -458764, 786432, 5, -393228, 786432, 5, -458763, 786432, 5, -393227, 786432, 5, -458762, 786432, 5, -393226, 786432, 5, -458761, 786432, 5, -393225, 786432, 5, -458760, 851968, 5, -393224, 851968, 5, -655377, 720896, 4, -589841, 720896, 5, -524305, 720896, 5, -655376, 786432, 4, -589840, 786432, 5, -524304, 786432, 5, -655375, 786432, 4, -589839, 786432, 5, -524303, 786432, 5, -655374, 786432, 4, -589838, 786432, 5, -524302, 786432, 5, -655373, 786432, 4, -589837, 786432, 5, -524301, 786432, 5, -655372, 786432, 4, -589836, 786432, 5, -524300, 786432, 5, -655371, 786432, 4, -589835, 786432, 5, -524299, 786432, 5, -655370, 786432, 4, -589834, 786432, 5, -524298, 786432, 5, -655369, 786432, 4, -589833, 786432, 5, -524297, 786432, 5, -655368, 851968, 4, -589832, 851968, 5, -524296, 851968, 5, -327697, 720896, 5, -327696, 786432, 5, -327695, 786432, 5, -327694, 786432, 5, -327693, 786432, 5, -327692, 786432, 5, -327691, 786432, 5, -327690, 786432, 5, -327689, 786432, 5, -327688, 851968, 5, -262161, 720896, 5, -262160, 786432, 5, -262159, 786432, 5, -262158, 786432, 5, -262157, 786432, 5, -262156, 786432, 5, -262155, 786432, 5, -262154, 786432, 5, -262153, 786432, 5, -262152, 851968, 5, -196625, 720896, 5, -196624, 786432, 5, -196623, 786432, 5, -196622, 786432, 5, -196621, 786432, 5, -196620, 786432, 5, -196619, 786432, 5, -196618, 786432, 5, -196617, 786432, 5, -196616, 851968, 5, 20, 786432, 4, 65556, 786432, 5, 131092, 786432, 5, 196628, 786432, 5, 262164, 786432, 5, 327700, 786432, 5, 21, 786432, 4, 65557, 786432, 5, 131093, 786432, 5, 196629, 786432, 5, 262165, 786432, 5, 327701, 786432, 5, 22, 786432, 4, 65558, 786432, 5, 131094, 786432, 5, 196630, 786432, 5, 262166, 786432, 5, 327702, 786432, 5, 23, 786432, 4, 65559, 786432, 5, 131095, 786432, 5, 196631, 786432, 5, 262167, 786432, 5, 327703, 786432, 5, 24, 786432, 4, 65560, 786432, 5, 131096, 786432, 5, 196632, 786432, 5, 262168, 786432, 5, 327704, 786432, 5, 25, 786432, 4, 65561, 786432, 5, 131097, 786432, 5, 196633, 786432, 5, 262169, 786432, 5, 327705, 786432, 5, 26, 786432, 4, 65562, 786432, 5, 131098, 786432, 5, 196634, 786432, 5, 262170, 786432, 5, 327706, 786432, 5, 27, 786432, 4, 65563, 786432, 5, 131099, 786432, 5, 196635, 786432, 5, 262171, 786432, 5, 327707, 786432, 5, -65498, 917504, 3, 38, 786432, 4, 65574, 786432, 5, 131110, 786432, 5, 196646, 786432, 5, 262182, 786432, 5, 327718, 786432, 5, 39, 786432, 4, 65575, 786432, 5, 131111, 786432, 5, 196647, 786432, 5, 262183, 786432, 5, 327719, 786432, 5, -65496, 983040, 3, 40, 786432, 4, 65576, 786432, 5, 131112, 786432, 5, 196648, 786432, 5, 262184, 786432, 5, 327720, 786432, 5, -65495, 983040, 3, 41, 786432, 4, 65577, 786432, 5, 131113, 786432, 5, 196649, 786432, 5, 262185, 786432, 5, 327721, 786432, 5, -65494, 983040, 3, 42, 786432, 4, 65578, 786432, 5, 131114, 786432, 5, 196650, 786432, 5, 262186, 786432, 5, 327722, 786432, 5, -65493, 983040, 3, 43, 786432, 4, 65579, 786432, 5, 131115, 786432, 5, 196651, 786432, 5, 262187, 786432, 5, 327723, 786432, 5, -65492, 983040, 3, 44, 786432, 4, 65580, 786432, 5, 131116, 786432, 5, 196652, 786432, 5, 262188, 786432, 5, 327724, 786432, 5, -65491, 983040, 3, 45, 786432, 4, 65581, 786432, 5, 131117, 786432, 5, 196653, 786432, 5, 262189, 786432, 5, 327725, 786432, 5, -65490, 983040, 3, 46, 786432, 4, 65582, 786432, 5, 131118, 786432, 5, 196654, 786432, 5, 262190, 786432, 5, 327726, 786432, 5, -65489, 983040, 3, 47, 786432, 4, 65583, 786432, 5, 131119, 786432, 5, 196655, 786432, 5, 262191, 786432, 5, 327727, 786432, 5, -65488, 1048576, 3, 48, 786432, 4, 65584, 786432, 5, 131120, 786432, 5, 196656, 786432, 5, 262192, 786432, 5, 327728, 786432, 5, 49, 851968, 4, 65585, 851968, 5, 131121, 851968, 5, 196657, 851968, 5, 262193, 851968, 5, 327729, 851968, 5, -65497, 983040, 3, -589774, 720896, 4, -524238, 720896, 5, -458702, 720896, 5, -393166, 720896, 5, -327630, 720896, 5, -262094, 720896, 5, -196558, 720896, 5, -131022, 720896, 5, -65486, 720896, 5, 50, 720896, 5, 65586, 720896, 5, 131122, 720896, 5, 196658, 720896, 5, 262194, 720896, 5, 327730, 720896, 5, -589773, 786432, 4, -524237, 786432, 5, -458701, 786432, 5, -393165, 786432, 5, -327629, 786432, 5, -262093, 786432, 5, -196557, 786432, 5, -131021, 786432, 5, -65485, 786432, 5, 51, 786432, 5, 65587, 786432, 5, 131123, 786432, 5, 196659, 786432, 5, 262195, 786432, 5, 327731, 786432, 5, -589772, 786432, 4, -524236, 786432, 5, -458700, 786432, 5, -393164, 786432, 5, -327628, 786432, 5, -262092, 786432, 5, -196556, 786432, 5, -131020, 786432, 5, -65484, 786432, 5, 52, 786432, 5, 65588, 786432, 5, 131124, 786432, 5, 196660, 786432, 5, 262196, 786432, 5, 327732, 786432, 5, -589771, 786432, 4, -524235, 786432, 5, -458699, 786432, 5, -393163, 786432, 5, -327627, 786432, 5, -262091, 786432, 5, -196555, 786432, 5, -131019, 786432, 5, -65483, 786432, 5, 53, 786432, 5, 65589, 786432, 5, 131125, 786432, 5, 196661, 786432, 5, 262197, 786432, 5, 327733, 786432, 5, -589770, 786432, 4, -524234, 786432, 5, -458698, 786432, 5, -393162, 786432, 5, -327626, 786432, 5, -262090, 786432, 5, -196554, 786432, 5, -131018, 786432, 5, -65482, 786432, 5, 54, 786432, 5, 65590, 786432, 5, 131126, 786432, 5, 196662, 786432, 5, 262198, 786432, 5, 327734, 786432, 5, -589769, 786432, 4, -524233, 786432, 5, -458697, 786432, 5, -393161, 786432, 5, -327625, 786432, 5, -262089, 786432, 5, -196553, 786432, 5, -131017, 786432, 5, -65481, 786432, 5, 55, 786432, 5, 65591, 786432, 5, 131127, 786432, 5, 196663, 786432, 5, 262199, 786432, 5, 327735, 786432, 5, -589768, 786432, 4, -524232, 786432, 5, -458696, 786432, 5, -393160, 786432, 5, -327624, 786432, 5, -262088, 786432, 5, -196552, 786432, 5, -131016, 786432, 5, -65480, 786432, 5, 56, 786432, 5, 65592, 786432, 5, 131128, 786432, 5, 196664, 786432, 5, 262200, 786432, 5, 327736, 786432, 5, -589767, 786432, 4, -524231, 786432, 5, -458695, 786432, 5, -393159, 786432, 5, -327623, 786432, 5, -262087, 786432, 5, -196551, 786432, 5, -131015, 786432, 5, -65479, 786432, 5, 57, 786432, 5, 65593, 786432, 5, 131129, 786432, 5, 196665, 786432, 5, 262201, 786432, 5, 327737, 786432, 5, -589766, 786432, 4, -524230, 786432, 5, -458694, 786432, 5, -393158, 786432, 5, -327622, 786432, 5, -262086, 786432, 5, -196550, 786432, 5, -131014, 786432, 5, -65478, 786432, 5, 58, 786432, 5, 65594, 786432, 5, 131130, 786432, 5, 196666, 786432, 5, 262202, 786432, 5, 327738, 786432, 5, -589765, 851968, 4, -524229, 851968, 5, -458693, 851968, 5, -393157, 851968, 5, -327621, 851968, 5, -262085, 851968, 5, -196549, 851968, 5, -131013, 851968, 5, -65477, 851968, 5, 59, 851968, 5, 65595, 851968, 5, 131131, 851968, 5, 196667, 851968, 5, 262203, 851968, 5, 327739, 851968, 5, 28, 786432, 4, 65564, 786432, 5, 131100, 786432, 5, 196636, 786432, 5, 262172, 786432, 5, 327708, 786432, 5, 29, 786432, 4, 65565, 786432, 5, 131101, 786432, 5, 196637, 786432, 5, 262173, 786432, 5, 327709, 786432, 5, 30, 786432, 4, 65566, 786432, 5, 131102, 786432, 5, 196638, 786432, 5, 262174, 786432, 5, 327710, 786432, 5, 31, 786432, 4, 65567, 786432, 5, 131103, 786432, 5, 196639, 786432, 5, 262175, 786432, 5, 327711, 786432, 5, 32, 786432, 4, 65568, 786432, 5, 131104, 786432, 5, 196640, 786432, 5, 262176, 786432, 5, 327712, 786432, 5, 33, 786432, 4, 65569, 786432, 5, 131105, 786432, 5, 196641, 786432, 5, 262177, 786432, 5, 327713, 786432, 5, 34, 786432, 4, 65570, 786432, 5, 131106, 786432, 5, 196642, 786432, 5, 262178, 786432, 5, 327714, 786432, 5, 35, 786432, 4, 65571, 786432, 5, 131107, 786432, 5, 196643, 786432, 5, 262179, 786432, 5, 327715, 786432, 5, 36, 786432, 4, 65572, 786432, 5, 131108, 786432, 5, 196644, 786432, 5, 262180, 786432, 5, 327716, 786432, 5, 37, 786432, 4, 65573, 786432, 5, 131109, 786432, 5, 196645, 786432, 5, 262181, 786432, 5, 327717, 786432, 5, -65487, 458752, 6, -131023, 458752, 5, -196559, 458752, 5, -262095, 458752, 5, -327631, 458752, 5, -393167, 458752, 5, -458703, 458752, 5, -524239, 458752, 5, -589775, 458752, 4)
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_v5qg2")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_oqmwp")]
+unique_name_in_owner = true
+visible = false
+
+[node name="CharacterBody2D" parent="." instance=ExtResource("5_pr2x5")]
+unique_name_in_owner = true
+position = Vector2(66, -28)
+
+[node name="RoomLeftPhantomCamera2D" type="Node2D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(66, -91.205)
+script = ExtResource("4_4b648")
+priority = 5
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D/PlayerVisuals")
+zoom = Vector2(2, 2)
+tween_resource = SubResource("Resource_ct1eh")
+follow_offset = Vector2(0, -63.205)
+follow_damping = true
+draw_limits = true
+limit_target = NodePath("../TileMap")
+limit_margin = Vector4i(-50, 0, -50, 0)
+
+[node name="RoomLeftArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(117, -174)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomLeftArea2D"]
+position = Vector2(-79, 43)
+shape = SubResource("RectangleShape2D_xmxri")
+debug_color = Color(0, 0.6, 0.701961, 0.0313726)
+
+[node name="RoomCentrePhantomCamera2D" type="Node2D" parent="."]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(1474, -149)
+script = ExtResource("4_4b648")
+follow_mode = 2
+zoom = Vector2(1.5, 1.5)
+tween_resource = SubResource("Resource_exr3j")
+follow_damping = true
+draw_limits = true
+limit_target = NodePath("../RoomCentreArea2D/CollisionShape2D")
+
+[node name="RoomCentreArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(755, -179)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomCentreArea2D"]
+position = Vector2(338, -28)
+shape = SubResource("RectangleShape2D_wtfjw")
+debug_color = Color(0, 0.6, 0.701961, 0)
+
+[node name="RoomRightArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(2065, -160)
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="RoomRightArea2D"]
+position = Vector2(255.75, -3.25)
+shape = SubResource("RectangleShape2D_daeuh")
+debug_color = Color(0, 0.6, 0.701961, 0)
+
+[node name="RoomRightPhantomCamera2D" type="Node2D" parent="."]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(2347, -156)
+scale = Vector2(1.0024, 1)
+script = ExtResource("4_4b648")
+follow_mode = 2
+zoom = Vector2(2, 2)
+tween_resource = SubResource("Resource_exr3j")
+follow_damping = true
+draw_limits = true
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(66, -91.205)
+zoom = Vector2(2, 2)
+process_callback = 0
+limit_left = -387
+limit_top = -528
+limit_right = 433
+limit_bottom = 288
+position_smoothing_speed = 10.0
+editor_draw_limits = true
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("9_w5e16")
+
+[editable path="CharacterBody2D"]
diff --git a/addons/phantom_camera/examples/example_scenes/2D/2d_tweening_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/2D/2d_tweening_example_scene.tscn
new file mode 100644
index 0000000..24f2f3b
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/2d_tweening_example_scene.tscn
@@ -0,0 +1,397 @@
+[gd_scene load_steps=19 format=3 uid="uid://cpyb3ucwcqj8l"]
+
+[ext_resource type="Texture2D" uid="uid://c77npili4pel4" path="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png" id="1_oo2bo"]
+[ext_resource type="PackedScene" uid="uid://dg7rhrymsrrrm" path="res://addons/phantom_camera/examples/ui/ui_inventory.tscn" id="2_as4e6"]
+[ext_resource type="PackedScene" uid="uid://iq5xd1ob1res" path="res://addons/phantom_camera/examples/ui/ui_sign.tscn" id="3_6yi7w"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="4_bb7en"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="5_kikl5"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="6_8u8cj"]
+[ext_resource type="Resource" uid="uid://euybd2w0bax" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres" id="6_gu0o0"]
+[ext_resource type="PackedScene" uid="uid://7kh0xydx0b1o" path="res://addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn" id="8_g1syc"]
+[ext_resource type="Script" uid="uid://t8wa4e5y5hcf" path="res://addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd" id="9_184pu"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="11_myq47"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_easgx"]
+texture = ExtResource("1_oo2bo")
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+7:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+7:1/0 = 0
+7:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+2:2/0 = 0
+3:2/0 = 0
+4:2/0 = 0
+7:2/0 = 0
+7:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:3/0 = 0
+4:3/0 = 0
+7:3/0 = 0
+7:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+3:4/0 = 0
+4:4/0 = 0
+5:4/0 = 0
+7:4/0 = 0
+3:5/0 = 0
+4:5/0 = 0
+7:5/0 = 0
+3:6/0 = 0
+4:6/0 = 0
+7:6/0 = 0
+2:7/0 = 0
+3:7/0 = 0
+4:7/0 = 0
+5:7/0 = 0
+8:0/0 = 0
+8:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:0/0 = 0
+9:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:0/0 = 0
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:0/0 = 0
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:0/0 = 0
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:0/0 = 0
+13:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:0/0 = 0
+14:0/0/physics_layer_1/polygon_0/points = PackedVector2Array(8, -8, 8, 8, -8, 8)
+14:0/0/custom_data_0 = &"Sign"
+15:0/0 = 0
+16:0/0 = 0
+8:1/0 = 0
+8:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:1/0 = 0
+9:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:1/0 = 0
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:1/0 = 0
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:1/0 = 0
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:1/0 = 0
+13:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:1/0 = 0
+15:1/0 = 0
+16:1/0 = 0
+8:2/0 = 0
+8:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:2/0 = 0
+9:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:2/0 = 0
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:2/0 = 0
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:2/0 = 0
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:2/0 = 0
+13:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:2/0 = 0
+15:2/0 = 0
+16:2/0 = 0
+8:3/0 = 0
+8:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+9:3/0 = 0
+9:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+10:3/0 = 0
+10:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+11:3/0 = 0
+12:3/0 = 0
+12:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:3/0 = 0
+13:3/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:3/0 = 0
+15:3/0 = 0
+16:3/0 = 0
+8:4/0 = 0
+9:4/0 = 0
+10:4/0 = 0
+11:4/0 = 0
+11:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-7, 2.5, -5, -2, -2.5, -5, 2, -7, 8, -8, 8, 8, -8, 8)
+12:4/0 = 0
+12:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:4/0 = 0
+13:4/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 0, -6, 4, -1.5, 6.5, 1.5, 8, 8, -8, 8)
+14:4/0 = 0
+14:4/0/physics_layer_1/polygon_0/points = PackedVector2Array(-8, -8, -8, 8, 8, 8, 8, -8)
+14:4/0/custom_data_0 = &"Inventory"
+15:4/0 = 0
+16:4/0 = 0
+8:5/0 = 0
+9:5/0 = 0
+10:5/0 = 0
+11:5/0 = 0
+11:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+12:5/0 = 0
+12:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:5/0 = 0
+13:5/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+14:5/0 = 0
+15:5/0 = 0
+16:5/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+10:6/0 = 0
+11:6/0 = 0
+11:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, 1, 6.5, -3, 3, -6.5, -1.5)
+12:6/0 = 0
+12:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 8, 8, -8, 8)
+13:6/0 = 0
+13:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8, -8, 8, -8, 6, -0.5, 3, 3.5, -1.5, 6.5, -8, 8)
+14:6/0 = 0
+15:6/0 = 0
+16:6/0 = 0
+5:3/0 = 0
+
+[sub_resource type="TileSet" id="TileSet_kf7eg"]
+physics_layer_0/collision_layer = 1
+physics_layer_1/collision_layer = 2
+physics_layer_1/collision_mask = 2
+custom_data_layer_0/name = "Type"
+custom_data_layer_0/type = 21
+sources/0 = SubResource("TileSetAtlasSource_easgx")
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_tgk1y"]
+size = Vector2(140, 160)
+
+[sub_resource type="Resource" id="Resource_mtp70"]
+script = ExtResource("6_8u8cj")
+duration = 0.6
+transition = 1
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_clm0y"]
+size = Vector2(104, 160)
+
+[sub_resource type="Resource" id="Resource_8jg5c"]
+script = ExtResource("6_8u8cj")
+duration = 0.3
+transition = 8
+ease = 2
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_uka0w"]
+size = Vector2(560, 160)
+
+[sub_resource type="Resource" id="Resource_e4e41"]
+script = ExtResource("6_8u8cj")
+duration = 1.2
+transition = 10
+ease = 2
+
+[node name="ExampleScene2D" type="Node2D"]
+
+[node name="Background" type="CanvasLayer" parent="."]
+layer = -3
+
+[node name="ColorRect" type="ColorRect" parent="Background"]
+auto_translate_mode = 2
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -311.0
+offset_top = -173.0
+offset_right = 981.0
+offset_bottom = 548.0
+grow_horizontal = 2
+grow_vertical = 2
+localize_numeral_system = false
+color = Color(0.137255, 0.14902, 0.196078, 1)
+
+[node name="TileMap" type="TileMap" parent="."]
+z_index = -1
+scale = Vector2(3, 3)
+tile_set = SubResource("TileSet_kf7eg")
+format = 2
+layer_0/name = "Background"
+layer_0/tile_data = PackedInt32Array(-393216, 655360, 2, -327680, 655360, 3, -262144, 655360, 3, -196608, 655360, 3, -131072, 655360, 3, -65536, 655360, 3, -393215, 720896, 2, -327679, 720896, 1, -262143, 720896, 1, -196607, 720896, 1, -131071, 720896, 1, -65535, 720896, 1, -393214, 786432, 2, -327678, 786432, 3, -262142, 786432, 3, -196606, 786432, 3, -131070, 786432, 3, -65534, 786432, 3)
+layer_1/name = "Terrain"
+layer_1/z_index = 1
+layer_1/tile_data = PackedInt32Array(1, 720896, 0, 2, 720896, 0, 3, 720896, 0, 4, 720896, 0, 5, 720896, 0, 6, 720896, 0, 7, 720896, 0, 8, 720896, 0, 9, 786432, 0, 65545, 786432, 1, 131081, 786432, 1, 196617, 786432, 1, 262153, 786432, 1, 327689, 786432, 1, 393225, 786432, 1, 65537, 720896, 1, 131073, 720896, 1, 196609, 720896, 1, 262145, 458752, 1, 327681, 720896, 1, 393217, 720896, 1, 65538, 720896, 1, 131074, 720896, 1, 196610, 720896, 1, 262146, 720896, 1, 327682, 720896, 1, 393218, 720896, 1, 65539, 720896, 1, 131075, 720896, 1, 196611, 720896, 1, 262147, 720896, 1, 327683, 720896, 1, 393219, 720896, 1, 65540, 458752, 1, 131076, 720896, 1, 196612, 720896, 1, 262148, 720896, 1, 327684, 720896, 1, 393220, 720896, 1, 65541, 720896, 1, 131077, 720896, 1, 196613, 720896, 1, 262149, 458752, 1, 327685, 720896, 1, 393221, 720896, 1, 65542, 720896, 1, 131078, 720896, 1, 196614, 720896, 1, 262150, 720896, 1, 327686, 720896, 1, 393222, 720896, 1, 65543, 720896, 1, 131079, 720896, 1, 196615, 720896, 1, 262151, 720896, 1, 327687, 720896, 1, 393223, 458752, 1, 65544, 720896, 1, 131080, 720896, 1, 196616, 458752, 1, 262152, 720896, 1, 327688, 720896, 1, 393224, 720896, 1, 65546, 524288, 5, 65547, 524288, 5, 65548, 524288, 5, 65549, 524288, 5, 131082, 524288, 6, 131083, 524288, 6, 131084, 524288, 6, 131085, 524288, 6, 196618, 720896, 1, 262154, 720896, 1, 196619, 720896, 1, 262155, 720896, 1, 196620, 720896, 1, 262156, 720896, 1, 196621, 720896, 1, 262157, 720896, 1, 65550, 524288, 5, 65551, 524288, 5, 65552, 524288, 5, 131086, 524288, 6, 131087, 524288, 6, 131088, 524288, 6, 196622, 720896, 1, 196623, 720896, 1, 262159, 720896, 1, 262160, 720896, 1, 196624, 720896, 1, 262158, 720896, 1, 17, 720896, 4, 65553, 720896, 5, 131089, 720896, 5, 196625, 720896, 5, 262161, 720896, 5, 18, 786432, 4, 19, 786432, 4, 20, 786432, 4, 21, 786432, 4, 22, 786432, 4, 23, 786432, 4, 65554, 786432, 5, 131090, 786432, 5, 196626, 786432, 5, 262162, 786432, 5, 65555, 786432, 5, 131091, 589824, 6, 196627, 786432, 5, 262163, 786432, 5, 65556, 786432, 5, 131092, 786432, 5, 196628, 786432, 5, 262164, 786432, 5, 65557, 786432, 5, 131093, 786432, 5, 196629, 786432, 5, 262165, 786432, 5, 65558, 786432, 5, 131094, 786432, 5, 196630, 786432, 5, 262166, 655360, 6, 65559, 786432, 5, 131095, 786432, 5, 196631, 786432, 5, 262167, 786432, 5, 327697, 720896, 5, 393233, 720896, 5, 327698, 589824, 6, 393234, 786432, 5, 327699, 786432, 5, 393235, 786432, 5, 327700, 786432, 5, 393236, 786432, 5, 327701, 786432, 5, 393237, 786432, 5, 327702, 786432, 5, 393238, 786432, 5, 327703, 786432, 5, 393239, 786432, 5, -131062, 720896, 4, -131061, 786432, 4, -131060, 786432, 4, -65526, 720896, 6, -65525, 786432, 6, -65524, 786432, 6, -131056, 851968, 4, -65520, 851968, 6, -131059, 786432, 4, -131058, 786432, 4, -131057, 786432, 4, -65523, 786432, 6, -65522, 786432, 6, -65521, 786432, 6, -65536, 917504, 2, -65535, 983040, 2, -65534, 1048576, 2, -65533, 917504, 2, -65532, 983040, 2, -65531, 1048576, 2, -65530, 917504, 2, -65529, 983040, 2, -65528, 1048576, 2, 65535, 655360, 0, 131071, 655360, 1, 196607, 655360, 1, 262143, 655360, 1, 327679, 655360, 1, 393215, 655360, 1, 458751, 655360, 1, 524287, 655360, 1, 589823, 655360, 1, 0, 720896, 0, 65536, 720896, 1, 131072, 720896, 1, 196608, 720896, 1, 262144, 720896, 1, 327680, 720896, 1, 393216, 720896, 1, 458752, 720896, 1, 524288, 720896, 1, 524289, 720896, 1, 524290, 720896, 1, 524291, 720896, 1, 524292, 720896, 1, 524293, 720896, 1, 524294, 720896, 1, 524295, 720896, 1, 524296, 720896, 1, 524297, 786432, 1, 458761, 786432, 1, 458760, 720896, 1, 458759, 720896, 1, 458758, 458752, 1, 458757, 720896, 1, 458756, 720896, 1, 458755, 720896, 1, 458754, 720896, 1, 458753, 720896, 1, -262145, 851968, 4, -196609, 851968, 5, -131073, 851968, 5, -65537, 851968, 5, -1, 851968, 6, -262146, 786432, 4, -262147, 786432, 4, -196610, 589824, 6, -196611, 786432, 5, -6, 786432, 5, -5, 786432, 5, -4, 786432, 5, -3, 786432, 5, -2, 786432, 5, -65538, 786432, 5, -131074, 786432, 5, -131075, 786432, 5, -65539, 655360, 6, 65534, 851968, 5, 131070, 851968, 5, 196606, 851968, 5, 262142, 851968, 5, 327678, 851968, 5, 393214, 851968, 5, 458750, 851968, 5, 65533, 786432, 5, 65532, 786432, 5, 65531, 786432, 5, 65530, 786432, 5, 65529, 720896, 5, 131066, 786432, 5, 196602, 786432, 5, 262138, 786432, 5, 262139, 786432, 5, 327675, 786432, 5, 131068, 786432, 5, 131069, 786432, 5, 196605, 786432, 5, 262141, 786432, 5, 327677, 786432, 5, 393213, 786432, 5, 458749, 786432, 5, 393212, 786432, 5, 393211, 786432, 5, 458748, 786432, 5, 327676, 655360, 6, 262140, 786432, 5, 196604, 786432, 5, 131067, 786432, 5, 196603, 589824, 6, 458747, 786432, 5, 458746, 786432, 5, 393210, 786432, 5, 327674, 786432, 5, -7, 720896, 5, 131065, 720896, 5, 196601, 720896, 5, 262137, 720896, 5, 327673, 720896, 5, 393209, 720896, 5, 458745, 720896, 5, -327684, 720896, 3, -196594, 720896, 3, -196597, 720896, 3, -65518, 720896, 3, -65516, 720896, 3, -327686, 1048576, 5, -327685, 720896, 3, -196595, 917504, 6, -65514, 983040, 6, -327683, 983040, 5, -65513, 1048576, 5, -262151, 720896, 4, -196615, 720896, 5, -131079, 720896, 5, -65543, 720896, 5, -262150, 786432, 4, -196614, 655360, 6, -131078, 786432, 5, -65542, 786432, 5, -262149, 786432, 4, -196613, 786432, 5, -131077, 786432, 5, -65541, 786432, 5, -262148, 786432, 4, -196612, 786432, 5, -131076, 786432, 5, -65540, 786432, 5, 458769, 720896, 5, 458775, 786432, 5, 458774, 786432, 5, 458773, 786432, 5, 458772, 786432, 5, 458771, 786432, 5, 458770, 786432, 5, 327690, 720896, 1, 393226, 720896, 1, 458762, 720896, 1, 524298, 720896, 1, 327691, 720896, 1, 393227, 720896, 1, 458763, 720896, 1, 524299, 720896, 1, 327692, 720896, 1, 393228, 720896, 1, 458764, 720896, 1, 524300, 720896, 1, 327693, 720896, 1, 393229, 720896, 1, 458765, 720896, 1, 524301, 720896, 1, 327694, 720896, 1, 393230, 720896, 1, 458766, 720896, 1, 524302, 720896, 1, 327695, 720896, 1, 393231, 720896, 1, 458767, 720896, 1, 524303, 720896, 1, 327696, 720896, 1, 393232, 720896, 1, 458768, 720896, 1, 524304, 720896, 1, 29, 851968, 4, 65565, 851968, 5, 131101, 851968, 5, 196637, 851968, 5, 262173, 851968, 5, 327709, 851968, 5, 393245, 851968, 5, 458781, 851968, 5, 24, 786432, 4, 25, 786432, 4, 26, 786432, 4, 27, 786432, 4, 28, 786432, 4, 65560, 786432, 5, 65561, 786432, 5, 65562, 786432, 5, 65563, 786432, 5, 65564, 786432, 5, 131100, 786432, 5, 196636, 589824, 6, 131099, 786432, 5, 131098, 786432, 5, 131097, 786432, 5, 131096, 786432, 5, 196632, 786432, 5, 262168, 786432, 5, 327704, 786432, 5, 393240, 786432, 5, 458776, 786432, 5, 196633, 786432, 5, 262169, 786432, 5, 327705, 786432, 5, 393241, 786432, 5, 458777, 786432, 5, 196634, 786432, 5, 262170, 786432, 5, 327706, 786432, 5, 393242, 655360, 6, 458778, 786432, 5, 196635, 786432, 5, 262171, 786432, 5, 327707, 786432, 5, 393243, 786432, 5, 458779, 786432, 5, 262172, 786432, 5, 327708, 786432, 5, 393244, 786432, 5, 458780, 786432, 5, -196593, 1048576, 6, -393182, 1048576, 6, -393185, 917504, 6, -393180, 983040, 6, -393184, 983040, 5, -65509, 720896, 3, -65510, 720896, 3, -393181, 720896, 3, -393183, 720896, 3, -65517, 720896, 3, -65515, 720896, 3, -327650, 720896, 4, -262114, 720896, 5, -196578, 720896, 5, -131042, 720896, 5, -65506, 720896, 5, -327649, 786432, 4, -327648, 786432, 4, -327647, 786432, 4, -327646, 786432, 4, -327645, 786432, 4, -327644, 786432, 4, -327643, 786432, 4, -65499, 786432, 5, -131035, 786432, 5, -196571, 786432, 5, -262107, 655360, 6, -262108, 786432, 5, -262109, 786432, 5, -262110, 786432, 5, -262111, 786432, 5, -262112, 786432, 5, -262113, 786432, 5, -196577, 655360, 6, -131041, 786432, 5, -65505, 786432, 5, -65500, 655360, 6, -131036, 589824, 6, -196572, 786432, 5, -196573, 786432, 5, -196574, 786432, 5, -196575, 786432, 5, -196576, 786432, 5, -131040, 589824, 6, -65504, 786432, 5, -65501, 786432, 5, -131037, 786432, 5, -131038, 786432, 5, -131039, 786432, 5, -65503, 786432, 5, -65502, 786432, 5, 524318, 720896, 6, 458782, 720896, 5, 393246, 720896, 5, 327710, 720896, 5, 262174, 720896, 5, 196638, 720896, 5, 131102, 720896, 5, 65566, 720896, 5, 30, 720896, 5, 524319, 786432, 6, 524320, 786432, 6, 524321, 786432, 6, 524322, 786432, 6, 524323, 786432, 6, 524324, 786432, 6, 524325, 786432, 6, 31, 786432, 5, 65567, 786432, 5, 131103, 786432, 5, 196639, 786432, 5, 262175, 786432, 5, 327711, 786432, 5, 393247, 655360, 6, 458783, 786432, 5, 32, 786432, 5, 65568, 786432, 5, 131104, 786432, 5, 196640, 786432, 5, 262176, 786432, 5, 327712, 786432, 5, 393248, 786432, 5, 458784, 786432, 5, 33, 786432, 5, 65569, 786432, 5, 131105, 655360, 6, 196641, 786432, 5, 262177, 786432, 5, 327713, 786432, 5, 393249, 786432, 5, 458785, 786432, 5, 34, 786432, 5, 65570, 786432, 5, 131106, 786432, 5, 196642, 786432, 5, 262178, 655360, 6, 327714, 655360, 6, 393250, 786432, 5, 458786, 786432, 5, 35, 786432, 5, 65571, 786432, 5, 131107, 786432, 5, 196643, 786432, 5, 262179, 786432, 5, 327715, 786432, 5, 393251, 786432, 5, 458787, 786432, 5, 36, 655360, 6, 65572, 786432, 5, 131108, 786432, 5, 196644, 786432, 5, 262180, 786432, 5, 327716, 786432, 5, 393252, 786432, 5, 458788, 655360, 6, 37, 786432, 5, 65573, 786432, 5, 131109, 786432, 5, 196645, 786432, 5, 262181, 655360, 6, 327717, 786432, 5, 393253, 786432, 5, 458789, 786432, 5, 524281, 720896, 5, 524285, 786432, 5, 524284, 786432, 5, 524283, 786432, 5, 524282, 786432, 5, 589818, 786432, 5, 589819, 786432, 5, 589820, 786432, 5, 655356, 786432, 5, 655357, 786432, 5, 589821, 786432, 5, 655355, 786432, 5, 655354, 786432, 5, 720890, 786432, 5, 720891, 786432, 5, 720892, 786432, 5, 720893, 786432, 5, 720894, 851968, 5, 786429, 786432, 5, 786428, 786432, 5, 786427, 786432, 5, 786426, 786432, 5, 851962, 786432, 5, 917498, 786432, 5, 917499, 786432, 5, 851964, 786432, 5, 851965, 786432, 5, 917501, 786432, 5, 917500, 786432, 5, 851963, 786432, 5, 589817, 720896, 5, 655353, 720896, 5, 720889, 720896, 5, 786425, 720896, 5, 851961, 720896, 5, 917497, 720896, 5, 524286, 851968, 5, 589822, 851968, 5, 655358, 851968, 5, 786430, 851968, 5, 851966, 851968, 5, 917502, 851968, 5, 589824, 720896, 1, 655360, 720896, 1, 720896, 720896, 1, 786432, 720896, 1, 851968, 720896, 1, 589825, 720896, 1, 655361, 720896, 1, 720897, 720896, 1, 786433, 720896, 1, 851969, 720896, 1, 589826, 720896, 1, 655362, 720896, 1, 720898, 720896, 1, 786434, 720896, 1, 851970, 720896, 1, 589827, 720896, 1, 655363, 720896, 1, 720899, 720896, 1, 786435, 720896, 1, 851971, 720896, 1, 589828, 720896, 1, 655364, 720896, 1, 720900, 720896, 1, 786436, 720896, 1, 851972, 720896, 1, 589829, 720896, 1, 655365, 720896, 1, 720901, 720896, 1, 786437, 720896, 1, 851973, 720896, 1, 589830, 720896, 1, 655366, 720896, 1, 720902, 720896, 1, 786438, 720896, 1, 851974, 720896, 1, 589831, 720896, 1, 655367, 720896, 1, 720903, 720896, 1, 786439, 720896, 1, 851975, 720896, 1, 589832, 720896, 1, 655368, 720896, 1, 720904, 720896, 1, 786440, 720896, 1, 851976, 720896, 1, 589833, 786432, 1, 655369, 786432, 1, 720905, 786432, 1, 786441, 786432, 1, 851977, 786432, 1, 655359, 655360, 1, 720895, 655360, 1, 786431, 655360, 1, 851967, 655360, 1, 917503, 655360, 1, 589834, 720896, 1, 655370, 720896, 1, 720906, 720896, 1, 786442, 720896, 1, 851978, 720896, 1, 589835, 720896, 1, 655371, 720896, 1, 720907, 720896, 1, 786443, 720896, 1, 851979, 720896, 1, 589836, 720896, 1, 655372, 720896, 1, 720908, 720896, 1, 786444, 720896, 1, 851980, 720896, 1, 589837, 720896, 1, 655373, 720896, 1, 720909, 720896, 1, 786445, 720896, 1, 851981, 720896, 1, 589838, 720896, 1, 655374, 720896, 1, 720910, 720896, 1, 786446, 720896, 1, 851982, 720896, 1, 589839, 720896, 1, 655375, 720896, 1, 720911, 720896, 1, 786447, 720896, 1, 851983, 720896, 1, 589840, 720896, 1, 655376, 720896, 1, 720912, 720896, 1, 786448, 720896, 1, 851984, 720896, 1, 851985, 720896, 6, 851986, 786432, 6, 851987, 786432, 6, 851988, 786432, 6, 851989, 786432, 6, 851990, 786432, 6, 851991, 786432, 6, 851992, 786432, 6, 851993, 786432, 6, 851994, 786432, 6, 851995, 786432, 6, 851996, 786432, 6, 851997, 851968, 6, 524306, 786432, 5, 589842, 786432, 5, 655378, 786432, 5, 720914, 786432, 5, 786450, 786432, 5, 524307, 786432, 5, 589843, 786432, 5, 655379, 786432, 5, 720915, 786432, 5, 786451, 786432, 5, 524308, 786432, 5, 589844, 786432, 5, 655380, 786432, 5, 720916, 786432, 5, 786452, 786432, 5, 524309, 786432, 5, 589845, 786432, 5, 655381, 786432, 5, 720917, 786432, 5, 786453, 786432, 5, 524310, 786432, 5, 589846, 786432, 5, 655382, 786432, 5, 720918, 786432, 5, 786454, 786432, 5, 524311, 786432, 5, 589847, 786432, 5, 655383, 786432, 5, 720919, 786432, 5, 786455, 786432, 5, 524312, 786432, 5, 589848, 786432, 5, 655384, 786432, 5, 720920, 786432, 5, 786456, 786432, 5, 524313, 786432, 5, 589849, 786432, 5, 655385, 786432, 5, 720921, 786432, 5, 786457, 786432, 5, 524314, 786432, 5, 589850, 786432, 5, 655386, 786432, 5, 720922, 786432, 5, 786458, 786432, 5, 524315, 786432, 5, 589851, 786432, 5, 655387, 786432, 5, 720923, 786432, 5, 786459, 786432, 5, 524316, 786432, 5, 589852, 786432, 5, 655388, 786432, 5, 720924, 786432, 5, 786460, 786432, 5, 524305, 720896, 5, 589841, 720896, 5, 655377, 720896, 5, 720913, 720896, 5, 786449, 720896, 5, 524317, 851968, 5, 589853, 851968, 5, 655389, 851968, 5, 720925, 851968, 5, 786461, 851968, 5, -262106, 786432, 5, -196570, 786432, 5, -131034, 786432, 5, -65498, 786432, 5, 38, 786432, 5, 65574, 786432, 5, 131110, 655360, 6, 196646, 786432, 5, 262182, 786432, 5, 327718, 786432, 5, 393254, 786432, 5, 458790, 786432, 5, -262105, 786432, 5, -196569, 786432, 5, -131033, 786432, 5, -65497, 786432, 5, 39, 786432, 5, 65575, 786432, 5, 131111, 786432, 5, 196647, 786432, 5, 262183, 786432, 5, 327719, 786432, 5, 393255, 655360, 6, 458791, 786432, 5, -262104, 786432, 5, -196568, 786432, 5, -131032, 786432, 5, -65496, 786432, 5, 40, 655360, 6, 65576, 786432, 5, 131112, 786432, 5, 196648, 786432, 5, 262184, 655360, 6, 327720, 786432, 5, 393256, 786432, 5, 458792, 786432, 5, -262103, 786432, 5, -196567, 655360, 6, -131031, 786432, 5, -65495, 786432, 5, 41, 786432, 5, 65577, 786432, 5, 131113, 786432, 5, 196649, 786432, 5, 262185, 786432, 5, 327721, 786432, 5, 393257, 786432, 5, 458793, 786432, 5, -262102, 786432, 5, -196566, 786432, 5, -131030, 786432, 5, -65494, 786432, 5, 42, 786432, 5, 65578, 786432, 5, 131114, 655360, 6, 196650, 786432, 5, 262186, 786432, 5, 327722, 786432, 5, 393258, 786432, 5, 458794, 655360, 6, 524326, 786432, 6, 524327, 786432, 6, 524328, 786432, 6, 524329, 786432, 6, 524330, 786432, 6, -327642, 786432, 4, -327641, 786432, 4, -327640, 786432, 4, -327639, 786432, 4, -327638, 786432, 4, -327632, 851968, 4, -262096, 851968, 5, -196560, 851968, 5, -131024, 851968, 5, -65488, 851968, 5, 48, 851968, 5, 65584, 851968, 5, 131120, 851968, 5, 196656, 851968, 5, 262192, 851968, 5, 327728, 851968, 5, 393264, 851968, 5, 458800, 851968, 5, 524336, 851968, 6, -327637, 786432, 4, -327636, 786432, 4, -327635, 786432, 4, -327634, 786432, 4, -327633, 786432, 4, -262101, 786432, 5, -196565, 786432, 5, -131029, 655360, 6, -65493, 786432, 5, 43, 786432, 5, 65579, 786432, 5, 131115, 786432, 5, 196651, 786432, 5, 262187, 786432, 5, 327723, 655360, 6, 393259, 786432, 5, 458795, 786432, 5, -262100, 786432, 5, -196564, 786432, 5, -131028, 786432, 5, -65492, 786432, 5, 44, 655360, 6, 65580, 786432, 5, 131116, 786432, 5, 196652, 786432, 5, 262188, 786432, 5, 327724, 786432, 5, 393260, 786432, 5, 458796, 786432, 5, -262099, 786432, 5, -196563, 786432, 5, -131027, 786432, 5, -65491, 786432, 5, 45, 786432, 5, 65581, 655360, 6, 131117, 786432, 5, 196653, 786432, 5, 262189, 786432, 5, 327725, 786432, 5, 393261, 786432, 5, 458797, 786432, 5, -262098, 655360, 6, -196562, 786432, 5, -131026, 786432, 5, -65490, 786432, 5, 46, 786432, 5, 65582, 786432, 5, 131118, 786432, 5, 196654, 655360, 6, 262190, 786432, 5, 327726, 786432, 5, 393262, 786432, 5, 458798, 655360, 6, -262097, 786432, 5, -196561, 786432, 5, -131025, 786432, 5, -65489, 786432, 5, 47, 786432, 5, 65583, 786432, 5, 131119, 786432, 5, 196655, 786432, 5, 262191, 786432, 5, 327727, 786432, 5, 393263, 786432, 5, 458799, 786432, 5, 524331, 786432, 6, 524332, 786432, 6, 524333, 786432, 6, 524334, 786432, 6, 524335, 786432, 6)
+
+[node name="UI" type="CanvasLayer" parent="."]
+
+[node name="UIInventory" parent="UI" instance=ExtResource("2_as4e6")]
+unique_name_in_owner = true
+visible = false
+
+[node name="UISign" parent="UI" instance=ExtResource("3_6yi7w")]
+unique_name_in_owner = true
+visible = false
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(227, -28)
+process_callback = 0
+position_smoothing_speed = 10.0
+
+[node name="PhantomCameraHost" type="Node" parent="Camera2D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("4_bb7en")
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera2D" type="Node2D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+top_level = true
+position = Vector2(227, -28)
+script = ExtResource("5_kikl5")
+priority = 5
+follow_mode = 2
+follow_target = NodePath("../CharacterBody2D/PlayerVisuals")
+tween_resource = ExtResource("6_gu0o0")
+tween_on_load = false
+follow_damping = true
+draw_limits = true
+
+[node name="Label" type="Label" parent="Player"]
+offset_left = 167.0
+offset_top = -132.0
+offset_right = 332.0
+offset_bottom = -68.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("11_myq47")
+text = "[WASD] to move
+[Space] to jump"
+
+[node name="CharacterBody2D" parent="Player" instance=ExtResource("8_g1syc")]
+position = Vector2(227, -28)
+
+[node name="WideArea" type="Area2D" parent="." node_paths=PackedStringArray("area_pcam")]
+position = Vector2(393, -40)
+collision_layer = 2
+collision_mask = 2
+script = ExtResource("9_184pu")
+area_pcam = NodePath("PhantomCamera2D")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="WideArea"]
+position = Vector2(0, -40)
+shape = SubResource("RectangleShape2D_tgk1y")
+
+[node name="ColorRect" type="ColorRect" parent="WideArea"]
+offset_left = -70.0
+offset_top = -120.0
+offset_right = 70.0
+offset_bottom = 40.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+color = Color(0.556863, 0.447059, 0.545098, 0.698039)
+
+[node name="Label" type="Label" parent="WideArea"]
+offset_left = -77.0
+offset_top = -250.0
+offset_right = 76.0
+offset_bottom = -120.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("11_myq47")
+text = "Transition Type:
+Sine
+
+Duration:
+0.6s"
+horizontal_alignment = 1
+
+[node name="PhantomCamera2D" type="Node2D" parent="WideArea"]
+position = Vector2(4, -100)
+script = ExtResource("5_kikl5")
+zoom = Vector2(0.8, 0.8)
+tween_resource = SubResource("Resource_mtp70")
+draw_limits = true
+
+[node name="UpperZoomArea" type="Area2D" parent="." node_paths=PackedStringArray("area_pcam")]
+position = Vector2(649, -135)
+collision_layer = 2
+collision_mask = 2
+script = ExtResource("9_184pu")
+area_pcam = NodePath("PhantomCamera2D")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="UpperZoomArea"]
+position = Vector2(0, -40)
+shape = SubResource("RectangleShape2D_clm0y")
+
+[node name="CollisionShape2D2" type="CollisionShape2D" parent="UpperZoomArea"]
+position = Vector2(0, -40)
+shape = SubResource("RectangleShape2D_clm0y")
+
+[node name="ColorRect" type="ColorRect" parent="UpperZoomArea"]
+offset_left = -52.0
+offset_top = -120.0
+offset_right = 52.0
+offset_bottom = 40.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+color = Color(0.556863, 0.447059, 0.545098, 0.698039)
+
+[node name="Label" type="Label" parent="UpperZoomArea"]
+offset_left = -74.0
+offset_top = -251.0
+offset_right = 79.0
+offset_bottom = -121.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("11_myq47")
+text = "Transition Type:
+Circ
+
+Duration:
+0.3s"
+horizontal_alignment = 1
+
+[node name="PhantomCamera2D" type="Node2D" parent="UpperZoomArea"]
+position = Vector2(2, -83)
+script = ExtResource("5_kikl5")
+zoom = Vector2(2, 2)
+tween_resource = SubResource("Resource_8jg5c")
+draw_limits = true
+
+[node name="ForwardArea" type="Area2D" parent="." node_paths=PackedStringArray("area_pcam")]
+position = Vector2(1136, -38)
+collision_layer = 2
+collision_mask = 2
+script = ExtResource("9_184pu")
+area_pcam = NodePath("PhantomCamera2D")
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="ForwardArea"]
+position = Vector2(0, -42)
+shape = SubResource("RectangleShape2D_uka0w")
+
+[node name="ColorRect" type="ColorRect" parent="ForwardArea"]
+offset_left = -280.0
+offset_top = -122.0
+offset_right = 280.0
+offset_bottom = 38.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+color = Color(0.556863, 0.447059, 0.545098, 0.698039)
+
+[node name="Label" type="Label" parent="ForwardArea"]
+offset_left = -76.0
+offset_top = -252.0
+offset_right = 77.0
+offset_bottom = -122.0
+theme_override_colors/font_color = Color(0.294118, 1, 0.631373, 1)
+theme_override_fonts/font = ExtResource("11_myq47")
+text = "Transition Type:
+Back
+
+Duration:
+1.2s"
+horizontal_alignment = 1
+
+[node name="PhantomCamera2D" type="Node2D" parent="ForwardArea"]
+position = Vector2(344, -46)
+script = ExtResource("5_kikl5")
+zoom = Vector2(0.9, 0.9)
+tween_resource = SubResource("Resource_e4e41")
+draw_limits = true
+
+[editable path="Player/CharacterBody2D"]
diff --git a/addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn b/addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn
new file mode 100644
index 0000000..f0dca01
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/2D/sub_scenes/playable_character_2d.tscn
@@ -0,0 +1,120 @@
+[gd_scene load_steps=10 format=3 uid="uid://7kh0xydx0b1o"]
+
+[ext_resource type="Script" uid="uid://cb46ypjv5p72s" path="res://addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd" id="1_jnc14"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="2_62b2n"]
+[ext_resource type="Texture2D" uid="uid://cscjjt55iw2cu" path="res://addons/phantom_camera/examples/textures/2D/player_sprite.svg" id="2_yr8cm"]
+[ext_resource type="Script" uid="uid://bhexx6mj1xv3q" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd" id="4_rloon"]
+[ext_resource type="Resource" uid="uid://cecrnq0wnkexh" path="res://addons/phantom_camera/examples/resources/tween/item_focus_phantom_camera_2d_tween.tres" id="5_4iyk1"]
+[ext_resource type="Resource" uid="uid://cllveybboaqk5" path="res://addons/phantom_camera/examples/resources/tween/inventory_phantom_camera_2d_tween.tres" id="6_2h6fv"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5hryl"]
+bg_color = Color(0.85098, 0.894118, 0.937255, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.113725, 0.113725, 0.113725, 1)
+corner_radius_top_left = 7
+corner_radius_top_right = 7
+corner_radius_bottom_right = 7
+corner_radius_bottom_left = 7
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_xj4ar"]
+size = Vector2(64, 57)
+
+[sub_resource type="RectangleShape2D" id="RectangleShape2D_18i13"]
+size = Vector2(64, 57)
+
+[node name="CharacterBody2D" type="CharacterBody2D"]
+script = ExtResource("1_jnc14")
+
+[node name="DarkOverlay" type="ColorRect" parent="."]
+unique_name_in_owner = true
+visible = false
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -1000.0
+offset_top = -1000.0
+offset_right = 1000.0
+offset_bottom = 1000.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 0.615686)
+
+[node name="PlayerVisuals" type="Node2D" parent="."]
+unique_name_in_owner = true
+
+[node name="PlayerSprite" type="Sprite2D" parent="PlayerVisuals"]
+unique_name_in_owner = true
+scale = Vector2(0.5, 0.5)
+texture = ExtResource("2_yr8cm")
+
+[node name="InteractionPrompt" type="Panel" parent="PlayerVisuals"]
+unique_name_in_owner = true
+visible = false
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -16.0
+offset_top = -66.0
+offset_right = 16.0
+offset_bottom = -34.0
+grow_horizontal = 2
+grow_vertical = 0
+size_flags_vertical = 0
+theme_override_styles/panel = SubResource("StyleBoxFlat_5hryl")
+
+[node name="Label" type="Label" parent="PlayerVisuals/InteractionPrompt"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -3.0
+offset_bottom = 5.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_color = Color(0, 0, 0, 1)
+theme_override_fonts/font = ExtResource("2_62b2n")
+theme_override_font_sizes/font_size = 26
+text = "F"
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
+position = Vector2(0, -0.5)
+shape = SubResource("RectangleShape2D_xj4ar")
+
+[node name="PlayerArea2D" type="Area2D" parent="."]
+unique_name_in_owner = true
+collision_layer = 2
+collision_mask = 2
+priority = 20
+
+[node name="CollisionShape2D" type="CollisionShape2D" parent="PlayerArea2D"]
+position = Vector2(0, -0.5)
+shape = SubResource("RectangleShape2D_18i13")
+
+[node name="ItemFocusPhantomCamera2D" type="Node2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(0, -122)
+script = ExtResource("4_rloon")
+zoom = Vector2(2, 2)
+frame_preview = false
+tween_resource = ExtResource("5_4iyk1")
+follow_damping_value = Vector2(0, 0)
+draw_limits = true
+
+[node name="InventoryPhantomCamera2D" type="Node2D" parent="."]
+unique_name_in_owner = true
+position = Vector2(-183, -5)
+script = ExtResource("4_rloon")
+zoom = Vector2(2.5, 2.5)
+frame_preview = false
+tween_resource = ExtResource("6_2h6fv")
+follow_damping_value = Vector2(0, 0)
+draw_limits = true
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_example_scene.tscn
new file mode 100644
index 0000000..d557c93
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_example_scene.tscn
@@ -0,0 +1,413 @@
+[gd_scene load_steps=41 format=3 uid="uid://cypbptekk8etg"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_u86qq"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="2_jl1he"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="3_an0dt"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="3_yfuq5"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="4_iy6qn"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="5_0ku52"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="6_prr6u"]
+[ext_resource type="Script" uid="uid://uvw6pg1ut0ms" path="res://addons/phantom_camera/examples/scripts/3D/npc.gd" id="7_nl3ax"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="8_xvqcg"]
+[ext_resource type="Script" uid="uid://bnhxcejvr6wi3" path="res://addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd" id="9_hqgwi"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="10_cd0kn"]
+
+[sub_resource type="Resource" id="Resource_jtk1d"]
+script = ExtResource("6_prr6u")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_o161n"]
+script = ExtResource("6_prr6u")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxMesh" id="BoxMesh_7tjw4"]
+size = Vector3(2, 0.5, 4)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hpllm"]
+transparency = 1
+albedo_color = Color(0.988235, 0.478431, 0.905882, 0.0901961)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_65o6h"]
+size = Vector3(2, 0.5, 4)
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_tpc7d"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_g0eml"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_v5iy7"]
+albedo_color = Color(0.988235, 0.478431, 0.905882, 1)
+
+[sub_resource type="Resource" id="Resource_tpvee"]
+script = ExtResource("8_xvqcg")
+duration = 0.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_bxbnv"]
+script = ExtResource("6_prr6u")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_wcrbb"]
+size = Vector3(6.8, 0.1, 5.4)
+
+[sub_resource type="Resource" id="Resource_7ih0k"]
+script = ExtResource("8_xvqcg")
+duration = 0.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_4iyps"]
+script = ExtResource("6_prr6u")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_ctyr8"]
+size = Vector3(7.4, 0.1, 3.6)
+
+[sub_resource type="Resource" id="Resource_x5y0u"]
+script = ExtResource("8_xvqcg")
+duration = 0.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_pgiyx"]
+script = ExtResource("6_prr6u")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_ua072"]
+size = Vector3(6.8, 0.1, 3.6)
+
+[sub_resource type="BoxMesh" id="BoxMesh_ugc3s"]
+size = Vector3(1, 1, 2)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_68thd"]
+albedo_color = Color(0.34902, 0.862745, 0.854902, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_wphly"]
+size = Vector3(1, 0.5, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_gyp5s"]
+size = Vector3(20, 40, 30)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_lfaqs"]
+size = Vector3(20, 40, 30)
+
+[sub_resource type="BoxMesh" id="BoxMesh_n70lt"]
+size = Vector3(14, 40, 6)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_jxmqm"]
+size = Vector3(14, 40, 6)
+
+[sub_resource type="BoxMesh" id="BoxMesh_x0tgm"]
+size = Vector3(8, 40, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_t67ef"]
+size = Vector3(50, 40, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_rmslh"]
+size = Vector3(0.5, 6, 13.5)
+
+[sub_resource type="BoxMesh" id="BoxMesh_242ij"]
+size = Vector3(2, 3, 3)
+
+[sub_resource type="BoxMesh" id="BoxMesh_niuda"]
+size = Vector3(8, 6, 0.5)
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.948876, 0.315649, 0, -0.315649, 0.948876, -2.53871, 2, 9.76232)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_u86qq")
+
+[node name="PlayerGroup" type="Node" parent="."]
+
+[node name="PlayerCharacterBody3D" parent="PlayerGroup" instance=ExtResource("2_jl1he")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.53871, 0.5, 7.26232)
+script = ExtResource("3_yfuq5")
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="PlayerGroup"]
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, -2.47682, -0.0708016, 7.93048)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("3_an0dt")
+font_size = 48
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="PlayerGroup" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999858, 0, 0, 0, 0.94884, 0.315632, 0, -0.315637, 0.948825, -2.53871, 2, 9.76232)
+top_level = true
+script = ExtResource("4_iy6qn")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = ExtResource("5_0ku52")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_jtk1d")
+follow_offset = Vector3(0, 1.5, 2.5)
+follow_damping = true
+
+[node name="NPCGroup" type="Node" parent="."]
+
+[node name="NPCPhantomCamera3D" type="Node3D" parent="NPCGroup"]
+unique_name_in_owner = true
+transform = Transform3D(0.616596, -0.109786, 0.779587, -2.23517e-08, 0.990229, 0.13945, -0.78728, -0.0859841, 0.610571, -2.98802, 1.50739, 1.19719)
+script = ExtResource("4_iy6qn")
+tween_resource = ExtResource("5_0ku52")
+camera_3d_resource = SubResource("Resource_o161n")
+
+[node name="NPCDescriptionLabel" type="Label3D" parent="NPCGroup"]
+transform = Transform3D(1, 0, 0, 0, 0.866026, 0.5, 0, -0.5, 0.866025, -3.04693, 0.367287, 0.953757)
+text = "Input Example"
+font = ExtResource("3_an0dt")
+
+[node name="NPCDialogueExampleLabel" type="Label3D" parent="NPCGroup"]
+unique_name_in_owner = true
+transform = Transform3D(1, 4.54671e-10, 1.65487e-10, 4.25644e-10, 0.939693, 0.34202, 0, -0.34202, 0.939693, -4.46738, 1.58641, -0.253679)
+modulate = Color(1, 0.603922, 0.254902, 1)
+text = "Press [ F ] to change camera"
+font = ExtResource("3_an0dt")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="NPCGroup"]
+transform = Transform3D(0.819152, 4.83851e-10, -0.573576, -3.92481e-09, 1, -6.3473e-09, 0.573576, 7.45058e-09, 0.819152, -3.46138, -0.4, 0.875321)
+mesh = SubResource("BoxMesh_7tjw4")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_hpllm")
+metadata/_edit_group_ = true
+
+[node name="NPCInteractionArea3D" type="Area3D" parent="NPCGroup/NPCInteractionZoneMesh"]
+unique_name_in_owner = true
+transform = Transform3D(1, -2.68591e-26, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+monitorable = false
+
+[node name="NPCInterationCollisionShape3D" type="CollisionShape3D" parent="NPCGroup/NPCInteractionZoneMesh/NPCInteractionArea3D"]
+shape = SubResource("BoxShape3D_65o6h")
+
+[node name="NPC" type="StaticBody3D" parent="NPCGroup"]
+transform = Transform3D(1, 4.83851e-10, 0, 4.25644e-10, 1, -7.45058e-09, 0, 7.45058e-09, 1, -4.56338, 0.5, -0.272679)
+script = ExtResource("7_nl3ax")
+
+[node name="PlayerCollisionShape3D2" type="CollisionShape3D" parent="NPCGroup/NPC"]
+transform = Transform3D(1, -2.68591e-26, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+shape = SubResource("CapsuleShape3D_tpc7d")
+
+[node name="NPCMesh" type="MeshInstance3D" parent="NPCGroup/NPC"]
+transform = Transform3D(1, -2.68591e-26, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+mesh = SubResource("CapsuleMesh_g0eml")
+skeleton = NodePath("../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_v5iy7")
+
+[node name="MoveToLocation" type="Node3D" parent="NPCGroup"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.70084, 0.5, 0.962891)
+
+[node name="FixedCameraTriggerZone" type="Node" parent="."]
+
+[node name="FixedCameraLabel" type="Label3D" parent="FixedCameraTriggerZone"]
+unique_name_in_owner = true
+transform = Transform3D(0.939693, 0.280167, -0.196175, 1.49012e-08, 0.573577, 0.819152, 0.34202, -0.769751, 0.538986, -0.538716, -0.247626, 3.13456)
+text = "Fixed Camera
+Example"
+font = ExtResource("3_an0dt")
+
+[node name="NorthRoomPhantomCamera3D" type="Node3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(0.38357, -0.555836, 0.737507, -0.105898, 0.766851, 0.633027, -0.917417, -0.320912, 0.235279, 6.89638, 4.73986, 0.115512)
+script = ExtResource("4_iy6qn")
+tween_resource = SubResource("Resource_tpvee")
+camera_3d_resource = SubResource("Resource_bxbnv")
+
+[node name="NorthRoomTrigger" type="Area3D" parent="FixedCameraTriggerZone" node_paths=PackedStringArray("area_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.45, -0.9)
+priority = 5
+script = ExtResource("9_hqgwi")
+area_pcam = NodePath("../NorthRoomPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedCameraTriggerZone/NorthRoomTrigger"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0, -0.4)
+shape = SubResource("BoxShape3D_wcrbb")
+
+[node name="EntryRoomPhantomCamera3D" type="Node3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(0.258818, -0.482963, 0.836515, 1.3027e-15, 0.866025, 0.499999, -0.965924, -0.129409, 0.224143, 6.69741, 4.73364, 4.02374)
+script = ExtResource("4_iy6qn")
+tween_resource = SubResource("Resource_7ih0k")
+camera_3d_resource = SubResource("Resource_4iyps")
+
+[node name="EntryRoomTrigger" type="Area3D" parent="FixedCameraTriggerZone" node_paths=PackedStringArray("area_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.00003, -0.454982, 3.00572)
+priority = 5
+script = ExtResource("9_hqgwi")
+area_pcam = NodePath("../EntryRoomPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedCameraTriggerZone/EntryRoomTrigger"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 0, 0.2)
+shape = SubResource("BoxShape3D_ctyr8")
+
+[node name="SouthRoomPhantomCamera3D" type="Node3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(-0.766043, -0.492403, 0.413175, 0, 0.642787, 0.766043, -0.642786, 0.586825, -0.492403, 6.89741, 4.73364, 5.62374)
+script = ExtResource("4_iy6qn")
+tween_resource = SubResource("Resource_x5y0u")
+camera_3d_resource = SubResource("Resource_pgiyx")
+
+[node name="SouthRoomTrigger" type="Area3D" parent="FixedCameraTriggerZone" node_paths=PackedStringArray("area_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.45, 6.7)
+priority = 5
+script = ExtResource("9_hqgwi")
+area_pcam = NodePath("../SouthRoomPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedCameraTriggerZone/SouthRoomTrigger"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0, 0.1)
+shape = SubResource("BoxShape3D_ua072")
+
+[node name="CSGMesh3D" type="CSGMesh3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.14238, 1.82571, 2.88655)
+mesh = SubResource("BoxMesh_ugc3s")
+material = SubResource("StandardMaterial3D_68thd")
+
+[node name="CSGMesh3D2" type="CSGMesh3D" parent="FixedCameraTriggerZone/CSGMesh3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00192642, -0.0120339, 0.00494432)
+operation = 2
+mesh = SubResource("BoxMesh_wphly")
+material = SubResource("StandardMaterial3D_68thd")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Environment" type="Node3D" parent="Environment"]
+
+[node name="Floor" parent="Environment/Environment" instance=ExtResource("10_cd0kn")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="West Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -16, 0.5, 0)
+metadata/_edit_group_ = true
+metadata/_edit_lock_ = true
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Environment/West Wall"]
+mesh = SubResource("BoxMesh_gyp5s")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/West Wall"]
+shape = SubResource("BoxShape3D_lfaqs")
+
+[node name="East Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16.999, 0.502, 0)
+metadata/_edit_group_ = true
+metadata/_edit_lock_ = true
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Environment/East Wall"]
+mesh = SubResource("BoxMesh_gyp5s")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/East Wall"]
+shape = SubResource("BoxShape3D_lfaqs")
+
+[node name="North Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, -6.90828)
+metadata/_edit_group_ = true
+metadata/_edit_lock_ = true
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Environment/North Wall"]
+mesh = SubResource("BoxMesh_n70lt")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/North Wall"]
+shape = SubResource("BoxShape3D_jxmqm")
+
+[node name="South Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 0.5, 9.087)
+metadata/_edit_group_ = true
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Environment/Environment/South Wall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 0)
+mesh = SubResource("BoxMesh_x0tgm")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/South Wall"]
+shape = SubResource("BoxShape3D_t67ef")
+
+[node name="FixedCamOuterWall" type="CSGMesh3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 2)
+use_collision = true
+mesh = SubResource("BoxMesh_rmslh")
+
+[node name="FixedCamOuterDoorway" type="CSGMesh3D" parent="Environment/Environment/FixedCamOuterWall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 1)
+operation = 2
+mesh = SubResource("BoxMesh_242ij")
+
+[node name="FixedCamNorthWall" type="CSGMesh3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2.5, 1)
+use_collision = true
+mesh = SubResource("BoxMesh_niuda")
+
+[node name="FixedCamNorthDoorway" type="CSGMesh3D" parent="Environment/Environment/FixedCamNorthWall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+operation = 2
+mesh = SubResource("BoxMesh_242ij")
+
+[node name="FixedCamSouthWall" type="CSGMesh3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2.5, 5.1)
+use_collision = true
+mesh = SubResource("BoxMesh_niuda")
+
+[node name="FixedCamSouthDoorway" type="CSGMesh3D" parent="Environment/Environment/FixedCamSouthWall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.50541, 1.19209e-07)
+operation = 2
+mesh = SubResource("BoxMesh_242ij")
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_framed_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_framed_example_scene.tscn
new file mode 100644
index 0000000..b7086c0
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_framed_example_scene.tscn
@@ -0,0 +1,158 @@
+[gd_scene load_steps=11 format=3 uid="uid://cx7x48cpi8gcd"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_6uslv"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_5cpe8"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="3_422w7"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_4qurp"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_uw36d"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="6_fcomr"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_i060b"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="7_iyghi"]
+
+[sub_resource type="Resource" id="Resource_wg1pr"]
+script = ExtResource("4_4qurp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("7_iyghi")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.793353, 0.608762, 0, -0.608762, 0.793353, 0, 2.93468, 3.17294)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_6uslv")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.99995, 0, 0, 0, 0.79324, 0.608671, 0, -0.608675, 0.793235, 0, 2.93468, 3.17294)
+top_level = true
+script = ExtResource("2_5cpe8")
+follow_mode = 5
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = ExtResource("3_422w7")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_wg1pr")
+follow_damping = true
+follow_distance = 4.0
+dead_zone_width = 0.139
+dead_zone_height = 0.14
+show_viewfinder_in_play = true
+spring_length = 4.0
+
+[node name="PlayerCharacterBody3D" parent="Player" instance=ExtResource("5_uw36d")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+script = ExtResource("6_fcomr")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_i060b")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.636134, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.54597, -0.540694, -3.39517)
+use_collision = true
+radius = 1.53269
+height = 2.5036
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.64877, -1.50101, 1.22863)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.4732, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.40027, -1.69814, 3.36997)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.13768, -0.599204, -1.04651)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.7976, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.84078, -0.497663, 4.44352)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.52545, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.88916, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.83837, -0.241718, 7.14677)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.34377, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.9834, 0.138478, -1.89037)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.38147, 0.0440434, 8.36617)
+use_collision = true
+size = Vector3(4.57784, 1.08809, 3.11285)
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_glued_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_glued_example_scene.tscn
new file mode 100644
index 0000000..e7ad802
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_glued_example_scene.tscn
@@ -0,0 +1,211 @@
+[gd_scene load_steps=15 format=3 uid="uid://d2lx45noxq685"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_7a3wq"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_158c0"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_ganw1"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_kig2n"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_caky3"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="6_b6ic4"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_kkbaa"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="7_i1dbs"]
+
+[sub_resource type="Resource" id="Resource_ucp3e"]
+script = ExtResource("3_ganw1")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_ab013"]
+script = ExtResource("4_kig2n")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2h36r"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_w3olp"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cw102"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("7_i1dbs")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Node3D" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.638767, 0.7694, 0, -0.7694, 0.638768, 0, 6.39, 7)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_7a3wq")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999954, 0, 0, 0, 0.638683, 0.769345, 0, -0.769298, 0.638723, 0, 6.39, 7)
+top_level = true
+script = ExtResource("2_158c0")
+priority = 5
+follow_mode = 1
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_ucp3e")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ab013")
+follow_damping = true
+follow_damping_value = Vector3(0.3, 0.3, 0.3)
+
+[node name="PlayerCharacterBody3D" parent="Player" instance=ExtResource("5_caky3")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.39, 7)
+script = ExtResource("6_b6ic4")
+enable_gravity = false
+
+[node name="PlayerVisual" parent="Player/PlayerCharacterBody3D" index="2"]
+visible = false
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.04486, 0.519002, -1.52506)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_w3olp")
+
+[node name="PlayerMeshInstance3D2" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.51494, 0.519, 4.06618)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_cw102")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_kkbaa")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.62737, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 24.9378, 0.31181, -5.46661)
+use_collision = true
+radius = 2.77591
+height = 1.62362
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.58617, 0.31181, 6.6322)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.774, 0.201103, 2.71259)
+use_collision = true
+radius = 1.41311
+height = 1.40221
+sides = 32
+
+[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.40488, 0.201101, 11.6804)
+use_collision = true
+radius = 2.21673
+height = 7.88261
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.20971, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.9771, -1.69814, -6.51262)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.555532, -0.599204, 8.81048)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.0611, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.21187, -1.90735e-06, 0.346393)
+use_collision = true
+inner_radius = 1.3
+outer_radius = 2.0
+sides = 32
+ring_sides = 18
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.9283, -1.90735e-06, 7.89765)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.49828, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.15267, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.3427, 0.335247, 8.22829)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.08027, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.7748, 0.138478, 5.20734)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30.1473, 1.78638, -1.60318)
+use_collision = true
+size = Vector3(4.57784, 4.57276, 3.11285)
+
+[editable path="Player/PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_group_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_group_example_scene.tscn
new file mode 100644
index 0000000..c60b01d
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_group_example_scene.tscn
@@ -0,0 +1,180 @@
+[gd_scene load_steps=13 format=3 uid="uid://cqy81q5p0tsda"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_3iw7y"]
+[ext_resource type="PackedScene" uid="uid://cb83in8f0tbb1" path="res://addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_3d.tscn" id="2_m6p13"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="3_65wck"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="4_b0eay"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_i3ale"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_5hq8j"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="7_7lab4"]
+
+[sub_resource type="Resource" id="Resource_1iman"]
+script = ExtResource("5_i3ale")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2h36r"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_w3olp"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cw102"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("7_7lab4")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Node3D" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.621367, 0.78352, 0, -0.78352, 0.621367, -7.26116, 10.1812, 8.76176)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_3iw7y")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerCharacterBody3D" parent="Player" instance=ExtResource("2_m6p13")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.083587, 0.5, 2.05493)
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_targets")]
+unique_name_in_owner = true
+transform = Transform3D(0.999954, 0, 0, 0, 0.621285, 0.783464, 0, -0.783416, 0.621322, -7.26116, 10.1812, 8.76176)
+top_level = true
+script = ExtResource("3_65wck")
+priority = 5
+follow_mode = 3
+follow_targets = [NodePath("../PlayerCharacterBody3D"), NodePath("../../NPCs/PlayerMeshInstance3D"), NodePath("../../NPCs/PlayerMeshInstance3D2")]
+tween_resource = ExtResource("4_b0eay")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_1iman")
+follow_damping = true
+follow_distance = 5.0
+auto_follow_distance = true
+auto_follow_distance_min = 5.0
+auto_follow_distance_max = 15.0
+auto_follow_distance_divisor = 20.0
+spring_length = 5.0
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.6059, 0.519002, 0.128472)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_w3olp")
+
+[node name="PlayerMeshInstance3D2" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.0461, 0.519, 0.249913)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_cw102")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_5hq8j")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Wall" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.52545, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.6511, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.9141, 0.31181, -5.46661)
+use_collision = true
+radius = 2.77591
+height = 1.62362
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.6099, 0.31181, 6.6322)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.81402, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.95333, -1.69814, -6.51262)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.4682, -0.599204, 8.81048)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -26.0848, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.90455, -1.90735e-06, 7.89765)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.1764, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.31901, 0.335247, 8.22829)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.94346, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -26.7985, 0.138478, 5.20734)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.1236, 1.78638, -1.60318)
+use_collision = true
+size = Vector3(4.57784, 4.57276, 3.11285)
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_path_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_path_example_scene.tscn
new file mode 100644
index 0000000..5b4597f
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_path_example_scene.tscn
@@ -0,0 +1,245 @@
+[gd_scene load_steps=25 format=3 uid="uid://oo1y1sjdmr6k"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_p8ccw"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_8itog"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="3_xqpq0"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_akuuo"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_0nadx"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="6_7h7mx"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="6_mkxip"]
+[ext_resource type="Script" uid="uid://cgknbkjar73w" path="res://addons/phantom_camera/examples/scripts/3D/path_follow.gd" id="7_g1m51"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="8_a1h2k"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="9_rk5lh"]
+
+[sub_resource type="Resource" id="Resource_ofv2c"]
+script = ExtResource("4_akuuo")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_01tho"]
+script = ExtResource("6_mkxip")
+duration = 1.2
+transition = 3
+ease = 2
+
+[sub_resource type="Resource" id="Resource_syh5m"]
+script = ExtResource("4_akuuo")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Curve3D" id="Curve3D_b33df"]
+_data = {
+"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -10),
+"tilts": PackedFloat32Array(0, 0)
+}
+point_count = 2
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_aovgi"]
+size = Vector3(6, 0.1, 10)
+
+[sub_resource type="BoxMesh" id="BoxMesh_0hdeh"]
+size = Vector3(6, 0.1, 10)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fsm1b"]
+transparency = 1
+albedo_color = Color(0.988235, 0.478431, 0.905882, 0.0901961)
+
+[sub_resource type="Resource" id="Resource_xci4c"]
+script = ExtResource("4_akuuo")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Curve3D" id="Curve3D_8uw2x"]
+_data = {
+"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0),
+"tilts": PackedFloat32Array(0, 0)
+}
+point_count = 2
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_ctnqu"]
+size = Vector3(12, 0.1, 4)
+
+[sub_resource type="BoxMesh" id="BoxMesh_f6dp8"]
+size = Vector3(12, 0.1, 4)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gwnkj"]
+transparency = 1
+albedo_color = Color(0.568403, 0.988235, 0.762724, 0.0901961)
+
+[sub_resource type="BoxMesh" id="BoxMesh_7l3dh"]
+
+[sub_resource type="BoxMesh" id="BoxMesh_as6ok"]
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(0.999996, -0.00216283, 0.00184472, 0, 0.648938, 0.760841, -0.00284268, -0.760838, 0.648936, 0, 2.5, 1.5)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_p8ccw")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999807, -0.00216249, 0.00184445, 0, 0.648836, 0.760728, -0.00284214, -0.760718, 0.648839, 0, 2.5, 1.5)
+top_level = true
+script = ExtResource("2_8itog")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D2")
+tween_resource = ExtResource("3_xqpq0")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ofv2c")
+follow_offset = Vector3(0, 2, 1.5)
+follow_damping = true
+
+[node name="PlayerCharacterBody3D2" parent="." instance=ExtResource("5_0nadx")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+script = ExtResource("6_7h7mx")
+
+[node name="Paths" type="Node" parent="."]
+
+[node name="PathPhantomCamera3D" type="Node3D" parent="Paths" node_paths=PackedStringArray("follow_target", "follow_path")]
+transform = Transform3D(-4.37114e-08, -1, -4.37114e-08, 0, -4.37114e-08, 1, -1, 4.37114e-08, 1.91069e-15, -0.31028, 7.9199, -1.60976)
+top_level = true
+script = ExtResource("2_8itog")
+priority = 2
+follow_mode = 4
+follow_target = NodePath("../../PlayerCharacterBody3D2")
+follow_path = NodePath("../FollowPath")
+tween_resource = SubResource("Resource_01tho")
+camera_3d_resource = SubResource("Resource_syh5m")
+follow_damping = true
+
+[node name="FollowPath" type="Path3D" parent="Paths"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.31028, 7.9199, -1.60976)
+curve = SubResource("Curve3D_b33df")
+
+[node name="StraightPathFollowTrigger" type="Area3D" parent="Paths" node_paths=PackedStringArray("path_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0420399, -0.45, -6.73666)
+priority = 5
+script = ExtResource("7_g1m51")
+path_pcam = NodePath("../PathPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Paths/StraightPathFollowTrigger"]
+shape = SubResource("BoxShape3D_aovgi")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Paths/StraightPathFollowTrigger/CollisionShape3D"]
+mesh = SubResource("BoxMesh_0hdeh")
+skeleton = NodePath("../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_fsm1b")
+metadata/_edit_group_ = true
+
+[node name="PathPhantomCamera3D2" type="Node3D" parent="Paths" node_paths=PackedStringArray("follow_target", "follow_path")]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 7.9199, -13.4572)
+top_level = true
+visible = false
+script = ExtResource("2_8itog")
+priority = 2
+follow_mode = 4
+follow_target = NodePath("../../PlayerCharacterBody3D2")
+follow_path = NodePath("../FollowPath2")
+tween_resource = SubResource("Resource_01tho")
+camera_3d_resource = SubResource("Resource_xci4c")
+follow_damping = true
+follow_damping_value = Vector3(0.6, 0.1, 0.1)
+
+[node name="FollowPath2" type="Path3D" parent="Paths"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.97141, 7.9199, -13.4572)
+curve = SubResource("Curve3D_8uw2x")
+
+[node name="StraightPathFollowTrigger2" type="Area3D" parent="Paths" node_paths=PackedStringArray("path_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0420399, 0, -13.7367)
+priority = 5
+script = ExtResource("7_g1m51")
+path_pcam = NodePath("../PathPhantomCamera3D2")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Paths/StraightPathFollowTrigger2"]
+shape = SubResource("BoxShape3D_ctnqu")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Paths/StraightPathFollowTrigger2/CollisionShape3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.45, 0)
+mesh = SubResource("BoxMesh_f6dp8")
+skeleton = NodePath("../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_gwnkj")
+metadata/_edit_group_ = true
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Floor3" parent="Environment" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(6, 0, 0, 0, 1, 0, 0, 0, 1, -0.44204, 0, 1.76334)
+
+[node name="Floor2" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 14, -0.516667, 1, -6.5)
+
+[node name="Floor5" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 14, 0.65, 1, -6.5)
+
+[node name="Floor4" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(2, 0, 0, 0, 3, 0, 0, 0, 1, 0.0666667, 1, -18)
+
+[node name="Floor6" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(0.333333, 0, 0, 0, 3, 0, 0, 0, 1, -0.766667, 1, -13)
+mesh = SubResource("BoxMesh_7l3dh")
+
+[node name="Floor8" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 6, -1.01667, 1, -15.5)
+mesh = SubResource("BoxMesh_as6ok")
+
+[node name="Floor9" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 6, 1.15, 1, -15.5)
+mesh = SubResource("BoxMesh_as6ok")
+
+[node name="Floor7" parent="Environment/Floor3" instance=ExtResource("8_a1h2k")]
+transform = Transform3D(0.333333, 0, 0, 0, 3, 0, 0, 0, 1, 0.9, 1, -13)
+mesh = SubResource("BoxMesh_7l3dh")
+
+[node name="NPCDescriptionLabel" type="Label3D" parent="Environment"]
+transform = Transform3D(5.21541e-08, -1, -7.7486e-07, -1.10675e-15, 2.23517e-07, 0.999999, -0.999999, -7.45058e-08, -5.68829e-14, -3.47306, 2.59595, -5.51755)
+text = "Camera follows player while confined to a Path3D"
+font = ExtResource("9_rk5lh")
+font_size = 64
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.02174, -0.455369, 0.570585)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("9_rk5lh")
+font_size = 48
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_simple_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_simple_example_scene.tscn
new file mode 100644
index 0000000..610e62a
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_simple_example_scene.tscn
@@ -0,0 +1,164 @@
+[gd_scene load_steps=12 format=3 uid="uid://c7uyfhhnrmkbx"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_gt67h"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_4ltlo"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_hldrt"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_pqibl"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_o4k7v"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="6_8yuc5"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_m6ich"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="7_pagh0"]
+
+[sub_resource type="Resource" id="Resource_28vpp"]
+script = ExtResource("3_hldrt")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_axopo"]
+script = ExtResource("4_pqibl")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("7_pagh0")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Node3D2" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.906308, 0.422618, 0, -0.422618, 0.906308, -13.2122, 2.5, 10.4016)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_gt67h")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999954, 0, 0, 0, 0.906188, 0.422588, 0, -0.422562, 0.906243, -13.2122, 2.5, 10.4016)
+top_level = true
+script = ExtResource("2_4ltlo")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_28vpp")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_axopo")
+follow_offset = Vector3(0, 2, 2)
+follow_damping = true
+
+[node name="PlayerCharacterBody3D" parent="Player" instance=ExtResource("5_o4k7v")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.2122, 0.5, 8.40162)
+script = ExtResource("6_8yuc5")
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_m6ich")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.6511, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.8332, -0.540694, -3.39517)
+use_collision = true
+radius = 1.53269
+height = 2.5036
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -16.936, -1.50101, 1.22863)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.81402, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -23.6875, -1.69814, 3.36997)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.14955, -0.599204, -1.04651)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -26.0848, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.44645, -0.497663, 4.44352)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.52545, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.1764, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -18.1256, 0.335247, 7.14677)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.94346, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.30382, 0.138478, -1.89037)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.04727, 0.0440434, 8.36617)
+use_collision = true
+size = Vector3(4.57784, 1.08809, 3.11285)
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_third_person_attribtues_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_third_person_attribtues_example_scene.tscn
new file mode 100644
index 0000000..593198c
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_third_person_attribtues_example_scene.tscn
@@ -0,0 +1,222 @@
+[gd_scene load_steps=22 format=3 uid="uid://bklrp02eywxsx"]
+
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="1_s26cy"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="2_m2d6w"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="3_l7kg8"]
+[ext_resource type="PackedScene" uid="uid://mskcwn1a1v6d" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn" id="4_qcyfd"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="5_8von1"]
+[ext_resource type="Script" uid="uid://bkr71vxe2t18n" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd" id="5_tarnu"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_o1fj6"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="7_amcmx"]
+[ext_resource type="Texture2D" uid="uid://c3mskbmvnpwux" path="res://addons/phantom_camera/examples/textures/3D/target.png" id="8_rjcgw"]
+
+[sub_resource type="Resource" id="Resource_8fhct"]
+script = ExtResource("2_m2d6w")
+duration = 0.3
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_7m0fv"]
+script = ExtResource("3_l7kg8")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_i42vj"]
+dof_blur_far_enabled = true
+dof_blur_far_distance = 5.99
+dof_blur_near_enabled = true
+dof_blur_near_distance = 0.05
+dof_blur_amount = 0.21
+
+[sub_resource type="Resource" id="Resource_e7t18"]
+script = ExtResource("2_m2d6w")
+duration = 0.4
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_jogxh"]
+script = ExtResource("3_l7kg8")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 1.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_fvhx5"]
+dof_blur_far_enabled = true
+dof_blur_far_distance = 31.1
+dof_blur_near_enabled = true
+dof_blur_near_distance = 1.79
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_fnb35"]
+dof_blur_far_enabled = true
+dof_blur_far_distance = 5.99
+dof_blur_near_enabled = true
+dof_blur_near_distance = 0.05
+dof_blur_amount = 0.21
+
+[sub_resource type="BoxMesh" id="BoxMesh_wsigl"]
+size = Vector3(1, 10, 20)
+
+[sub_resource type="Resource" id="Resource_afrr1"]
+script = ExtResource("2_m2d6w")
+duration = 0.6
+transition = 3
+ease = 1
+
+[sub_resource type="Resource" id="Resource_unpfd"]
+script = ExtResource("3_l7kg8")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_sm466"]
+top_radius = 1.51
+height = 0.2
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hp48l"]
+transparency = 1
+albedo_texture = ExtResource("8_rjcgw")
+uv1_scale = Vector3(1.91, 1.91, 1.91)
+uv1_offset = Vector3(0.025, -0.927, 0)
+
+[node name="Root" type="Node3D"]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.499999, 0, -0.5, 0.866023, -0.0194088, 2.25688, 9.63713)
+script = ExtResource("1_s26cy")
+priority = 10
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_8fhct")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_7m0fv")
+attributes = SubResource("CameraAttributesPractical_i42vj")
+follow_damping = true
+follow_distance = 3.5
+spring_length = 3.5
+
+[node name="PlayerAimPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.953716, -0.0418501, 0.297778, 0, 0.990266, 0.139173, -0.300705, -0.132731, 0.944432, 0.427258, 1.68564, 7.6237)
+script = ExtResource("1_s26cy")
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_e7t18")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_jogxh")
+attributes = SubResource("CameraAttributesPractical_fvhx5")
+follow_offset = Vector3(0, 0.97, -0.399)
+follow_damping_value = Vector3(0, 0, 0)
+follow_distance = 1.5
+spring_length = 1.5
+
+[node name="PlayerCharacterBody3D" parent="." instance=ExtResource("4_qcyfd")]
+unique_name_in_owner = true
+transform = Transform3D(0.999903, 0.0139622, 0, -0.0139622, 0.999903, 0, 0, 0, 1, -0.0194088, 0.506884, 6.60605)
+script = ExtResource("5_tarnu")
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, -0.0194088, 2.25688, 9.63713)
+attributes = SubResource("CameraAttributesPractical_fnb35")
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("5_8von1")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Wall" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall2" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall3" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, 10.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall4" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, -9.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="CeilingPhantomCamera3D" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(-4.37114e-08, -1, 2.98023e-08, 0, 2.98023e-08, 1, -1, 4.37114e-08, -1.3027e-15, -0.200665, 13.366, -0.162648)
+script = ExtResource("1_s26cy")
+tween_resource = SubResource("Resource_afrr1")
+camera_3d_resource = SubResource("Resource_unpfd")
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 1.44357)
+visible = false
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("7_amcmx")
+font_size = 48
+
+[node name="MovementInstructionsLabel3" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 0.817134)
+visible = false
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Right Mouse Click] to \"aim\""
+font = ExtResource("7_amcmx")
+font_size = 48
+
+[node name="MovementInstructionsLabel2" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.0440154, -0.490478, -6.30248)
+visible = false
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Space] to toggle PCam"
+font = ExtResource("7_amcmx")
+font_size = 48
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.260217, 1.60477, -9.07797)
+mesh = SubResource("CylinderMesh_sm466")
+surface_material_override/0 = SubResource("StandardMaterial3D_hp48l")
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="."]
+transform = Transform3D(-1, -8.74228e-08, 3.82137e-15, 0, -4.37114e-08, -1, 8.74228e-08, -1, 4.37114e-08, 0.0525861, 1.60477, 9.98156)
+mesh = SubResource("CylinderMesh_sm466")
+surface_material_override/0 = SubResource("StandardMaterial3D_hp48l")
+
+[editable path="PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_third_person_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_third_person_example_scene.tscn
new file mode 100644
index 0000000..2adc6b5
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_follow_third_person_example_scene.tscn
@@ -0,0 +1,190 @@
+[gd_scene load_steps=17 format=3 uid="uid://ceelq6qrb41uf"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="2_47xf2"]
+[ext_resource type="Script" uid="uid://bkr71vxe2t18n" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd" id="2_uhq7m"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_whx47"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_lii5s"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_jt2lp"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="5_oc4q1"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="7_kg7u1"]
+[ext_resource type="PackedScene" uid="uid://mskcwn1a1v6d" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn" id="7_kut0u"]
+
+[sub_resource type="Resource" id="Resource_8fhct"]
+script = ExtResource("2_47xf2")
+duration = 0.3
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_7m0fv"]
+script = ExtResource("5_jt2lp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_e7t18"]
+script = ExtResource("2_47xf2")
+duration = 0.4
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_jogxh"]
+script = ExtResource("5_jt2lp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 1.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxMesh" id="BoxMesh_wsigl"]
+size = Vector3(1, 10, 20)
+
+[sub_resource type="BoxMesh" id="BoxMesh_bj3re"]
+size = Vector3(1, 7, 7)
+
+[sub_resource type="Resource" id="Resource_afrr1"]
+script = ExtResource("2_47xf2")
+duration = 0.6
+transition = 3
+ease = 1
+
+[sub_resource type="Resource" id="Resource_ioijp"]
+script = ExtResource("5_jt2lp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[node name="Root" type="Node3D"]
+
+[node name="PlayerCharacterBody3D" parent="." instance=ExtResource("7_kut0u")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+script = ExtResource("2_uhq7m")
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.866023, 0.499997, 0, -0.499999, 0.866021, 0, 2.24999, 3.03107)
+script = ExtResource("2_whx47")
+priority = 10
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_8fhct")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_7m0fv")
+follow_damping = true
+follow_distance = 3.5
+spring_length = 3.5
+
+[node name="PlayerAimPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.953716, -0.0104945, 0.300522, 0, 0.99939, 0.0348995, -0.300706, -0.0332842, 0.953135, 0.450783, 1.35235, 1.0307)
+script = ExtResource("2_whx47")
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_e7t18")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_jogxh")
+follow_offset = Vector3(0, 0.8, -0.399)
+follow_distance = 1.5
+spring_length = 1.5
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 2.24999, 3.03107)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("5_oc4q1")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Wall" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall5" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.133, 3, -6.5)
+mesh = SubResource("BoxMesh_bj3re")
+metadata/_edit_lock_ = true
+
+[node name="Wall6" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.5, 3, 0)
+mesh = SubResource("BoxMesh_bj3re")
+metadata/_edit_lock_ = true
+
+[node name="Wall7" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.5, 3, 0)
+mesh = SubResource("BoxMesh_bj3re")
+metadata/_edit_lock_ = true
+
+[node name="Wall2" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall3" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, 10.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall4" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, -9.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="CeilingPhantomCamera3D" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(-4.37114e-08, -1, 2.98023e-08, 0, 2.98023e-08, 1, -1, 4.37114e-08, -1.3027e-15, -0.200665, 13.366, -0.162648)
+script = ExtResource("2_whx47")
+tween_resource = SubResource("Resource_afrr1")
+camera_3d_resource = SubResource("Resource_ioijp")
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 1.44357)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("7_kg7u1")
+font_size = 48
+
+[node name="MovementInstructionsLabel3" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 0.817134)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Right Mouse Click] to \"aim\""
+font = ExtResource("7_kg7u1")
+font_size = 48
+
+[node name="MovementInstructionsLabel2" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.0440154, -0.490478, -6.30248)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Space] to toggle PCam"
+font = ExtResource("7_kg7u1")
+font_size = 48
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_look_at_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_look_at_example_scene.tscn
new file mode 100644
index 0000000..6fe3289
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_look_at_example_scene.tscn
@@ -0,0 +1,200 @@
+[gd_scene load_steps=15 format=3 uid="uid://dsfixtpa5xwqt"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_jbmnd"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_t3gk2"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_b2lea"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_mqo2b"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_pxkua"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="6_3rtu0"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_uuxs3"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="7_0dyt0"]
+
+[sub_resource type="Resource" id="Resource_pwcgo"]
+script = ExtResource("3_b2lea")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_ft2w3"]
+script = ExtResource("4_mqo2b")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2h36r"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_w3olp"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cw102"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("7_0dyt0")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(0.999765, 0.010421, -0.0189909, 0, 0.876683, 0.481069, 0.0216623, -0.480956, 0.876477, -0.137901, 4.03222, 6.36446)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_jbmnd")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("look_at_target")]
+transform = Transform3D(0.999765, 0.010421, -0.018991, 0, 0.876683, 0.481069, 0.0216623, -0.480956, 0.876478, -0.137901, 4.03222, 6.36446)
+script = ExtResource("2_t3gk2")
+priority = 10
+look_at_mode = 2
+look_at_target = NodePath("../PlayerCharacterBody3D2")
+tween_resource = SubResource("Resource_pwcgo")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ft2w3")
+look_at_damping = true
+
+[node name="PlayerCharacterBody3D2" parent="." instance=ExtResource("5_pxkua")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+script = ExtResource("6_3rtu0")
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.96028, 0.519002, -1.52506)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_w3olp")
+
+[node name="PlayerMeshInstance3D2" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.59952, 0.519, 4.06618)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_cw102")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_uuxs3")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.00548, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 25.5597, 0.31181, -5.46661)
+use_collision = true
+radius = 2.77591
+height = 1.62362
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.96428, 0.31181, 6.6322)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.3959, 0.201103, 2.71259)
+use_collision = true
+radius = 1.41311
+height = 1.40221
+sides = 32
+
+[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.02677, 0.201101, 11.6804)
+use_collision = true
+radius = 2.21673
+height = 7.88261
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.8316, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.5989, -1.69814, -6.51262)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.17742, -0.599204, 8.81048)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.4392, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.58998, -1.90735e-06, 0.346393)
+use_collision = true
+inner_radius = 1.3
+outer_radius = 2.0
+sides = 32
+ring_sides = 18
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 22.5502, -1.90735e-06, 7.89765)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.1202, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.53078, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.9646, 0.335247, 8.22829)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.70216, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.1529, 0.138478, 5.20734)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30.7692, 1.78638, -1.60318)
+use_collision = true
+size = Vector3(4.57784, 4.57276, 3.11285)
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_noise_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_noise_example_scene.tscn
new file mode 100644
index 0000000..34aac82
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_noise_example_scene.tscn
@@ -0,0 +1,195 @@
+[gd_scene load_steps=21 format=3 uid="uid://d0fyuvesb472p"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_25rmy"]
+[ext_resource type="Script" uid="uid://x5g7kf5k2mac" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd" id="2_7nd2u"]
+[ext_resource type="Script" uid="uid://cuffvge5ad4aa" path="res://addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd" id="3_t4fhv"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="4_tnm2f"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="5_4webr"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="6_dmm4a"]
+[ext_resource type="Script" uid="uid://ccmiitq0sdh7j" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd" id="7_2vtho"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="8_bw5oq"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="9_jpkpr"]
+[ext_resource type="FontFile" uid="uid://dve7mgsjik4dg" path="res://addons/phantom_camera/fonts/Nunito-Regular.ttf" id="10_8pr3k"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="11_vp57v"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_yvgu3"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vc6km"]
+albedo_color = Color(0.988235, 0.498039, 0.498039, 1)
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_lsrh7"]
+radius = 0.269454
+
+[sub_resource type="Resource" id="Resource_lhgur"]
+script = ExtResource("5_4webr")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_ghjuj"]
+script = ExtResource("6_dmm4a")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_2l4w0"]
+script = ExtResource("3_t4fhv")
+amplitude = 40.0
+frequency = 0.2
+randomize_noise_seed = 0
+noise_seed = 0
+rotational_noise = true
+positional_noise = false
+rotational_multiplier_x = 1.0
+rotational_multiplier_y = 1.0
+rotational_multiplier_z = 0.0
+positional_multiplier_x = 0.1
+positional_multiplier_y = 0.1
+positional_multiplier_z = 0.1
+
+[sub_resource type="Resource" id="Resource_6tnhy"]
+script = ExtResource("3_t4fhv")
+amplitude = 10.0
+frequency = 20.0
+randomize_noise_seed = 0
+noise_seed = 928
+rotational_noise = true
+positional_noise = false
+rotational_multiplier_x = 1.0
+rotational_multiplier_y = 1.0
+rotational_multiplier_z = 0.1
+positional_multiplier_x = 1.0
+positional_multiplier_y = 1.0
+positional_multiplier_z = 1.0
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qi01t"]
+albedo_texture = ExtResource("9_jpkpr")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ey47a"]
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_bottom_right = 20
+expand_margin_bottom = 6.0
+
+[node name="Root2" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(-0.0372114, 0.0351643, 0.998689, -5.82077e-11, 0.999381, -0.0351886, -0.999307, -0.00130942, -0.0371883, -16.46, 0.503767, 4.249)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_25rmy")
+
+[node name="PlayerCharacterBody3D" type="CharacterBody3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.999897, 0.0143636, 0, -0.0143636, 0.999897, 0, 0, 0, 1, -16.46, 0.503767, 4.249)
+script = ExtResource("2_7nd2u")
+
+[node name="PlayerVisual" type="MeshInstance3D" parent="PlayerCharacterBody3D"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.76837e-05, 0.00331134, 0)
+mesh = SubResource("CapsuleMesh_yvgu3")
+surface_material_override/0 = SubResource("StandardMaterial3D_vc6km")
+
+[node name="PlayerArea3D" type="Area3D" parent="PlayerCharacterBody3D"]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerCharacterBody3D/PlayerArea3D"]
+shape = SubResource("CapsuleShape3D_lsrh7")
+
+[node name="PlayerCollisionShape3D" type="CollisionShape3D" parent="PlayerCharacterBody3D"]
+shape = SubResource("CapsuleShape3D_lsrh7")
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.00441533, 0, 0.999915, 0, 0.999995, 0, -0.999923, 0, 0.00441529, -16.46, 0.503767, 4.249)
+top_level = true
+script = ExtResource("4_tnm2f")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_lhgur")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ghjuj")
+noise = SubResource("Resource_2l4w0")
+noise_emitter_layer = 1
+
+[node name="PlayerPhantomCameraNoiseEmitter3D" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(-4.37085e-08, 0, 0.999925, 0, 0.999995, 0, -0.999933, 0, -4.37081e-08, -16.46, 0.503767, 4.249)
+script = ExtResource("7_2vtho")
+noise = SubResource("Resource_6tnhy")
+duration = 0.1
+decay_time = 0.1
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("8_bw5oq")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.525, 6.539, 2.5)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 8.83707, 6.53866, -1.80739)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -38.9392, 6.53866, -1.80739)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.525, 6.539, 6)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="EmitterTip" type="Panel" parent="."]
+unique_name_in_owner = true
+visible = false
+anchors_preset = -1
+anchor_right = 0.3
+anchor_bottom = 0.1
+theme_override_styles/panel = SubResource("StyleBoxFlat_ey47a")
+
+[node name="Guidance" type="RichTextLabel" parent="EmitterTip"]
+layout_mode = 1
+anchors_preset = -1
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 8
+theme_override_fonts/normal_font = ExtResource("10_8pr3k")
+theme_override_fonts/bold_font = ExtResource("11_vp57v")
+theme_override_font_sizes/normal_font_size = 18
+theme_override_font_sizes/bold_font_size = 24
+bbcode_enabled = true
+text = "[center]Press [b]Q[/b] to trigger Noise Emitter"
+fit_content = true
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_tweening_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_tweening_example_scene.tscn
new file mode 100644
index 0000000..d596960
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/3d_tweening_example_scene.tscn
@@ -0,0 +1,293 @@
+[gd_scene load_steps=23 format=3 uid="uid://cvnbgtbaxwj5p"]
+
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="1_d55xf"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="2_d1opf"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="3_4whss"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="4_8ap1e"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_1sgnu"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="6_lr46m"]
+[ext_resource type="Script" uid="uid://bnhxcejvr6wi3" path="res://addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd" id="7_istoq"]
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="7_x1jex"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="8_qepee"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="9_ptb3h"]
+
+[sub_resource type="Resource" id="Resource_0dtvs"]
+script = ExtResource("5_1sgnu")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_j6fha"]
+size = Vector3(5, 0.1, 4)
+
+[sub_resource type="BoxMesh" id="BoxMesh_xg4en"]
+size = Vector3(5, 0.1, 4)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2dct5"]
+transparency = 1
+albedo_color = Color(0.988235, 0.478431, 0.905882, 0.0901961)
+
+[sub_resource type="Resource" id="Resource_v8ndi"]
+script = ExtResource("8_qepee")
+duration = 0.6
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_kmep1"]
+script = ExtResource("5_1sgnu")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_uxg44"]
+script = ExtResource("8_qepee")
+duration = 0.3
+transition = 1
+ease = 2
+
+[sub_resource type="Resource" id="Resource_eu3bc"]
+script = ExtResource("5_1sgnu")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_0nci0"]
+script = ExtResource("8_qepee")
+duration = 0.3
+transition = 8
+ease = 2
+
+[sub_resource type="Resource" id="Resource_u0lff"]
+script = ExtResource("5_1sgnu")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_50m5g"]
+script = ExtResource("8_qepee")
+duration = 1.2
+transition = 10
+ease = 2
+
+[sub_resource type="Resource" id="Resource_rexf8"]
+script = ExtResource("5_1sgnu")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[node name="Root" type="Node3D"]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Floor" parent="Environment" instance=ExtResource("1_d55xf")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+physics_interpolation_mode = 1
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 2.5, 2)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("2_d1opf")
+
+[node name="------------------" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999889, 0, 0, 0, 0.707092, 0.707088, 0, -0.707092, 0.707088, 0, 2.5, 2)
+top_level = true
+script = ExtResource("3_4whss")
+priority = 3
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = ExtResource("4_8ap1e")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_0dtvs")
+follow_offset = Vector3(0, 2, 2)
+follow_damping = true
+
+[node name="PlayerCharacterBody3D" parent="." instance=ExtResource("6_lr46m")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, 0)
+script = ExtResource("7_x1jex")
+
+[node name="-------------------" type="Node" parent="."]
+
+[node name="Tweening Example" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1.97)
+
+[node name="Linear" type="Node3D" parent="Tweening Example"]
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Linear" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("7_istoq")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Linear/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Linear/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Linear"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 4.8, 3.3)
+script = ExtResource("3_4whss")
+tween_resource = SubResource("Resource_v8ndi")
+camera_3d_resource = SubResource("Resource_kmep1")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Linear"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, -1.8, 0.5, 0)
+text = "Transition Type:
+Linear
+
+Duration:
+0.6s"
+font = ExtResource("9_ptb3h")
+font_size = 48
+
+[node name="Sine" type="Node3D" parent="Tweening Example"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -7.4)
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Sine" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("7_istoq")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Sine/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Sine/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Sine"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 4.8, 3.3)
+script = ExtResource("3_4whss")
+tween_resource = SubResource("Resource_uxg44")
+camera_3d_resource = SubResource("Resource_eu3bc")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Sine"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, 1.7, 0.5, 0)
+text = "Transition Type:
+Sine
+
+Duration:
+0.3s"
+font = ExtResource("9_ptb3h")
+font_size = 72
+
+[node name="Circ" type="Node3D" parent="Tweening Example"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -14.1)
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Circ" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("7_istoq")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Circ/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Circ/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Circ"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 4.8, 3.3)
+script = ExtResource("3_4whss")
+tween_resource = SubResource("Resource_0nci0")
+camera_3d_resource = SubResource("Resource_u0lff")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Circ"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, -1.8, 0.5, 0)
+text = "Transition Type:
+Circ
+
+Duration:
+0.3s"
+font = ExtResource("9_ptb3h")
+font_size = 72
+
+[node name="Back" type="Node3D" parent="Tweening Example"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -21)
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Back" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("7_istoq")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Back/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Back/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Back"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, -0.8, 4.8, 3.3)
+script = ExtResource("3_4whss")
+tween_resource = SubResource("Resource_50m5g")
+camera_3d_resource = SubResource("Resource_rexf8")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Back"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, 1.7, 0.5, 0)
+text = "Transition Type:
+Back
+
+Duration:
+1.2s"
+font = ExtResource("9_ptb3h")
+font_size = 48
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_3d.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_3d.tscn
new file mode 100644
index 0000000..b402af7
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_3d.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=5 format=3 uid="uid://cb83in8f0tbb1"]
+
+[ext_resource type="Script" uid="uid://tgv6xpi88sd0" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd" id="1_pl87s"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_8efyg"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2cfaw"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_r3ldp"]
+albedo_color = Color(0.988235, 0.498039, 0.498039, 1)
+
+[node name="PlayerCharacterBody3D2" type="CharacterBody3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.083587, 0.507, 2.05493)
+script = ExtResource("1_pl87s")
+metadata/_edit_group_ = true
+
+[node name="PlayerArea3D" type="Area3D" parent="."]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerArea3D"]
+shape = SubResource("CapsuleShape3D_8efyg")
+
+[node name="PlayerCollisionShape3D" type="CollisionShape3D" parent="."]
+shape = SubResource("CapsuleShape3D_8efyg")
+
+[node name="PlayerVisual" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="PlayerModel" type="MeshInstance3D" parent="PlayerVisual"]
+mesh = SubResource("CapsuleMesh_2cfaw")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_r3ldp")
diff --git a/addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_third_person_3d.tscn b/addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_third_person_3d.tscn
new file mode 100644
index 0000000..9285cbb
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D-4.4/sub_scenes/playable_character_third_person_3d.tscn
@@ -0,0 +1,43 @@
+[gd_scene load_steps=6 format=3 uid="uid://bhd1kwv2fwj1y"]
+
+[ext_resource type="Script" uid="uid://bkr71vxe2t18n" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd" id="1_skas8"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_s61dn"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_47f0o"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mv4do"]
+albedo_color = Color(0.988235, 0.498039, 0.498039, 1)
+
+[sub_resource type="PrismMesh" id="PrismMesh_wg1x3"]
+size = Vector3(0.5, 0.5, 0.3)
+
+[node name="PlayerCharacterBody3D" type="CharacterBody3D"]
+transform = Transform3D(0.999903, 0.0139622, 0, -0.0139622, 0.999903, 0, 0, 0, 1, -0.0194088, 0.506884, -0.0163251)
+collision_layer = 2
+script = ExtResource("1_skas8")
+metadata/_edit_group_ = true
+
+[node name="PlayerArea3D" type="Area3D" parent="."]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerArea3D"]
+shape = SubResource("CapsuleShape3D_s61dn")
+
+[node name="PlayerCollisionShape3D" type="CollisionShape3D" parent="."]
+shape = SubResource("CapsuleShape3D_s61dn")
+
+[node name="PlayerVisual" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="PlayerVisual"]
+transform = Transform3D(1, 0, 0, 0, 1, 4.65661e-10, 0, 0, 1, 0, 0, 0)
+mesh = SubResource("CapsuleMesh_47f0o")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_mv4do")
+
+[node name="PlayerDirection" type="MeshInstance3D" parent="PlayerVisual"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, -9.31323e-10, 1, 4.65661e-10, 2.98023e-08, 0, 1, -0.0156226, 1.08631, 0)
+mesh = SubResource("PrismMesh_wg1x3")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_mv4do")
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_example_scene.tscn
new file mode 100644
index 0000000..5886011
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_example_scene.tscn
@@ -0,0 +1,412 @@
+[gd_scene load_steps=40 format=3 uid="uid://ci12ytew5vwty"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_wn7ww"]
+[ext_resource type="Script" uid="uid://uvw6pg1ut0ms" path="res://addons/phantom_camera/examples/scripts/3D/npc.gd" id="2_2n1da"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="2_e7gxt"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="2_tvx5n"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_y3dy8"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="3_f5qrw"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="4_a27nb"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_m2vbn"]
+[ext_resource type="Script" uid="uid://bnhxcejvr6wi3" path="res://addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd" id="4_moad5"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="7_jitt8"]
+
+[sub_resource type="Resource" id="Resource_jtk1d"]
+script = ExtResource("4_m2vbn")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_o161n"]
+script = ExtResource("4_m2vbn")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxMesh" id="BoxMesh_7tjw4"]
+size = Vector3(2, 0.5, 4)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hpllm"]
+transparency = 1
+albedo_color = Color(0.988235, 0.478431, 0.905882, 0.0901961)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_65o6h"]
+size = Vector3(2, 0.5, 4)
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_tpc7d"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_g0eml"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_v5iy7"]
+albedo_color = Color(0.988235, 0.478431, 0.905882, 1)
+
+[sub_resource type="Resource" id="Resource_tpvee"]
+script = ExtResource("7_jitt8")
+duration = 0.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_bxbnv"]
+script = ExtResource("4_m2vbn")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_wcrbb"]
+size = Vector3(6.8, 0.1, 5.4)
+
+[sub_resource type="Resource" id="Resource_7ih0k"]
+script = ExtResource("7_jitt8")
+duration = 0.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_4iyps"]
+script = ExtResource("4_m2vbn")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_ctyr8"]
+size = Vector3(7.4, 0.1, 3.6)
+
+[sub_resource type="Resource" id="Resource_x5y0u"]
+script = ExtResource("7_jitt8")
+duration = 0.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_pgiyx"]
+script = ExtResource("4_m2vbn")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_ua072"]
+size = Vector3(6.8, 0.1, 3.6)
+
+[sub_resource type="BoxMesh" id="BoxMesh_ugc3s"]
+size = Vector3(1, 1, 2)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_68thd"]
+albedo_color = Color(0.34902, 0.862745, 0.854902, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_wphly"]
+size = Vector3(1, 0.5, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_gyp5s"]
+size = Vector3(20, 40, 30)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_lfaqs"]
+size = Vector3(20, 40, 30)
+
+[sub_resource type="BoxMesh" id="BoxMesh_n70lt"]
+size = Vector3(14, 40, 6)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_jxmqm"]
+size = Vector3(14, 40, 6)
+
+[sub_resource type="BoxMesh" id="BoxMesh_x0tgm"]
+size = Vector3(8, 40, 1)
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_t67ef"]
+size = Vector3(50, 40, 1)
+
+[sub_resource type="BoxMesh" id="BoxMesh_rmslh"]
+size = Vector3(0.5, 6, 13.5)
+
+[sub_resource type="BoxMesh" id="BoxMesh_242ij"]
+size = Vector3(2, 3, 3)
+
+[sub_resource type="BoxMesh" id="BoxMesh_niuda"]
+size = Vector3(8, 6, 0.5)
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.948876, 0.315649, 0, -0.315649, 0.948876, -2.53871, 2, 9.76232)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_wn7ww")
+
+[node name="PlayerGroup" type="Node" parent="."]
+
+[node name="PlayerCharacterBody3D" parent="PlayerGroup" instance=ExtResource("2_tvx5n")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.53871, 0.5, 7.26232)
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="PlayerGroup"]
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, -2.47682, -0.0708016, 7.93048)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("2_e7gxt")
+font_size = 48
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="PlayerGroup" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999858, 0, 0, 0, 0.94884, 0.315632, 0, -0.315637, 0.948825, -2.53871, 2, 9.76232)
+top_level = true
+script = ExtResource("2_y3dy8")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = ExtResource("4_a27nb")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_jtk1d")
+follow_offset = Vector3(0, 1.5, 2.5)
+follow_damping = true
+
+[node name="NPCGroup" type="Node" parent="."]
+
+[node name="NPCPhantomCamera3D" type="Node3D" parent="NPCGroup"]
+unique_name_in_owner = true
+transform = Transform3D(0.616596, -0.109786, 0.779587, -2.23517e-08, 0.990229, 0.13945, -0.78728, -0.0859841, 0.610571, -2.98802, 1.50739, 1.19719)
+script = ExtResource("2_y3dy8")
+tween_resource = ExtResource("4_a27nb")
+camera_3d_resource = SubResource("Resource_o161n")
+
+[node name="NPCDescriptionLabel" type="Label3D" parent="NPCGroup"]
+transform = Transform3D(1, 0, 0, 0, 0.866026, 0.5, 0, -0.5, 0.866025, -3.04693, 0.367287, 0.953757)
+text = "Input Example"
+font = ExtResource("2_e7gxt")
+
+[node name="NPCDialogueExampleLabel" type="Label3D" parent="NPCGroup"]
+unique_name_in_owner = true
+transform = Transform3D(1, 4.54671e-10, 1.65487e-10, 4.25644e-10, 0.939693, 0.34202, 0, -0.34202, 0.939693, -4.46738, 1.58641, -0.253679)
+modulate = Color(1, 0.603922, 0.254902, 1)
+text = "Press [ F ] to change camera"
+font = ExtResource("2_e7gxt")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="NPCGroup"]
+transform = Transform3D(0.819152, 4.83851e-10, -0.573576, -3.92481e-09, 1, -6.3473e-09, 0.573576, 7.45058e-09, 0.819152, -3.46138, -0.4, 0.875321)
+mesh = SubResource("BoxMesh_7tjw4")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_hpllm")
+metadata/_edit_group_ = true
+
+[node name="NPCInteractionArea3D" type="Area3D" parent="NPCGroup/NPCInteractionZoneMesh"]
+unique_name_in_owner = true
+transform = Transform3D(1, -2.68591e-26, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+monitorable = false
+
+[node name="NPCInterationCollisionShape3D" type="CollisionShape3D" parent="NPCGroup/NPCInteractionZoneMesh/NPCInteractionArea3D"]
+shape = SubResource("BoxShape3D_65o6h")
+
+[node name="NPC" type="StaticBody3D" parent="NPCGroup"]
+transform = Transform3D(1, 4.83851e-10, 0, 4.25644e-10, 1, -7.45058e-09, 0, 7.45058e-09, 1, -4.56338, 0.5, -0.272679)
+script = ExtResource("2_2n1da")
+
+[node name="PlayerCollisionShape3D2" type="CollisionShape3D" parent="NPCGroup/NPC"]
+transform = Transform3D(1, -2.68591e-26, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+shape = SubResource("CapsuleShape3D_tpc7d")
+
+[node name="NPCMesh" type="MeshInstance3D" parent="NPCGroup/NPC"]
+transform = Transform3D(1, -2.68591e-26, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+mesh = SubResource("CapsuleMesh_g0eml")
+skeleton = NodePath("../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_v5iy7")
+
+[node name="MoveToLocation" type="Node3D" parent="NPCGroup"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.70084, 0.5, 0.962891)
+
+[node name="FixedCameraTriggerZone" type="Node" parent="."]
+
+[node name="FixedCameraLabel" type="Label3D" parent="FixedCameraTriggerZone"]
+unique_name_in_owner = true
+transform = Transform3D(0.939693, 0.280167, -0.196175, 1.49012e-08, 0.573577, 0.819152, 0.34202, -0.769751, 0.538986, -0.538716, -0.247626, 3.13456)
+text = "Fixed Camera
+Example"
+font = ExtResource("2_e7gxt")
+
+[node name="NorthRoomPhantomCamera3D" type="Node3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(0.38357, -0.555836, 0.737507, -0.105898, 0.766851, 0.633027, -0.917417, -0.320912, 0.235279, 6.89638, 4.73986, 0.115512)
+script = ExtResource("2_y3dy8")
+tween_resource = SubResource("Resource_tpvee")
+camera_3d_resource = SubResource("Resource_bxbnv")
+
+[node name="NorthRoomTrigger" type="Area3D" parent="FixedCameraTriggerZone" node_paths=PackedStringArray("area_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.45, -0.9)
+priority = 5
+script = ExtResource("4_moad5")
+area_pcam = NodePath("../NorthRoomPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedCameraTriggerZone/NorthRoomTrigger"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0, -0.4)
+shape = SubResource("BoxShape3D_wcrbb")
+
+[node name="EntryRoomPhantomCamera3D" type="Node3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(0.258818, -0.482963, 0.836515, 1.3027e-15, 0.866025, 0.499999, -0.965924, -0.129409, 0.224143, 6.69741, 4.73364, 4.02374)
+script = ExtResource("2_y3dy8")
+tween_resource = SubResource("Resource_7ih0k")
+camera_3d_resource = SubResource("Resource_4iyps")
+
+[node name="EntryRoomTrigger" type="Area3D" parent="FixedCameraTriggerZone" node_paths=PackedStringArray("area_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3.00003, -0.454982, 3.00572)
+priority = 5
+script = ExtResource("4_moad5")
+area_pcam = NodePath("../EntryRoomPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedCameraTriggerZone/EntryRoomTrigger"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.3, 0, 0.2)
+shape = SubResource("BoxShape3D_ctyr8")
+
+[node name="SouthRoomPhantomCamera3D" type="Node3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(-0.766043, -0.492403, 0.413175, 0, 0.642787, 0.766043, -0.642786, 0.586825, -0.492403, 6.89741, 4.73364, 5.62374)
+script = ExtResource("2_y3dy8")
+tween_resource = SubResource("Resource_x5y0u")
+camera_3d_resource = SubResource("Resource_pgiyx")
+
+[node name="SouthRoomTrigger" type="Area3D" parent="FixedCameraTriggerZone" node_paths=PackedStringArray("area_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.45, 6.7)
+priority = 5
+script = ExtResource("4_moad5")
+area_pcam = NodePath("../SouthRoomPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="FixedCameraTriggerZone/SouthRoomTrigger"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.6, 0, 0.1)
+shape = SubResource("BoxShape3D_ua072")
+
+[node name="CSGMesh3D" type="CSGMesh3D" parent="FixedCameraTriggerZone"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.14238, 1.82571, 2.88655)
+mesh = SubResource("BoxMesh_ugc3s")
+material = SubResource("StandardMaterial3D_68thd")
+
+[node name="CSGMesh3D2" type="CSGMesh3D" parent="FixedCameraTriggerZone/CSGMesh3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00192642, -0.0120339, 0.00494432)
+operation = 2
+mesh = SubResource("BoxMesh_wphly")
+material = SubResource("StandardMaterial3D_68thd")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Environment" type="Node3D" parent="Environment"]
+
+[node name="Floor" parent="Environment/Environment" instance=ExtResource("3_f5qrw")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="West Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -16, 0.5, 0)
+metadata/_edit_group_ = true
+metadata/_edit_lock_ = true
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Environment/West Wall"]
+mesh = SubResource("BoxMesh_gyp5s")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/West Wall"]
+shape = SubResource("BoxShape3D_lfaqs")
+
+[node name="East Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 16.999, 0.502, 0)
+metadata/_edit_group_ = true
+metadata/_edit_lock_ = true
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Environment/East Wall"]
+mesh = SubResource("BoxMesh_gyp5s")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/East Wall"]
+shape = SubResource("BoxShape3D_lfaqs")
+
+[node name="North Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.5, -6.90828)
+metadata/_edit_group_ = true
+metadata/_edit_lock_ = true
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Environment/Environment/North Wall"]
+mesh = SubResource("BoxMesh_n70lt")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/North Wall"]
+shape = SubResource("BoxShape3D_jxmqm")
+
+[node name="South Wall" type="StaticBody3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.25, 0.5, 9.087)
+metadata/_edit_group_ = true
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="Environment/Environment/South Wall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 0)
+mesh = SubResource("BoxMesh_x0tgm")
+skeleton = NodePath("")
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Environment/Environment/South Wall"]
+shape = SubResource("BoxShape3D_t67ef")
+
+[node name="FixedCamOuterWall" type="CSGMesh3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.5, 2)
+use_collision = true
+mesh = SubResource("BoxMesh_rmslh")
+
+[node name="FixedCamOuterDoorway" type="CSGMesh3D" parent="Environment/Environment/FixedCamOuterWall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 1)
+operation = 2
+mesh = SubResource("BoxMesh_242ij")
+
+[node name="FixedCamNorthWall" type="CSGMesh3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2.5, 1)
+use_collision = true
+mesh = SubResource("BoxMesh_niuda")
+
+[node name="FixedCamNorthDoorway" type="CSGMesh3D" parent="Environment/Environment/FixedCamNorthWall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+operation = 2
+mesh = SubResource("BoxMesh_242ij")
+
+[node name="FixedCamSouthWall" type="CSGMesh3D" parent="Environment/Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 2.5, 5.1)
+use_collision = true
+mesh = SubResource("BoxMesh_niuda")
+
+[node name="FixedCamSouthDoorway" type="CSGMesh3D" parent="Environment/Environment/FixedCamSouthWall"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.50541, 1.19209e-07)
+operation = 2
+mesh = SubResource("BoxMesh_242ij")
+
+[editable path="PlayerGroup/PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_framed_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_framed_example_scene.tscn
new file mode 100644
index 0000000..0be8a65
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_framed_example_scene.tscn
@@ -0,0 +1,156 @@
+[gd_scene load_steps=10 format=3 uid="uid://c4llb3gsbfv1a"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_7824u"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_g1bv4"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="3_420vh"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_oqbub"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_t4fso"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="5_c0upu"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_f2w3x"]
+
+[sub_resource type="Resource" id="Resource_wg1pr"]
+script = ExtResource("4_oqbub")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("5_c0upu")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.793353, 0.608762, 0, -0.608762, 0.793353, 0.083587, 2.94168, 5.22787)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_7824u")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.99995, 0, 0, 0, 0.79324, 0.608671, 0, -0.608675, 0.793235, 0, 2.43468, 3.17294)
+top_level = true
+script = ExtResource("2_g1bv4")
+follow_mode = 5
+follow_target = NodePath("../PlayerCharacterBody3D2/PlayerVisual")
+tween_resource = ExtResource("3_420vh")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_wg1pr")
+follow_damping = true
+follow_distance = 4.0
+dead_zone_width = 0.161
+dead_zone_height = 0.386
+show_viewfinder_in_play = true
+spring_length = 4.0
+
+[node name="PlayerCharacterBody3D2" parent="Player" instance=ExtResource("5_f2w3x")]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_t4fso")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.636134, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.54597, -0.540694, -3.39517)
+use_collision = true
+radius = 1.53269
+height = 2.5036
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.64877, -1.50101, 1.22863)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.4732, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.40027, -1.69814, 3.36997)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.13768, -0.599204, -1.04651)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.7976, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.84078, -0.497663, 4.44352)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.52545, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.88916, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.83837, -0.241718, 7.14677)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.34377, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.9834, 0.138478, -1.89037)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.38147, 0.0440434, 8.36617)
+use_collision = true
+size = Vector3(4.57784, 1.08809, 3.11285)
+
+[editable path="Player/PlayerCharacterBody3D2"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_glued_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_glued_example_scene.tscn
new file mode 100644
index 0000000..0fbcc3b
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_glued_example_scene.tscn
@@ -0,0 +1,208 @@
+[gd_scene load_steps=14 format=3 uid="uid://dw2yflu7up2rr"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_pmeux"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_q1ygp"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_hpix1"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_8qqha"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_evdoo"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_vqgn5"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="5_wr3bq"]
+
+[sub_resource type="Resource" id="Resource_ucp3e"]
+script = ExtResource("3_hpix1")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_ab013"]
+script = ExtResource("4_evdoo")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2h36r"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_w3olp"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cw102"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("5_wr3bq")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Node3D" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.638767, 0.7694, 0, -0.7694, 0.638768, 0, 6.39, 7)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_pmeux")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999954, 0, 0, 0, 0.638683, 0.769345, 0, -0.769298, 0.638723, 0, 6.39, 7)
+top_level = true
+script = ExtResource("2_q1ygp")
+priority = 5
+follow_mode = 1
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = SubResource("Resource_ucp3e")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ab013")
+follow_damping = true
+follow_damping_value = Vector3(0.3, 0.3, 0.3)
+
+[node name="PlayerCharacterBody3D" parent="Player" instance=ExtResource("5_vqgn5")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 6.39, 7)
+enable_gravity = false
+
+[node name="PlayerVisual" parent="Player/PlayerCharacterBody3D" index="2"]
+visible = false
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.04486, 0.519002, -1.52506)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_w3olp")
+
+[node name="PlayerMeshInstance3D2" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.51494, 0.519, 4.06618)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_cw102")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_8qqha")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.62737, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 24.9378, 0.31181, -5.46661)
+use_collision = true
+radius = 2.77591
+height = 1.62362
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.58617, 0.31181, 6.6322)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.774, 0.201103, 2.71259)
+use_collision = true
+radius = 1.41311
+height = 1.40221
+sides = 32
+
+[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.40488, 0.201101, 11.6804)
+use_collision = true
+radius = 2.21673
+height = 7.88261
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.20971, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.9771, -1.69814, -6.51262)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.555532, -0.599204, 8.81048)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.0611, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -5.21187, -1.90735e-06, 0.346393)
+use_collision = true
+inner_radius = 1.3
+outer_radius = 2.0
+sides = 32
+ring_sides = 18
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 21.9283, -1.90735e-06, 7.89765)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.49828, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.15267, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.3427, 0.335247, 8.22829)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.08027, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.7748, 0.138478, 5.20734)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30.1473, 1.78638, -1.60318)
+use_collision = true
+size = Vector3(4.57784, 4.57276, 3.11285)
+
+[editable path="Player/PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_group_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_group_example_scene.tscn
new file mode 100644
index 0000000..29eb009
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_group_example_scene.tscn
@@ -0,0 +1,180 @@
+[gd_scene load_steps=13 format=3 uid="uid://dbfiy6svpcqap"]
+
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="1_r00ve"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_pi7mp"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="2_wnlkq"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="3_1eb12"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="3_a5igg"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="3_wr1tj"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_70gws"]
+
+[sub_resource type="Resource" id="Resource_1iman"]
+script = ExtResource("5_70gws")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2h36r"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_w3olp"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cw102"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("3_a5igg")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Node3D" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.906308, 0.422618, 0, -0.422618, 0.906308, -7.26116, 5.72974, 12.279)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("3_wr1tj")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerCharacterBody3D2" parent="Player" instance=ExtResource("2_wnlkq")]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_targets")]
+unique_name_in_owner = true
+transform = Transform3D(0.999954, 0, 0, 0, 0.906188, 0.422588, 0, -0.422562, 0.906243, -7.30295, 5.45858, 11.2744)
+top_level = true
+script = ExtResource("2_pi7mp")
+priority = 5
+follow_mode = 3
+follow_targets = [NodePath("../PlayerCharacterBody3D2"), NodePath("../../NPCs/PlayerMeshInstance3D"), NodePath("../../NPCs/PlayerMeshInstance3D2")]
+tween_resource = ExtResource("3_1eb12")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_1iman")
+follow_damping = true
+follow_distance = 5.0
+auto_follow_distance = true
+auto_follow_distance_min = 5.0
+auto_follow_distance_max = 15.0
+auto_follow_distance_divisor = 20.0
+spring_length = 5.0
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.6059, 0.519002, 0.128472)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_w3olp")
+
+[node name="PlayerMeshInstance3D2" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.0461, 0.519, 0.249913)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_cw102")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("1_r00ve")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Wall" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.52545, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.6511, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.9141, 0.31181, -5.46661)
+use_collision = true
+radius = 2.77591
+height = 1.62362
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.6099, 0.31181, 6.6322)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.81402, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.95333, -1.69814, -6.51262)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.4682, -0.599204, 8.81048)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -26.0848, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.90455, -1.90735e-06, 7.89765)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.1764, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.31901, 0.335247, 8.22829)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.94346, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -26.7985, 0.138478, 5.20734)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.1236, 1.78638, -1.60318)
+use_collision = true
+size = Vector3(4.57784, 4.57276, 3.11285)
+
+[editable path="Player/PlayerCharacterBody3D2"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_path_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_path_example_scene.tscn
new file mode 100644
index 0000000..e2d196a
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_path_example_scene.tscn
@@ -0,0 +1,244 @@
+[gd_scene load_steps=24 format=3 uid="uid://dxx7ngi0emt8h"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_lm5n8"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="3_bd7x3"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="3_od2r4"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_dfdlo"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_hni7n"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="4_lfwkm"]
+[ext_resource type="Script" uid="uid://cgknbkjar73w" path="res://addons/phantom_camera/examples/scripts/3D/path_follow.gd" id="5_vdqkm"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_vms5c"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="6_obo83"]
+
+[sub_resource type="Resource" id="Resource_ofv2c"]
+script = ExtResource("4_hni7n")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_01tho"]
+script = ExtResource("4_lfwkm")
+duration = 1.2
+transition = 3
+ease = 2
+
+[sub_resource type="Resource" id="Resource_syh5m"]
+script = ExtResource("4_hni7n")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Curve3D" id="Curve3D_b33df"]
+_data = {
+"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -10),
+"tilts": PackedFloat32Array(0, 0)
+}
+point_count = 2
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_aovgi"]
+size = Vector3(6, 0.1, 10)
+
+[sub_resource type="BoxMesh" id="BoxMesh_0hdeh"]
+size = Vector3(6, 0.1, 10)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_fsm1b"]
+transparency = 1
+albedo_color = Color(0.988235, 0.478431, 0.905882, 0.0901961)
+
+[sub_resource type="Resource" id="Resource_xci4c"]
+script = ExtResource("4_hni7n")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Curve3D" id="Curve3D_8uw2x"]
+_data = {
+"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0),
+"tilts": PackedFloat32Array(0, 0)
+}
+point_count = 2
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_ctnqu"]
+size = Vector3(12, 0.1, 4)
+
+[sub_resource type="BoxMesh" id="BoxMesh_f6dp8"]
+size = Vector3(12, 0.1, 4)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gwnkj"]
+transparency = 1
+albedo_color = Color(0.568403, 0.988235, 0.762724, 0.0901961)
+
+[sub_resource type="BoxMesh" id="BoxMesh_7l3dh"]
+
+[sub_resource type="BoxMesh" id="BoxMesh_as6ok"]
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.999996, -0.00216283, 0.00184472, 0, 0.648938, 0.760841, -0.00284268, -0.760838, 0.648936, 0, 2.507, 1.5)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_lm5n8")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999807, -0.00216249, 0.00184445, 0, 0.648836, 0.760728, -0.00284214, -0.760718, 0.648839, 0, 2.507, 1.5)
+top_level = true
+script = ExtResource("3_bd7x3")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D2/PlayerVisual")
+tween_resource = ExtResource("3_od2r4")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ofv2c")
+follow_offset = Vector3(0, 2, 1.5)
+follow_damping = true
+
+[node name="PlayerCharacterBody3D2" parent="." instance=ExtResource("5_vms5c")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.507, 0)
+
+[node name="Paths" type="Node" parent="."]
+
+[node name="PathPhantomCamera3D" type="Node3D" parent="Paths" node_paths=PackedStringArray("follow_target", "follow_path")]
+transform = Transform3D(-4.37114e-08, -1, -4.37114e-08, 0, -4.37114e-08, 1, -1, 4.37114e-08, 1.91069e-15, -0.31028, 7.9199, -1.60976)
+top_level = true
+script = ExtResource("3_bd7x3")
+priority = 2
+follow_mode = 4
+follow_target = NodePath("../../PlayerCharacterBody3D2/PlayerVisual")
+follow_path = NodePath("../FollowPath")
+tween_resource = SubResource("Resource_01tho")
+camera_3d_resource = SubResource("Resource_syh5m")
+follow_damping = true
+
+[node name="FollowPath" type="Path3D" parent="Paths"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.31028, 7.9199, -1.60976)
+curve = SubResource("Curve3D_b33df")
+
+[node name="StraightPathFollowTrigger" type="Area3D" parent="Paths" node_paths=PackedStringArray("path_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0420399, -0.45, -6.73666)
+priority = 5
+script = ExtResource("5_vdqkm")
+path_pcam = NodePath("../PathPhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Paths/StraightPathFollowTrigger"]
+shape = SubResource("BoxShape3D_aovgi")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Paths/StraightPathFollowTrigger/CollisionShape3D"]
+mesh = SubResource("BoxMesh_0hdeh")
+skeleton = NodePath("../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_fsm1b")
+metadata/_edit_group_ = true
+
+[node name="PathPhantomCamera3D2" type="Node3D" parent="Paths" node_paths=PackedStringArray("follow_target", "follow_path")]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 7.9199, -13.4572)
+top_level = true
+visible = false
+script = ExtResource("3_bd7x3")
+priority = 2
+follow_mode = 4
+follow_target = NodePath("../../PlayerCharacterBody3D2/PlayerVisual")
+follow_path = NodePath("../FollowPath2")
+tween_resource = SubResource("Resource_01tho")
+camera_3d_resource = SubResource("Resource_xci4c")
+follow_damping = true
+follow_damping_value = Vector3(0.6, 0.1, 0.1)
+
+[node name="FollowPath2" type="Path3D" parent="Paths"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.97141, 7.9199, -13.4572)
+curve = SubResource("Curve3D_8uw2x")
+
+[node name="StraightPathFollowTrigger2" type="Area3D" parent="Paths" node_paths=PackedStringArray("path_pcam")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0420399, 0, -13.7367)
+priority = 5
+script = ExtResource("5_vdqkm")
+path_pcam = NodePath("../PathPhantomCamera3D2")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Paths/StraightPathFollowTrigger2"]
+shape = SubResource("BoxShape3D_ctnqu")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Paths/StraightPathFollowTrigger2/CollisionShape3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.45, 0)
+mesh = SubResource("BoxMesh_f6dp8")
+skeleton = NodePath("../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_gwnkj")
+metadata/_edit_group_ = true
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Floor3" parent="Environment" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(6, 0, 0, 0, 1, 0, 0, 0, 1, -0.44204, 0, 1.76334)
+
+[node name="Floor2" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 14, -0.516667, 1, -6.5)
+
+[node name="Floor5" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 14, 0.65, 1, -6.5)
+
+[node name="Floor4" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(2, 0, 0, 0, 3, 0, 0, 0, 1, 0.0666667, 1, -18)
+
+[node name="Floor6" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(0.333333, 0, 0, 0, 3, 0, 0, 0, 1, -0.766667, 1, -13)
+mesh = SubResource("BoxMesh_7l3dh")
+
+[node name="Floor8" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 6, -1.01667, 1, -15.5)
+mesh = SubResource("BoxMesh_as6ok")
+
+[node name="Floor9" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(0.166667, 0, 0, 0, 3, 0, 0, 0, 6, 1.15, 1, -15.5)
+mesh = SubResource("BoxMesh_as6ok")
+
+[node name="Floor7" parent="Environment/Floor3" instance=ExtResource("4_dfdlo")]
+transform = Transform3D(0.333333, 0, 0, 0, 3, 0, 0, 0, 1, 0.9, 1, -13)
+mesh = SubResource("BoxMesh_7l3dh")
+
+[node name="NPCDescriptionLabel" type="Label3D" parent="Environment"]
+transform = Transform3D(5.21541e-08, -1, -7.7486e-07, -1.10675e-15, 2.23517e-07, 0.999999, -0.999999, -7.45058e-08, -5.68829e-14, -3.47306, 2.59595, -5.51755)
+text = "Camera follows player while confined to a Path3D"
+font = ExtResource("6_obo83")
+font_size = 64
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.02174, -0.455369, 0.570585)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("6_obo83")
+font_size = 48
+
+[editable path="PlayerCharacterBody3D2"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_simple_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_simple_example_scene.tscn
new file mode 100644
index 0000000..a3da80d
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_simple_example_scene.tscn
@@ -0,0 +1,163 @@
+[gd_scene load_steps=11 format=3 uid="uid://buglvjwpn85ny"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_3tok8"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_grjck"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_j3f4l"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_4u2y6"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_sielv"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="5_1tybo"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="5_7ywxt"]
+
+[sub_resource type="Resource" id="Resource_28vpp"]
+script = ExtResource("3_j3f4l")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_axopo"]
+script = ExtResource("4_sielv")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("5_1tybo")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Node3D2" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.906308, 0.422618, 0, -0.422618, 0.906308, -13.2122, 2.5, 10.4016)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_3tok8")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Player" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="Player" node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999954, 0, 0, 0, 0.906188, 0.422588, 0, -0.422562, 0.906243, -13.2122, 2.5, 10.4016)
+top_level = true
+script = ExtResource("2_grjck")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = SubResource("Resource_28vpp")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_axopo")
+follow_offset = Vector3(0, 2, 2)
+follow_damping = true
+
+[node name="PlayerCharacterBody3D" parent="Player" instance=ExtResource("5_7ywxt")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.2122, 0.5, 8.40162)
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_4u2y6")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.6511, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.8332, -0.540694, -3.39517)
+use_collision = true
+radius = 1.53269
+height = 2.5036
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -16.936, -1.50101, 1.22863)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.81402, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -23.6875, -1.69814, 3.36997)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.14955, -0.599204, -1.04651)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -26.0848, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.44645, -0.497663, 4.44352)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.52545, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -21.1764, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -18.1256, 0.335247, 7.14677)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.94346, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3.30382, 0.138478, -1.89037)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.04727, 0.0440434, 8.36617)
+use_collision = true
+size = Vector3(4.57784, 1.08809, 3.11285)
+
+[editable path="Player/PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_third_person_attribtues_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_third_person_attribtues_example_scene.tscn
new file mode 100644
index 0000000..664c40e
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_third_person_attribtues_example_scene.tscn
@@ -0,0 +1,219 @@
+[gd_scene load_steps=21 format=3 uid="uid://5pjtxclcnx4f"]
+
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="1_s26cy"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="2_m2d6w"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="3_l7kg8"]
+[ext_resource type="PackedScene" uid="uid://mskcwn1a1v6d" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn" id="4_qcyfd"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="5_8von1"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="6_o1fj6"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="7_amcmx"]
+[ext_resource type="Texture2D" uid="uid://c3mskbmvnpwux" path="res://addons/phantom_camera/examples/textures/3D/target.png" id="8_rjcgw"]
+
+[sub_resource type="Resource" id="Resource_8fhct"]
+script = ExtResource("2_m2d6w")
+duration = 0.3
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_7m0fv"]
+script = ExtResource("3_l7kg8")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_i42vj"]
+dof_blur_far_enabled = true
+dof_blur_far_distance = 5.99
+dof_blur_near_enabled = true
+dof_blur_near_distance = 0.05
+dof_blur_amount = 0.21
+
+[sub_resource type="Resource" id="Resource_e7t18"]
+script = ExtResource("2_m2d6w")
+duration = 0.4
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_jogxh"]
+script = ExtResource("3_l7kg8")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 1.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_fvhx5"]
+dof_blur_far_enabled = true
+dof_blur_far_distance = 31.1
+dof_blur_near_enabled = true
+dof_blur_near_distance = 1.79
+
+[sub_resource type="CameraAttributesPractical" id="CameraAttributesPractical_pst8q"]
+dof_blur_far_enabled = true
+dof_blur_far_distance = 5.99
+dof_blur_near_enabled = true
+dof_blur_near_distance = 0.05
+dof_blur_amount = 0.21
+
+[sub_resource type="BoxMesh" id="BoxMesh_wsigl"]
+size = Vector3(1, 10, 20)
+
+[sub_resource type="Resource" id="Resource_afrr1"]
+script = ExtResource("2_m2d6w")
+duration = 0.6
+transition = 3
+ease = 1
+
+[sub_resource type="Resource" id="Resource_unpfd"]
+script = ExtResource("3_l7kg8")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CylinderMesh" id="CylinderMesh_sm466"]
+top_radius = 1.51
+height = 0.2
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_hp48l"]
+transparency = 1
+albedo_texture = ExtResource("8_rjcgw")
+uv1_scale = Vector3(1.91, 1.91, 1.91)
+uv1_offset = Vector3(0.025, -0.927, 0)
+
+[node name="Root" type="Node3D"]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.499999, 0, -0.5, 0.866023, -0.0194088, 2.25688, 9.63713)
+script = ExtResource("1_s26cy")
+priority = 10
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = SubResource("Resource_8fhct")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_7m0fv")
+attributes = SubResource("CameraAttributesPractical_i42vj")
+follow_damping = true
+follow_distance = 3.5
+spring_length = 3.5
+
+[node name="PlayerAimPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.953716, -0.0418501, 0.297778, 0, 0.990266, 0.139173, -0.300705, -0.132731, 0.944432, 0.427258, 1.68564, 7.6237)
+script = ExtResource("1_s26cy")
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = SubResource("Resource_e7t18")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_jogxh")
+attributes = SubResource("CameraAttributesPractical_fvhx5")
+follow_offset = Vector3(0, 0.97, -0.399)
+follow_damping_value = Vector3(0, 0, 0)
+follow_distance = 1.5
+spring_length = 1.5
+
+[node name="PlayerCharacterBody3D" parent="." instance=ExtResource("4_qcyfd")]
+unique_name_in_owner = true
+transform = Transform3D(0.999903, 0.0139622, 0, -0.0139622, 0.999903, 0, 0, 0, 1, -0.0194088, 0.506884, 6.60605)
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, -0.0194088, 2.25688, 9.63713)
+attributes = SubResource("CameraAttributesPractical_pst8q")
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("5_8von1")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Wall" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall2" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall3" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, 10.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall4" parent="Environment" instance=ExtResource("6_o1fj6")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, -9.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="CeilingPhantomCamera3D" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(-4.37114e-08, -1, 2.98023e-08, 0, 2.98023e-08, 1, -1, 4.37114e-08, -1.3027e-15, -0.200665, 13.366, -0.162648)
+script = ExtResource("1_s26cy")
+tween_resource = SubResource("Resource_afrr1")
+camera_3d_resource = SubResource("Resource_unpfd")
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 1.44357)
+visible = false
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("7_amcmx")
+font_size = 48
+
+[node name="MovementInstructionsLabel3" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 0.817134)
+visible = false
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Right Mouse Click] to \"aim\""
+font = ExtResource("7_amcmx")
+font_size = 48
+
+[node name="MovementInstructionsLabel2" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.0440154, -0.490478, -6.30248)
+visible = false
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Space] to toggle PCam"
+font = ExtResource("7_amcmx")
+font_size = 48
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0.260217, 1.60477, -9.07797)
+mesh = SubResource("CylinderMesh_sm466")
+surface_material_override/0 = SubResource("StandardMaterial3D_hp48l")
+
+[node name="MeshInstance3D3" type="MeshInstance3D" parent="."]
+transform = Transform3D(-1, -8.74228e-08, 3.82137e-15, 0, -4.37114e-08, -1, 8.74228e-08, -1, 4.37114e-08, 0.0525861, 1.60477, 9.98156)
+mesh = SubResource("CylinderMesh_sm466")
+surface_material_override/0 = SubResource("StandardMaterial3D_hp48l")
+
+[editable path="PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_follow_third_person_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_third_person_example_scene.tscn
new file mode 100644
index 0000000..e2fe01a
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_follow_third_person_example_scene.tscn
@@ -0,0 +1,188 @@
+[gd_scene load_steps=16 format=3 uid="uid://4i5csj0s34nb"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="2_47xf2"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_whx47"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_lii5s"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_jt2lp"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="5_oc4q1"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="7_kg7u1"]
+[ext_resource type="PackedScene" uid="uid://mskcwn1a1v6d" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn" id="7_kut0u"]
+
+[sub_resource type="Resource" id="Resource_8fhct"]
+script = ExtResource("2_47xf2")
+duration = 0.3
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_7m0fv"]
+script = ExtResource("5_jt2lp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_e7t18"]
+script = ExtResource("2_47xf2")
+duration = 0.4
+transition = 2
+ease = 1
+
+[sub_resource type="Resource" id="Resource_jogxh"]
+script = ExtResource("5_jt2lp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 1.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxMesh" id="BoxMesh_wsigl"]
+size = Vector3(1, 10, 20)
+
+[sub_resource type="BoxMesh" id="BoxMesh_bj3re"]
+size = Vector3(1, 7, 7)
+
+[sub_resource type="Resource" id="Resource_afrr1"]
+script = ExtResource("2_47xf2")
+duration = 0.6
+transition = 3
+ease = 1
+
+[sub_resource type="Resource" id="Resource_ioijp"]
+script = ExtResource("5_jt2lp")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[node name="Root" type="Node3D"]
+
+[node name="PlayerCharacterBody3D" parent="." instance=ExtResource("7_kut0u")]
+unique_name_in_owner = true
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.866023, 0.499997, 0, -0.499999, 0.866021, -0.0194088, 2.25687, 3.01475)
+script = ExtResource("2_whx47")
+priority = 10
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = SubResource("Resource_8fhct")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_7m0fv")
+follow_damping = true
+follow_distance = 3.5
+spring_length = 3.5
+
+[node name="PlayerAimPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.953716, -0.0104945, 0.300522, 0, 0.99939, 0.0348995, -0.300706, -0.0332842, 0.953135, 0.431374, 1.35923, 1.01438)
+script = ExtResource("2_whx47")
+follow_mode = 6
+follow_target = NodePath("../PlayerCharacterBody3D/PlayerVisual")
+tween_resource = SubResource("Resource_e7t18")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_jogxh")
+follow_offset = Vector3(0, 0.8, -0.399)
+follow_distance = 1.5
+spring_length = 1.5
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, -0.0194088, 2.25687, 3.01475)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("5_oc4q1")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="Wall" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall5" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6.133, 3, -6.5)
+mesh = SubResource("BoxMesh_bj3re")
+metadata/_edit_lock_ = true
+
+[node name="Wall6" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.5, 3, 0)
+mesh = SubResource("BoxMesh_bj3re")
+metadata/_edit_lock_ = true
+
+[node name="Wall7" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.5, 3, 0)
+mesh = SubResource("BoxMesh_bj3re")
+metadata/_edit_lock_ = true
+
+[node name="Wall2" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 9.5, 4.5, 0)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall3" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, 10.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="Wall4" parent="Environment" instance=ExtResource("4_lii5s")]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 0, 4.5, -9.5)
+mesh = SubResource("BoxMesh_wsigl")
+metadata/_edit_lock_ = true
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="CeilingPhantomCamera3D" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(-4.37114e-08, -1, 2.98023e-08, 0, 2.98023e-08, 1, -1, 4.37114e-08, -1.3027e-15, -0.200665, 13.366, -0.162648)
+script = ExtResource("2_whx47")
+tween_resource = SubResource("Resource_afrr1")
+camera_3d_resource = SubResource("Resource_ioijp")
+
+[node name="MovementInstructionsLabel" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 1.44357)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[WASD] to move"
+font = ExtResource("7_kg7u1")
+font_size = 48
+
+[node name="MovementInstructionsLabel3" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0.0505604, -0.484909, 0.817134)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Right Mouse Click] to \"aim\""
+font = ExtResource("7_kg7u1")
+font_size = 48
+
+[node name="MovementInstructionsLabel2" type="Label3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, -0.0440154, -0.490478, -6.30248)
+modulate = Color(0.294118, 1, 0.631373, 1)
+text = "[Space] to toggle PCam"
+font = ExtResource("7_kg7u1")
+font_size = 48
+
+[editable path="PlayerCharacterBody3D"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_look_at_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_look_at_example_scene.tscn
new file mode 100644
index 0000000..c50d90b
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_look_at_example_scene.tscn
@@ -0,0 +1,198 @@
+[gd_scene load_steps=14 format=3 uid="uid://bdhrdhbux7sjg"]
+
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="1_i2pjc"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_lldvu"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_8md3q"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_dqss1"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="4_2i811"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_m3qpq"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="5_u5qhp"]
+
+[sub_resource type="Resource" id="Resource_pwcgo"]
+script = ExtResource("3_dqss1")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_ft2w3"]
+script = ExtResource("4_m3qpq")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2h36r"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_w3olp"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_cw102"]
+albedo_color = Color(0.227451, 0.337255, 0.576471, 1)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_auy8m"]
+albedo_texture = ExtResource("5_u5qhp")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.998682, 0.0324725, -0.0397495, 0, 0.774433, 0.632656, 0.0513272, -0.631822, 0.773412, -0.137901, 4.03222, 6.36446)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_lldvu")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("look_at_target")]
+transform = Transform3D(0.999694, 0.0136487, -0.0206552, -0.000166996, 0.838005, 0.545663, 0.0247567, -0.545492, 0.837751, -0.137901, 4.03222, 6.36446)
+script = ExtResource("2_8md3q")
+priority = 10
+look_at_mode = 2
+look_at_target = NodePath("../PlayerCharacterBody3D2/PlayerVisual")
+tween_resource = SubResource("Resource_pwcgo")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ft2w3")
+look_at_damping = true
+
+[node name="PlayerCharacterBody3D2" parent="." instance=ExtResource("1_i2pjc")]
+
+[node name="NPCs" type="Node" parent="."]
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.96028, 0.519002, -1.52506)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_w3olp")
+
+[node name="PlayerMeshInstance3D2" type="MeshInstance3D" parent="NPCs"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.59952, 0.519, 4.06618)
+mesh = SubResource("CapsuleMesh_2h36r")
+skeleton = NodePath("")
+surface_material_override/0 = SubResource("StandardMaterial3D_cw102")
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("4_2i811")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGCylinder3D" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.00548, 0.805455, -6.37532)
+use_collision = true
+radius = 1.71971
+height = 2.61091
+sides = 32
+
+[node name="CSGCylinder3D5" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 25.5597, 0.31181, -5.46661)
+use_collision = true
+radius = 2.77591
+height = 1.62362
+sides = 32
+
+[node name="CSGCylinder3D6" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.96428, 0.31181, 6.6322)
+use_collision = true
+radius = 1.57419
+height = 3.47475
+sides = 32
+
+[node name="CSGCylinder3D3" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.3959, 0.201103, 2.71259)
+use_collision = true
+radius = 1.41311
+height = 1.40221
+sides = 32
+
+[node name="CSGCylinder3D4" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.02677, 0.201101, 11.6804)
+use_collision = true
+radius = 2.21673
+height = 7.88261
+sides = 32
+
+[node name="CSGCylinder3D2" type="CSGCylinder3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8.8316, 0.805455, -8.78984)
+use_collision = true
+radius = 0.956285
+height = 2.61091
+sides = 32
+
+[node name="CSGSphere3D" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 15.5989, -1.69814, -6.51262)
+use_collision = true
+radius = 3.34732
+rings = 32
+
+[node name="CSGSphere3D2" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.17742, -0.599204, 8.81048)
+use_collision = true
+radius = 2.65844
+rings = 32
+
+[node name="CSGSphere3D3" type="CSGSphere3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -13.4392, -0.599204, -2.42244)
+use_collision = true
+radius = 2.14606
+rings = 32
+
+[node name="CSGTorus3D" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.58998, -1.90735e-06, 0.346393)
+use_collision = true
+inner_radius = 1.3
+outer_radius = 2.0
+sides = 32
+ring_sides = 18
+
+[node name="CSGTorus3D2" type="CSGTorus3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 22.5502, -1.90735e-06, 7.89765)
+use_collision = true
+inner_radius = 0.971543
+outer_radius = 2.15226
+sides = 32
+ring_sides = 18
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 10.1202, 6.53866, -12.6331)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_auy8m")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.53078, 0.760708, -6.1376)
+use_collision = true
+size = Vector3(2.64182, 2.52142, 2.30997)
+
+[node name="CSGBox3D5" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 14.9646, 0.335247, 8.22829)
+use_collision = true
+size = Vector3(3.80964, 1.67049, 0.932048)
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.70216, 0.138478, -4.36159)
+use_collision = true
+size = Vector3(1.53893, 1.27695, 1.80814)
+
+[node name="CSGBox3D6" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -14.1529, 0.138478, 5.20734)
+use_collision = true
+size = Vector3(4.03502, 1.27695, 5.2198)
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 30.7692, 1.78638, -1.60318)
+use_collision = true
+size = Vector3(4.57784, 4.57276, 3.11285)
+
+[editable path="PlayerCharacterBody3D2"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_noise_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_noise_example_scene.tscn
new file mode 100644
index 0000000..0e63ea5
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_noise_example_scene.tscn
@@ -0,0 +1,210 @@
+[gd_scene load_steps=22 format=3 uid="uid://p7s5t3tthmo"]
+
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="1_ggfbg"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="2_dreow"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="3_f8fcw"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="4_mjtut"]
+[ext_resource type="Script" uid="uid://cuffvge5ad4aa" path="res://addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd" id="4_poyyk"]
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="5_d6uqs"]
+[ext_resource type="Script" uid="uid://b3n22atuw76sm" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd" id="6_fbad7"]
+[ext_resource type="Script" uid="uid://ccmiitq0sdh7j" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd" id="6_n8u0x"]
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="6_vpla5"]
+[ext_resource type="FontFile" uid="uid://dve7mgsjik4dg" path="res://addons/phantom_camera/fonts/Nunito-Regular.ttf" id="10_0thai"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="11_i8r8q"]
+
+[sub_resource type="Resource" id="Resource_t3bgw"]
+script = ExtResource("4_poyyk")
+amplitude = 30.0
+frequency = 2.0
+randomize_noise_seed = 1
+noise_seed = 0
+rotational_noise = true
+positional_noise = false
+rotational_multiplier_x = 0.1
+rotational_multiplier_y = 0.1
+rotational_multiplier_z = 0.0
+positional_multiplier_x = 0.0
+positional_multiplier_y = 0.0
+positional_multiplier_z = 0.0
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_yvgu3"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_vc6km"]
+albedo_color = Color(0.988235, 0.498039, 0.498039, 1)
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_lsrh7"]
+radius = 0.269454
+
+[sub_resource type="Resource" id="Resource_lhgur"]
+script = ExtResource("3_f8fcw")
+duration = 1.0
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_ghjuj"]
+script = ExtResource("4_mjtut")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_2l4w0"]
+script = ExtResource("4_poyyk")
+amplitude = 40.0
+frequency = 0.2
+randomize_noise_seed = 0
+noise_seed = 0
+rotational_noise = true
+positional_noise = false
+rotational_multiplier_x = 1.0
+rotational_multiplier_y = 1.0
+rotational_multiplier_z = 0.0
+positional_multiplier_x = 0.1
+positional_multiplier_y = 0.1
+positional_multiplier_z = 0.1
+
+[sub_resource type="Resource" id="Resource_6tnhy"]
+script = ExtResource("4_poyyk")
+amplitude = 10.0
+frequency = 4.2
+randomize_noise_seed = 0
+noise_seed = 928
+rotational_noise = true
+positional_noise = false
+rotational_multiplier_x = 1.0
+rotational_multiplier_y = 1.0
+rotational_multiplier_z = 0.1
+positional_multiplier_x = 1.0
+positional_multiplier_y = 1.0
+positional_multiplier_z = 1.0
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_qi01t"]
+albedo_texture = ExtResource("6_vpla5")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ey47a"]
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_bottom_right = 20
+expand_margin_bottom = 6.0
+
+[node name="Root" type="Node3D"]
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.0129783, 0.0962422, 0.995273, 0, 0.995357, -0.0962503, -0.999916, 0.00124916, 0.012918, -16.46, 0.503767, 4.249)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("1_ggfbg")
+
+[node name="PlayerCharacterBody3D" type="CharacterBody3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(0.999897, 0.0143636, 0, -0.0143636, 0.999897, 0, 0, 0, 1, -16.46, 0.503767, 4.249)
+script = ExtResource("6_fbad7")
+run_noise = SubResource("Resource_t3bgw")
+
+[node name="PlayerVisual" type="MeshInstance3D" parent="PlayerCharacterBody3D"]
+unique_name_in_owner = true
+visible = false
+mesh = SubResource("CapsuleMesh_yvgu3")
+surface_material_override/0 = SubResource("StandardMaterial3D_vc6km")
+
+[node name="PlayerArea3D" type="Area3D" parent="PlayerCharacterBody3D"]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerCharacterBody3D/PlayerArea3D"]
+shape = SubResource("CapsuleShape3D_lsrh7")
+
+[node name="PlayerCollisionShape3D" type="CollisionShape3D" parent="PlayerCharacterBody3D"]
+shape = SubResource("CapsuleShape3D_lsrh7")
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.00441533, 0, 0.999915, 0, 0.999995, 0, -0.999923, 0, 0.00441529, -16.46, 0.503767, 4.249)
+top_level = true
+script = ExtResource("2_dreow")
+priority = 10
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D")
+tween_resource = SubResource("Resource_lhgur")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_ghjuj")
+noise = SubResource("Resource_2l4w0")
+noise_emitter_layer = 1
+
+[node name="PlayerPhantomCameraNoiseEmitter3D" type="Node3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(-4.37085e-08, 0, 0.999925, 0, 0.999995, 0, -0.999933, 0, -4.37081e-08, -16.46, 0.503767, 4.249)
+script = ExtResource("6_n8u0x")
+noise = SubResource("Resource_6tnhy")
+duration = 0.1
+decay_time = 0.1
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="Floor" parent="Environment" instance=ExtResource("5_d6uqs")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="CSGBox3D" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.525, 6.539, 2.5)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="CSGBox3D3" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, 8.83707, 6.53866, -1.80739)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="CSGBox3D4" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(-4.37114e-08, 0, -1, 0, 1, 0, 1, 0, -4.37114e-08, -38.9392, 6.53866, -1.80739)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="CSGBox3D2" type="CSGBox3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.525, 6.539, 6)
+use_collision = true
+size = Vector3(178.429, 14.0773, 1)
+material = SubResource("StandardMaterial3D_qi01t")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="EmitterTip" type="Panel" parent="."]
+unique_name_in_owner = true
+visible = false
+anchors_preset = -1
+anchor_right = 0.3
+anchor_bottom = 0.1
+theme_override_styles/panel = SubResource("StyleBoxFlat_ey47a")
+
+[node name="Guidance" type="RichTextLabel" parent="EmitterTip"]
+layout_mode = 1
+anchors_preset = -1
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_vertical = 8
+theme_override_fonts/normal_font = ExtResource("10_0thai")
+theme_override_fonts/bold_font = ExtResource("11_i8r8q")
+theme_override_font_sizes/normal_font_size = 18
+theme_override_font_sizes/bold_font_size = 24
+bbcode_enabled = true
+text = "[center]Press [b]Q[/b] to trigger Noise Emitter"
+fit_content = true
diff --git a/addons/phantom_camera/examples/example_scenes/3D/3d_tweening_example_scene.tscn b/addons/phantom_camera/examples/example_scenes/3D/3d_tweening_example_scene.tscn
new file mode 100644
index 0000000..2524686
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/3d_tweening_example_scene.tscn
@@ -0,0 +1,291 @@
+[gd_scene load_steps=22 format=3 uid="uid://5xtssqdfilal"]
+
+[ext_resource type="PackedScene" uid="uid://cixlwqycoox8h" path="res://addons/phantom_camera/examples/models/3d_cube_dark.tscn" id="1_ydeog"]
+[ext_resource type="Script" uid="uid://bd046eokvcnu2" path="res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd" id="2_b2yrt"]
+[ext_resource type="Script" uid="uid://csjccrhj5wnx7" path="res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd" id="3_m2w30"]
+[ext_resource type="Resource" uid="uid://cptfoggk2ok67" path="res://addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres" id="4_425ma"]
+[ext_resource type="Script" uid="uid://b8hhnqsugykly" path="res://addons/phantom_camera/scripts/resources/camera_3d_resource.gd" id="5_cn3g7"]
+[ext_resource type="Script" uid="uid://bnhxcejvr6wi3" path="res://addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd" id="5_h0ouh"]
+[ext_resource type="PackedScene" uid="uid://bulsh7s0ibmao" path="res://addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn" id="6_gcjyn"]
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="6_wup4d"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="8_60rny"]
+
+[sub_resource type="Resource" id="Resource_0dtvs"]
+script = ExtResource("5_cn3g7")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_j6fha"]
+size = Vector3(5, 0.1, 4)
+
+[sub_resource type="BoxMesh" id="BoxMesh_xg4en"]
+size = Vector3(5, 0.1, 4)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_2dct5"]
+transparency = 1
+albedo_color = Color(0.988235, 0.478431, 0.905882, 0.0901961)
+
+[sub_resource type="Resource" id="Resource_v8ndi"]
+script = ExtResource("6_wup4d")
+duration = 0.6
+transition = 0
+ease = 2
+
+[sub_resource type="Resource" id="Resource_kmep1"]
+script = ExtResource("5_cn3g7")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_uxg44"]
+script = ExtResource("6_wup4d")
+duration = 0.3
+transition = 1
+ease = 2
+
+[sub_resource type="Resource" id="Resource_eu3bc"]
+script = ExtResource("5_cn3g7")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_0nci0"]
+script = ExtResource("6_wup4d")
+duration = 0.3
+transition = 8
+ease = 2
+
+[sub_resource type="Resource" id="Resource_u0lff"]
+script = ExtResource("5_cn3g7")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[sub_resource type="Resource" id="Resource_50m5g"]
+script = ExtResource("6_wup4d")
+duration = 1.2
+transition = 10
+ease = 2
+
+[sub_resource type="Resource" id="Resource_rexf8"]
+script = ExtResource("5_cn3g7")
+keep_aspect = 1
+cull_mask = 1048575
+h_offset = 0.0
+v_offset = 0.0
+projection = 0
+fov = 75.0
+size = 1.0
+frustum_offset = Vector2(0, 0)
+near = 0.05
+far = 4000.0
+
+[node name="Root" type="Node3D"]
+
+[node name="Environment" type="Node" parent="."]
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="Environment"]
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 8, 0)
+metadata/_edit_lock_ = true
+
+[node name="Floor" parent="Environment" instance=ExtResource("1_ydeog")]
+transform = Transform3D(1000, 0, 0, 0, 1, 0, 0, 0, 1000, 0, -1, 0)
+metadata/_edit_lock_ = true
+
+[node name="MainCamera3D" type="Camera3D" parent="."]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 5.08867e-06, 2.00003, 2.00013)
+
+[node name="PhantomCameraHost" type="Node" parent="MainCamera3D"]
+process_priority = 300
+process_physics_priority = 300
+script = ExtResource("2_b2yrt")
+
+[node name="------------------" type="Node" parent="."]
+
+[node name="PlayerPhantomCamera3D" type="Node3D" parent="." node_paths=PackedStringArray("follow_target")]
+unique_name_in_owner = true
+transform = Transform3D(0.999889, 0, 0, 0, 0.707092, 0.707088, 0, -0.707092, 0.707088, 0, 2, 2)
+top_level = true
+script = ExtResource("3_m2w30")
+priority = 3
+follow_mode = 2
+follow_target = NodePath("../PlayerCharacterBody3D2/PlayerVisual")
+tween_resource = ExtResource("4_425ma")
+tween_on_load = false
+camera_3d_resource = SubResource("Resource_0dtvs")
+follow_offset = Vector3(0, 2, 2)
+follow_damping = true
+
+[node name="PlayerCharacterBody3D2" parent="." instance=ExtResource("6_gcjyn")]
+
+[node name="-------------------" type="Node" parent="."]
+
+[node name="Tweening Example" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1.97)
+
+[node name="Linear" type="Node3D" parent="Tweening Example"]
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Linear" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("5_h0ouh")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Linear/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Linear/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Linear"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 4.8, 3.3)
+script = ExtResource("3_m2w30")
+tween_resource = SubResource("Resource_v8ndi")
+camera_3d_resource = SubResource("Resource_kmep1")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Linear"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, -1.8, 0.5, 0)
+text = "Transition Type:
+Linear
+
+Duration:
+0.6s"
+font = ExtResource("8_60rny")
+font_size = 48
+
+[node name="Sine" type="Node3D" parent="Tweening Example"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -7.4)
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Sine" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("5_h0ouh")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Sine/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Sine/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Sine"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 4.8, 3.3)
+script = ExtResource("3_m2w30")
+tween_resource = SubResource("Resource_uxg44")
+camera_3d_resource = SubResource("Resource_eu3bc")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Sine"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, 1.7, 0.5, 0)
+text = "Transition Type:
+Sine
+
+Duration:
+0.3s"
+font = ExtResource("8_60rny")
+font_size = 72
+
+[node name="Circ" type="Node3D" parent="Tweening Example"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -14.1)
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Circ" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("5_h0ouh")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Circ/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Circ/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Circ"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, 0, 4.8, 3.3)
+script = ExtResource("3_m2w30")
+tween_resource = SubResource("Resource_0nci0")
+camera_3d_resource = SubResource("Resource_u0lff")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Circ"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, -1.8, 0.5, 0)
+text = "Transition Type:
+Circ
+
+Duration:
+0.3s"
+font = ExtResource("8_60rny")
+font_size = 72
+
+[node name="Back" type="Node3D" parent="Tweening Example"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -21)
+
+[node name="EntryRoomTrigger" type="Area3D" parent="Tweening Example/Back" node_paths=PackedStringArray("area_pcam")]
+priority = 5
+script = ExtResource("5_h0ouh")
+area_pcam = NodePath("../PhantomCamera3D")
+metadata/_edit_group_ = true
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Tweening Example/Back/EntryRoomTrigger"]
+shape = SubResource("BoxShape3D_j6fha")
+
+[node name="NPCInteractionZoneMesh" type="MeshInstance3D" parent="Tweening Example/Back/EntryRoomTrigger"]
+mesh = SubResource("BoxMesh_xg4en")
+skeleton = NodePath("../../../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_2dct5")
+metadata/_edit_group_ = true
+
+[node name="PhantomCamera3D" type="Node3D" parent="Tweening Example/Back"]
+transform = Transform3D(1, 0, 0, 0, 0.642788, 0.766044, 0, -0.766044, 0.642788, -0.8, 4.8, 3.3)
+script = ExtResource("3_m2w30")
+tween_resource = SubResource("Resource_50m5g")
+camera_3d_resource = SubResource("Resource_rexf8")
+
+[node name="TweenNameLabel" type="Label3D" parent="Tweening Example/Back"]
+transform = Transform3D(1, 0, 0, 0, 0.695913, 0.718126, 0, -0.718126, 0.695913, 1.7, 0.5, 0)
+text = "Transition Type:
+Back
+
+Duration:
+1.2s"
+font = ExtResource("8_60rny")
+font_size = 48
+
+[editable path="PlayerCharacterBody3D2"]
diff --git a/addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn b/addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn
new file mode 100644
index 0000000..26460f4
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_3d.tscn
@@ -0,0 +1,30 @@
+[gd_scene load_steps=5 format=3 uid="uid://bulsh7s0ibmao"]
+
+[ext_resource type="Script" uid="uid://dut3e76k2c71n" path="res://addons/phantom_camera/examples/scripts/3D/player_controller.gd" id="1_6hh6c"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_8efyg"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_2cfaw"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_r3ldp"]
+albedo_color = Color(0.988235, 0.498039, 0.498039, 1)
+
+[node name="PlayerCharacterBody3D2" type="CharacterBody3D"]
+script = ExtResource("1_6hh6c")
+metadata/_edit_group_ = true
+
+[node name="PlayerArea3D" type="Area3D" parent="."]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerArea3D"]
+shape = SubResource("CapsuleShape3D_8efyg")
+
+[node name="PlayerCollisionShape3D" type="CollisionShape3D" parent="."]
+shape = SubResource("CapsuleShape3D_8efyg")
+
+[node name="PlayerVisual" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="PlayerModel" type="MeshInstance3D" parent="PlayerVisual"]
+mesh = SubResource("CapsuleMesh_2cfaw")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_r3ldp")
diff --git a/addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn b/addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn
new file mode 100644
index 0000000..f2cc7f5
--- /dev/null
+++ b/addons/phantom_camera/examples/example_scenes/3D/sub_scenes/playable_character_third_person_3d.tscn
@@ -0,0 +1,43 @@
+[gd_scene load_steps=6 format=3 uid="uid://mskcwn1a1v6d"]
+
+[ext_resource type="Script" uid="uid://34uhyq3cpi67" path="res://addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd" id="1_0dnfe"]
+
+[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_s61dn"]
+
+[sub_resource type="CapsuleMesh" id="CapsuleMesh_47f0o"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mv4do"]
+albedo_color = Color(0.988235, 0.498039, 0.498039, 1)
+
+[sub_resource type="PrismMesh" id="PrismMesh_wg1x3"]
+size = Vector3(0.5, 0.5, 0.3)
+
+[node name="PlayerCharacterBody3D" type="CharacterBody3D"]
+transform = Transform3D(0.999903, 0.0139622, 0, -0.0139622, 0.999903, 0, 0, 0, 1, -0.0194088, 0.506884, -0.0163251)
+collision_layer = 2
+script = ExtResource("1_0dnfe")
+metadata/_edit_group_ = true
+
+[node name="PlayerArea3D" type="Area3D" parent="."]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="PlayerArea3D"]
+shape = SubResource("CapsuleShape3D_s61dn")
+
+[node name="PlayerCollisionShape3D" type="CollisionShape3D" parent="."]
+shape = SubResource("CapsuleShape3D_s61dn")
+
+[node name="PlayerVisual" type="Node3D" parent="."]
+unique_name_in_owner = true
+
+[node name="PlayerMeshInstance3D" type="MeshInstance3D" parent="PlayerVisual"]
+transform = Transform3D(1, 0, 0, 0, 1, 4.65661e-10, 0, 0, 1, 0, 0, 0)
+mesh = SubResource("CapsuleMesh_47f0o")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_mv4do")
+
+[node name="PlayerDirection" type="MeshInstance3D" parent="PlayerVisual"]
+unique_name_in_owner = true
+transform = Transform3D(1, 0, 0, -9.31323e-10, 1, 4.65661e-10, 2.98023e-08, 0, 1, -0.0156226, 1.08631, 0)
+mesh = SubResource("PrismMesh_wg1x3")
+skeleton = NodePath("../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_mv4do")
diff --git a/addons/phantom_camera/examples/models/3d_cube_dark.tscn b/addons/phantom_camera/examples/models/3d_cube_dark.tscn
new file mode 100644
index 0000000..feab949
--- /dev/null
+++ b/addons/phantom_camera/examples/models/3d_cube_dark.tscn
@@ -0,0 +1,15 @@
+[gd_scene load_steps=4 format=3 uid="uid://cixlwqycoox8h"]
+
+[ext_resource type="Texture2D" uid="uid://bj7h2fc5jx4ax" path="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png" id="1_836jx"]
+
+[sub_resource type="BoxMesh" id="BoxMesh_d24c3"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_aox6v"]
+albedo_texture = ExtResource("1_836jx")
+uv1_triplanar = true
+uv1_world_triplanar = true
+
+[node name="3DPrototypeCube" type="CSGMesh3D"]
+use_collision = true
+mesh = SubResource("BoxMesh_d24c3")
+material = SubResource("StandardMaterial3D_aox6v")
diff --git a/addons/phantom_camera/examples/resources/tween/fixed_camera_tween.tres b/addons/phantom_camera/examples/resources/tween/fixed_camera_tween.tres
new file mode 100644
index 0000000..8888f93
--- /dev/null
+++ b/addons/phantom_camera/examples/resources/tween/fixed_camera_tween.tres
@@ -0,0 +1,9 @@
+[gd_resource type="Resource" script_class="PhantomCameraTween" load_steps=2 format=3 uid="uid://c1v786g5agaw5"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="1_ptlie"]
+
+[resource]
+script = ExtResource("1_ptlie")
+duration = 0.0
+transition = 0
+ease = 2
diff --git a/addons/phantom_camera/examples/resources/tween/inventory_phantom_camera_2d_tween.tres b/addons/phantom_camera/examples/resources/tween/inventory_phantom_camera_2d_tween.tres
new file mode 100644
index 0000000..5a0708e
--- /dev/null
+++ b/addons/phantom_camera/examples/resources/tween/inventory_phantom_camera_2d_tween.tres
@@ -0,0 +1,9 @@
+[gd_resource type="Resource" script_class="PhantomCameraTween" load_steps=2 format=3 uid="uid://cllveybboaqk5"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="1_7yoy0"]
+
+[resource]
+script = ExtResource("1_7yoy0")
+duration = 0.6
+transition = 5
+ease = 1
diff --git a/addons/phantom_camera/examples/resources/tween/item_focus_phantom_camera_2d_tween.tres b/addons/phantom_camera/examples/resources/tween/item_focus_phantom_camera_2d_tween.tres
new file mode 100644
index 0000000..8464eff
--- /dev/null
+++ b/addons/phantom_camera/examples/resources/tween/item_focus_phantom_camera_2d_tween.tres
@@ -0,0 +1,9 @@
+[gd_resource type="Resource" script_class="PhantomCameraTween" load_steps=2 format=3 uid="uid://cecrnq0wnkexh"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="1_sq5ls"]
+
+[resource]
+script = ExtResource("1_sq5ls")
+duration = 0.6
+transition = 8
+ease = 1
diff --git a/addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres b/addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres
new file mode 100644
index 0000000..5258ea7
--- /dev/null
+++ b/addons/phantom_camera/examples/resources/tween/player_phantom_camera_2d_tween.tres
@@ -0,0 +1,9 @@
+[gd_resource type="Resource" script_class="PhantomCameraTween" load_steps=2 format=3 uid="uid://euybd2w0bax"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="1_by4ei"]
+
+[resource]
+script = ExtResource("1_by4ei")
+duration = 0.6
+transition = 3
+ease = 1
diff --git a/addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres b/addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres
new file mode 100644
index 0000000..e586199
--- /dev/null
+++ b/addons/phantom_camera/examples/resources/tween/player_phantom_camera_3d_tween.tres
@@ -0,0 +1,9 @@
+[gd_resource type="Resource" script_class="PhantomCameraTween" load_steps=2 format=3 uid="uid://cptfoggk2ok67"]
+
+[ext_resource type="Script" uid="uid://8umksf8e80fw" path="res://addons/phantom_camera/scripts/resources/tween_resource.gd" id="1_q5tix"]
+
+[resource]
+script = ExtResource("1_q5tix")
+duration = 0.6
+transition = 3
+ease = 2
diff --git a/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd
new file mode 100644
index 0000000..06e9180
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd
@@ -0,0 +1,36 @@
+extends Node2D
+
+@onready var pcam_room_left: PhantomCamera2D = %RoomLeftPhantomCamera2D
+@onready var pcam_room_centre: PhantomCamera2D = %RoomCentrePhantomCamera2D
+@onready var pcam_room_right: PhantomCamera2D = %RoomRightPhantomCamera2D
+
+@onready var player: Node2D = %CharacterBody2D/%PlayerVisuals
+
+@onready var area_2d_room_left: Area2D = %RoomLeftArea2D
+@onready var area_2d_room_centre: Area2D = %RoomCentreArea2D
+@onready var area_2d_room_right: Area2D = %RoomRightArea2D
+
+
+func _ready():
+ pcam_room_left.set_follow_offset(Vector2(0, -80))
+ pcam_room_right.set_follow_offset(Vector2(0, -80))
+
+ area_2d_room_left.body_entered.connect(_on_body_entered.bind(pcam_room_left))
+ area_2d_room_centre.body_entered.connect(_on_body_entered.bind(pcam_room_centre))
+ area_2d_room_right.body_entered.connect(_on_body_entered.bind(pcam_room_right))
+
+ area_2d_room_left.body_exited.connect(_on_body_exited.bind(pcam_room_left))
+ area_2d_room_centre.body_exited.connect(_on_body_exited.bind(pcam_room_centre))
+ area_2d_room_right.body_exited.connect(_on_body_exited.bind(pcam_room_right))
+
+
+func _on_body_entered(body: Node2D, pcam: PhantomCamera2D) -> void:
+ if body == player.get_parent():
+ pcam.set_follow_target(player)
+ pcam.set_priority(20)
+
+
+func _on_body_exited(body: Node2D, pcam: PhantomCamera2D) -> void:
+ if body == player.get_parent():
+ pcam.set_priority(0)
+ pcam.set_follow_target(null)
diff --git a/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd.uid b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd.uid
new file mode 100644
index 0000000..1a32325
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween.gd.uid
@@ -0,0 +1 @@
+uid://c5yewe1hewu7j
diff --git a/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd
new file mode 100644
index 0000000..970c52e
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd
@@ -0,0 +1,36 @@
+extends Node2D
+
+@onready var pcam_room_left: PhantomCamera2D = %RoomLeftPhantomCamera2D
+@onready var pcam_room_centre: PhantomCamera2D = %RoomCentrePhantomCamera2D
+@onready var pcam_room_right: PhantomCamera2D = %RoomRightPhantomCamera2D
+
+@onready var player: Node2D = %CharacterBody2D
+
+@onready var area_2d_room_left: Area2D = %RoomLeftArea2D
+@onready var area_2d_room_centre: Area2D = %RoomCentreArea2D
+@onready var area_2d_room_right: Area2D = %RoomRightArea2D
+
+
+func _ready():
+ pcam_room_left.set_follow_offset(Vector2(0, -80))
+ pcam_room_right.set_follow_offset(Vector2(0, -80))
+
+ area_2d_room_left.body_entered.connect(_on_body_entered.bind(pcam_room_left))
+ area_2d_room_centre.body_entered.connect(_on_body_entered.bind(pcam_room_centre))
+ area_2d_room_right.body_entered.connect(_on_body_entered.bind(pcam_room_right))
+
+ area_2d_room_left.body_exited.connect(_on_body_exited.bind(pcam_room_left))
+ area_2d_room_centre.body_exited.connect(_on_body_exited.bind(pcam_room_centre))
+ area_2d_room_right.body_exited.connect(_on_body_exited.bind(pcam_room_right))
+
+
+func _on_body_entered(body: Node2D, pcam: PhantomCamera2D) -> void:
+ if body == player:
+ pcam.set_follow_target(player)
+ pcam.set_priority(20)
+
+
+func _on_body_exited(body: Node2D, pcam: PhantomCamera2D) -> void:
+ if body == player:
+ pcam.set_priority(0)
+ pcam.set_follow_target(null)
diff --git a/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd.uid b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd.uid
new file mode 100644
index 0000000..fc56f1c
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/2d_room_limit_tween_4.3.gd.uid
@@ -0,0 +1 @@
+uid://dtcuvut1eklnd
diff --git a/addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd b/addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd
new file mode 100644
index 0000000..db0209c
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd
@@ -0,0 +1,16 @@
+extends Area2D
+
+@export var area_pcam: PhantomCamera2D
+
+func _ready() -> void:
+ connect("area_entered", _entered_area)
+ connect("area_exited", _exited_area)
+
+func _entered_area(area_2d: Area2D) -> void:
+ if area_2d.get_parent() is CharacterBody2D:
+ area_pcam.set_priority(20)
+
+func _exited_area(area_2d: Area2D) -> void:
+ if area_2d.get_parent() is CharacterBody2D:
+ area_pcam.set_priority(0)
+
diff --git a/addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd.uid b/addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd.uid
new file mode 100644
index 0000000..c5ac45f
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/2d_trigger_area.gd.uid
@@ -0,0 +1 @@
+uid://t8wa4e5y5hcf
diff --git a/addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd
new file mode 100644
index 0000000..8003bb3
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd
@@ -0,0 +1,189 @@
+extends CharacterBody2D
+
+@onready var _player_area2d = %PlayerArea2D
+@onready var _player_visuals: Node2D = %PlayerVisuals
+@onready var _player_sprite: Sprite2D = %PlayerSprite
+@onready var _interaction_prompt: Panel = %InteractionPrompt
+@onready var _ui_sign: Control
+@onready var _dark_overlay: ColorRect = %DarkOverlay
+
+const KEY_STRINGNAME: StringName = "Key"
+const ACTION_STRINGNAME: StringName = "Action"
+const INPUT_MOVE_LEFT_STRINGNAME: StringName = "move_left"
+const INPUT_MOVE_RIGHT_STRINGNAME: StringName = "move_right"
+
+const SPEED = 350.0
+const JUMP_VELOCITY = -750.0
+
+# Get the gravity from the project settings to be synced with RigidBody nodes.
+var gravity: int = 2400
+var _is_interactive: bool
+var _can_open_inventory: bool
+var _movement_disabled: bool
+var tween: Tween
+var _interactive_UI: Control
+var _active_pcam: PhantomCamera2D
+
+var _physics_body_trans_last: Transform2D
+var _physics_body_trans_current: Transform2D
+
+enum InteractiveType {
+ NONE = 0,
+ ITEM = 1,
+ INVENTORY = 2,
+}
+var _interactive_object: InteractiveType = InteractiveType.NONE
+
+var InputMovementDic: Dictionary = {
+ INPUT_MOVE_LEFT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_A,
+ ACTION_STRINGNAME: INPUT_MOVE_LEFT_STRINGNAME
+ },
+ INPUT_MOVE_RIGHT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_D,
+ ACTION_STRINGNAME: INPUT_MOVE_RIGHT_STRINGNAME
+ },
+}
+
+
+func _ready() -> void:
+ _player_area2d.body_shape_entered.connect(_show_prompt)
+ _player_area2d.body_shape_exited.connect(_hide_prompt)
+
+ _ui_sign = owner.get_node("%UISign")
+
+ for input in InputMovementDic:
+ var key_val = InputMovementDic[input].get(KEY_STRINGNAME)
+ var action_val = InputMovementDic[input].get(ACTION_STRINGNAME)
+
+ var movement_input = InputEventKey.new()
+ movement_input.physical_keycode = key_val
+ InputMap.add_action(action_val)
+ InputMap.action_add_event(action_val, movement_input)
+
+ _player_visuals.top_level = true
+
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor >= 3:
+ printerr("Please run the other 2D example scenes, in the 2D-4.3 directory, for more up-to-date example setups.")
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if _is_interactive:
+ if Input.is_physical_key_pressed(KEY_F):
+ if tween:
+ tween.kill()
+
+ if not _movement_disabled:
+ tween = get_tree().create_tween()
+
+ _movement_disabled = true
+ _active_pcam.set_priority(10)
+
+ _show_interactive_node(_interactive_UI)
+ _interactive_node_logic()
+
+ else:
+ _hide_interactive_node(_interactive_UI)
+ _interactive_node_logic()
+
+
+ if Input.is_physical_key_pressed(KEY_ESCAPE) and _movement_disabled:
+ _hide_interactive_node(_interactive_UI)
+ _interactive_node_logic()
+
+
+func _show_interactive_node(UI: Control) -> void:
+ UI.modulate.a = 0
+ UI.visible = true
+ tween.tween_property(UI, "modulate", Color.WHITE, 1).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CIRC)
+
+
+func _hide_interactive_node(UI: Control) -> void:
+ _movement_disabled = false
+ _active_pcam.set_priority(0)
+ UI.visible = false
+
+
+func _interactive_node_logic() -> void:
+ match _interactive_object:
+ 2:
+ if _movement_disabled:
+ _dark_overlay.set_visible(true)
+ else:
+ _dark_overlay.set_visible(false)
+
+
+func _physics_process(delta: float) -> void:
+ _physics_body_trans_last = _physics_body_trans_current
+ _physics_body_trans_current = global_transform
+
+ if not is_on_floor():
+ velocity.y += gravity * delta
+
+ if Input.is_action_just_pressed("ui_accept") and is_on_floor():
+ velocity.y = JUMP_VELOCITY
+
+ if _movement_disabled: return
+
+ var input_dir: = Input.get_axis(
+ INPUT_MOVE_LEFT_STRINGNAME,
+ INPUT_MOVE_RIGHT_STRINGNAME
+ )
+
+ if input_dir:
+ velocity.x = input_dir * SPEED
+ if input_dir > 0:
+ _player_sprite.set_flip_h(false)
+ elif input_dir < 0:
+ _player_sprite.set_flip_h(true)
+ else:
+ velocity.x = move_toward(velocity.x, 0, SPEED)
+
+ move_and_slide()
+
+
+func _process(delta) -> void:
+ _player_visuals.global_position = _physics_body_trans_last.interpolate_with(
+ _physics_body_trans_current,
+ Engine.get_physics_interpolation_fraction()
+ ).origin
+
+
+func _show_prompt(body_rid: RID, body: Node2D, body_shape_index: int, local_shape: int) -> void:
+ if body is TileMap:
+ var tile_map: TileMap = body
+
+ var tile_coords: Vector2i = tile_map.get_coords_for_body_rid(body_rid)
+ var cell_data: TileData = tile_map.get_cell_tile_data(1, tile_coords)
+
+ if cell_data:
+ var cell_data_type: StringName = cell_data.get_custom_data("Type")
+# var cell_global_pos: Vector2 = tile_map.to_global(tile_map.map_to_local(tile_coords))
+ _is_interactive = true
+ _interaction_prompt.set_visible(true)
+
+ match cell_data_type:
+ "Sign":
+ _interactive_UI = owner.get_node("%UISign")
+ _active_pcam = %ItemFocusPhantomCamera2D
+ _interactive_object = InteractiveType.ITEM
+ "Inventory":
+ _interactive_UI = owner.get_node("%UIInventory")
+ _interactive_object = InteractiveType.INVENTORY
+ _active_pcam = %InventoryPhantomCamera2D
+
+
+func _hide_prompt(body_rid: RID, body: Node2D, body_shape_index: int, local_shape: int) -> void:
+ if body is TileMap:
+ var tile_map: TileMap = body
+
+ var tile_coords: Vector2i = tile_map.get_coords_for_body_rid(body_rid)
+ var cell_data: TileData = tile_map.get_cell_tile_data(1, tile_coords)
+
+ if cell_data:
+ _interaction_prompt.set_visible(false)
+ _is_interactive = false
+ _interactive_UI = null
+ _interactive_object = InteractiveType.NONE
+ _active_pcam = null
diff --git a/addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd.uid b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd.uid
new file mode 100644
index 0000000..2f250b4
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d.gd.uid
@@ -0,0 +1 @@
+uid://cb46ypjv5p72s
diff --git a/addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd
new file mode 100644
index 0000000..41ab5e2
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd
@@ -0,0 +1,179 @@
+extends CharacterBody2D
+
+@onready var _player_area2d = %PlayerArea2D
+@onready var _player_visuals: Node2D = %PlayerVisuals
+@onready var _player_sprite: Sprite2D = %PlayerSprite
+@onready var _interaction_prompt: Panel = %InteractionPrompt
+@onready var _ui_sign: Control
+@onready var _dark_overlay: ColorRect = %DarkOverlay
+@onready var _noise_emitter: PhantomCameraNoiseEmitter2D
+
+const KEY_STRINGNAME: StringName = "Key"
+const ACTION_STRINGNAME: StringName = "Action"
+const INPUT_MOVE_LEFT_STRINGNAME: StringName = "move_left"
+const INPUT_MOVE_RIGHT_STRINGNAME: StringName = "move_right"
+
+const SPEED = 350.0
+const JUMP_VELOCITY = -750.0
+
+# Get the gravity from the project settings to be synced with RigidBody nodes.
+var gravity: int = 2400
+var _is_interactive: bool
+var _can_open_inventory: bool
+var _movement_disabled: bool
+var tween: Tween
+var _interactive_UI: Control
+var _active_pcam: PhantomCamera2D
+
+enum InteractiveType {
+ NONE = 0,
+ ITEM = 1,
+ INVENTORY = 2,
+}
+var _interactive_object: InteractiveType = InteractiveType.NONE
+
+var InputMovementDic: Dictionary = {
+ INPUT_MOVE_LEFT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_A,
+ ACTION_STRINGNAME: INPUT_MOVE_LEFT_STRINGNAME
+ },
+ INPUT_MOVE_RIGHT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_D,
+ ACTION_STRINGNAME: INPUT_MOVE_RIGHT_STRINGNAME
+ },
+}
+
+
+func _ready() -> void:
+ _player_area2d.body_shape_entered.connect(_show_prompt)
+ _player_area2d.body_shape_exited.connect(_hide_prompt)
+
+ _ui_sign = owner.get_node("%UISign")
+
+ for input in InputMovementDic:
+ var key_val = InputMovementDic[input].get(KEY_STRINGNAME)
+ var action_val = InputMovementDic[input].get(ACTION_STRINGNAME)
+
+ var movement_input = InputEventKey.new()
+ movement_input.physical_keycode = key_val
+ InputMap.add_action(action_val)
+ InputMap.action_add_event(action_val, movement_input)
+
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor < 3:
+ printerr("This scene is designed to only work properly in Godot 4.3 or later that supports 2D Physics Interpolation.")
+ printerr("Please run the other 2D example scenes in the other directory.")
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if _is_interactive:
+ if Input.is_physical_key_pressed(KEY_F):
+ if tween:
+ tween.kill()
+
+ if not _movement_disabled:
+ tween = get_tree().create_tween()
+
+ _movement_disabled = true
+ _active_pcam.set_priority(10)
+
+ _show_interactive_node(_interactive_UI)
+ _interactive_node_logic()
+
+ else:
+ _hide_interactive_node(_interactive_UI)
+ _interactive_node_logic()
+
+
+ if Input.is_physical_key_pressed(KEY_ESCAPE) and _movement_disabled:
+ _hide_interactive_node(_interactive_UI)
+ _interactive_node_logic()
+
+ if Input.is_physical_key_pressed(KEY_Q):
+ if get_node_or_null("%PlayerPhantomCameraNoiseEmitter2D"):
+ %PlayerPhantomCameraNoiseEmitter2D.emit()
+
+
+func _show_interactive_node(UI: Control) -> void:
+ UI.modulate.a = 0
+ UI.visible = true
+ tween.tween_property(UI, "modulate", Color.WHITE, 1).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CIRC)
+
+
+func _hide_interactive_node(UI: Control) -> void:
+ _movement_disabled = false
+ _active_pcam.set_priority(0)
+ UI.visible = false
+
+
+func _interactive_node_logic() -> void:
+ match _interactive_object:
+ 2:
+ if _movement_disabled:
+ _dark_overlay.set_visible(true)
+ else:
+ _dark_overlay.set_visible(false)
+
+
+func _physics_process(delta: float) -> void:
+ if not is_on_floor():
+ velocity.y += gravity * delta
+
+ if Input.is_action_just_pressed("ui_accept") and is_on_floor():
+ velocity.y = JUMP_VELOCITY
+
+ if _movement_disabled: return
+
+ var input_dir: = Input.get_axis(
+ INPUT_MOVE_LEFT_STRINGNAME,
+ INPUT_MOVE_RIGHT_STRINGNAME
+ )
+
+ if input_dir:
+ velocity.x = input_dir * SPEED
+ if input_dir > 0:
+ _player_sprite.set_flip_h(false)
+ elif input_dir < 0:
+ _player_sprite.set_flip_h(true)
+ else:
+ velocity.x = move_toward(velocity.x, 0, SPEED)
+
+ move_and_slide()
+
+
+func _show_prompt(body_rid: RID, body: Node2D, body_shape_index: int, local_shape: int) -> void:
+ if body.is_class("TileMapLayer"): # TODO - Using string reference to support Godot 4.2
+ var tile_map := body
+ var tile_coords: Vector2i = tile_map.get_coords_for_body_rid(body_rid)
+ var cell_data: TileData = tile_map.get_cell_tile_data(tile_coords)
+
+ if cell_data:
+ var cell_data_type: StringName = cell_data.get_custom_data("Type")
+# var cell_global_pos: Vector2 = tile_map.to_global(tile_map.map_to_local(tile_coords))
+ _is_interactive = true
+ _interaction_prompt.set_visible(true)
+
+ match cell_data_type:
+ "Sign":
+ _interactive_UI = owner.get_node("%UISign")
+ _active_pcam = %ItemFocusPhantomCamera2D
+ _interactive_object = InteractiveType.ITEM
+ "Inventory":
+ _interactive_UI = owner.get_node("%UIInventory")
+ _interactive_object = InteractiveType.INVENTORY
+ _active_pcam = %InventoryPhantomCamera2D
+
+
+func _hide_prompt(body_rid: RID, body: Node2D, body_shape_index: int, local_shape: int) -> void:
+ if body.is_class("TileMapLayer"): # TODO - Using string reference to support Godot 4.2
+ var tile_map := body
+
+ var tile_coords: Vector2i = tile_map.get_coords_for_body_rid(body_rid)
+ var cell_data: TileData = tile_map.get_cell_tile_data(tile_coords)
+
+ if cell_data:
+ _interaction_prompt.set_visible(false)
+ _is_interactive = false
+ _interactive_UI = null
+ _interactive_object = InteractiveType.NONE
+ _active_pcam = null
diff --git a/addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd.uid b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd.uid
new file mode 100644
index 0000000..411f7c4
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/2D/player_character_body_2d_4.3.gd.uid
@@ -0,0 +1 @@
+uid://cnnaky2ns2pn4
diff --git a/addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd b/addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd
new file mode 100644
index 0000000..4fc9764
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd
@@ -0,0 +1,26 @@
+extends Area3D
+
+@export var area_pcam: PhantomCamera3D
+
+var initial_camera_position: Vector3
+var initial_camera_rotation: Vector3
+
+var tween: Tween
+var tween_duration: float = 0.9
+
+
+func _ready() -> void:
+ connect("area_entered", _entered_area)
+ connect("area_exited", _exited_area)
+
+
+func _entered_area(area_3D: Area3D) -> void:
+ if area_3D.get_parent() is CharacterBody3D:
+ area_pcam.set_priority(20)
+
+
+func _exited_area(area_3D: Area3D) -> void:
+ if area_3D.get_parent() is CharacterBody3D:
+ area_pcam.set_priority(0)
+
+
diff --git a/addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd.uid b/addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd.uid
new file mode 100644
index 0000000..5499427
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/3d_trigger_area.gd.uid
@@ -0,0 +1 @@
+uid://bnhxcejvr6wi3
diff --git a/addons/phantom_camera/examples/scripts/3D/npc.gd b/addons/phantom_camera/examples/scripts/3D/npc.gd
new file mode 100644
index 0000000..3d9a523
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/npc.gd
@@ -0,0 +1,71 @@
+extends Node3D
+
+@onready var npc_pcam: PhantomCamera3D = %NPCPhantomCamera3D
+@onready var dialogueArea: Area3D = %NPCInteractionArea3D
+@onready var dialogueLabel3D: Label3D = %NPCDialogueExampleLabel
+
+@onready var player: CharacterBody3D = %PlayerCharacterBody3D
+
+@onready var move_to_location: Vector3 = %MoveToLocation.get_global_position()
+
+var dialogue_label_initial_position: Vector3
+var dialogue_label_initial_rotation: Vector3
+
+var tween: Tween
+var tween_duration: float = 0.9
+var tween_transition: Tween.TransitionType = Tween.TRANS_QUAD
+
+var interactable: bool
+var is_interacting: bool
+
+func _ready() -> void:
+ dialogueArea.connect("area_entered", _interactable)
+ dialogueArea.connect("area_exited", _not_interactable)
+
+ dialogueLabel3D.set_visible(false)
+
+ dialogue_label_initial_position = dialogueLabel3D.get_global_position()
+ dialogue_label_initial_rotation = dialogueLabel3D.get_global_rotation()
+
+ npc_pcam.tween_completed.connect(_on_tween_started)
+
+
+
+func _on_tween_started() -> void:
+ player.movement_enabled = false
+
+
+func _interactable(area_3D: Area3D) -> void:
+ if area_3D.get_parent() is CharacterBody3D:
+ dialogueLabel3D.set_visible(true)
+ interactable = true
+
+ var tween: Tween = get_tree().create_tween().set_trans(tween_transition).set_ease(Tween.EASE_IN_OUT).set_loops()
+ tween.tween_property(dialogueLabel3D, "global_position", dialogue_label_initial_position - Vector3(0, -0.2, 0), tween_duration)
+ tween.tween_property(dialogueLabel3D, "position", dialogue_label_initial_position, tween_duration)
+
+
+func _not_interactable(area_3D: Area3D) -> void:
+ if area_3D.get_parent() is CharacterBody3D:
+ dialogueLabel3D.set_visible(false)
+ interactable = false
+
+
+func _input(event) -> void:
+ if not interactable: return
+
+ if event is InputEventKey and event.pressed:
+ if event.keycode == KEY_F:
+ var tween: Tween = get_tree().create_tween() \
+ .set_parallel(true) \
+ .set_trans(Tween.TRANS_QUART) \
+ .set_ease(Tween.EASE_IN_OUT)
+ if not is_interacting:
+ npc_pcam.priority = 20
+ tween.tween_property(player, "global_position", move_to_location, 0.6).set_trans(tween_transition)
+ tween.tween_property(dialogueLabel3D, "rotation", Vector3(deg_to_rad(-20), deg_to_rad(53), 0), 0.6).set_trans(tween_transition)
+ else:
+ npc_pcam.priority = 0
+ tween.tween_property(dialogueLabel3D, "rotation", dialogue_label_initial_rotation, 0.9)
+ player.movement_enabled = true
+ is_interacting = !is_interacting
diff --git a/addons/phantom_camera/examples/scripts/3D/npc.gd.uid b/addons/phantom_camera/examples/scripts/3D/npc.gd.uid
new file mode 100644
index 0000000..7464c88
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/npc.gd.uid
@@ -0,0 +1 @@
+uid://uvw6pg1ut0ms
diff --git a/addons/phantom_camera/examples/scripts/3D/path_follow.gd b/addons/phantom_camera/examples/scripts/3D/path_follow.gd
new file mode 100644
index 0000000..8535e4c
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/path_follow.gd
@@ -0,0 +1,17 @@
+extends Node
+
+@export var path_pcam: PhantomCamera3D
+
+func _ready() -> void:
+ connect("area_entered", _entered_area)
+ connect("area_exited", _exited_area)
+
+
+func _entered_area(area_3D: Area3D) -> void:
+ if area_3D.get_parent() is CharacterBody3D:
+ path_pcam.set_priority(20)
+
+
+func _exited_area(area_3D: Area3D) -> void:
+ if area_3D.get_parent() is CharacterBody3D:
+ path_pcam.set_priority(0)
diff --git a/addons/phantom_camera/examples/scripts/3D/path_follow.gd.uid b/addons/phantom_camera/examples/scripts/3D/path_follow.gd.uid
new file mode 100644
index 0000000..c2f56ca
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/path_follow.gd.uid
@@ -0,0 +1 @@
+uid://cgknbkjar73w
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller.gd b/addons/phantom_camera/examples/scripts/3D/player_controller.gd
new file mode 100644
index 0000000..b136103
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller.gd
@@ -0,0 +1,103 @@
+extends CharacterBody3D
+
+@export var SPEED: float = 5.0
+@export var JUMP_VELOCITY: float = 4.5
+@export var enable_gravity = true
+
+@onready var _camera: Camera3D
+
+@onready var _player_visual: Node3D = %PlayerVisual
+
+# Get the gravity from the project settings to be synced with RigidBody nodes.
+var gravity: float = 9.8
+
+var movement_enabled: bool = true
+
+var _physics_body_trans_last: Transform3D
+var _physics_body_trans_current: Transform3D
+
+const KEY_STRINGNAME: StringName = "Key"
+const ACTION_STRINGNAME: StringName = "Action"
+
+const INPUT_MOVE_UP_STRINGNAME: StringName = "move_up"
+const INPUT_MOVE_DOWM_STRINGNAME: StringName = "move_down"
+const INPUT_MOVE_LEFT_STRINGNAME: StringName = "move_left"
+const INPUT_MOVE_RIGHT_STRINGNAME: StringName = "move_right"
+
+var InputMovementDic: Dictionary = {
+ INPUT_MOVE_UP_STRINGNAME: {
+ KEY_STRINGNAME: KEY_W,
+ ACTION_STRINGNAME: INPUT_MOVE_UP_STRINGNAME
+ },
+ INPUT_MOVE_DOWM_STRINGNAME: {
+ KEY_STRINGNAME: KEY_S,
+ ACTION_STRINGNAME: INPUT_MOVE_DOWM_STRINGNAME
+ },
+ INPUT_MOVE_LEFT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_A,
+ ACTION_STRINGNAME: INPUT_MOVE_LEFT_STRINGNAME
+ },
+ INPUT_MOVE_RIGHT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_D,
+ ACTION_STRINGNAME: INPUT_MOVE_RIGHT_STRINGNAME
+ },
+}
+
+
+func _ready() -> void:
+ for input in InputMovementDic:
+ var key_val = InputMovementDic[input].get(KEY_STRINGNAME)
+ var action_val = InputMovementDic[input].get(ACTION_STRINGNAME)
+
+ _camera = owner.get_node("%MainCamera3D")
+
+ var movement_input = InputEventKey.new()
+ movement_input.physical_keycode = key_val
+ InputMap.add_action(action_val)
+ InputMap.action_add_event(action_val, movement_input)
+
+ _player_visual.top_level = true
+
+
+func _physics_process(delta: float) -> void:
+ _physics_body_trans_last = _physics_body_trans_current
+ _physics_body_trans_current = global_transform
+
+ # Add the gravity.
+ if enable_gravity and not is_on_floor():
+ velocity.y -= gravity * delta
+
+ if not movement_enabled: return
+
+ # Get the input direction and handle the movement/deceleration.
+ # As good practice, you should replace UI actions with custom gameplay actions.
+ var input_dir: Vector2 = Input.get_vector(
+ INPUT_MOVE_LEFT_STRINGNAME,
+ INPUT_MOVE_RIGHT_STRINGNAME,
+ INPUT_MOVE_UP_STRINGNAME,
+ INPUT_MOVE_DOWM_STRINGNAME
+ )
+
+ var cam_dir: Vector3 = -_camera.global_transform.basis.z
+
+ var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
+ if direction:
+ var move_dir: Vector3 = Vector3.ZERO
+ move_dir.x = direction.x
+ move_dir.z = direction.z
+
+ move_dir = move_dir.rotated(Vector3.UP, _camera.rotation.y).normalized()
+ velocity.x = move_dir.x * SPEED
+ velocity.z = move_dir.z * SPEED
+ else:
+ velocity.x = move_toward(velocity.x, 0, SPEED)
+ velocity.z = move_toward(velocity.z, 0, SPEED)
+
+ move_and_slide()
+
+
+func _process(_delta: float) -> void:
+ _player_visual.global_transform = _physics_body_trans_last.interpolate_with(
+ _physics_body_trans_current,
+ Engine.get_physics_interpolation_fraction()
+ )
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller.gd.uid b/addons/phantom_camera/examples/scripts/3D/player_controller.gd.uid
new file mode 100644
index 0000000..c23173c
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller.gd.uid
@@ -0,0 +1 @@
+uid://dut3e76k2c71n
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd b/addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd
new file mode 100644
index 0000000..e907eca
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd
@@ -0,0 +1,84 @@
+extends CharacterBody3D
+
+@export var SPEED: float = 5.0
+@export var JUMP_VELOCITY: float = 4.5
+@export var enable_gravity = true
+
+@onready var _camera: Camera3D
+
+# Get the gravity from the project settings to be synced with RigidBody nodes.
+var gravity: float = 9.8
+
+var movement_enabled: bool = true
+
+const KEY_STRINGNAME: StringName = "Key"
+const ACTION_STRINGNAME: StringName = "Action"
+
+const INPUT_MOVE_UP_STRINGNAME: StringName = "move_up"
+const INPUT_MOVE_DOWM_STRINGNAME: StringName = "move_down"
+const INPUT_MOVE_LEFT_STRINGNAME: StringName = "move_left"
+const INPUT_MOVE_RIGHT_STRINGNAME: StringName = "move_right"
+
+var InputMovementDic: Dictionary = {
+ INPUT_MOVE_UP_STRINGNAME: {
+ KEY_STRINGNAME: KEY_W,
+ ACTION_STRINGNAME: INPUT_MOVE_UP_STRINGNAME
+ },
+ INPUT_MOVE_DOWM_STRINGNAME: {
+ KEY_STRINGNAME: KEY_S,
+ ACTION_STRINGNAME: INPUT_MOVE_DOWM_STRINGNAME
+ },
+ INPUT_MOVE_LEFT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_A,
+ ACTION_STRINGNAME: INPUT_MOVE_LEFT_STRINGNAME
+ },
+ INPUT_MOVE_RIGHT_STRINGNAME: {
+ KEY_STRINGNAME: KEY_D,
+ ACTION_STRINGNAME: INPUT_MOVE_RIGHT_STRINGNAME
+ },
+}
+
+
+func _ready() -> void:
+ for input in InputMovementDic:
+ var key_val = InputMovementDic[input].get(KEY_STRINGNAME)
+ var action_val = InputMovementDic[input].get(ACTION_STRINGNAME)
+
+ _camera = owner.get_node("%MainCamera3D")
+
+ var movement_input = InputEventKey.new()
+ movement_input.physical_keycode = key_val
+ InputMap.add_action(action_val)
+ InputMap.action_add_event(action_val, movement_input)
+
+
+func _physics_process(delta: float) -> void:
+ # Add the gravity.
+ if enable_gravity and not is_on_floor():
+ velocity.y -= gravity * delta
+
+ if not movement_enabled: return
+
+ # Get the input direction and handle the movement/deceleration.
+ # As good practice, you should replace UI actions with custom gameplay actions.
+ var input_dir: Vector2 = Input.get_vector(
+ INPUT_MOVE_LEFT_STRINGNAME,
+ INPUT_MOVE_RIGHT_STRINGNAME,
+ INPUT_MOVE_UP_STRINGNAME,
+ INPUT_MOVE_DOWM_STRINGNAME
+ )
+
+ var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
+ if direction:
+ var move_dir: Vector3 = Vector3.ZERO
+ move_dir.x = direction.x
+ move_dir.z = direction.z
+
+ move_dir = move_dir.rotated(Vector3.UP, _camera.rotation.y).normalized()
+ velocity.x = move_dir.x * SPEED
+ velocity.z = move_dir.z * SPEED
+ else:
+ velocity.x = move_toward(velocity.x, 0, SPEED)
+ velocity.z = move_toward(velocity.z, 0, SPEED)
+
+ move_and_slide()
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd.uid b/addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd.uid
new file mode 100644
index 0000000..182faff
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_4.4.gd.uid
@@ -0,0 +1 @@
+uid://tgv6xpi88sd0
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd
new file mode 100644
index 0000000..7e2da3f
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd
@@ -0,0 +1,54 @@
+extends "player_controller.gd"
+
+@onready var _player_pcam: PhantomCamera3D = %PlayerPhantomCamera3D
+
+@onready var _player_character: CharacterBody3D = %PlayerCharacterBody3D
+
+@export var mouse_sensitivity: float = 0.05
+
+@export var min_pitch: float = -89.9
+@export var max_pitch: float = 50
+
+@export var min_yaw: float = 0
+@export var max_yaw: float = 360
+
+@export var run_noise: PhantomCameraNoise3D
+
+func _ready() -> void:
+ super()
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+
+ if get_node_or_null("%PlayerPhantomCameraNoiseEmitter3D"):
+ %EmitterTip.visible = true
+
+
+func _physics_process(delta: float) -> void:
+ super(delta)
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if event is InputEventKey:
+ if get_node_or_null("%PlayerPhantomCameraNoiseEmitter3D"):
+ if event.keycode == KEY_Q and event.is_pressed():
+ %PlayerPhantomCameraNoiseEmitter3D.emit()
+
+ if event is InputEventMouseMotion:
+ var pcam_rotation_degrees: Vector3
+
+ # Assigns the current 3D rotation of the SpringArm3D node - so it starts off where it is in the editor
+ pcam_rotation_degrees = _player_pcam.rotation_degrees
+
+ # Change the X rotation
+ pcam_rotation_degrees.x -= event.relative.y * mouse_sensitivity
+
+ # Clamp the rotation in the X axis so it go over or under the target
+ pcam_rotation_degrees.x = clampf(pcam_rotation_degrees.x, min_pitch, max_pitch)
+
+ # Change the Y rotation value
+ pcam_rotation_degrees.y -= event.relative.x * mouse_sensitivity
+
+ # Sets the rotation to fully loop around its target, but witout going below or exceeding 0 and 360 degrees respectively
+ pcam_rotation_degrees.y = wrapf(pcam_rotation_degrees.y, min_yaw, max_yaw)
+
+ # Change the SpringArm3D node's rotation and rotate around its target
+ _player_pcam.rotation_degrees = pcam_rotation_degrees
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd.uid b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd.uid
new file mode 100644
index 0000000..c31115d
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person.gd.uid
@@ -0,0 +1 @@
+uid://b3n22atuw76sm
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd
new file mode 100644
index 0000000..33e4601
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd
@@ -0,0 +1,54 @@
+extends "player_controller_4.4.gd"
+
+@onready var _player_pcam: PhantomCamera3D = %PlayerPhantomCamera3D
+
+@onready var _player_character: CharacterBody3D = %PlayerCharacterBody3D
+
+@export var mouse_sensitivity: float = 0.05
+
+@export var min_pitch: float = -89.9
+@export var max_pitch: float = 50
+
+@export var min_yaw: float = 0
+@export var max_yaw: float = 360
+
+@export var run_noise: PhantomCameraNoise3D
+
+func _ready() -> void:
+ super()
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+
+ if get_node_or_null("%PlayerPhantomCameraNoiseEmitter3D"):
+ %EmitterTip.visible = true
+
+
+func _physics_process(delta: float) -> void:
+ super(delta)
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if event is InputEventKey:
+ if get_node_or_null("%PlayerPhantomCameraNoiseEmitter3D"):
+ if event.keycode == KEY_Q and event.is_pressed():
+ %PlayerPhantomCameraNoiseEmitter3D.emit()
+
+ if event is InputEventMouseMotion:
+ var pcam_rotation_degrees: Vector3
+
+ # Assigns the current 3D rotation of the SpringArm3D node - so it starts off where it is in the editor
+ pcam_rotation_degrees = _player_pcam.rotation_degrees
+
+ # Change the X rotation
+ pcam_rotation_degrees.x -= event.relative.y * mouse_sensitivity
+
+ # Clamp the rotation in the X axis so it go over or under the target
+ pcam_rotation_degrees.x = clampf(pcam_rotation_degrees.x, min_pitch, max_pitch)
+
+ # Change the Y rotation value
+ pcam_rotation_degrees.y -= event.relative.x * mouse_sensitivity
+
+ # Sets the rotation to fully loop around its target, but witout going below or exceeding 0 and 360 degrees respectively
+ pcam_rotation_degrees.y = wrapf(pcam_rotation_degrees.y, min_yaw, max_yaw)
+
+ # Change the SpringArm3D node's rotation and rotate around its target
+ _player_pcam.rotation_degrees = pcam_rotation_degrees
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd.uid b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd.uid
new file mode 100644
index 0000000..ee16763
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_first_person_4.4.gd.uid
@@ -0,0 +1 @@
+uid://x5g7kf5k2mac
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd
new file mode 100644
index 0000000..f5d5579
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd
@@ -0,0 +1,87 @@
+extends "player_controller.gd"
+
+@onready var _player_pcam: PhantomCamera3D
+@onready var _aim_pcam: PhantomCamera3D
+@onready var _player_direction: Node3D = %PlayerDirection
+@onready var _ceiling_pcam: PhantomCamera3D
+
+@export var mouse_sensitivity: float = 0.05
+
+@export var min_pitch: float = -89.9
+@export var max_pitch: float = 50
+
+@export var min_yaw: float = 0
+@export var max_yaw: float = 360
+
+
+
+func _ready() -> void:
+ super()
+
+ _player_pcam = owner.get_node("%PlayerPhantomCamera3D")
+ _aim_pcam = owner.get_node("%PlayerAimPhantomCamera3D")
+ _ceiling_pcam = owner.get_node("%CeilingPhantomCamera3D")
+
+ if _player_pcam.get_follow_mode() == _player_pcam.FollowMode.THIRD_PERSON:
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+
+
+func _physics_process(delta: float) -> void:
+ super(delta)
+
+ if velocity.length() > 0.2:
+ var look_direction: Vector2 = Vector2(velocity.z, velocity.x)
+ _player_direction.rotation.y = look_direction.angle()
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if _player_pcam.get_follow_mode() == _player_pcam.FollowMode.THIRD_PERSON:
+ var active_pcam: PhantomCamera3D
+
+ _set_pcam_rotation(_player_pcam, event)
+ _set_pcam_rotation(_aim_pcam, event)
+ if _player_pcam.get_priority() > _aim_pcam.get_priority():
+ _toggle_aim_pcam(event)
+ else:
+ _toggle_aim_pcam(event)
+
+ if event is InputEventKey and event.pressed:
+ if event.keycode == KEY_SPACE:
+ if _ceiling_pcam.get_priority() < 30 and _player_pcam.is_active():
+ _ceiling_pcam.set_priority(30)
+ else:
+ _ceiling_pcam.set_priority(1)
+
+
+func _set_pcam_rotation(pcam: PhantomCamera3D, event: InputEvent) -> void:
+ if event is InputEventMouseMotion:
+ var pcam_rotation_degrees: Vector3
+
+ # Assigns the current 3D rotation of the SpringArm3D node - so it starts off where it is in the editor
+ pcam_rotation_degrees = pcam.get_third_person_rotation_degrees()
+
+ # Change the X rotation
+ pcam_rotation_degrees.x -= event.relative.y * mouse_sensitivity
+
+ # Clamp the rotation in the X axis so it go over or under the target
+ pcam_rotation_degrees.x = clampf(pcam_rotation_degrees.x, min_pitch, max_pitch)
+
+ # Change the Y rotation value
+ pcam_rotation_degrees.y -= event.relative.x * mouse_sensitivity
+
+ # Sets the rotation to fully loop around its target, but witout going below or exceeding 0 and 360 degrees respectively
+ pcam_rotation_degrees.y = wrapf(pcam_rotation_degrees.y, min_yaw, max_yaw)
+
+ # Change the SpringArm3D node's rotation and rotate around its target
+ pcam.set_third_person_rotation_degrees(pcam_rotation_degrees)
+
+
+func _toggle_aim_pcam(event: InputEvent) -> void:
+ if event is InputEventMouseButton \
+ and event.is_pressed() \
+ and event.button_index == 2 \
+ and (_player_pcam.is_active() or _aim_pcam.is_active()):
+ if _player_pcam.get_priority() > _aim_pcam.get_priority():
+ _aim_pcam.set_priority(30)
+ else:
+ _aim_pcam.set_priority(0)
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd.uid b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd.uid
new file mode 100644
index 0000000..2218a6a
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person.gd.uid
@@ -0,0 +1 @@
+uid://34uhyq3cpi67
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd
new file mode 100644
index 0000000..d0d56d4
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd
@@ -0,0 +1,86 @@
+extends "player_controller_4.4.gd"
+
+@onready var _player_pcam: PhantomCamera3D
+@onready var _aim_pcam: PhantomCamera3D
+@onready var _player_direction: Node3D = %PlayerDirection
+@onready var _ceiling_pcam: PhantomCamera3D
+
+@export var mouse_sensitivity: float = 0.05
+
+@export var min_pitch: float = -89.9
+@export var max_pitch: float = 50
+
+@export var min_yaw: float = 0
+@export var max_yaw: float = 360
+
+
+func _ready() -> void:
+ super()
+
+ _player_pcam = owner.get_node("%PlayerPhantomCamera3D")
+ _aim_pcam = owner.get_node("%PlayerAimPhantomCamera3D")
+ _ceiling_pcam = owner.get_node("%CeilingPhantomCamera3D")
+
+ if _player_pcam.get_follow_mode() == _player_pcam.FollowMode.THIRD_PERSON:
+ Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
+
+
+func _physics_process(delta: float) -> void:
+ super(delta)
+
+ if velocity.length() > 0.2:
+ var look_direction: Vector2 = Vector2(velocity.z, velocity.x)
+ _player_direction.rotation.y = look_direction.angle()
+
+
+func _unhandled_input(event: InputEvent) -> void:
+ if _player_pcam.get_follow_mode() == _player_pcam.FollowMode.THIRD_PERSON:
+ var active_pcam: PhantomCamera3D
+
+ _set_pcam_rotation(_player_pcam, event)
+ _set_pcam_rotation(_aim_pcam, event)
+ if _player_pcam.get_priority() > _aim_pcam.get_priority():
+ _toggle_aim_pcam(event)
+ else:
+ _toggle_aim_pcam(event)
+
+ if event is InputEventKey and event.pressed:
+ if event.keycode == KEY_SPACE:
+ if _ceiling_pcam.get_priority() < 30 and _player_pcam.is_active():
+ _ceiling_pcam.set_priority(30)
+ else:
+ _ceiling_pcam.set_priority(1)
+
+
+func _set_pcam_rotation(pcam: PhantomCamera3D, event: InputEvent) -> void:
+ if event is InputEventMouseMotion:
+ var pcam_rotation_degrees: Vector3
+
+ # Assigns the current 3D rotation of the SpringArm3D node - so it starts off where it is in the editor
+ pcam_rotation_degrees = pcam.get_third_person_rotation_degrees()
+
+ # Change the X rotation
+ pcam_rotation_degrees.x -= event.relative.y * mouse_sensitivity
+
+ # Clamp the rotation in the X axis so it go over or under the target
+ pcam_rotation_degrees.x = clampf(pcam_rotation_degrees.x, min_pitch, max_pitch)
+
+ # Change the Y rotation value
+ pcam_rotation_degrees.y -= event.relative.x * mouse_sensitivity
+
+ # Sets the rotation to fully loop around its target, but witout going below or exceeding 0 and 360 degrees respectively
+ pcam_rotation_degrees.y = wrapf(pcam_rotation_degrees.y, min_yaw, max_yaw)
+
+ # Change the SpringArm3D node's rotation and rotate around its target
+ pcam.set_third_person_rotation_degrees(pcam_rotation_degrees)
+
+
+func _toggle_aim_pcam(event: InputEvent) -> void:
+ if event is InputEventMouseButton \
+ and event.is_pressed() \
+ and event.button_index == 2 \
+ and (_player_pcam.is_active() or _aim_pcam.is_active()):
+ if _player_pcam.get_priority() > _aim_pcam.get_priority():
+ _aim_pcam.set_priority(30)
+ else:
+ _aim_pcam.set_priority(0)
diff --git a/addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd.uid b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd.uid
new file mode 100644
index 0000000..6346cec
--- /dev/null
+++ b/addons/phantom_camera/examples/scripts/3D/player_controller_third_person_4.4.gd.uid
@@ -0,0 +1 @@
+uid://bkr71vxe2t18n
diff --git a/addons/phantom_camera/examples/textures/2D/inventory_container.png b/addons/phantom_camera/examples/textures/2D/inventory_container.png
new file mode 100644
index 0000000..7dd5178
Binary files /dev/null and b/addons/phantom_camera/examples/textures/2D/inventory_container.png differ
diff --git a/addons/phantom_camera/examples/textures/2D/inventory_container.png.import b/addons/phantom_camera/examples/textures/2D/inventory_container.png.import
new file mode 100644
index 0000000..72be1e5
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/2D/inventory_container.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7cs6me43ufh3"
+path="res://.godot/imported/inventory_container.png-12241277f279bfc4bf7d5dad6b3e8ff2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/examples/textures/2D/inventory_container.png"
+dest_files=["res://.godot/imported/inventory_container.png-12241277f279bfc4bf7d5dad6b3e8ff2.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
diff --git a/addons/phantom_camera/examples/textures/2D/level_spritesheet.png b/addons/phantom_camera/examples/textures/2D/level_spritesheet.png
new file mode 100644
index 0000000..939cb28
Binary files /dev/null and b/addons/phantom_camera/examples/textures/2D/level_spritesheet.png differ
diff --git a/addons/phantom_camera/examples/textures/2D/level_spritesheet.png.import b/addons/phantom_camera/examples/textures/2D/level_spritesheet.png.import
new file mode 100644
index 0000000..520372a
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/2D/level_spritesheet.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c77npili4pel4"
+path="res://.godot/imported/level_spritesheet.png-26a44dd21a040a5480d5ccba54377d99.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/examples/textures/2D/level_spritesheet.png"
+dest_files=["res://.godot/imported/level_spritesheet.png-26a44dd21a040a5480d5ccba54377d99.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
diff --git a/addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png b/addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png
new file mode 100644
index 0000000..2012b3a
Binary files /dev/null and b/addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png differ
diff --git a/addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png.import b/addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png.import
new file mode 100644
index 0000000..425002e
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cwep0on2tthn7"
+path="res://.godot/imported/phantom_camera_2d_sprite.png-deab230b83ae03aeb308a08ff66b8dbc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/examples/textures/2D/phantom_camera_2d_sprite.png"
+dest_files=["res://.godot/imported/phantom_camera_2d_sprite.png-deab230b83ae03aeb308a08ff66b8dbc.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
diff --git a/addons/phantom_camera/examples/textures/2D/player_sprite.svg b/addons/phantom_camera/examples/textures/2D/player_sprite.svg
new file mode 100644
index 0000000..ce261b1
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/2D/player_sprite.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/phantom_camera/examples/textures/2D/player_sprite.svg.import b/addons/phantom_camera/examples/textures/2D/player_sprite.svg.import
new file mode 100644
index 0000000..84c750b
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/2D/player_sprite.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cscjjt55iw2cu"
+path="res://.godot/imported/player_sprite.svg-8862ecb19e12152eb892607676f3831f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/examples/textures/2D/player_sprite.svg"
+dest_files=["res://.godot/imported/player_sprite.svg-8862ecb19e12152eb892607676f3831f.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=8.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/phantom_camera/examples/textures/2D/sign_prompt.png b/addons/phantom_camera/examples/textures/2D/sign_prompt.png
new file mode 100644
index 0000000..a730837
Binary files /dev/null and b/addons/phantom_camera/examples/textures/2D/sign_prompt.png differ
diff --git a/assets/sprites/objects/chest_01.png.import b/addons/phantom_camera/examples/textures/2D/sign_prompt.png.import
similarity index 65%
rename from assets/sprites/objects/chest_01.png.import
rename to addons/phantom_camera/examples/textures/2D/sign_prompt.png.import
index 3065d16..a5799d3 100644
--- a/assets/sprites/objects/chest_01.png.import
+++ b/addons/phantom_camera/examples/textures/2D/sign_prompt.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://cqe5b8whdo7qi"
-path="res://.godot/imported/chest_01.png-e50b4b546c81afcaf19ee08cbf0a5e97.ctex"
+uid="uid://bloouh2jtndx1"
+path="res://.godot/imported/sign_prompt.png-18d451127e1cd1a16367acd23cec47e5.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/objects/chest_01.png"
-dest_files=["res://.godot/imported/chest_01.png-e50b4b546c81afcaf19ee08cbf0a5e97.ctex"]
+source_file="res://addons/phantom_camera/examples/textures/2D/sign_prompt.png"
+dest_files=["res://.godot/imported/sign_prompt.png-18d451127e1cd1a16367acd23cec47e5.ctex"]
[params]
diff --git a/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png b/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png
new file mode 100644
index 0000000..4aeae6c
Binary files /dev/null and b/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png differ
diff --git a/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import b/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import
new file mode 100644
index 0000000..e80c132
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bj7h2fc5jx4ax"
+path.s3tc="res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/examples/textures/3D/checker_pattern_dark.png"
+dest_files=["res://.godot/imported/checker_pattern_dark.png-70cedad2d3abf4ad6166d6614eefa7fb.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+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=0
diff --git a/addons/phantom_camera/examples/textures/3D/target.png b/addons/phantom_camera/examples/textures/3D/target.png
new file mode 100644
index 0000000..96fed65
Binary files /dev/null and b/addons/phantom_camera/examples/textures/3D/target.png differ
diff --git a/addons/phantom_camera/examples/textures/3D/target.png.import b/addons/phantom_camera/examples/textures/3D/target.png.import
new file mode 100644
index 0000000..6de0861
--- /dev/null
+++ b/addons/phantom_camera/examples/textures/3D/target.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c3mskbmvnpwux"
+path.s3tc="res://.godot/imported/target.png-878c5e8d057c8a9a4c2322d4ab88e9ef.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/examples/textures/3D/target.png"
+dest_files=["res://.godot/imported/target.png-878c5e8d057c8a9a4c2322d4ab88e9ef.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+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=0
diff --git a/addons/phantom_camera/examples/ui/ui_inventory.tscn b/addons/phantom_camera/examples/ui/ui_inventory.tscn
new file mode 100644
index 0000000..cddd377
--- /dev/null
+++ b/addons/phantom_camera/examples/ui/ui_inventory.tscn
@@ -0,0 +1,37 @@
+[gd_scene load_steps=3 format=3 uid="uid://dg7rhrymsrrrm"]
+
+[ext_resource type="Texture2D" uid="uid://b7cs6me43ufh3" path="res://addons/phantom_camera/examples/textures/2D/inventory_container.png" id="1_pi2dp"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="2_0rdcn"]
+
+[node name="Control" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="TextureRect" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 4
+anchor_top = 0.5
+anchor_bottom = 0.5
+offset_left = 28.0
+offset_top = -255.0
+offset_right = 908.0
+offset_bottom = 183.0
+grow_vertical = 2
+texture = ExtResource("1_pi2dp")
+
+[node name="Label" type="Label" parent="TextureRect"]
+layout_mode = 0
+offset_left = 345.0
+offset_top = 12.0
+offset_right = 535.0
+offset_bottom = 60.0
+theme_override_colors/font_color = Color(0.356863, 0.105882, 0.133333, 1)
+theme_override_fonts/font = ExtResource("2_0rdcn")
+theme_override_font_sizes/font_size = 32
+text = "Inventory"
+horizontal_alignment = 1
+uppercase = true
diff --git a/addons/phantom_camera/examples/ui/ui_sign.tscn b/addons/phantom_camera/examples/ui/ui_sign.tscn
new file mode 100644
index 0000000..1b891ef
--- /dev/null
+++ b/addons/phantom_camera/examples/ui/ui_sign.tscn
@@ -0,0 +1,83 @@
+[gd_scene load_steps=4 format=3 uid="uid://iq5xd1ob1res"]
+
+[ext_resource type="Texture2D" uid="uid://bloouh2jtndx1" path="res://addons/phantom_camera/examples/textures/2D/sign_prompt.png" id="1_tftrk"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="2_y5454"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_r4h3u"]
+bg_color = Color(0.470588, 0.6, 0.45098, 1)
+corner_radius_top_right = 47
+corner_radius_bottom_left = 40
+
+[node name="Control" type="Control"]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+
+[node name="TextureRect" type="TextureRect" parent="."]
+layout_mode = 1
+anchors_preset = 5
+anchor_left = 0.5
+anchor_right = 0.5
+offset_left = -273.568
+offset_top = 47.0
+offset_right = 273.568
+offset_bottom = 413.0
+grow_horizontal = 2
+texture = ExtResource("1_tftrk")
+metadata/_edit_group_ = true
+
+[node name="Label" type="Label" parent="TextureRect"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = 25.0
+offset_top = 64.0
+offset_right = -25.0
+offset_bottom = -88.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_color = Color(0.207843, 0.0470588, 0.0666667, 1)
+theme_override_fonts/font = ExtResource("2_y5454")
+theme_override_font_sizes/font_size = 62
+text = "Stay Awhile
+and read"
+horizontal_alignment = 1
+vertical_alignment = 1
+uppercase = true
+
+[node name="Panel" type="Panel" parent="."]
+visible = false
+layout_mode = 1
+anchors_preset = 5
+anchor_left = 0.5
+anchor_right = 0.5
+offset_left = -240.0
+offset_right = 240.0
+offset_bottom = 200.0
+grow_horizontal = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_r4h3u")
+metadata/_edit_use_anchors_ = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+alignment = 1
+
+[node name="Label2" type="Label" parent="Panel/VBoxContainer"]
+layout_mode = 2
+text = "Example Textsdadassa
+"
+horizontal_alignment = 1
+vertical_alignment = 1
diff --git a/addons/phantom_camera/fonts/Nunito-Black.ttf b/addons/phantom_camera/fonts/Nunito-Black.ttf
new file mode 100644
index 0000000..1081731
Binary files /dev/null and b/addons/phantom_camera/fonts/Nunito-Black.ttf differ
diff --git a/addons/phantom_camera/fonts/Nunito-Black.ttf.import b/addons/phantom_camera/fonts/Nunito-Black.ttf.import
new file mode 100644
index 0000000..8e68c5f
--- /dev/null
+++ b/addons/phantom_camera/fonts/Nunito-Black.ttf.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://c4mm3of2mc8o5"
+path="res://.godot/imported/Nunito-Black.ttf-2a374efbc207a97a99b8c70bdc4b7cbb.fontdata"
+
+[deps]
+
+source_file="res://addons/phantom_camera/fonts/Nunito-Black.ttf"
+dest_files=["res://.godot/imported/Nunito-Black.ttf-2a374efbc207a97a99b8c70bdc4b7cbb.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+keep_rounding_remainders=true
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/phantom_camera/fonts/Nunito-Regular.ttf b/addons/phantom_camera/fonts/Nunito-Regular.ttf
new file mode 100644
index 0000000..dfd0fcb
Binary files /dev/null and b/addons/phantom_camera/fonts/Nunito-Regular.ttf differ
diff --git a/addons/phantom_camera/fonts/Nunito-Regular.ttf.import b/addons/phantom_camera/fonts/Nunito-Regular.ttf.import
new file mode 100644
index 0000000..11c0c62
--- /dev/null
+++ b/addons/phantom_camera/fonts/Nunito-Regular.ttf.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="font_data_dynamic"
+type="FontFile"
+uid="uid://dve7mgsjik4dg"
+path="res://.godot/imported/Nunito-Regular.ttf-b6054d499efa1a10921004862b1e217a.fontdata"
+
+[deps]
+
+source_file="res://addons/phantom_camera/fonts/Nunito-Regular.ttf"
+dest_files=["res://.godot/imported/Nunito-Regular.ttf-b6054d499efa1a10921004862b1e217a.fontdata"]
+
+[params]
+
+Rendering=null
+antialiasing=1
+generate_mipmaps=false
+disable_embedded_bitmaps=true
+multichannel_signed_distance_field=false
+msdf_pixel_range=8
+msdf_size=48
+allow_system_fallback=true
+force_autohinter=false
+hinting=1
+subpixel_positioning=1
+keep_rounding_remainders=true
+oversampling=0.0
+Fallbacks=null
+fallbacks=[]
+Compress=null
+compress=true
+preload=[]
+language_support={}
+script_support={}
+opentype_features={}
diff --git a/addons/phantom_camera/icons/misc/PriorityOverride.svg b/addons/phantom_camera/icons/misc/PriorityOverride.svg
new file mode 100644
index 0000000..de7fd01
--- /dev/null
+++ b/addons/phantom_camera/icons/misc/PriorityOverride.svg
@@ -0,0 +1,14 @@
+
diff --git a/addons/phantom_camera/icons/misc/PriorityOverride.svg.import b/addons/phantom_camera/icons/misc/PriorityOverride.svg.import
new file mode 100644
index 0000000..d78acf5
--- /dev/null
+++ b/addons/phantom_camera/icons/misc/PriorityOverride.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dy8eifa6aw2en"
+path="res://.godot/imported/PriorityOverride.svg-e76e07f4bbd98169f119e17fe5f2f03f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/misc/PriorityOverride.svg"
+dest_files=["res://.godot/imported/PriorityOverride.svg-e76e07f4bbd98169f119e17fe5f2f03f.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/phantom_camera/icons/phantom_camera_2d.svg b/addons/phantom_camera/icons/phantom_camera_2d.svg
new file mode 100644
index 0000000..0c67805
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_2d.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/phantom_camera/icons/phantom_camera_2d.svg.import b/addons/phantom_camera/icons/phantom_camera_2d.svg.import
new file mode 100644
index 0000000..724069f
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_2d.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dchkkx4v3ikpw"
+path="res://.godot/imported/phantom_camera_2d.svg-e5483cbc858fc5f95f7210b1649dff0d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_2d.svg"
+dest_files=["res://.godot/imported/phantom_camera_2d.svg-e5483cbc858fc5f95f7210b1649dff0d.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/phantom_camera/icons/phantom_camera_3d.svg b/addons/phantom_camera/icons/phantom_camera_3d.svg
new file mode 100644
index 0000000..db18730
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_3d.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/phantom_camera/icons/phantom_camera_3d.svg.import b/addons/phantom_camera/icons/phantom_camera_3d.svg.import
new file mode 100644
index 0000000..eedbd2f
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_3d.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c71drpb8o4prn"
+path="res://.godot/imported/phantom_camera_3d.svg-41ed612e834470377fb56eebffa083fe.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_3d.svg"
+dest_files=["res://.godot/imported/phantom_camera_3d.svg-41ed612e834470377fb56eebffa083fe.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/phantom_camera/icons/phantom_camera_camera_3d_resource.svg b/addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg
new file mode 100644
index 0000000..282adf2
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg.import b/addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg.import
new file mode 100644
index 0000000..4b0e8bc
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dnaykbu6ue5lo"
+path="res://.godot/imported/phantom_camera_camera_3d_resource.svg-f8bf8d1a5b7442fd6933bfbed999d57d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg"
+dest_files=["res://.godot/imported/phantom_camera_camera_3d_resource.svg-f8bf8d1a5b7442fd6933bfbed999d57d.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/phantom_camera/icons/phantom_camera_gizmo.svg b/addons/phantom_camera/icons/phantom_camera_gizmo.svg
new file mode 100644
index 0000000..d791ce0
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_gizmo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import b/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import
new file mode 100644
index 0000000..7b49608
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_gizmo.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://e36npe2rbxyg"
+path.s3tc="res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_gizmo.svg"
+dest_files=["res://.godot/imported/phantom_camera_gizmo.svg-ba1aacb9b1c5f4ef401d3bd3697a542b.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+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=0
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/phantom_camera/icons/phantom_camera_glow_logo.png b/addons/phantom_camera/icons/phantom_camera_glow_logo.png
new file mode 100644
index 0000000..41ad8de
Binary files /dev/null and b/addons/phantom_camera/icons/phantom_camera_glow_logo.png differ
diff --git a/addons/phantom_camera/icons/phantom_camera_glow_logo.png.import b/addons/phantom_camera/icons/phantom_camera_glow_logo.png.import
new file mode 100644
index 0000000..d68be86
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_glow_logo.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cjli3p2b8mfyh"
+path="res://.godot/imported/phantom_camera_glow_logo.png-078f944973b55b32029ba02980211fe0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_glow_logo.png"
+dest_files=["res://.godot/imported/phantom_camera_glow_logo.png-078f944973b55b32029ba02980211fe0.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
diff --git a/addons/phantom_camera/icons/phantom_camera_host.svg b/addons/phantom_camera/icons/phantom_camera_host.svg
new file mode 100644
index 0000000..4ff3ada
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_host.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/phantom_camera/icons/phantom_camera_host.svg.import b/addons/phantom_camera/icons/phantom_camera_host.svg.import
new file mode 100644
index 0000000..430d67d
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_host.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://5fatldiu7dd5"
+path="res://.godot/imported/phantom_camera_host.svg-3150f8f2d82ca9ecab9a3a415da21c5b.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_host.svg"
+dest_files=["res://.godot/imported/phantom_camera_host.svg-3150f8f2d82ca9ecab9a3a415da21c5b.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=true
diff --git a/addons/phantom_camera/icons/phantom_camera_logo.png b/addons/phantom_camera/icons/phantom_camera_logo.png
new file mode 100644
index 0000000..bc43e56
Binary files /dev/null and b/addons/phantom_camera/icons/phantom_camera_logo.png differ
diff --git a/assets/sprites/characters/player.png.import b/addons/phantom_camera/icons/phantom_camera_logo.png.import
similarity index 64%
rename from assets/sprites/characters/player.png.import
rename to addons/phantom_camera/icons/phantom_camera_logo.png.import
index 1947c9d..6f51cf4 100644
--- a/assets/sprites/characters/player.png.import
+++ b/addons/phantom_camera/icons/phantom_camera_logo.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://c6f2oguxl2r4u"
-path="res://.godot/imported/player.png-4508374c418cbfe7b79c5beffcb67188.ctex"
+uid="uid://cc0wmici0eic8"
+path="res://.godot/imported/phantom_camera_logo.png-8b8d347b5e4800c86cd8095d030a3e5a.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/characters/player.png"
-dest_files=["res://.godot/imported/player.png-4508374c418cbfe7b79c5beffcb67188.ctex"]
+source_file="res://addons/phantom_camera/icons/phantom_camera_logo.png"
+dest_files=["res://.godot/imported/phantom_camera_logo.png-8b8d347b5e4800c86cd8095d030a3e5a.ctex"]
[params]
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg b/addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg
new file mode 100644
index 0000000..f6fbad3
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg.import b/addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg.import
new file mode 100644
index 0000000..c24e060
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b2r7mhd780y8d"
+path="res://.godot/imported/phantom_camera_noise_emitter_2d.svg-1b3d37fe36964dc86a6ea6681d0772bb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg"
+dest_files=["res://.godot/imported/phantom_camera_noise_emitter_2d.svg-1b3d37fe36964dc86a6ea6681d0772bb.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/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg b/addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg
new file mode 100644
index 0000000..c567f6a
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg.import b/addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg.import
new file mode 100644
index 0000000..de23002
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cby76y7m6xn4f"
+path.s3tc="res://.godot/imported/phantom_camera_noise_emitter_3d.svg-9b90fe54aa618f65d52ac94515d41ea4.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg"
+dest_files=["res://.godot/imported/phantom_camera_noise_emitter_3d.svg-9b90fe54aa618f65d52ac94515d41ea4.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+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=0
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg b/addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg
new file mode 100644
index 0000000..9b9bcb2
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg.import b/addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg.import
new file mode 100644
index 0000000..4dd736b
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dw4iy855s0atm"
+path.s3tc="res://.godot/imported/phantom_camera_noise_emitter_gizmo.svg-9a593802655a8d5038c7f55deab3882d.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg"
+dest_files=["res://.godot/imported/phantom_camera_noise_emitter_gizmo.svg-9a593802655a8d5038c7f55deab3882d.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+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=0
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_resource.svg b/addons/phantom_camera/icons/phantom_camera_noise_resource.svg
new file mode 100644
index 0000000..d3c6deb
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_resource.svg
@@ -0,0 +1,5 @@
+
diff --git a/addons/phantom_camera/icons/phantom_camera_noise_resource.svg.import b/addons/phantom_camera/icons/phantom_camera_noise_resource.svg.import
new file mode 100644
index 0000000..8cefc36
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_noise_resource.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://fudwitkewe70"
+path="res://.godot/imported/phantom_camera_noise_resource.svg-a81ed223714edd2c0d9cfa00be0c3f58.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_noise_resource.svg"
+dest_files=["res://.godot/imported/phantom_camera_noise_resource.svg-a81ed223714edd2c0d9cfa00be0c3f58.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/phantom_camera/icons/phantom_camera_tween.svg b/addons/phantom_camera/icons/phantom_camera_tween.svg
new file mode 100644
index 0000000..6956fb5
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_tween.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/phantom_camera/icons/phantom_camera_tween.svg.import b/addons/phantom_camera/icons/phantom_camera_tween.svg.import
new file mode 100644
index 0000000..3db67fb
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_tween.svg.import
@@ -0,0 +1,38 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dphl04mdf3220"
+path="res://.godot/imported/phantom_camera_tween.svg-16faced08ef4a5f3458264d894230dbd.ctex"
+metadata={
+"has_editor_variant": true,
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_tween.svg"
+dest_files=["res://.godot/imported/phantom_camera_tween.svg-16faced08ef4a5f3458264d894230dbd.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=true
diff --git a/addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg b/addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg
new file mode 100644
index 0000000..6d3bcd4
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg.import b/addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg.import
new file mode 100644
index 0000000..c182784
--- /dev/null
+++ b/addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d4j4hrb7yusyq"
+path="res://.godot/imported/phantom_camera_updater_panel_icon.svg-19823e6cbee8115f8b2554d0ee6e79db.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg"
+dest_files=["res://.godot/imported/phantom_camera_updater_panel_icon.svg-19823e6cbee8115f8b2554d0ee6e79db.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/phantom_camera/icons/viewfinder/Camera2DIcon.svg b/addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg
new file mode 100644
index 0000000..59efad4
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg.import b/addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg.import
new file mode 100644
index 0000000..e8fb35d
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ccnsrg8hq74p2"
+path="res://.godot/imported/Camera2DIcon.svg-300e6f57281180711c5ecf391104d4ba.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg"
+dest_files=["res://.godot/imported/Camera2DIcon.svg-300e6f57281180711c5ecf391104d4ba.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/phantom_camera/icons/viewfinder/Camera3DIcon.svg b/addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg
new file mode 100644
index 0000000..2366c3f
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg.import b/addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg.import
new file mode 100644
index 0000000..79708e2
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dkiefpjsrj37n"
+path="res://.godot/imported/Camera3DIcon.svg-4805c46004db1c89cc9443dd740693f5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg"
+dest_files=["res://.godot/imported/Camera3DIcon.svg-4805c46004db1c89cc9443dd740693f5.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/phantom_camera/icons/viewfinder/SceneTypesIcon.svg b/addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg
new file mode 100644
index 0000000..87e3f79
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg.import b/addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg.import
new file mode 100644
index 0000000..364c4ed
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dk7omm0x44suj"
+path="res://.godot/imported/SceneTypesIcon.svg-66e2255bd3398007bec03a5cbfa4d0aa.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg"
+dest_files=["res://.godot/imported/SceneTypesIcon.svg-66e2255bd3398007bec03a5cbfa4d0aa.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/phantom_camera/icons/viewfinder/Select.svg b/addons/phantom_camera/icons/viewfinder/Select.svg
new file mode 100644
index 0000000..34b109b
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/Select.svg
@@ -0,0 +1,3 @@
+
diff --git a/addons/phantom_camera/icons/viewfinder/Select.svg.import b/addons/phantom_camera/icons/viewfinder/Select.svg.import
new file mode 100644
index 0000000..81b41c9
--- /dev/null
+++ b/addons/phantom_camera/icons/viewfinder/Select.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://rghrkoqrm2ig"
+path="res://.godot/imported/Select.svg-cdf90b8b400d3b91a023b70c6a823894.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/viewfinder/Select.svg"
+dest_files=["res://.godot/imported/Select.svg-cdf90b8b400d3b91a023b70c6a823894.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=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/phantom_camera/icons/warning.svg b/addons/phantom_camera/icons/warning.svg
new file mode 100644
index 0000000..63dbedf
--- /dev/null
+++ b/addons/phantom_camera/icons/warning.svg
@@ -0,0 +1,4 @@
+
diff --git a/addons/phantom_camera/icons/warning.svg.import b/addons/phantom_camera/icons/warning.svg.import
new file mode 100644
index 0000000..2895b37
--- /dev/null
+++ b/addons/phantom_camera/icons/warning.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cjlv0bg7byjx0"
+path="res://.godot/imported/warning.svg-c1b21c265e0842bbdc9ed10735995366.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/phantom_camera/icons/warning.svg"
+dest_files=["res://.godot/imported/warning.svg-c1b21c265e0842bbdc9ed10735995366.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=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/phantom_camera/inspector/phantom_camera_inspector_plugin.gd b/addons/phantom_camera/inspector/phantom_camera_inspector_plugin.gd
new file mode 100644
index 0000000..d5a0d22
--- /dev/null
+++ b/addons/phantom_camera/inspector/phantom_camera_inspector_plugin.gd
@@ -0,0 +1,46 @@
+@tool
+extends EditorInspectorPlugin
+
+#var _phantom_camera_script: Script = preload("res://addons/phantom_camera/scripts/phantom_camera.gd")
+
+
+# TODO - Enable again once work is resumed for inspector based tasks
+
+#func _can_handle(object) -> bool:
+# return object is _phantom_camera_script
+
+
+func _parse_category(object: Object, category: String) -> void:
+
+ var _margin_container: MarginContainer = MarginContainer.new()
+ var _margin_v: float = 20
+ _margin_container.add_theme_constant_override("margin_left", 10)
+ _margin_container.add_theme_constant_override("margin_top", _margin_v)
+ _margin_container.add_theme_constant_override("margin_right", 10)
+ _margin_container.add_theme_constant_override("margin_bottom", _margin_v)
+ add_custom_control(_margin_container)
+
+ var _vbox_container: VBoxContainer = VBoxContainer.new()
+ _margin_container.add_child(_vbox_container)
+
+ var align_with_view_button = Button.new()
+ align_with_view_button.connect("pressed", _align_camera_with_view.bind(object))
+ align_with_view_button.set_custom_minimum_size(Vector2(0, 60))
+ align_with_view_button.set_text("Align with view")
+ _vbox_container.add_child(align_with_view_button)
+
+ var preview_camera_button = Button.new()
+ preview_camera_button.connect("pressed", _preview_camera.bind(object))
+ preview_camera_button.set_custom_minimum_size(Vector2(0, 60))
+ preview_camera_button.set_text("Preview Camera")
+ _vbox_container.add_child(preview_camera_button)
+
+
+
+func _align_camera_with_view(object: Object) -> void:
+ print("Aligning camera with view")
+ print(object)
+
+func _preview_camera(object: Object) -> void:
+ print("Previewing camera")
+ print(object)
diff --git a/addons/phantom_camera/inspector/phantom_camera_inspector_plugin.gd.uid b/addons/phantom_camera/inspector/phantom_camera_inspector_plugin.gd.uid
new file mode 100644
index 0000000..325b3c0
--- /dev/null
+++ b/addons/phantom_camera/inspector/phantom_camera_inspector_plugin.gd.uid
@@ -0,0 +1 @@
+uid://dhwxgngr7sn6d
diff --git a/addons/phantom_camera/panel/editor.tscn b/addons/phantom_camera/panel/editor.tscn
new file mode 100644
index 0000000..0c75c6a
--- /dev/null
+++ b/addons/phantom_camera/panel/editor.tscn
@@ -0,0 +1,23 @@
+[gd_scene load_steps=4 format=3 uid="uid://cfdoaceoosi1w"]
+
+[ext_resource type="Script" uid="uid://cgfwg3paxkj2x" path="res://addons/phantom_camera/scripts/panel/editor.gd" id="1_86hp7"]
+[ext_resource type="PackedScene" uid="uid://cuqkqsp3ikv5u" path="res://addons/phantom_camera/panel/updater/update_button.tscn" id="1_oowcd"]
+[ext_resource type="PackedScene" uid="uid://dbkr3d716wtx0" path="res://addons/phantom_camera/panel/viewfinder/viewfinder_panel.tscn" id="2_xecnk"]
+
+[node name="EditorPanel" type="VBoxContainer"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_86hp7")
+
+[node name="UpdateButton" parent="." instance=ExtResource("1_oowcd")]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_horizontal = 8
+size_flags_vertical = 1
+
+[node name="ViewfinderPanel" parent="." instance=ExtResource("2_xecnk")]
+unique_name_in_owner = true
+layout_mode = 2
diff --git a/addons/phantom_camera/panel/updater/download_update_panel.tscn b/addons/phantom_camera/panel/updater/download_update_panel.tscn
new file mode 100644
index 0000000..5fa49e3
--- /dev/null
+++ b/addons/phantom_camera/panel/updater/download_update_panel.tscn
@@ -0,0 +1,253 @@
+[gd_scene load_steps=15 format=3 uid="uid://b25fl4usw0nlp"]
+
+[ext_resource type="Script" uid="uid://cjblcocen12r3" path="res://addons/phantom_camera/scripts/panel/updater/download_update_panel.gd" id="1_sx5xq"]
+[ext_resource type="Texture2D" uid="uid://cc0wmici0eic8" path="res://addons/phantom_camera/icons/phantom_camera_logo.png" id="2_f3yo7"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="3_h8uk3"]
+[ext_resource type="FontFile" uid="uid://dve7mgsjik4dg" path="res://addons/phantom_camera/fonts/Nunito-Regular.ttf" id="4_gwh4i"]
+[ext_resource type="Texture2D" uid="uid://censw3w53gldn" path="res://addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png" id="5_bonti"]
+
+[sub_resource type="ImageTexture" id="ImageTexture_sjwi2"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_insma"]
+bg_color = Color(0.0190018, 0.21903, 0.16997, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.0980392, 0.572549, 0.458824, 1)
+border_blend = true
+corner_radius_bottom_right = 12
+corner_radius_bottom_left = 12
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8m63d"]
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.105882, 0.619608, 0.498039, 1)
+corner_radius_bottom_right = 12
+corner_radius_bottom_left = 12
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yn22d"]
+bg_color = Color(0.0117647, 0.164706, 0.12549, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.0980392, 0.572549, 0.458824, 1)
+border_blend = true
+corner_radius_bottom_right = 12
+corner_radius_bottom_left = 12
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_djsbc"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_xtrn6"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_o12j0"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_buptb"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_g3tf0"]
+
+[node name="DownloadUpdatePanel" type="Control"]
+custom_minimum_size = Vector2(300, 350)
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_bottom = -61.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_sx5xq")
+
+[node name="DownloadHTTPRequest" type="HTTPRequest" parent="."]
+unique_name_in_owner = true
+
+[node name="Timer" type="Timer" parent="DownloadHTTPRequest"]
+one_shot = true
+
+[node name="VBox" type="VBoxContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_constants/separation = 2
+
+[node name="VBoxContainer2" type="VBoxContainer" parent="VBox"]
+layout_mode = 2
+theme_override_constants/separation = -20
+
+[node name="MarginContainer" type="MarginContainer" parent="VBox/VBoxContainer2"]
+layout_mode = 2
+theme_override_constants/margin_top = 12
+
+[node name="Logo" type="TextureRect" parent="VBox/VBoxContainer2/MarginContainer"]
+unique_name_in_owner = true
+clip_contents = true
+custom_minimum_size = Vector2(300, 160)
+layout_mode = 2
+texture = ExtResource("2_f3yo7")
+expand_mode = 3
+stretch_mode = 5
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBox/VBoxContainer2"]
+layout_mode = 2
+theme_override_constants/separation = -5
+
+[node name="NameLabel" type="Label" parent="VBox/VBoxContainer2/VBoxContainer"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.960784, 0.960784, 0.960784, 1)
+theme_override_fonts/font = ExtResource("3_h8uk3")
+theme_override_font_sizes/font_size = 32
+text = "Phantom Camera"
+horizontal_alignment = 1
+
+[node name="DownloadVersionLabel" type="Label" parent="VBox/VBoxContainer2/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_colors/font_color = Color(0.960784, 0.960784, 0.960784, 1)
+theme_override_fonts/font = ExtResource("4_gwh4i")
+theme_override_font_sizes/font_size = 18
+text = "v1.2.3 is available for download."
+horizontal_alignment = 1
+
+[node name="CurrentVersionLabel" type="Label" parent="VBox"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+theme_override_fonts/font = ExtResource("4_gwh4i")
+text = "Current version: 0.0.0"
+horizontal_alignment = 1
+
+[node name="Center2" type="CenterContainer" parent="VBox"]
+layout_mode = 2
+
+[node name="NotesButton" type="LinkButton" parent="VBox/Center2"]
+layout_mode = 2
+theme_override_colors/font_color = Color(0.917647, 0.631373, 0.368627, 1)
+theme_override_colors/font_hover_color = Color(0.721569, 0.454902, 0.192157, 1)
+theme_override_fonts/font = ExtResource("3_h8uk3")
+theme_override_font_sizes/font_size = 18
+text = "Release Notes"
+
+[node name="Center" type="CenterContainer" parent="VBox"]
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBox/Center"]
+layout_mode = 2
+
+[node name="BreakingLabel" type="Label" parent="VBox/Center/VBoxContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+theme_override_colors/font_color = Color(0.72549, 0.227451, 0.34902, 1)
+theme_override_fonts/font = ExtResource("3_h8uk3")
+theme_override_font_sizes/font_size = 18
+text = "Potential Breaking Changes
+in new release"
+horizontal_alignment = 1
+uppercase = true
+
+[node name="BreakingMarginContainer" type="MarginContainer" parent="VBox/Center/VBoxContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="VBox/Center/VBoxContainer/BreakingMarginContainer"]
+layout_mode = 2
+
+[node name="RichTextLabel2" type="RichTextLabel" parent="VBox/Center/VBoxContainer/BreakingMarginContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_fonts/normal_font = ExtResource("4_gwh4i")
+theme_override_fonts/bold_font = ExtResource("3_h8uk3")
+theme_override_fonts/mono_font = ExtResource("3_h8uk3")
+theme_override_font_sizes/normal_font_size = 18
+theme_override_font_sizes/bold_font_size = 14
+theme_override_font_sizes/mono_font_size = 12
+bbcode_enabled = true
+text = "[center][b]I am prepared for any breaking
+changes that may occur from this update[/b][/center]"
+fit_content = true
+
+[node name="BreakingOptionButton" type="OptionButton" parent="VBox/Center/VBoxContainer/BreakingMarginContainer/VBoxContainer"]
+unique_name_in_owner = true
+visible = false
+layout_mode = 2
+mouse_default_cursor_shape = 2
+theme_override_fonts/font = ExtResource("3_h8uk3")
+theme_override_font_sizes/font_size = 18
+theme_override_icons/arrow = SubResource("ImageTexture_sjwi2")
+theme_override_styles/normal = SubResource("StyleBoxFlat_insma")
+theme_override_styles/hover = SubResource("StyleBoxFlat_8m63d")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_yn22d")
+alignment = 1
+item_count = 2
+selected = 0
+popup/item_0/text = "Confirm choice"
+popup/item_0/id = 0
+popup/item_1/text = "Yes, I am prepared"
+popup/item_1/id = 1
+
+[node name="DownloadButton" type="Button" parent="VBox/Center/VBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(240, 90)
+layout_mode = 2
+mouse_default_cursor_shape = 2
+theme_override_styles/normal = SubResource("StyleBoxEmpty_djsbc")
+theme_override_styles/hover = SubResource("StyleBoxEmpty_xtrn6")
+theme_override_styles/pressed = SubResource("StyleBoxEmpty_o12j0")
+theme_override_styles/disabled = SubResource("StyleBoxEmpty_buptb")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_g3tf0")
+
+[node name="DownloadButtonBG" type="NinePatchRect" parent="VBox/Center/VBoxContainer/DownloadButton"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+texture = ExtResource("5_bonti")
+patch_margin_left = 38
+patch_margin_top = 37
+patch_margin_right = 38
+patch_margin_bottom = 39
+
+[node name="UpdateLabel" type="Label" parent="VBox/Center/VBoxContainer/DownloadButton"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 14
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_top = -14.5
+offset_bottom = 14.5
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_color = Color(0.960784, 0.960784, 0.960784, 1)
+theme_override_colors/font_shadow_color = Color(0, 0, 0, 1)
+theme_override_fonts/font = ExtResource("3_h8uk3")
+theme_override_font_sizes/font_size = 20
+text = "Update"
+horizontal_alignment = 1
+uppercase = true
+
+[node name="MarginContainer" type="MarginContainer" parent="VBox"]
+layout_mode = 2
+theme_override_constants/margin_top = 10
+
+[node name="RichTextLabel" type="RichTextLabel" parent="VBox/MarginContainer"]
+layout_mode = 2
+theme_override_fonts/normal_font = ExtResource("4_gwh4i")
+theme_override_fonts/mono_font = ExtResource("3_h8uk3")
+theme_override_font_sizes/normal_font_size = 12
+theme_override_font_sizes/mono_font_size = 12
+bbcode_enabled = true
+text = "[center]The updater can be disabled within:
+[code]Project Settings / Phantom Camera / Updater / Enable Updater[/code][/center]"
+fit_content = true
+
+[connection signal="pressed" from="VBox/Center2/NotesButton" to="." method="_on_notes_button_pressed"]
diff --git a/addons/phantom_camera/panel/updater/update_button.tscn b/addons/phantom_camera/panel/updater/update_button.tscn
new file mode 100644
index 0000000..a6efba1
--- /dev/null
+++ b/addons/phantom_camera/panel/updater/update_button.tscn
@@ -0,0 +1,101 @@
+[gd_scene load_steps=10 format=3 uid="uid://cuqkqsp3ikv5u"]
+
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="1_5e5k4"]
+[ext_resource type="Script" uid="uid://bwc42i46603qn" path="res://addons/phantom_camera/scripts/panel/updater/update_button.gd" id="1_xtaw5"]
+[ext_resource type="Texture2D" uid="uid://d4j4hrb7yusyq" path="res://addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg" id="2_c4d83"]
+[ext_resource type="PackedScene" uid="uid://b25fl4usw0nlp" path="res://addons/phantom_camera/panel/updater/download_update_panel.tscn" id="2_vtgcx"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c7fd1"]
+content_margin_left = 10.0
+content_margin_top = 4.0
+content_margin_right = 10.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0980392, 0.415686, 0.341176, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 20
+corner_radius_top_right = 20
+corner_radius_bottom_right = 20
+corner_radius_bottom_left = 20
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_y83dj"]
+content_margin_left = 10.0
+content_margin_top = 4.0
+content_margin_right = 10.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.960784, 0.960784, 0.960784, 1)
+corner_radius_top_left = 20
+corner_radius_top_right = 20
+corner_radius_bottom_right = 20
+corner_radius_bottom_left = 20
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_slf6e"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_lekqh"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dr4n4"]
+content_margin_bottom = 20.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+
+[node name="UpdateButton" type="Button"]
+visible = false
+offset_left = 1.0
+offset_right = 149.0
+offset_bottom = 28.0
+size_flags_vertical = 3
+theme_override_colors/font_color = Color(0.960784, 0.960784, 0.960784, 1)
+theme_override_colors/font_hover_color = Color(0.939288, 0.917743, 0.892615, 1)
+theme_override_colors/icon_normal_color = Color(0.960784, 0.960784, 0.960784, 1)
+theme_override_fonts/font = ExtResource("1_5e5k4")
+theme_override_font_sizes/font_size = 14
+theme_override_styles/normal = SubResource("StyleBoxFlat_c7fd1")
+theme_override_styles/hover = SubResource("StyleBoxFlat_y83dj")
+theme_override_styles/pressed = SubResource("StyleBoxEmpty_slf6e")
+theme_override_styles/focus = SubResource("StyleBoxEmpty_lekqh")
+text = "Update available"
+icon = ExtResource("2_c4d83")
+script = ExtResource("1_xtaw5")
+
+[node name="HTTPRequest" type="HTTPRequest" parent="."]
+unique_name_in_owner = true
+
+[node name="DownloadDialog" type="AcceptDialog" parent="."]
+unique_name_in_owner = true
+transparent_bg = true
+title = "New Update"
+initial_position = 2
+size = Vector2i(450, 480)
+transient = false
+unresizable = true
+borderless = true
+keep_title_visible = false
+content_scale_mode = 1
+theme_override_constants/buttons_separation = 30
+theme_override_styles/panel = SubResource("StyleBoxFlat_dr4n4")
+ok_button_text = "Close"
+
+[node name="DownloadUpdatePanel" parent="DownloadDialog" instance=ExtResource("2_vtgcx")]
+unique_name_in_owner = true
+offset_left = 2.0
+offset_top = 2.0
+offset_right = -2.0
+offset_bottom = -80.0
+
+[node name="NeedsReloadDialog" type="ConfirmationDialog" parent="."]
+unique_name_in_owner = true
+
+[node name="UpdateFailedDialog" type="AcceptDialog" parent="."]
+unique_name_in_owner = true
diff --git a/addons/phantom_camera/panel/viewfinder/deadzone_style_box.tres b/addons/phantom_camera/panel/viewfinder/deadzone_style_box.tres
new file mode 100644
index 0000000..7353299
--- /dev/null
+++ b/addons/phantom_camera/panel/viewfinder/deadzone_style_box.tres
@@ -0,0 +1,14 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://dpa7yvxlq043a"]
+
+[resource]
+bg_color = Color(0.227451, 0.72549, 0.603922, 0.2)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_detail = 1
+expand_margin_left = 1.0
+expand_margin_top = 1.0
+expand_margin_right = 1.0
+expand_margin_bottom = 1.0
diff --git a/addons/phantom_camera/panel/viewfinder/host_list/host_list.tscn b/addons/phantom_camera/panel/viewfinder/host_list/host_list.tscn
new file mode 100644
index 0000000..332415d
--- /dev/null
+++ b/addons/phantom_camera/panel/viewfinder/host_list/host_list.tscn
@@ -0,0 +1,87 @@
+[gd_scene load_steps=8 format=3 uid="uid://mbjdav5oqves"]
+
+[ext_resource type="Script" uid="uid://c84cxry3t35ny" path="res://addons/phantom_camera/scripts/panel/viewfinder/host_list.gd" id="1_h6ayt"]
+[ext_resource type="Texture2D" uid="uid://5fatldiu7dd5" path="res://addons/phantom_camera/icons/phantom_camera_host.svg" id="1_xlgqb"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_w002y"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kq7gm"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.960784, 0.960784, 0.960784, 1)
+corner_radius_top_right = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ynag5"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.960784, 0.960784, 0.960784, 1)
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_radius_top_right = 6
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q2svd"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_right = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0jvt"]
+content_margin_left = 0.0
+content_margin_top = 8.0
+content_margin_right = 0.0
+content_margin_bottom = 8.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_right = 10
+
+[node name="PCamHostList" type="VBoxContainer"]
+anchors_preset = 9
+anchor_bottom = 1.0
+size_flags_horizontal = 0
+size_flags_vertical = 0
+theme_override_constants/separation = -2
+alignment = 2
+script = ExtResource("1_h6ayt")
+
+[node name="HostListButton" type="Button" parent="."]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(40, 40)
+layout_mode = 2
+size_flags_horizontal = 0
+theme_override_colors/icon_hover_color = Color(0.0784314, 0.109804, 0.129412, 1)
+theme_override_colors/icon_hover_pressed_color = Color(0.0784314, 0.109804, 0.129412, 1)
+theme_override_styles/focus = SubResource("StyleBoxEmpty_w002y")
+theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_kq7gm")
+theme_override_styles/hover = SubResource("StyleBoxFlat_ynag5")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_kq7gm")
+theme_override_styles/normal = SubResource("StyleBoxFlat_q2svd")
+icon = ExtResource("1_xlgqb")
+expand_icon = true
+
+[node name="ScrollContainer" type="ScrollContainer" parent="."]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_e0jvt")
+horizontal_scroll_mode = 0
+
+[node name="HostListContainer" type="VBoxContainer" parent="ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_constants/separation = 8
diff --git a/addons/phantom_camera/panel/viewfinder/host_list/host_list_item.tscn b/addons/phantom_camera/panel/viewfinder/host_list/host_list_item.tscn
new file mode 100644
index 0000000..9ce67e5
--- /dev/null
+++ b/addons/phantom_camera/panel/viewfinder/host_list/host_list_item.tscn
@@ -0,0 +1,68 @@
+[gd_scene load_steps=10 format=3 uid="uid://btn6jgv0vix7"]
+
+[ext_resource type="FontFile" uid="uid://dve7mgsjik4dg" path="res://addons/phantom_camera/fonts/Nunito-Regular.ttf" id="1_anjxo"]
+[ext_resource type="Theme" uid="uid://bhppejri5dbsf" path="res://addons/phantom_camera/themes/theme.tres" id="1_wql5t"]
+[ext_resource type="Texture2D" uid="uid://rghrkoqrm2ig" path="res://addons/phantom_camera/icons/viewfinder/Select.svg" id="2_71b6g"]
+[ext_resource type="ButtonGroup" uid="uid://dfu0b8jbtyr1n" path="res://addons/phantom_camera/panel/viewfinder/host_list/host_list_item_group.tres" id="3_06a0y"]
+[ext_resource type="Script" uid="uid://bv24ubx8mutw7" path="res://addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd" id="3_a5o8b"]
+[ext_resource type="Texture2D" uid="uid://cjlv0bg7byjx0" path="res://addons/phantom_camera/icons/warning.svg" id="3_qgpy7"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_0rxfi"]
+content_margin_right = 6.0
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_llqnh"]
+
+[sub_resource type="Theme" id="Theme_7h15c"]
+Button/colors/icon_hover_color = Color(0.960784, 0.960784, 0.960784, 1)
+Button/colors/icon_hover_pressed_color = Color(0.227451, 0.72549, 0.603922, 1)
+Button/colors/icon_normal_color = Color(0.227451, 0.72549, 0.603922, 1)
+Button/colors/icon_pressed_color = Color(0.227451, 0.72549, 0.603922, 1)
+Button/constants/icon_max_width = 20
+Button/styles/focus = SubResource("StyleBoxEmpty_llqnh")
+
+[node name="HostListItem" type="PanelContainer"]
+offset_right = 229.0
+offset_bottom = 34.0
+theme_override_styles/panel = SubResource("StyleBoxEmpty_0rxfi")
+script = ExtResource("3_a5o8b")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+theme_override_constants/separation = 2
+
+[node name="SelectPCamHost" type="Button" parent="HBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+tooltip_text = "Select the Phantom Camera Host node from the scene hierarchy"
+focus_mode = 0
+theme = SubResource("Theme_7h15c")
+icon = ExtResource("2_71b6g")
+flat = true
+
+[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 8
+
+[node name="ErrorPCamHost" type="TextureRect" parent="HBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(18, 18)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 4
+tooltip_text = "This Phantom Camera Host node will not affect a Camera node.
+See the warning in the Scene Tree for more information."
+texture = ExtResource("3_qgpy7")
+expand_mode = 1
+
+[node name="SwitchPCamHost" type="Button" parent="HBoxContainer/HBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(40, 0)
+layout_mode = 2
+tooltip_text = "Change the viewfinder's camera to the camera of this Phantom Camera Host"
+theme = ExtResource("1_wql5t")
+theme_override_fonts/font = ExtResource("1_anjxo")
+theme_override_font_sizes/font_size = 18
+toggle_mode = true
+button_group = ExtResource("3_06a0y")
+text = "{ PCamHostName }"
diff --git a/addons/phantom_camera/panel/viewfinder/host_list/host_list_item_group.tres b/addons/phantom_camera/panel/viewfinder/host_list/host_list_item_group.tres
new file mode 100644
index 0000000..64c4600
--- /dev/null
+++ b/addons/phantom_camera/panel/viewfinder/host_list/host_list_item_group.tres
@@ -0,0 +1,3 @@
+[gd_resource type="ButtonGroup" format=3 uid="uid://dfu0b8jbtyr1n"]
+
+[resource]
diff --git a/addons/phantom_camera/panel/viewfinder/viewfinder_panel.tscn b/addons/phantom_camera/panel/viewfinder/viewfinder_panel.tscn
new file mode 100644
index 0000000..b6caa0d
--- /dev/null
+++ b/addons/phantom_camera/panel/viewfinder/viewfinder_panel.tscn
@@ -0,0 +1,563 @@
+[gd_scene load_steps=28 format=3 uid="uid://dbkr3d716wtx0"]
+
+[ext_resource type="Script" uid="uid://drmv3363t8amc" path="res://addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd" id="1_utvi8"]
+[ext_resource type="StyleBox" uid="uid://dpa7yvxlq043a" path="res://addons/phantom_camera/panel/viewfinder/deadzone_style_box.tres" id="2_uabt4"]
+[ext_resource type="FontFile" uid="uid://c4mm3of2mc8o5" path="res://addons/phantom_camera/fonts/Nunito-Black.ttf" id="3_li3i3"]
+[ext_resource type="Texture2D" uid="uid://5fatldiu7dd5" path="res://addons/phantom_camera/icons/phantom_camera_host.svg" id="4_lcg1p"]
+[ext_resource type="FontFile" uid="uid://dve7mgsjik4dg" path="res://addons/phantom_camera/fonts/Nunito-Regular.ttf" id="5_4jhax"]
+[ext_resource type="Texture2D" uid="uid://dy8eifa6aw2en" path="res://addons/phantom_camera/icons/misc/PriorityOverride.svg" id="6_ptuth"]
+[ext_resource type="Script" uid="uid://c84cxry3t35ny" path="res://addons/phantom_camera/scripts/panel/viewfinder/host_list.gd" id="7_kpij0"]
+[ext_resource type="Theme" uid="uid://bhppejri5dbsf" path="res://addons/phantom_camera/themes/theme.tres" id="8_b4akn"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fle8t"]
+bg_color = Color(0.227451, 0.72549, 0.603922, 0.2)
+draw_center = false
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_detail = 1
+expand_margin_left = 1.0
+expand_margin_top = 1.0
+expand_margin_right = 1.0
+expand_margin_bottom = 1.0
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xmo1t"]
+draw_center = false
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0.745098, 0.858824, 0.380392, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q7vs4"]
+bg_color = Color(0.929412, 0.87451, 0.619608, 1)
+border_width_left = 1
+border_width_top = 1
+border_width_right = 1
+border_width_bottom = 1
+border_color = Color(0, 0, 0, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_iho1a"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_obaj6"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_4b76l"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_yh38y"]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(0.129412, 0.407843, 0.337255, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.988235, 0.498039, 0.498039, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gci88"]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(0.180392, 0.576471, 0.482353, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fsxik"]
+content_margin_left = 10.0
+content_margin_top = 10.0
+content_margin_right = 10.0
+content_margin_bottom = 10.0
+bg_color = Color(0.129412, 0.407843, 0.337255, 1)
+border_width_left = 4
+border_width_top = 4
+border_width_right = 4
+border_width_bottom = 4
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_g5wua"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_x4bx8"]
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_840sd"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ja3vm"]
+bg_color = Color(0.53, 0.1643, 0.255725, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_blend = true
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mk273"]
+bg_color = Color(0.43, 0.1333, 0.207475, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_blend = true
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_agqdu"]
+bg_color = Color(0.72549, 0.227451, 0.34902, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_blend = true
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_w002y"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_kq7gm"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.960784, 0.960784, 0.960784, 1)
+corner_radius_top_right = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ynag5"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.960784, 0.960784, 0.960784, 1)
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_radius_top_right = 6
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_q2svd"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_right = 8
+
+[node name="ViewfinderPanel" type="Control"]
+clip_contents = true
+custom_minimum_size = Vector2(0, 300)
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+mouse_filter = 2
+script = ExtResource("1_utvi8")
+
+[node name="Viewfinder" type="Control" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+metadata/_edit_lock_ = true
+
+[node name="SubViewportContainer" type="SubViewportContainer" parent="Viewfinder"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+stretch = true
+
+[node name="SubViewport" type="SubViewport" parent="Viewfinder/SubViewportContainer"]
+unique_name_in_owner = true
+handle_input_locally = false
+canvas_item_default_texture_filter = 0
+gui_disable_input = true
+size = Vector2i(1152, 648)
+size_2d_override_stretch = true
+render_target_update_mode = 4
+
+[node name="Camera2D" type="Camera2D" parent="Viewfinder/SubViewportContainer/SubViewport"]
+unique_name_in_owner = true
+editor_draw_screen = false
+
+[node name="DeadZoneHBoxContainer" type="HBoxContainer" parent="Viewfinder"]
+unique_name_in_owner = true
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_constants/separation = 0
+
+[node name="DeadZoneLeftHBoxContainer" type="VBoxContainer" parent="Viewfinder/DeadZoneHBoxContainer"]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 2
+theme_override_constants/separation = 0
+
+[node name="DeadZoneLeftTopPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneLeftHBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneLeftCenterPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneLeftHBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneLeftBottomPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneLeftHBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneCenterHBoxContainer" type="VBoxContainer" parent="Viewfinder/DeadZoneHBoxContainer"]
+unique_name_in_owner = true
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 4
+mouse_filter = 2
+theme_override_constants/separation = 0
+
+[node name="DeadZoneCenterTopPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneCenterHBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneCenterCenterPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneCenterHBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 4
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_fle8t")
+
+[node name="DeadZoneCenterBottomPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneCenterHBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneRightHBoxContainer" type="VBoxContainer" parent="Viewfinder/DeadZoneHBoxContainer"]
+clip_contents = true
+layout_mode = 2
+size_flags_horizontal = 3
+mouse_filter = 2
+theme_override_constants/separation = 0
+
+[node name="DeadZoneRightTopPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneRightHBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneRightCenterPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneRightHBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="DeadZoneRightBottomPanel" type="Panel" parent="Viewfinder/DeadZoneHBoxContainer/DeadZoneRightHBoxContainer"]
+layout_mode = 2
+size_flags_vertical = 3
+mouse_filter = 2
+theme_override_styles/panel = ExtResource("2_uabt4")
+
+[node name="AspectRatioContainer" type="AspectRatioContainer" parent="Viewfinder"]
+unique_name_in_owner = true
+clip_contents = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+ratio = 1.77778
+
+[node name="CameraViewportPanel" type="Panel" parent="Viewfinder/AspectRatioContainer"]
+layout_mode = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_xmo1t")
+
+[node name="TargetPoint" type="Panel" parent="Viewfinder/AspectRatioContainer/CameraViewportPanel"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -3.0
+offset_top = -3.0
+offset_right = 3.0
+offset_bottom = 3.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_q7vs4")
+
+[node name="NoSupportMsg" type="Label" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -8.0
+offset_bottom = -8.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_fonts/font = ExtResource("3_li3i3")
+theme_override_font_sizes/font_size = 24
+theme_override_styles/normal = SubResource("StyleBoxEmpty_iho1a")
+text = "Control scenes are not supported"
+horizontal_alignment = 1
+vertical_alignment = 1
+metadata/_edit_lock_ = true
+
+[node name="EmptyStateControl" type="Control" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+metadata/_edit_use_anchors_ = true
+metadata/_edit_lock_ = true
+
+[node name="BGColorRect" type="ColorRect" parent="EmptyStateControl"]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0, 0, 0, 1)
+metadata/_edit_lock_ = true
+
+[node name="VBoxContainer" type="VBoxContainer" parent="EmptyStateControl"]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -200.0
+offset_top = -112.0
+offset_right = 200.0
+offset_bottom = 112.0
+grow_horizontal = 2
+grow_vertical = 2
+alignment = 1
+
+[node name="EmptyStateIcon" type="TextureRect" parent="EmptyStateControl/VBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(0, 64)
+layout_mode = 2
+texture = ExtResource("4_lcg1p")
+expand_mode = 1
+stretch_mode = 5
+
+[node name="EmptyStateText" type="RichTextLabel" parent="EmptyStateControl/VBoxContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme_override_colors/default_color = Color(1, 1, 1, 1)
+theme_override_fonts/normal_font = ExtResource("5_4jhax")
+theme_override_fonts/bold_font = ExtResource("3_li3i3")
+theme_override_font_sizes/normal_font_size = 24
+theme_override_font_sizes/bold_font_size = 24
+theme_override_styles/focus = SubResource("StyleBoxEmpty_obaj6")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_iho1a")
+bbcode_enabled = true
+text = "[center][b]NodeType[/b] Description [/center]"
+fit_content = true
+
+[node name="AddNodeButton" type="Button" parent="EmptyStateControl/VBoxContainer"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(400, 54)
+layout_mode = 2
+size_flags_horizontal = 4
+focus_mode = 0
+theme_override_colors/font_color = Color(1, 1, 1, 1)
+theme_override_fonts/font = ExtResource("3_li3i3")
+theme_override_font_sizes/font_size = 24
+theme_override_styles/focus = SubResource("StyleBoxEmpty_4b76l")
+theme_override_styles/hover = SubResource("StyleBoxFlat_yh38y")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_gci88")
+theme_override_styles/normal = SubResource("StyleBoxFlat_fsxik")
+
+[node name="AddNodeTypeText" type="RichTextLabel" parent="EmptyStateControl/VBoxContainer/AddNodeButton"]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = 9.0
+offset_bottom = -11.0
+grow_horizontal = 2
+grow_vertical = 2
+mouse_filter = 2
+theme_override_colors/default_color = Color(1, 1, 1, 1)
+theme_override_fonts/normal_font = ExtResource("5_4jhax")
+theme_override_fonts/bold_font = ExtResource("3_li3i3")
+theme_override_font_sizes/normal_font_size = 24
+theme_override_font_sizes/bold_font_size = 24
+theme_override_styles/focus = SubResource("StyleBoxEmpty_g5wua")
+theme_override_styles/normal = SubResource("StyleBoxEmpty_x4bx8")
+bbcode_enabled = true
+text = "[center]Add [img=32]res://addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg[/img] [b]{NodeType}[/b][/center]"
+scroll_active = false
+
+[node name="PriorityOverrideButton" type="Button" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+offset_left = 5.0
+offset_top = 5.0
+offset_right = 165.0
+offset_bottom = 57.0
+mouse_default_cursor_shape = 2
+theme_override_styles/focus = SubResource("StyleBoxEmpty_840sd")
+theme_override_styles/hover = SubResource("StyleBoxFlat_ja3vm")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_mk273")
+theme_override_styles/normal = SubResource("StyleBoxFlat_agqdu")
+metadata/_edit_lock_ = true
+
+[node name="PriorityOverrideIcon" type="TextureRect" parent="PriorityOverrideButton"]
+layout_mode = 1
+offset_left = 8.0
+offset_top = 4.0
+offset_right = 32.0
+offset_bottom = 28.0
+texture = ExtResource("6_ptuth")
+expand_mode = 1
+
+[node name="PriorityOverrideByLabel" type="Label" parent="PriorityOverrideButton"]
+layout_mode = 0
+offset_left = 30.0
+offset_top = 1.0
+offset_right = 140.0
+offset_bottom = 24.0
+theme_override_fonts/font = ExtResource("3_li3i3")
+theme_override_font_sizes/font_size = 14
+text = "OVERRIDDEN BY"
+vertical_alignment = 1
+
+[node name="PriorityOverrideNameLabel" type="Label" parent="PriorityOverrideButton"]
+unique_name_in_owner = true
+layout_mode = 0
+offset_left = 6.0
+offset_top = 21.0
+offset_right = 153.0
+offset_bottom = 44.0
+theme_override_fonts/font = ExtResource("5_4jhax")
+theme_override_font_sizes/font_size = 14
+text = "PCam_Name
+"
+vertical_alignment = 1
+text_overrun_behavior = 3
+
+[node name="SizeLabel" type="Label" parent="."]
+unique_name_in_owner = true
+layout_mode = 1
+anchors_preset = 4
+anchor_top = 0.5
+anchor_bottom = 0.5
+offset_top = -11.5
+offset_right = 40.0
+offset_bottom = 11.5
+grow_vertical = 2
+
+[node name="PCamHostList" type="VBoxContainer" parent="."]
+unique_name_in_owner = true
+visible = false
+layout_mode = 1
+anchors_preset = -1
+anchor_bottom = 1.0
+offset_top = 588.0
+grow_vertical = 2
+size_flags_horizontal = 0
+size_flags_vertical = 0
+theme_override_constants/separation = -2
+alignment = 2
+script = ExtResource("7_kpij0")
+
+[node name="HostListButton" type="Button" parent="PCamHostList"]
+unique_name_in_owner = true
+custom_minimum_size = Vector2(40, 40)
+layout_mode = 2
+size_flags_horizontal = 0
+theme_override_colors/icon_hover_pressed_color = Color(0.0784314, 0.109804, 0.129412, 1)
+theme_override_colors/icon_hover_color = Color(0.0784314, 0.109804, 0.129412, 1)
+theme_override_styles/focus = SubResource("StyleBoxEmpty_w002y")
+theme_override_styles/hover_pressed = SubResource("StyleBoxFlat_kq7gm")
+theme_override_styles/hover = SubResource("StyleBoxFlat_ynag5")
+theme_override_styles/pressed = SubResource("StyleBoxFlat_kq7gm")
+theme_override_styles/normal = SubResource("StyleBoxFlat_q2svd")
+icon = ExtResource("4_lcg1p")
+expand_icon = true
+
+[node name="PanelContainer" type="PanelContainer" parent="PCamHostList"]
+layout_mode = 2
+size_flags_vertical = 3
+theme = ExtResource("8_b4akn")
+
+[node name="ScrollContainer" type="ScrollContainer" parent="PCamHostList/PanelContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+size_flags_vertical = 3
+theme = ExtResource("8_b4akn")
+horizontal_scroll_mode = 0
+
+[node name="HostListContainer" type="VBoxContainer" parent="PCamHostList/PanelContainer/ScrollContainer"]
+unique_name_in_owner = true
+layout_mode = 2
+theme = ExtResource("8_b4akn")
diff --git a/addons/phantom_camera/plugin.cfg b/addons/phantom_camera/plugin.cfg
new file mode 100644
index 0000000..f52ffd5
--- /dev/null
+++ b/addons/phantom_camera/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Phantom Camera"
+description="Control the movement and dynamically tween 2D & 3D cameras positions. Built for Godot 4. Inspired by Cinemachine."
+author="Marcus Skov"
+version="0.9.1.2"
+script="plugin.gd"
diff --git a/addons/phantom_camera/plugin.gd b/addons/phantom_camera/plugin.gd
new file mode 100644
index 0000000..39ef25d
--- /dev/null
+++ b/addons/phantom_camera/plugin.gd
@@ -0,0 +1,181 @@
+@tool
+extends EditorPlugin
+
+#region Constants
+
+const PCAM_HOST: String = "PhantomCameraHost"
+const PCAM_2D: String = "PhantomCamera2D"
+const PCAM_3D: String = "PhantomCamera3D"
+const PCAM_NOISE_EMITTER_2D: String = "PhantomCameraNoiseEmitter2D"
+const PCAM_NOISE_EMITTER_3D: String = "PhantomCameraNoiseEmitter3D"
+
+const PCam3DPlugin: Script = preload("res://addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd")
+const PCam3DNoiseEmitterPlugin: Script = preload("res://addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd")
+const EditorPanel: PackedScene = preload("res://addons/phantom_camera/panel/editor.tscn")
+const updater_constants: Script = preload("res://addons/phantom_camera/scripts/panel/updater/updater_constants.gd")
+const PHANTOM_CAMERA_MANAGER: StringName = "PhantomCameraManager"
+
+#endregion
+
+#region Private Variables
+
+var _settings_show_jitter_tips: String = "phantom_camera/tips/show_jitter_tips"
+var _settings_enable_editor_shortcut: String = "phantom_camera/general/enable_editor_shortcut"
+var _settings_editor_shortcut: String = "phantom_camera/general/editor_shortcut"
+
+# TODO - Pending merge of https://github.com/godotengine/godot/pull/102889 - Should only support Godot version after the release that is featured in
+#var _editor_shortcut: Shortcut = Shortcut.new()
+#var _editor_shortcut_input: InputEventKey
+#endregion
+
+#region Public Variables
+
+var pcam_3d_gizmo_plugin = PCam3DPlugin.new()
+var pcam_3d_noise_emitter_gizmo_plugin = PCam3DNoiseEmitterPlugin.new()
+
+var editor_panel_instance: Control
+var panel_button: Button
+#var viewfinder_panel_instance
+
+
+#endregion
+
+#region Private Functions
+
+func _enable_plugin() -> void:
+ print_rich("Phantom Camera documentation can be found at: [url=https://phantom-camera.dev]https://phantom-camera.dev[/url]")
+ if not Engine.has_singleton(PHANTOM_CAMERA_MANAGER):
+ add_autoload_singleton(PHANTOM_CAMERA_MANAGER, "res://addons/phantom_camera/scripts/managers/phantom_camera_manager.gd")
+
+
+func _disable_plugin() -> void:
+ remove_autoload_singleton(PHANTOM_CAMERA_MANAGER)
+
+
+func _enter_tree() -> void:
+ add_autoload_singleton(PHANTOM_CAMERA_MANAGER, "res://addons/phantom_camera/scripts/managers/phantom_camera_manager.gd")
+
+ # Phantom Camera Nodes
+ add_custom_type(PCAM_2D, "Node2D", preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd"), preload("res://addons/phantom_camera/icons/phantom_camera_2d.svg"))
+ add_custom_type(PCAM_3D, "Node3D", preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd"), preload("res://addons/phantom_camera/icons/phantom_camera_2d.svg"))
+ add_custom_type(PCAM_HOST, "Node", preload("res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd"), preload("res://addons/phantom_camera/icons/phantom_camera_2d.svg"))
+ add_custom_type(PCAM_NOISE_EMITTER_2D, "Node2D", preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd"), preload("res://addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg"))
+ add_custom_type(PCAM_NOISE_EMITTER_3D, "Node3D", preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd"), preload("res://addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg"))
+
+ # Phantom Camera 3D Gizmo
+ add_node_3d_gizmo_plugin(pcam_3d_gizmo_plugin)
+ add_node_3d_gizmo_plugin(pcam_3d_noise_emitter_gizmo_plugin)
+
+ var setting_updater_mode: String
+ var setting_updater_mode_default: int
+ if FileAccess.file_exists("res://dev_scenes/3d/dev_scene_3d.tscn"): # For forks
+ setting_updater_mode = "Off, Console Output"
+ setting_updater_mode_default = 1
+ else: # For end-users
+ setting_updater_mode = "Off, Console Output, Updater Window"
+ setting_updater_mode_default = 2
+
+ if not ProjectSettings.has_setting(updater_constants.setting_updater_mode):
+ ProjectSettings.set_setting(updater_constants.setting_updater_mode, setting_updater_mode_default)
+ ProjectSettings.add_property_info({
+ "name": updater_constants.setting_updater_mode,
+ "type": TYPE_INT,
+ "hint": PROPERTY_HINT_ENUM,
+ "hint_string": setting_updater_mode,
+ })
+ ProjectSettings.set_initial_value(updater_constants.setting_updater_mode, setting_updater_mode_default)
+ ProjectSettings.set_as_basic(updater_constants.setting_updater_mode, true)
+
+
+ ## Setting for enabling / disabling Jitter tips in the Output
+ if not ProjectSettings.has_setting(_settings_show_jitter_tips):
+ ProjectSettings.set_setting(_settings_show_jitter_tips, true)
+ ProjectSettings.add_property_info({
+ "name": _settings_show_jitter_tips,
+ "type": TYPE_BOOL,
+ })
+ ProjectSettings.set_initial_value(_settings_show_jitter_tips, true)
+ ProjectSettings.set_as_basic(_settings_show_jitter_tips, true)
+
+
+# TODO - Pending merge of https://github.com/godotengine/godot/pull/102889 - Should only support Godot version after this release
+# if not ProjectSettings.has_setting(_settings_enable_editor_shortcut):
+# ProjectSettings.set_setting(_settings_enable_editor_shortcut, false)
+# ProjectSettings.set_initial_value(_settings_enable_editor_shortcut, false)
+
+# TODO - Pending merge of https://github.com/godotengine/godot/pull/102889 - Should only support Godot version after this release
+# _viewfinder_shortcut_default.events = [editor_shortcut]
+# if ProjectSettings.get_setting(_settings_enable_editor_shortcut):
+# if not ProjectSettings.has_setting(_settings_editor_shortcut):
+# ProjectSettings.set_setting(_settings_editor_shortcut, _editor_shortcut)
+# ProjectSettings.set_initial_value(_settings_editor_shortcut, _editor_shortcut)
+
+
+ # TODO - Should be disabled unless in editor
+ # Viewfinder
+ editor_panel_instance = EditorPanel.instantiate()
+ editor_panel_instance.editor_plugin = self
+ panel_button = add_control_to_bottom_panel(editor_panel_instance, "Phantom Camera")
+ panel_button.toggled.connect(_btn_toggled)
+ if panel_button.toggle_mode: _btn_toggled(true)
+
+ scene_changed.connect(editor_panel_instance.viewfinder.scene_changed)
+ scene_changed.connect(_scene_changed)
+
+
+func _exit_tree() -> void:
+ panel_button.toggled.disconnect(_btn_toggled)
+ scene_changed.disconnect(editor_panel_instance.viewfinder.scene_changed)
+ scene_changed.disconnect(_scene_changed)
+
+ remove_control_from_bottom_panel(editor_panel_instance)
+ editor_panel_instance.queue_free()
+
+ remove_node_3d_gizmo_plugin(pcam_3d_gizmo_plugin)
+ remove_node_3d_gizmo_plugin(pcam_3d_noise_emitter_gizmo_plugin)
+
+ remove_custom_type(PCAM_2D)
+ remove_custom_type(PCAM_3D)
+ remove_custom_type(PCAM_HOST)
+ remove_custom_type(PCAM_NOISE_EMITTER_2D)
+ remove_custom_type(PCAM_NOISE_EMITTER_3D)
+
+ remove_autoload_singleton(PHANTOM_CAMERA_MANAGER)
+# if get_tree().root.get_node_or_null(String(PHANTOM_CAMERA_MANAGER)):
+# remove_autoload_singleton(PHANTOM_CAMERA_MANAGER)
+
+
+func _btn_toggled(toggled_on: bool):
+ editor_panel_instance.viewfinder.set_visibility(toggled_on)
+# if toggled_on:
+# editor_panel_instance.viewfinder.viewfinder_visible = true
+# editor_panel_instance.viewfinder.visibility_check()
+# else:
+# editor_panel_instance.viewfinder.viewfinder_visible = false
+
+func _make_visible(visible):
+ if editor_panel_instance:
+ editor_panel_instance.set_visible(visible)
+
+## TODO - Signal can be added directly to the editor_panel with the changes in Godot 4.5 (https://github.com/godotengine/godot/pull/102986)
+func _scene_changed(scene_root: Node) -> void:
+ editor_panel_instance.viewfinder.scene_changed(scene_root)
+
+# TODO - Pending merge of https://github.com/godotengine/godot/pull/102889 - Should only support Godot version after this release
+#func _set_editor_shortcut() -> InputEventKey:
+# var shortcut: InputEventKey = InputEventKey.new()
+# shortcut.keycode = 67 # Key = C
+# shortcut.alt_pressed = true
+# return shortcut
+
+#endregion
+
+
+#region Public Functions
+
+func get_version() -> String:
+ var config: ConfigFile = ConfigFile.new()
+ config.load(get_script().resource_path.get_base_dir() + "/plugin.cfg")
+ return config.get_value("plugin", "version")
+
+#endregion
diff --git a/addons/phantom_camera/plugin.gd.uid b/addons/phantom_camera/plugin.gd.uid
new file mode 100644
index 0000000..1c23477
--- /dev/null
+++ b/addons/phantom_camera/plugin.gd.uid
@@ -0,0 +1 @@
+uid://c7g41in1osxi6
diff --git a/addons/phantom_camera/scripts/gizmos/custom_gizmo.gd b/addons/phantom_camera/scripts/gizmos/custom_gizmo.gd
new file mode 100644
index 0000000..08d7275
--- /dev/null
+++ b/addons/phantom_camera/scripts/gizmos/custom_gizmo.gd
@@ -0,0 +1,91 @@
+extends EditorNode3DGizmoPlugin
+class_name CustomPluginGizmo
+
+var _gizmo_name
+var gizmo_name: String: set = set_gizmo_name
+
+var _gizmo_icon: Texture2D
+var gizmo_icon: Texture2D: set = set_gizmo_icon
+
+var _gizmo_spatial_script: Script
+var gizmo_spatial_script: Script: set = set_gizmo_spatial_script
+
+var _gizmo_scale: float = 0.035
+
+
+func set_gizmo_name(name: String) -> void:
+ _gizmo_name = name
+
+
+func set_gizmo_icon(icon: Texture2D) -> void:
+ _gizmo_icon = icon
+
+
+func set_gizmo_spatial_script(script: Script) -> void:
+ _gizmo_spatial_script = script
+
+
+func _get_gizmo_name() -> String:
+ return _gizmo_name
+
+func _has_gizmo(spatial: Node3D):
+ return spatial.get_script() == _gizmo_spatial_script
+
+
+func _init() -> void:
+ create_icon_material(_gizmo_name, _gizmo_icon, false, Color.WHITE)
+ create_material("main", Color8(252, 127, 127, 255))
+
+
+func _draw_frustum() -> PackedVector3Array:
+ var lines = PackedVector3Array()
+
+ var dis: float = 0.25
+ var width: float = dis * 1.25
+ var len: float = dis * 1.5
+
+ # Straight line
+# lines.push_back(Vector3(0, 0, 0))
+# lines.push_back(Vector3(0, 0, -len))
+
+ # Trapezoid
+ lines.push_back(Vector3(0, 0, 0))
+ lines.push_back(Vector3(-width, dis, -len))
+
+ lines.push_back(Vector3(0, 0, 0))
+ lines.push_back(Vector3(width, dis, -len))
+
+ lines.push_back(Vector3(0, 0, 0))
+ lines.push_back(Vector3(-width, -dis, -len))
+
+ lines.push_back(Vector3(0, 0, 0))
+ lines.push_back(Vector3(width, -dis, -len))
+
+ # Square
+ ## Left
+ lines.push_back(Vector3(-width, dis, -len))
+ lines.push_back(Vector3(-width, -dis, -len))
+
+ ## Bottom
+ lines.push_back(Vector3(-width, -dis, -len))
+ lines.push_back(Vector3(width, -dis, -len))
+
+ ## Right
+ lines.push_back(Vector3(width, -dis, -len))
+ lines.push_back(Vector3(width, dis, -len))
+
+ ## Top
+ lines.push_back(Vector3(width, dis, -len))
+ lines.push_back(Vector3(-width, dis, -len))
+
+ return lines
+
+
+func _redraw(gizmo: EditorNode3DGizmo):
+ gizmo.clear()
+
+ var icon: Material = get_material(_gizmo_name, gizmo)
+ gizmo.add_unscaled_billboard(icon, _gizmo_scale)
+
+ var material = get_material("main", gizmo)
+ gizmo.add_lines(_draw_frustum(), material)
diff --git a/addons/phantom_camera/scripts/gizmos/custom_gizmo.gd.uid b/addons/phantom_camera/scripts/gizmos/custom_gizmo.gd.uid
new file mode 100644
index 0000000..6994efc
--- /dev/null
+++ b/addons/phantom_camera/scripts/gizmos/custom_gizmo.gd.uid
@@ -0,0 +1 @@
+uid://dtl7jcjp6c17i
diff --git a/addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd b/addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd
new file mode 100644
index 0000000..c992320
--- /dev/null
+++ b/addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd
@@ -0,0 +1,12 @@
+extends CustomPluginGizmo
+
+var _spatial_script: Script = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd")
+var _icon: Texture2D = preload("res://addons/phantom_camera/icons/phantom_camera_gizmo.svg")
+
+
+func _init() -> void:
+ set_gizmo_name("PhantomCamera")
+ set_gizmo_spatial_script(_spatial_script)
+ set_gizmo_icon(_icon)
+
+ super()
diff --git a/addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd.uid b/addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd.uid
new file mode 100644
index 0000000..06b8ab6
--- /dev/null
+++ b/addons/phantom_camera/scripts/gizmos/phantom_camera_gizmo_plugin_3d.gd.uid
@@ -0,0 +1 @@
+uid://cbq5f2pi5mesy
diff --git a/addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd b/addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd
new file mode 100644
index 0000000..3dd4d3e
--- /dev/null
+++ b/addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd
@@ -0,0 +1,29 @@
+extends EditorNode3DGizmoPlugin
+
+var _spatial_script: Script = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd")
+var _gizmo_icon: Texture2D = preload("res://addons/phantom_camera/icons/phantom_camera_noise_emitter_gizmo.svg")
+
+var _gizmo_name: StringName = "PhantomCameraNoiseEmitter"
+
+func _init() -> void:
+ create_material("main", Color8(252, 127, 127, 255))
+ create_handle_material("handles")
+ create_icon_material(_gizmo_name, _gizmo_icon, false, Color.WHITE)
+
+
+func _has_gizmo(node: Node3D):
+ return node.get_script() == _spatial_script
+
+
+func _get_gizmo_name() -> String:
+ return _gizmo_name
+
+
+func _redraw(gizmo: EditorNode3DGizmo):
+ gizmo.clear()
+
+ var icon: Material = get_material(_gizmo_name, gizmo)
+ gizmo.add_unscaled_billboard(icon, 0.035)
+
+ #var material = get_material("main", gizmo)
+ #gizmo.add_lines(_draw_frustum(), material)
diff --git a/addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd.uid b/addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd.uid
new file mode 100644
index 0000000..2b93b6c
--- /dev/null
+++ b/addons/phantom_camera/scripts/gizmos/phantom_camera_noise_emitter_gizmo_plugin_3d.gd.uid
@@ -0,0 +1 @@
+uid://dddokcd2ug05i
diff --git a/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd b/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd
new file mode 100644
index 0000000..b6edb7c
--- /dev/null
+++ b/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd
@@ -0,0 +1,150 @@
+@tool
+extends Node
+
+const _CONSTANTS = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+#region Signals
+
+# Noise
+signal noise_2d_emitted(noise_output: Transform2D, emitter_layer: int)
+signal noise_3d_emitted(noise_output: Transform3D, emitter_layer: int)
+
+# PCam Host
+signal pcam_host_added_to_scene(pcam_host: PhantomCameraHost)
+signal pcam_host_removed_from_scene(pcam_host: PhantomCameraHost)
+
+# PCam
+signal pcam_added_to_scene(pcam: Node)
+signal pcam_removed_from_scene(pcam: Node)
+
+# Priority
+signal pcam_priority_changed(pcam: Node)
+signal pcam_visibility_changed(pcam: Node)
+
+signal pcam_teleport(pcam: Node)
+
+# Limit (2D)
+signal limit_2d_changed(side: int, limit: int)
+signal draw_limit_2d(enabled: bool)
+
+# Camera3DResource (3D)
+signal camera_3d_resource_changed(property: String, value: Variant)
+
+# Viewfinder Signals
+signal viewfinder_pcam_host_switch(pcam_host: PhantomCameraHost)
+signal pcam_priority_override(pcam: Node, shouldOverride: bool)
+signal pcam_dead_zone_changed(pcam: Node)
+signal pcam_host_layer_changed(pcam: Node)
+
+#endregion
+
+#region Private Variables
+
+var _phantom_camera_host_list: Array[PhantomCameraHost]
+var _phantom_camera_2d_list: Array[PhantomCamera2D]
+var _phantom_camera_3d_list: Array[Node] ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+
+#endregion
+
+#region Public Variables
+
+var phantom_camera_hosts: Array[PhantomCameraHost]:
+ get:
+ return _phantom_camera_host_list
+
+var phantom_camera_2ds: Array[PhantomCamera2D]:
+ get:
+ return _phantom_camera_2d_list
+
+var phantom_camera_3ds: Array[Node]: ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+ get:
+ return _phantom_camera_3d_list
+
+
+var screen_size: Vector2i
+
+#endregion
+
+#region Private Functions
+
+func _enter_tree() -> void:
+ if not Engine.has_singleton(_CONSTANTS.PCAM_MANAGER_NODE_NAME):
+ Engine.register_singleton(_CONSTANTS.PCAM_MANAGER_NODE_NAME, self)
+ Engine.physics_jitter_fix = 0
+
+
+func _ready() -> void:
+ # Setting default screensize
+ screen_size = Vector2i(
+ ProjectSettings.get_setting("display/window/size/viewport_width"),
+ ProjectSettings.get_setting("display/window/size/viewport_height")
+ )
+
+ # For editor
+ if Engine.is_editor_hint():
+ ProjectSettings.settings_changed.connect(func():
+ screen_size = Vector2i(
+ ProjectSettings.get_setting("display/window/size/viewport_width"),
+ ProjectSettings.get_setting("display/window/size/viewport_height")
+ )
+ )
+ # For runtime
+ else:
+ get_tree().get_root().size_changed.connect(func():
+ screen_size = get_viewport().get_visible_rect().size
+ )
+
+#endregion
+
+#region Public Functions
+
+func pcam_host_added(caller: Node) -> void:
+ if is_instance_of(caller, PhantomCameraHost):
+ _phantom_camera_host_list.append(caller)
+ pcam_host_added_to_scene.emit(caller)
+ else:
+ printerr("This method can only be called from a PhantomCameraHost node")
+
+func pcam_host_removed(caller: Node) -> void:
+ if is_instance_of(caller, PhantomCameraHost):
+ _phantom_camera_host_list.erase(caller)
+ pcam_host_removed_from_scene.emit(caller)
+ else:
+ printerr("This method can only be called from a PhantomCameraHost node")
+
+
+func pcam_added(caller) -> void:
+ if is_instance_of(caller, PhantomCamera2D):
+ _phantom_camera_2d_list.append(caller)
+ pcam_added_to_scene.emit(caller)
+ elif caller.is_class("PhantomCamera3D"): ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+ _phantom_camera_3d_list.append(caller)
+ pcam_added_to_scene.emit(caller)
+
+func pcam_removed(caller) -> void:
+ if is_instance_of(caller, PhantomCamera2D):
+ _phantom_camera_2d_list.erase(caller)
+ pcam_removed_from_scene.emit(caller)
+ elif caller.is_class("PhantomCamera3D"): ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+ _phantom_camera_3d_list.erase(caller)
+ pcam_removed_from_scene.emit(caller)
+ else:
+ printerr("This method can only be called from a PhantomCamera node")
+
+
+func get_phantom_camera_hosts() -> Array[PhantomCameraHost]:
+ return _phantom_camera_host_list
+
+func get_phantom_camera_2ds() -> Array[PhantomCamera2D]:
+ return _phantom_camera_2d_list
+
+func get_phantom_camera_3ds() -> Array: ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+ return _phantom_camera_3d_list
+
+
+func scene_changed() -> void:
+ _phantom_camera_2d_list.clear()
+ _phantom_camera_3d_list.clear()
+ _phantom_camera_host_list.clear()
+
+#endregion
diff --git a/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd.uid b/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd.uid
new file mode 100644
index 0000000..ea18023
--- /dev/null
+++ b/addons/phantom_camera/scripts/managers/phantom_camera_manager.gd.uid
@@ -0,0 +1 @@
+uid://duq6jhf6unyis
diff --git a/addons/phantom_camera/scripts/panel/editor.gd b/addons/phantom_camera/scripts/panel/editor.gd
new file mode 100644
index 0000000..a10018c
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/editor.gd
@@ -0,0 +1,23 @@
+@tool
+extends VBoxContainer
+
+#region Onready
+
+@onready var updater: Control = %UpdateButton
+@onready var viewfinder: Control = %ViewfinderPanel
+
+#endregion
+
+#region Public Variables
+
+var editor_plugin: EditorPlugin
+
+#endregion
+
+
+#region Private Functions
+
+func _ready():
+ updater.editor_plugin = editor_plugin
+
+#endregion
diff --git a/addons/phantom_camera/scripts/panel/editor.gd.uid b/addons/phantom_camera/scripts/panel/editor.gd.uid
new file mode 100644
index 0000000..6be368c
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/editor.gd.uid
@@ -0,0 +1 @@
+uid://cgfwg3paxkj2x
diff --git a/addons/phantom_camera/scripts/panel/updater/download_update_panel.gd b/addons/phantom_camera/scripts/panel/updater/download_update_panel.gd
new file mode 100644
index 0000000..b19beb9
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/updater/download_update_panel.gd
@@ -0,0 +1,162 @@
+#######################################################################
+# Credit goes to the Dialogue Manager plugin for this script
+# Check it out at: https://github.com/nathanhoad/godot_dialogue_manager
+#######################################################################
+
+@tool
+extends Control
+
+#region Constants
+
+const TEMP_FILE_NAME = "user://temp.zip"
+
+#endregion
+
+
+#region Signals
+
+signal failed()
+signal updated(updated_to_version: String)
+
+#endregion
+
+
+#region @onready
+
+#@onready var logo: TextureRect = %Logo
+@onready var _download_verion: Label = %DownloadVersionLabel
+@onready var _download_http_request: HTTPRequest = %DownloadHTTPRequest
+@onready var _download_button: Button = %DownloadButton
+@onready var _download_button_bg: NinePatchRect = %DownloadButtonBG
+@onready var _download_label: Label = %UpdateLabel
+
+@onready var _breaking_label: Label = %BreakingLabel
+@onready var _breaking_margin_container: MarginContainer = %BreakingMarginContainer
+@onready var _breaking_options_button: OptionButton = %BreakingOptionButton
+#@onready var current_version_label: Label = %CurrentVersionLabel
+
+#endregion
+
+
+#region Variables
+
+# Todo - For 4.2 upgrade - Shows current version
+var _download_dialogue: AcceptDialog
+var _button_texture_default: Texture2D = load("res://addons/phantom_camera/assets/PhantomCameraBtnPrimaryDefault.png")
+var _button_texture_hover: Texture2D = load("res://addons/phantom_camera/assets/PhantomCameraBtnPrimaryHover.png")
+
+var next_version_release: Dictionary:
+ set(value):
+ next_version_release = value
+ _download_verion.text = "%s update is available for download" % value.tag_name.substr(1)
+ # Todo - For 4.2 upgrade
+ #current_version_label.text = "Current version is " + editor_plugin.get_version()
+ get:
+ return next_version_release
+
+var _breaking_window_height: float = 520
+var _breaking_window_height_update: float = 600
+
+#endregion
+
+
+#region Private Functions
+
+func _ready() -> void:
+ _download_http_request.request_completed.connect(_on_http_request_request_completed)
+ _download_button.pressed.connect(_on_download_button_pressed)
+ _download_button.mouse_entered.connect(_on_mouse_entered)
+ _download_button.mouse_exited.connect(_on_mouse_exited)
+
+ _breaking_label.hide()
+ _breaking_margin_container.hide()
+ _breaking_options_button.hide()
+
+ _breaking_options_button.item_selected.connect(_on_item_selected)
+
+
+func _on_item_selected(index: int) -> void:
+ if index == 1:
+ _download_button.show()
+ _download_dialogue.size = Vector2(_download_dialogue.size.x, _breaking_window_height_update)
+ else:
+ _download_button.hide()
+ _download_dialogue.size = Vector2(_download_dialogue.size.x, _breaking_window_height)
+
+
+func _on_download_button_pressed() -> void:
+ _download_http_request.request(next_version_release.zipball_url)
+ _download_button.disabled = true
+ _download_label.text = "Downloading..."
+ _download_button_bg.hide()
+
+
+func _on_mouse_entered() -> void:
+ _download_button_bg.set_texture(_button_texture_hover)
+
+
+func _on_mouse_exited() -> void:
+ _download_button_bg.set_texture(_button_texture_default)
+
+
+func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
+ if result != HTTPRequest.RESULT_SUCCESS:
+ failed.emit()
+ return
+
+ # Save the downloaded zip
+ var zip_file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE)
+ zip_file.store_buffer(body)
+ zip_file.close()
+
+ OS.move_to_trash(ProjectSettings.globalize_path("res://addons/phantom_camera"))
+
+ var zip_reader: ZIPReader = ZIPReader.new()
+ zip_reader.open(TEMP_FILE_NAME)
+ var files: PackedStringArray = zip_reader.get_files()
+
+ var base_path = files[1]
+ # Remove archive folder
+ files.remove_at(0)
+ # Remove assets folder
+ files.remove_at(0)
+
+ for path in files:
+ var new_file_path: String = path.replace(base_path, "")
+ if path.ends_with("/"):
+ DirAccess.make_dir_recursive_absolute("res://addons/%s" % new_file_path)
+ else:
+ var file: FileAccess = FileAccess.open("res://addons/%s" % new_file_path, FileAccess.WRITE)
+ file.store_buffer(zip_reader.read_file(path))
+
+ zip_reader.close()
+ DirAccess.remove_absolute(TEMP_FILE_NAME)
+
+ updated.emit(next_version_release.tag_name.substr(1))
+
+
+func _on_notes_button_pressed() -> void:
+ OS.shell_open(next_version_release.html_url)
+
+#endregion
+
+#region Public Functions
+
+func show_updater_warning(next_version_number: Array, current_version_number: Array) -> void:
+ var current_version_number_0: int = current_version_number[0] as int
+ var current_version_number_1: int = current_version_number[1] as int
+
+ var next_version_number_0: int = next_version_number[0] as int # Major release number in the new release
+ var next_version_number_1: int = next_version_number[1] as int # Minor release number in the new release
+
+ if next_version_number_0 > current_version_number_0 or \
+ next_version_number_1 > current_version_number_1:
+ _breaking_label.show()
+ _breaking_margin_container.show()
+ _breaking_options_button.show()
+ _download_button.hide()
+
+ _download_dialogue = get_parent()
+ _download_dialogue.size = Vector2(_download_dialogue.size.x, _breaking_window_height)
+
+#endregion
diff --git a/addons/phantom_camera/scripts/panel/updater/download_update_panel.gd.uid b/addons/phantom_camera/scripts/panel/updater/download_update_panel.gd.uid
new file mode 100644
index 0000000..ff50946
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/updater/download_update_panel.gd.uid
@@ -0,0 +1 @@
+uid://cjblcocen12r3
diff --git a/addons/phantom_camera/scripts/panel/updater/update_button.gd b/addons/phantom_camera/scripts/panel/updater/update_button.gd
new file mode 100644
index 0000000..686535c
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/updater/update_button.gd
@@ -0,0 +1,177 @@
+#######################################################################
+# Credit goes to the Dialogue Manager plugin for this script
+# Check it out at: https://github.com/nathanhoad/godot_dialogue_manager
+#######################################################################
+
+@tool
+extends Button
+
+#region Constants
+
+const REMOTE_RELEASE_URL: StringName = "https://api.github.com/repos/ramokz/phantom-camera/releases"
+const UPDATER_CONSTANTS := preload("res://addons/phantom_camera/scripts/panel/updater/updater_constants.gd")
+
+#endregion
+
+
+#region @onready
+
+@onready var http_request: HTTPRequest = %HTTPRequest
+@onready var download_dialog: AcceptDialog = %DownloadDialog
+@onready var download_update_panel: Control = %DownloadUpdatePanel
+@onready var needs_reload_dialog: AcceptDialog = %NeedsReloadDialog
+@onready var update_failed_dialog: AcceptDialog = %UpdateFailedDialog
+
+#endregion
+
+
+#region Variables
+
+# The main editor plugin
+var editor_plugin: EditorPlugin
+
+var needs_reload: bool = false
+
+# A lambda that gets called just before refreshing the plugin. Return false to stop the reload.
+var on_before_refresh: Callable = func(): return true
+
+#endregion
+
+
+#region Private Functions
+
+func _ready() -> void:
+ hide()
+
+ # Check for updates on GitHub Releases
+ check_for_update()
+
+ pressed.connect(_on_update_button_pressed)
+ http_request.request_completed.connect(_request_request_completed)
+ download_update_panel.updated.connect(_on_download_update_panel_updated)
+ needs_reload_dialog.confirmed.connect(_on_needs_reload_dialog_confirmed)
+
+
+func _request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
+ if result != HTTPRequest.RESULT_SUCCESS: return
+
+ if not editor_plugin: return
+ var current_version: String = editor_plugin.get_version()
+
+ # Work out the next version from the releases information on GitHub
+ var response: Array = JSON.parse_string(body.get_string_from_utf8())
+ if typeof(response) != TYPE_ARRAY: return
+
+ # GitHub releases are in order of creation, not order of version
+ var versions: Array = response.filter(func(release):
+ var version: String = release.tag_name.substr(1)
+ return version_to_number(version) > version_to_number(current_version)
+ )
+
+ if versions.size() > 0:
+ if ProjectSettings.get_setting(UPDATER_CONSTANTS.setting_updater_mode) == 1: ## For console output mode
+
+ print_rich("
+[color=#3AB99A] ********[/color]
+[color=#3AB99A] ************[/color]
+[color=#3AB99A]**************[/color]
+[color=#3AB99A]****** *** *[/color]
+[color=#3AB99A]****** ***[/color]
+[color=#3AB99A]********** *****[/color]
+[color=#3AB99A]******** ***********[/color]
+[color=#3AB99A]******** *********** **[/color]
+[color=#3AB99A]********* **************[/color]
+[color=#3AB99A]********** *************[/color]
+[color=#3AB99A]** ** ** ******* **[/color]
+[font_size=18][b]New Phantom Camera version is available[/b][/font_size]")
+
+ if FileAccess.file_exists("res://dev_scenes/3d/dev_scene_3d.tscn"):
+ print_rich("[font_size=14][color=#EAA15E][b]As you're using a fork of the project, you will need to update it manually[/b][/color][/font_size]")
+
+ print_rich("[font_size=12]If you don't want to see this message, then it can be disabled inside:\n[code]Project Settings/Phantom Camera/Updater/Show New Release Info on Editor Launch in Output[/code]")
+
+ return
+
+ download_update_panel.next_version_release = versions[0]
+ download_update_panel.show_updater_warning(
+ versions[0].tag_name.substr(1).split("."),
+ current_version.split(".")
+ )
+ _set_scale()
+ editor_plugin.panel_button.add_theme_color_override("font_color", Color("#3AB99A"))
+ editor_plugin.panel_button.icon = load("res://addons/phantom_camera/icons/phantom_camera_updater_panel_icon.svg")
+ editor_plugin.panel_button.add_theme_color_override("icon_normal_color", Color("#3AB99A"))
+ show()
+
+
+func _on_update_button_pressed() -> void:
+ if needs_reload:
+ var will_refresh = on_before_refresh.call()
+ if will_refresh:
+ EditorInterface.restart_editor(true)
+ else:
+ _set_scale()
+ download_dialog.popup_centered()
+
+
+func _set_scale() -> void:
+ var scale: float = EditorInterface.get_editor_scale()
+ download_dialog.min_size = Vector2(300, 250) * scale
+
+
+func _on_download_dialog_close_requested() -> void:
+ download_dialog.hide()
+
+
+func _on_download_update_panel_updated(updated_to_version: String) -> void:
+ download_dialog.hide()
+
+ needs_reload_dialog.dialog_text = "Reload to finish update"
+ needs_reload_dialog.ok_button_text = "Reload"
+ needs_reload_dialog.cancel_button_text = "Cancel"
+ needs_reload_dialog.popup_centered()
+
+ needs_reload = true
+ text = "Reload Project"
+
+
+func _on_download_update_panel_failed() -> void:
+ download_dialog.hide()
+ update_failed_dialog.dialog_text = "Updated Failed"
+ update_failed_dialog.popup_centered()
+
+
+func _on_needs_reload_dialog_confirmed() -> void:
+ EditorInterface.restart_editor(true)
+
+
+func _on_timer_timeout() -> void:
+ if not needs_reload:
+ check_for_update()
+
+#endregion
+
+
+#region Public Functions
+
+# Convert a version number to an actually comparable number
+func version_to_number(version: String) -> int:
+ var regex = RegEx.new()
+ regex.compile("[a-zA-Z]+")
+ if regex.search(str(version)): return 0
+
+ var bits = version.split(".")
+ var version_bit: int
+ var multiplier: int = 10000
+ for i in bits.size():
+ version_bit += bits[i].to_int() * multiplier / (10 ** (i))
+
+ return version_bit
+
+
+func check_for_update() -> void:
+ if ProjectSettings.get_setting(UPDATER_CONSTANTS.setting_updater_mode) == 0: return
+
+ http_request.request(REMOTE_RELEASE_URL)
+
+#endregion
diff --git a/addons/phantom_camera/scripts/panel/updater/update_button.gd.uid b/addons/phantom_camera/scripts/panel/updater/update_button.gd.uid
new file mode 100644
index 0000000..cb88ddc
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/updater/update_button.gd.uid
@@ -0,0 +1 @@
+uid://bwc42i46603qn
diff --git a/addons/phantom_camera/scripts/panel/updater/updater_constants.gd b/addons/phantom_camera/scripts/panel/updater/updater_constants.gd
new file mode 100644
index 0000000..94ac2ad
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/updater/updater_constants.gd
@@ -0,0 +1,8 @@
+extends RefCounted
+
+# Plugin Project Settings Sections
+const setting_phantom_camera: StringName = "phantom_camera/"
+const setting_updater_name: StringName = setting_phantom_camera + "updater/"
+
+# Updater Settings
+const setting_updater_mode: StringName = setting_updater_name + "updater_mode"
diff --git a/addons/phantom_camera/scripts/panel/updater/updater_constants.gd.uid b/addons/phantom_camera/scripts/panel/updater/updater_constants.gd.uid
new file mode 100644
index 0000000..1f86170
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/updater/updater_constants.gd.uid
@@ -0,0 +1 @@
+uid://c8qkbc38waor2
diff --git a/addons/phantom_camera/scripts/panel/viewfinder/host_list.gd b/addons/phantom_camera/scripts/panel/viewfinder/host_list.gd
new file mode 100644
index 0000000..662e598
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/viewfinder/host_list.gd
@@ -0,0 +1,112 @@
+@tool
+extends VBoxContainer
+
+#region Constants
+
+const _constants := preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+const _host_list_item: PackedScene = preload("res://addons/phantom_camera/panel/viewfinder/host_list/host_list_item.tscn")
+
+#endregion
+
+signal pcam_host_removed(pcam_host: PhantomCameraHost)
+
+@onready var _host_list_button: Button = %HostListButton
+@onready var _host_list_scroll_container: ScrollContainer = %ScrollContainer
+@onready var _host_list_item_container: VBoxContainer = %HostListContainer
+
+var _host_list_open: bool = false
+
+var _bottom_offset_value: float
+
+var _pcam_host_list: Array[PhantomCameraHost]
+var _pcam_manager: Node
+
+var _viewfinder_panel: Control
+
+#region Private Functions
+
+func _ready() -> void:
+ _host_list_button.pressed.connect(_host_list_button_pressed)
+ if Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME):
+ _pcam_manager = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME)
+ _pcam_manager.pcam_host_removed_from_scene.connect(_remove_pcam_host)
+
+ if not get_parent() is Control: return # To prevent errors when opening the scene on its own
+ _viewfinder_panel = get_parent()
+ _viewfinder_panel.resized.connect(_set_offset_top)
+
+ _host_list_item_container.resized.connect(_set_offset_top)
+
+
+func _set_offset_top() -> void:
+ offset_top = _set_host_list_size()
+
+
+func _host_list_button_pressed() -> void:
+ _host_list_open = !_host_list_open
+
+ var tween: Tween = create_tween()
+ var max_duration: float = 0.6
+
+ # 300 being the minimum size of the viewfinder's height
+ var duration: float = clampf(
+ max_duration / (300 / _host_list_item_container.size.y),
+ 0.3,
+ max_duration)
+
+ tween.tween_property(self, "offset_top", _set_host_list_size(), duration)\
+ .set_ease(Tween.EASE_OUT)\
+ .set_trans(Tween.TRANS_QUINT)
+
+
+func _set_host_list_size() -> float:
+ if not _host_list_open:
+ return clampf(
+ _viewfinder_panel.size.y - \
+ _host_list_item_container.size.y - \
+ _host_list_button.size.y - 20,
+ 0,
+ INF
+ )
+ else:
+ return (_viewfinder_panel.size.y - _host_list_button.size.y / 2)
+
+
+func _remove_pcam_host(pcam_host: PhantomCameraHost) -> void:
+ if _pcam_host_list.has(pcam_host):
+ _pcam_host_list.erase(pcam_host)
+
+ var freed_pcam_host: Control
+ for host_list_item_instance in _host_list_item_container.get_children():
+ if not host_list_item_instance.pcam_host == pcam_host: continue
+ freed_pcam_host = host_list_item_instance
+ host_list_item_instance.queue_free()
+
+#endregion
+
+#region Public Functions
+
+func add_pcam_host(pcam_host: PhantomCameraHost, is_default: bool) -> void:
+ if _pcam_host_list.has(pcam_host): return
+
+ _pcam_host_list.append(pcam_host)
+
+ var host_list_item_instance: PanelContainer = _host_list_item.instantiate()
+ var switch_pcam_host_button: Button = host_list_item_instance.get_node("%SwitchPCamHost")
+ if is_default: switch_pcam_host_button.button_pressed = true
+
+ if not pcam_host.tree_exiting.is_connected(_remove_pcam_host):
+ pcam_host.tree_exiting.connect(_remove_pcam_host.bind(pcam_host))
+
+ host_list_item_instance.pcam_host = pcam_host
+
+ _host_list_item_container.add_child(host_list_item_instance)
+
+
+func clear_pcam_host_list() -> void:
+ _pcam_host_list.clear()
+
+ for host_list_item_instance in _host_list_item_container.get_children():
+ host_list_item_instance.queue_free()
+
+#endregion
diff --git a/addons/phantom_camera/scripts/panel/viewfinder/host_list.gd.uid b/addons/phantom_camera/scripts/panel/viewfinder/host_list.gd.uid
new file mode 100644
index 0000000..6923d3e
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/viewfinder/host_list.gd.uid
@@ -0,0 +1 @@
+uid://c84cxry3t35ny
diff --git a/addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd b/addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd
new file mode 100644
index 0000000..5707974
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd
@@ -0,0 +1,58 @@
+@tool
+extends Control
+
+const button_group_resource: ButtonGroup = preload("res://addons/phantom_camera/panel/viewfinder/host_list/host_list_item_group.tres")
+const _constants = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+@onready var select_pcam_host: Button = %SelectPCamHost
+@onready var switch_pcam_host: Button = %SwitchPCamHost
+
+var pcam_host: PhantomCameraHost:
+ set(value):
+ pcam_host = value
+ if not is_instance_valid(value): return
+ if not pcam_host.renamed.is_connected(_rename_pcam_host):
+ pcam_host.renamed.connect(_rename_pcam_host)
+ pcam_host.has_error.connect(_pcam_host_has_error)
+ get:
+ return pcam_host
+
+var _pcam_manager: Node
+
+#region Private fucntions
+
+func _ready() -> void:
+ switch_pcam_host.button_group = button_group_resource
+ select_pcam_host.pressed.connect(_select_pcam)
+ switch_pcam_host.pressed.connect(_switch_pcam_host)
+
+ if not is_instance_valid(pcam_host): return
+ switch_pcam_host.text = pcam_host.name
+
+ _pcam_host_has_error()
+
+
+func _pcam_host_has_error() -> void:
+ if pcam_host.show_warning:
+ %ErrorPCamHost.visible = true
+ else:
+ %ErrorPCamHost.visible = false
+
+
+func _rename_pcam_host() -> void:
+ switch_pcam_host.text = pcam_host.name
+
+
+func _select_pcam() -> void:
+ EditorInterface.get_selection().clear()
+ EditorInterface.get_selection().add_node(pcam_host)
+
+
+func _switch_pcam_host() -> void:
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ if not is_instance_valid(_pcam_manager):
+ _pcam_manager = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME)
+
+ _pcam_manager.viewfinder_pcam_host_switch.emit(pcam_host)
+
+#endregion
diff --git a/addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd.uid b/addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd.uid
new file mode 100644
index 0000000..9df2919
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/viewfinder/host_list_item.gd.uid
@@ -0,0 +1 @@
+uid://bv24ubx8mutw7
diff --git a/addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd b/addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd
new file mode 100644
index 0000000..fe163a7
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd
@@ -0,0 +1,605 @@
+@tool
+extends Control
+
+#region Constants
+
+const _constants = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+# TODO - Should be in a central location
+const _camera_2d_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/viewfinder/Camera2DIcon.svg")
+const _camera_3d_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/viewfinder/Camera3DIcon.svg")
+const _pcam_host_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/phantom_camera_host.svg")
+const _pcam_2D_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/phantom_camera_2d.svg")
+const _pcam_3D_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/phantom_camera_3d.svg")
+
+const _overlay_color_alpha: float = 0.3
+
+#endregion
+
+#region @onready
+
+@onready var dead_zone_center_hbox: VBoxContainer = %DeadZoneCenterHBoxContainer
+@onready var dead_zone_center_center_panel: Panel = %DeadZoneCenterCenterPanel
+@onready var dead_zone_left_center_panel: Panel = %DeadZoneLeftCenterPanel
+@onready var dead_zone_right_center_panel: Panel = %DeadZoneRightCenterPanel
+@onready var target_point: Panel = %TargetPoint
+
+@onready var aspect_ratio_container: AspectRatioContainer = %AspectRatioContainer
+@onready var camera_viewport_panel: Panel = aspect_ratio_container.get_child(0)
+@onready var _viewfinder: Control = %Viewfinder
+@onready var _dead_zone_h_box_container: Control = %DeadZoneHBoxContainer
+@onready var sub_viewport: SubViewport = %SubViewport
+
+@onready var _empty_state_control: Control = %EmptyStateControl
+@onready var _empty_state_icon: TextureRect = %EmptyStateIcon
+@onready var _empty_state_text: RichTextLabel = %EmptyStateText
+@onready var _add_node_button: Button = %AddNodeButton
+@onready var _add_node_button_text: RichTextLabel = %AddNodeTypeText
+
+@onready var _priority_override_button: Button = %PriorityOverrideButton
+@onready var _priority_override_name_label: Label = %PriorityOverrideNameLabel
+
+@onready var _camera_2d: Camera2D = %Camera2D
+
+@onready var _pcam_host_list: VBoxContainer = %PCamHostList
+
+#endregion
+
+#region Private Variables
+
+var _no_open_scene_icon: CompressedTexture2D = preload("res://addons/phantom_camera/icons/viewfinder/SceneTypesIcon.svg")
+var _no_open_scene_string: String = "[b]2D[/b] or [b]3D[/b] scene open"
+
+var _selected_camera: Node
+var _active_pcam: Node
+
+var _is_2d: bool
+
+var _pcam_manager: Node
+
+var _root_node: Node
+
+#endregion
+
+#region Public Variables
+
+var pcam_host_group: Array[PhantomCameraHost]
+
+var is_scene: bool
+
+var viewfinder_visible: bool
+
+var min_horizontal: float
+var max_horizontal: float
+var min_vertical: float
+var max_vertical: float
+
+var pcam_host: PhantomCameraHost
+
+#endregion
+
+
+#region Private Functions
+
+func _ready() -> void:
+ if not Engine.is_editor_hint():
+ set_process(true)
+ camera_viewport_panel.self_modulate.a = 0
+
+ _root_node = get_tree().current_scene
+
+ if _root_node is Node2D || _root_node is Node3D:
+ %SubViewportContainer.visible = false
+ if _root_node is Node2D:
+ _is_2d = true
+ else:
+ _is_2d = false
+
+ _set_viewfinder(_root_node, false)
+
+ if not Engine.is_editor_hint():
+ _empty_state_control.visible = false
+
+ _priority_override_button.visible = false
+
+ # Triggered when viewport size is changed in Project Settings
+ ProjectSettings.settings_changed.connect(_settings_changed)
+
+ # PCam Host List
+ _pcam_host_list.visible = false
+ _assign_manager()
+ _visibility_check()
+
+
+func _pcam_host_switch(new_pcam_host: PhantomCameraHost) -> void:
+ _set_viewfinder_camera(new_pcam_host, true)
+
+
+func _exit_tree() -> void:
+ if aspect_ratio_container.resized.is_connected(_resized):
+ aspect_ratio_container.resized.disconnect(_resized)
+
+ if _add_node_button.pressed.is_connected(_visibility_check):
+ _add_node_button.pressed.disconnect(_visibility_check)
+
+ if is_instance_valid(_active_pcam):
+ if _active_pcam.dead_zone_changed.is_connected(_on_dead_zone_changed):
+ _active_pcam.dead_zone_changed.disconnect(_on_dead_zone_changed)
+
+ if _priority_override_button.pressed.is_connected(_select_override_pcam):
+ _priority_override_button.pressed.disconnect(_select_override_pcam)
+
+
+func _process(_delta: float) -> void:
+ if Engine.is_editor_hint() and not viewfinder_visible: return
+ if not is_instance_valid(_active_pcam): return
+
+ var unprojected_position_clamped: Vector2 = Vector2(
+ clamp(_active_pcam.viewport_position.x, min_horizontal, max_horizontal),
+ clamp(_active_pcam.viewport_position.y, min_vertical, max_vertical)
+ )
+
+ if not Engine.is_editor_hint():
+ target_point.position = camera_viewport_panel.size * unprojected_position_clamped - target_point.size / 2
+
+ if not _is_2d: return
+ if not is_instance_valid(pcam_host): return
+ if not is_instance_valid(pcam_host.camera_2d): return
+
+ var window_size_height: float = ProjectSettings.get_setting("display/window/size/viewport_height")
+ sub_viewport.size_2d_override = sub_viewport.size * (window_size_height / sub_viewport.size.y)
+
+ _camera_2d.global_transform = pcam_host.camera_2d.global_transform
+ _camera_2d.offset = pcam_host.camera_2d.offset
+ _camera_2d.zoom = pcam_host.camera_2d.zoom
+ _camera_2d.ignore_rotation = pcam_host.camera_2d.ignore_rotation
+ _camera_2d.anchor_mode = pcam_host.camera_2d.anchor_mode
+ _camera_2d.limit_left = pcam_host.camera_2d.limit_left
+ _camera_2d.limit_top = pcam_host.camera_2d.limit_top
+ _camera_2d.limit_right = pcam_host.camera_2d.limit_right
+ _camera_2d.limit_bottom = pcam_host.camera_2d.limit_bottom
+
+
+func _settings_changed() -> void:
+ var viewport_width: float = ProjectSettings.get_setting("display/window/size/viewport_width")
+ var viewport_height: float = ProjectSettings.get_setting("display/window/size/viewport_height")
+ var ratio: float = viewport_width / viewport_height
+ aspect_ratio_container.set_ratio(ratio)
+ camera_viewport_panel.size.x = viewport_width / (viewport_height / sub_viewport.size.y)
+
+ # Applies Project Settings to Viewport
+ sub_viewport.canvas_item_default_texture_filter = ProjectSettings.get_setting("rendering/textures/canvas_textures/default_texture_filter")
+
+ # TODO - Add resizer for Framed Viewfinder
+
+
+func _visibility_check() -> void:
+ if not viewfinder_visible: return
+
+ var pcam_host: PhantomCameraHost
+ var has_camera: bool = false
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+
+ if not Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).get_phantom_camera_hosts().is_empty():
+ has_camera = true
+ pcam_host = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).get_phantom_camera_hosts()[0]
+
+ var root: Node = EditorInterface.get_edited_scene_root()
+ if root is Node2D:
+ var camera_2d: Camera2D
+
+ if has_camera:
+ camera_2d = pcam_host.camera_2d
+ else:
+ camera_2d = _get_camera_2d()
+
+ _is_2d = true
+ is_scene = true
+ _add_node_button.visible = true
+ _check_camera(root, camera_2d)
+ elif root is Node3D:
+ var camera_3d: Camera3D
+ if has_camera:
+ camera_3d = pcam_host.camera_3d
+ elif root.get_viewport() != null:
+ if root.get_viewport().get_camera_3d() != null:
+ camera_3d = root.get_viewport().get_camera_3d()
+
+ _is_2d = false
+ is_scene = true
+ _add_node_button.visible = true
+ _check_camera(root, camera_3d)
+ else:
+ # Is not a 2D or 3D scene
+ is_scene = false
+ _set_empty_viewfinder_state(_no_open_scene_string, _no_open_scene_icon)
+ _add_node_button.visible = false
+
+ # Checks if a new scene is created and changes viewfinder accordingly
+ if not get_tree().node_added.is_connected(_node_added_to_scene):
+ get_tree().node_added.connect(_node_added_to_scene)
+
+ if not _priority_override_button.pressed.is_connected(_select_override_pcam):
+ _priority_override_button.pressed.connect(_select_override_pcam)
+
+
+func _node_added_to_scene(node: Node) -> void:
+ if node is Node2D or node is Node3D:
+ get_tree().node_added.disconnect(_node_added_to_scene)
+ _visibility_check()
+
+
+func _get_camera_2d() -> Camera2D:
+ var edited_scene_root: Node = EditorInterface.get_edited_scene_root()
+
+ if edited_scene_root == null: return null
+
+ var viewport: Viewport = edited_scene_root.get_viewport()
+ if viewport == null: return null
+
+ var viewport_rid: RID = viewport.get_viewport_rid()
+ if viewport_rid == null: return null
+
+ var camerasGroupName: String = "__cameras_%d" % viewport_rid.get_id()
+ var cameras: Array[Node] = get_tree().get_nodes_in_group(camerasGroupName)
+
+ for camera in cameras:
+ if camera is Camera2D and camera.is_current:
+ return camera
+
+ return null
+
+
+func _check_camera(root: Node, camera: Node) -> void:
+ var camera_string: String
+ var pcam_string: String
+ var color: Color
+ var camera_icon: CompressedTexture2D
+ var pcam_icon: CompressedTexture2D
+
+ if _is_2d:
+ camera_string = _constants.CAMERA_2D_NODE_NAME
+ pcam_string = _constants.PCAM_2D_NODE_NAME
+ color = _constants.COLOR_2D
+ camera_icon = _camera_2d_icon
+ pcam_icon = _pcam_2D_icon
+ else:
+ camera_string = _constants.CAMERA_3D_NODE_NAME
+ pcam_string = _constants.PCAM_3D_NODE_NAME
+ color = _constants.COLOR_3D
+ camera_icon = _camera_3d_icon
+ pcam_icon = _pcam_3D_icon
+
+ if camera:
+# Has Camera
+ if camera.get_children().size() > 0:
+ for cam_child in camera.get_children():
+ if cam_child is PhantomCameraHost:
+ pcam_host = cam_child
+
+ if pcam_host:
+ if get_tree().root.get_node(_constants.PCAM_MANAGER_NODE_NAME).get_phantom_camera_2ds() or \
+ get_tree().root.get_node(_constants.PCAM_MANAGER_NODE_NAME).get_phantom_camera_3ds():
+ # Pcam exists in tree
+ _set_viewfinder(root, true)
+ _set_viewfinder_state()
+ %NoSupportMsg.visible = false
+ else:
+# No PCam in scene
+ _update_button(pcam_string, pcam_icon, color)
+ _set_empty_viewfinder_state(pcam_string, pcam_icon)
+ else:
+# No PCamHost in scene
+ _update_button(_constants.PCAM_HOST_NODE_NAME, _pcam_host_icon, _constants.PCAM_HOST_COLOR)
+ _set_empty_viewfinder_state(_constants.PCAM_HOST_NODE_NAME, _pcam_host_icon)
+ else:
+# No PCamHost in scene
+ _update_button(_constants.PCAM_HOST_NODE_NAME, _pcam_host_icon, _constants.PCAM_HOST_COLOR)
+ _set_empty_viewfinder_state(_constants.PCAM_HOST_NODE_NAME, _pcam_host_icon)
+ else:
+# No Camera
+ _update_button(camera_string, camera_icon, color)
+ _set_empty_viewfinder_state(camera_string, camera_icon)
+
+
+func _update_button(text: String, icon: CompressedTexture2D, color: Color) -> void:
+ _add_node_button_text.set_text("[center]Add [img=32]" + icon.resource_path + "[/img] [b]"+ text + "[/b][/center]");
+ var button_theme_hover: StyleBoxFlat = _add_node_button.get_theme_stylebox("hover")
+ button_theme_hover.border_color = color
+ _add_node_button.add_theme_stylebox_override("hover", button_theme_hover)
+
+
+func _set_viewfinder_state() -> void:
+ _empty_state_control.visible = false
+ _viewfinder.visible = true
+
+ if is_instance_valid(_active_pcam):
+ if _active_pcam.get_follow_mode() == _active_pcam.FollowMode.FRAMED:
+ _dead_zone_h_box_container.visible = true
+ target_point.visible = true
+ else:
+ _dead_zone_h_box_container.visible = false
+ target_point.visible = false
+
+
+func _set_empty_viewfinder_state(text: String, icon: CompressedTexture2D) -> void:
+ _viewfinder.visible = false
+ _framed_view_visible(false)
+
+ _empty_state_control.visible = true
+ _empty_state_icon.texture = icon
+ if icon == _no_open_scene_icon:
+ _empty_state_text.set_text("[center]No " + text + "[/center]")
+ else:
+ _empty_state_text.set_text("[center]No [b]" + text + "[/b] in scene[/center]")
+
+ if _add_node_button.pressed.is_connected(_add_node):
+ _add_node_button.pressed.disconnect(_add_node)
+
+ _add_node_button.pressed.connect(_add_node.bind(text))
+
+
+func _add_node(node_type: String) -> void:
+ var scene_root: Node = EditorInterface.get_edited_scene_root()
+
+ match node_type:
+ _no_open_scene_string:
+ pass
+ _constants.CAMERA_2D_NODE_NAME:
+ var camera: Camera2D = Camera2D.new()
+ _instantiate_node(scene_root, camera, _constants.CAMERA_2D_NODE_NAME)
+ _constants.CAMERA_3D_NODE_NAME:
+ var camera: Camera3D = Camera3D.new()
+ _instantiate_node(scene_root, camera, _constants.CAMERA_3D_NODE_NAME)
+ _constants.PCAM_HOST_NODE_NAME:
+ var pcam_host: PhantomCameraHost = PhantomCameraHost.new()
+ var camera_owner: Node
+ if _is_2d:
+ camera_owner = _get_camera_2d()
+ else:
+ camera_owner = get_tree().get_edited_scene_root().get_viewport().get_camera_3d()
+ _instantiate_node(
+ scene_root,
+ pcam_host,
+ _constants.PCAM_HOST_NODE_NAME,
+ camera_owner
+ )
+ _constants.PCAM_2D_NODE_NAME:
+ var pcam_2D: PhantomCamera2D = PhantomCamera2D.new()
+ _instantiate_node(scene_root, pcam_2D, _constants.PCAM_2D_NODE_NAME)
+ _constants.PCAM_3D_NODE_NAME:
+ var pcam_3D: PhantomCamera3D = PhantomCamera3D.new()
+ _instantiate_node(scene_root, pcam_3D, _constants.PCAM_3D_NODE_NAME)
+
+ _visibility_check()
+
+
+func _instantiate_node(scene_root: Node, node: Node, name: String, parent: Node = scene_root) -> void:
+ node.set_name(name)
+ parent.add_child(node)
+ node.owner = scene_root
+
+
+func _set_viewfinder(root: Node, editor: bool) -> void:
+ pcam_host_group = get_tree().root.get_node(_constants.PCAM_MANAGER_NODE_NAME).get_phantom_camera_hosts()
+ if pcam_host_group.size() != 0:
+ if pcam_host_group.size() == 1:
+ _pcam_host_list.visible = false
+ _set_viewfinder_camera(pcam_host_group[0], editor)
+ else:
+ _pcam_host_list.visible = true
+ _set_viewfinder_camera(pcam_host_group[0], editor)
+ for i in pcam_host_group.size():
+ var is_default: bool = false
+ if i == 0:
+ is_default = true
+ _pcam_host_list.add_pcam_host(pcam_host_group[i], is_default)
+
+
+func _set_viewfinder_camera(new_pcam_host: PhantomCameraHost, editor: bool) -> void:
+ pcam_host = new_pcam_host
+
+ if _is_2d:
+ _selected_camera = pcam_host.camera_2d
+
+ if editor:
+ sub_viewport.disable_3d = true
+ pcam_host = pcam_host
+ _camera_2d.zoom = pcam_host.camera_2d.zoom
+ _camera_2d.offset = pcam_host.camera_2d.offset
+ _camera_2d.ignore_rotation = pcam_host.camera_2d.ignore_rotation
+
+ sub_viewport.world_2d = pcam_host.camera_2d.get_world_2d()
+ sub_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
+ sub_viewport.render_target_clear_mode = SubViewport.CLEAR_MODE_ALWAYS
+ sub_viewport.size_2d_override_stretch = true
+ else:
+ _selected_camera = pcam_host.camera_3d
+ if editor:
+ var camera_3d_rid: RID = _selected_camera.get_camera_rid()
+ sub_viewport.disable_3d = false
+ sub_viewport.world_3d = pcam_host.camera_3d.get_world_3d()
+ RenderingServer.viewport_attach_camera(sub_viewport.get_viewport_rid(), camera_3d_rid)
+
+ if _selected_camera.keep_aspect == Camera3D.KeepAspect.KEEP_HEIGHT:
+ aspect_ratio_container.set_stretch_mode(AspectRatioContainer.STRETCH_HEIGHT_CONTROLS_WIDTH)
+ else:
+ aspect_ratio_container.set_stretch_mode(AspectRatioContainer.STRETCH_WIDTH_CONTROLS_HEIGHT)
+
+ set_process(true)
+
+ if not pcam_host.viewfinder_update.is_connected(_on_update_editor_viewfinder):
+ pcam_host.viewfinder_update.connect(_on_update_editor_viewfinder)
+
+ if not pcam_host.viewfinder_disable_dead_zone.is_connected(_disconnect_dead_zone):
+ pcam_host.viewfinder_disable_dead_zone.connect(_disconnect_dead_zone)
+
+ if not aspect_ratio_container.resized.is_connected(_resized):
+ aspect_ratio_container.resized.connect(_resized)
+
+ if is_instance_valid(pcam_host.get_active_pcam()):
+ _active_pcam = pcam_host.get_active_pcam()
+ else:
+ _framed_view_visible(false)
+ _active_pcam = null
+ return
+
+ if not _active_pcam.follow_mode == PhantomCamera2D.FollowMode.FRAMED: return
+
+ _framed_view_visible(true)
+ _on_dead_zone_changed()
+ _connect_dead_zone()
+
+
+func _connect_dead_zone() -> void:
+ if not _active_pcam and is_instance_valid(pcam_host.get_active_pcam()):
+ _active_pcam = pcam_host.get_active_pcam()
+
+ if not _active_pcam.dead_zone_changed.is_connected(_on_dead_zone_changed):
+ _active_pcam.dead_zone_changed.connect(_on_dead_zone_changed)
+
+ _framed_view_visible(true)
+ _viewfinder.visible = true
+ _on_dead_zone_changed()
+
+func _disconnect_dead_zone() -> void:
+ if not is_instance_valid(_active_pcam): return
+ _framed_view_visible(_is_framed_pcam())
+
+ if _active_pcam.follow_mode_changed.is_connected(_check_follow_mode):
+ _active_pcam.follow_mode_changed.disconnect(_check_follow_mode)
+
+ if _active_pcam.dead_zone_changed.is_connected(_on_dead_zone_changed):
+ _active_pcam.dead_zone_changed.disconnect(_on_dead_zone_changed)
+
+
+func _resized() -> void:
+ _on_dead_zone_changed()
+
+
+func _is_framed_pcam() -> bool:
+ if not is_instance_valid(pcam_host): return false
+ _active_pcam = pcam_host.get_active_pcam()
+ if not is_instance_valid(_active_pcam): return false
+ if not _active_pcam.follow_mode == PhantomCamera2D.FollowMode.FRAMED: return false
+
+ return true
+
+
+func _framed_view_visible(should_show: bool) -> void:
+ if should_show:
+ target_point.visible = true
+ _dead_zone_h_box_container.visible = true
+ else:
+ target_point.visible = false
+ _dead_zone_h_box_container.visible = false
+
+
+func _on_dead_zone_changed() -> void:
+ if not is_instance_valid(_active_pcam): return
+ if not _active_pcam.follow_mode == _active_pcam.FollowMode.FRAMED: return
+
+ # Waits until the camera_viewport_panel has been resized when launching the game
+ if camera_viewport_panel.size.x == 0:
+ await camera_viewport_panel.resized
+
+ if not _active_pcam == pcam_host.get_active_pcam():
+ _active_pcam == pcam_host.get_active_pcam()
+
+ var dead_zone_width: float = _active_pcam.dead_zone_width * camera_viewport_panel.size.x
+ var dead_zone_height: float = _active_pcam.dead_zone_height * camera_viewport_panel.size.y
+ dead_zone_center_hbox.set_custom_minimum_size(Vector2(dead_zone_width, 0))
+ dead_zone_center_center_panel.set_custom_minimum_size(Vector2(0, dead_zone_height))
+ dead_zone_left_center_panel.set_custom_minimum_size(Vector2(0, dead_zone_height))
+ dead_zone_right_center_panel.set_custom_minimum_size(Vector2(0, dead_zone_height))
+
+ min_horizontal = 0.5 - _active_pcam.dead_zone_width / 2
+ max_horizontal = 0.5 + _active_pcam.dead_zone_width / 2
+ min_vertical = 0.5 - _active_pcam.dead_zone_height / 2
+ max_vertical = 0.5 + _active_pcam.dead_zone_height / 2
+
+
+func _check_follow_mode() -> void:
+ _framed_view_visible(_is_framed_pcam())
+
+
+func _on_update_editor_viewfinder(check_framed_view: bool = false) -> void:
+ _active_pcam = pcam_host.get_active_pcam()
+
+ if not is_instance_valid(_active_pcam): return
+
+ if not _active_pcam.follow_mode_changed.is_connected(_check_follow_mode):
+ _active_pcam.follow_mode_changed.connect(_check_follow_mode)
+
+ if _active_pcam.priority_override:
+ _priority_override_button.visible = true
+ _priority_override_name_label.set_text(_active_pcam.name)
+ _priority_override_button.set_tooltip_text(_active_pcam.name)
+ else:
+ _priority_override_button.visible = false
+
+ _framed_view_visible(false)
+ if not check_framed_view: return
+ if _is_framed_pcam(): _connect_dead_zone()
+
+
+func _select_override_pcam() -> void:
+ EditorInterface.get_selection().clear()
+ EditorInterface.get_selection().add_node(_active_pcam)
+
+
+func _assign_manager() -> void:
+ if not is_instance_valid(_pcam_manager):
+ if Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME):
+ _pcam_manager = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME)
+ _pcam_manager.pcam_host_added_to_scene.connect(_pcam_changed)
+ _pcam_manager.pcam_host_removed_from_scene.connect(_pcam_host_removed_from_scene)
+
+ _pcam_manager.pcam_added_to_scene.connect(_pcam_changed)
+ _pcam_manager.pcam_removed_from_scene.connect(_pcam_changed)
+
+ _pcam_manager.viewfinder_pcam_host_switch.connect(_pcam_host_switch)
+
+
+func _pcam_host_removed_from_scene(pcam_host: PhantomCameraHost) -> void:
+ if _pcam_manager.phantom_camera_hosts.size() < 2:
+ _pcam_host_list.visible = false
+
+ _visibility_check()
+
+
+func _pcam_changed(pcam: Node) -> void:
+ _visibility_check()
+
+#endregion
+
+
+#region Public Functions
+
+func set_visibility(visible: bool) -> void:
+ if visible:
+ viewfinder_visible = true
+ _visibility_check()
+ else:
+ viewfinder_visible = false
+
+
+func update_dead_zone() -> void:
+ _set_viewfinder(_root_node, true)
+
+
+## TODO - Signal can be added directly to this file with the changes in Godot 4.5 (https://github.com/godotengine/godot/pull/102986)
+func scene_changed(scene_root: Node) -> void:
+ _assign_manager()
+ _priority_override_button.visible = false
+ _pcam_host_list.clear_pcam_host_list()
+
+ if not scene_root is Node2D and not scene_root is Node3D:
+ is_scene = false
+ _pcam_host_list.visible = false
+ _set_empty_viewfinder_state(_no_open_scene_string, _no_open_scene_icon)
+ _add_node_button.visible = false
+ else:
+ _visibility_check()
+
+#endregion
diff --git a/addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd.uid b/addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd.uid
new file mode 100644
index 0000000..f115630
--- /dev/null
+++ b/addons/phantom_camera/scripts/panel/viewfinder/viewfinder.gd.uid
@@ -0,0 +1 @@
+uid://drmv3363t8amc
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd
new file mode 100644
index 0000000..4f31c05
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd
@@ -0,0 +1,1607 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_2d.svg")
+class_name PhantomCamera2D
+extends Node2D
+
+## Controls a scene's [Camera2D] and applies logic to it.
+##
+## The scene's [param Camera2D] will follow the position of the
+## [param PhantomCamera2D] with the highest priority.
+## Each instance can have different positional and rotational logic applied
+## to them.
+
+#region Constants
+
+const _constants := preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+#endregion
+
+#region Signals
+
+## Emitted when the [param PhantomCamera2D] becomes active.
+signal became_active
+
+## Emitted when the [param PhantomCamera2D] becomes inactive.
+signal became_inactive
+
+## Emitted when the follow_mode changes.
+## Note: This is for internal use only
+signal follow_mode_changed
+
+## Emitted when [member follow_target] changes.
+signal follow_target_changed
+
+## Emitted when dead zones changes.[br]
+## [b]Note:[/b] Only applicable in [param Framed] [enum FollowMode].
+signal dead_zone_changed
+
+## Emitted when a target touches the edge of the dead zone in [param Framed] [enum FollowMode].
+signal dead_zone_reached(side: Vector2)
+
+## Emitted when the [param Camera2D] starts to tween to another [param PhantomCamera2D].
+signal tween_started
+
+## Emitted when the [param Camera2D] is to tweening towards another [param PhantomCamera2D].
+signal is_tweening
+
+## Emitted when the tween is interrupted due to another [param PhantomCamera2D]
+## becoming active. The argument is the [param PhantomCamera2D] that interrupted
+## the tween.
+signal tween_interrupted(pcam_2d: PhantomCamera2D)
+
+## Emitted when the [param Camera2D] completes its tween to the
+## [param PhantomCamera2D].
+signal tween_completed
+
+## Emitted when Noise should be applied to the Camera2D.
+signal noise_emitted(noise_output: Transform2D)
+
+signal physics_target_changed
+
+#endregion
+
+#region Enums
+
+## Determines the positional logic for a given [param PhantomCamera2D]
+## [br][br]
+## The different modes have different functionalities and purposes, so choosing
+## the correct one depends on what each [param PhantomCamera2D] is meant to do.
+enum FollowMode {
+ NONE = 0, ## Default - No follow logic is applied.
+ GLUED = 1, ## Sticks to its target.
+ SIMPLE = 2, ## Follows its target with an optional offset.
+ GROUP = 3, ## Follows multiple targets with option to dynamically reframe itself.
+ PATH = 4, ## Follows a target while being positionally confined to a [Path2D] node.
+ FRAMED = 5, ## Applies a dead zone on the frame and only follows its target when it tries to leave it.
+}
+
+## Determines how often an inactive [param PhantomCamera2D] should update
+## its positional and rotational values. This is meant to reduce the amount
+## of calculations inactive [param PhantomCamera2D] are doing when idling to
+## improve performance.
+enum InactiveUpdateMode {
+ ALWAYS, ## Always updates the [param PhantomCamera2D], even when it's inactive.
+ NEVER, ## Never updates the [param PhantomCamera2D] when it's inactive. Reduces the amount of computational resources when inactive.
+# EXPONENTIALLY,
+}
+
+enum FollowLockAxis {
+ NONE = 0,
+ X = 1,
+ Y = 2,
+ XY = 3,
+}
+
+#endregion
+
+#region Exported Properties
+
+## To quickly preview a [param PhantomCamera2D] without adjusting its
+## [member priority], this property allows the selected PCam to ignore the
+## Priority system altogether and forcefully become the active one. It's
+## partly designed to work within the Viewfinder, and will be disabled when
+## running a build export of the game.
+@export var priority_override: bool = false:
+ set(value):
+ priority_override = value
+ if Engine.is_editor_hint():
+ if value:
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).pcam_priority_override.emit(self, true)
+ else:
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).pcam_priority_override.emit(self, false)
+ get:
+ return priority_override
+
+
+## It defines which [param PhantomCamera2D] a scene's [param Camera2D] should
+## be corresponding with and be attached to. This is decided by the PCam with
+## the highest [param Priority].
+## [br][br]
+## Changing [param Priority] will send an event to the scene's
+## [PhantomCameraHost], which will then determine whether if the
+## [param Priority] value is greater than or equal to the currently
+## highest [param PhantomCamera2D]'s in the scene. The [param PhantomCamera2D]
+## with the highest value will then reattach the [param Camera2D] accordingly.
+@export var priority: int = 0:
+ set = set_priority,
+ get = get_priority
+
+
+## Determines the positional logic for a given [param PhantomCamera2D].
+## The different modes have different functionalities and purposes, so
+## choosing the correct one depends on what each [param PhantomCamera2D]
+## is meant to do.
+@export var follow_mode: FollowMode = FollowMode.NONE:
+ set(value):
+ follow_mode = value
+
+ if follow_mode == FollowMode.NONE:
+ _should_follow = false
+ top_level = false
+ _is_parents_physics()
+ notify_property_list_changed()
+ return
+
+ match follow_mode:
+ FollowMode.PATH:
+ if is_instance_valid(follow_path):
+ _should_follow_checker()
+ FollowMode.GROUP:
+ _follow_targets_size_check()
+ _:
+ _should_follow_checker()
+
+ if follow_mode == FollowMode.FRAMED:
+ if _follow_framed_initial_set and follow_target:
+ _follow_framed_initial_set = false
+ dead_zone_changed.connect(_on_dead_zone_changed)
+ else:
+ if dead_zone_changed.is_connected(_on_dead_zone_changed):
+ dead_zone_changed.disconnect(_on_dead_zone_changed)
+
+ top_level = true
+ follow_mode_changed.emit()
+ notify_property_list_changed()
+ get:
+ return follow_mode
+
+## Determines which target should be followed.
+## The [param Camera2D] will follow the position of the Follow Target
+## based on the [member follow_mode] type and its parameters.
+@export var follow_target: Node2D = null:
+ set = set_follow_target,
+ get = get_follow_target
+
+### Defines the targets that the [param PhantomCamera2D] should be following.
+@export var follow_targets: Array[Node2D] = []:
+ set = set_follow_targets,
+ get = get_follow_targets
+
+## Determines the [Path2D] the [param PhantomCamera2D]
+## should be bound to.
+## The [param PhantomCamera2D] will follow the position of the
+## [member follow_target] while sticking to the closest point on this path.
+@export var follow_path: Path2D = null:
+ set = set_follow_path,
+ get = get_follow_path
+
+
+## Applies a zoom level to the [param PhantomCamera2D], which effectively
+## overrides the [param zoom] property of the [param Camera2D] node.
+@export var zoom: Vector2 = Vector2.ONE:
+ set = set_zoom,
+ get = get_zoom
+
+
+## If enabled, will snap the [param Camera2D] to whole pixels as it moves.
+## [br][br]
+## This should be particularly useful in pixel art projects,
+## where assets should always be aligned to the monitor's pixels to avoid
+## unintended stretching.
+@export var snap_to_pixel: bool = false:
+ set = set_snap_to_pixel,
+ get = get_snap_to_pixel
+
+
+## Enables a preview of what the [PhantomCamera2D] will see in the
+## scene. It works identically to how a [param Camera2D] shows which area
+## will be visible during runtime. Likewise, this too will be affected by the
+## [member zoom] property and the [param viewport_width] and
+## [param Viewport Height] defined in the [param Project Settings].
+@export var frame_preview: bool = true:
+ set(value):
+ frame_preview = value
+ queue_redraw()
+ get:
+ return frame_preview
+
+
+## Defines how the [param PhantomCamera2D] transition between one another.
+## Changing the tween values for a given [param PhantomCamera2D]
+## determines how transitioning to that instance will look like.
+## This is a resource type that can be either used for one
+## [param PhantomCamera] or reused across multiple - both 2D and 3D.
+## By default, all [param PhantomCameras] will use a [param linear]
+## transition, [param easeInOut] ease with a [param 1s] duration.
+@export var tween_resource: PhantomCameraTween = PhantomCameraTween.new():
+ set = set_tween_resource,
+ get = get_tween_resource
+
+## If enabled, the moment a [param PhantomCamera2D] is instantiated into
+## a scene, and has the highest priority, it will perform its tween transition.
+## This is most obvious if a [param PhantomCamera2D] has a long duration and
+## is attached to a playable character that can be moved the moment a scene
+## is loaded. Disabling the [param tween_on_load] property will
+## disable this behaviour and skip the tweening entirely when instantiated.
+@export var tween_on_load: bool = true:
+ set = set_tween_on_load,
+ get = get_tween_on_load
+
+
+## Determines how often an inactive [param PhantomCamera2D] should update
+## its positional and rotational values. This is meant to reduce the amount
+## of calculations inactive [param PhantomCamera2Ds] are doing when idling
+## to improve performance.
+@export var inactive_update_mode: InactiveUpdateMode = InactiveUpdateMode.ALWAYS
+
+
+## Determines which layers this [param PhantomCamera2D] should be able to communicate with [PhantomCameraHost] nodes.[br]
+## A corresponding layer needs to be set on the [PhantomCameraHost] node.
+@export_flags_2d_render var host_layers: int = 1:
+ set = set_host_layers,
+ get = get_host_layers
+
+
+@export_group("Follow Parameters")
+## Offsets the [member follow_target] position.
+@export var follow_offset: Vector2 = Vector2.ZERO:
+ set = set_follow_offset,
+ get = get_follow_offset
+
+## Applies a damping effect on the [param Camera2D]'s movement.
+## Leading to heavier / slower camera movement as the targeted node moves around.
+## This is useful to avoid sharp and rapid camera movement.
+@export var follow_damping: bool = false:
+ set = set_follow_damping,
+ get = get_follow_damping
+
+## Defines the damping amount. The ideal range should be somewhere between 0-1.[br][br]
+## The damping amount can be specified in the individual axis.[br][br]
+## [b]Lower value[/b] = faster / sharper camera movement.[br]
+## [b]Higher value[/b] = slower / heavier camera movement.
+@export var follow_damping_value: Vector2 = Vector2(0.1, 0.1):
+ set = set_follow_damping_value,
+ get = get_follow_damping_value
+
+## Prevents the [param PhantomCamera2D] from moving in a designated axis.
+## This can be enabled or disabled at runtime or from the editor directly.
+@export var follow_axis_lock: FollowLockAxis = FollowLockAxis.NONE:
+ set = set_lock_axis,
+ get = get_lock_axis
+var _follow_axis_is_locked: bool = false
+var _follow_axis_lock_value: Vector2 = Vector2.ZERO
+
+
+@export_subgroup("Follow Group")
+## Enables the [param PhantomCamera2D] to dynamically zoom in and out based on
+## the targets' distances between each other.
+## Once enabled, the [param Camera2D] will stay as zoomed in as possible,
+## limited by the [member auto_zoom_max] and start zooming out as the targets
+## move further apart, limited by the [member auto_zoom_min].
+## Note: Enabling this property hides and disables the [member zoom] property
+## as this effectively overrides that value.
+@export var auto_zoom: bool = false:
+ set = set_auto_zoom,
+ get = get_auto_zoom
+
+## Sets the param minimum zoom amount, in other words how far away the
+## [param Camera2D] can be from scene.[br][br]
+## This only works when [member auto_zoom] is enabled.
+@export var auto_zoom_min: float = 1:
+ set = set_auto_zoom_min,
+ get = get_auto_zoom_min
+
+## Sets the maximum zoom amount, in other words how close the [param Camera2D]
+## can move towards the scene.[br][br]
+## This only works when [member auto_zoom] is enabled.
+@export var auto_zoom_max: float = 5:
+ set = set_auto_zoom_max,
+ get = get_auto_zoom_max
+
+## Determines how close to the edges the targets are allowed to be.
+## This is useful to avoid targets being cut off at the edges of the screen.
+## [br][br]
+## The Vector4 parameter order goes: [param Left] - [param Top] - [param Right]
+## - [param Bottom].
+@export var auto_zoom_margin: Vector4 = Vector4.ZERO:
+ set = set_auto_zoom_margin,
+ get = get_auto_zoom_margin
+
+
+@export_subgroup("Dead Zones")
+## Defines the horizontal dead zone area. While the target is within it, the
+## [param PhantomCamera2D] will not move in the horizontal axis.
+## If the targeted node leaves the horizontal bounds, the
+## [param PhantomCamera2D] will follow the target horizontally to keep
+## it within bounds.
+@export_range(0, 1) var dead_zone_width: float = 0:
+ set(value):
+ dead_zone_width = value
+ dead_zone_changed.emit()
+ get:
+ return dead_zone_width
+
+## Defines the vertical dead zone area. While the target is within it, the
+## [param PhantomCamera2D] will not move in the vertical axis.
+## If the targeted node leaves the vertical bounds, the
+## [param PhantomCamera2D] will follow the target horizontally to keep
+## it within bounds.
+@export_range(0, 1) var dead_zone_height: float = 0:
+ set(value):
+ dead_zone_height = value
+ dead_zone_changed.emit()
+ get:
+ return dead_zone_height
+
+## Enables the [param dead zones] to be visible when running the game from the editor.
+## [br]
+## [param dead zones] will never be visible in build exports.
+@export var show_viewfinder_in_play: bool = false
+
+
+@export_group("Limit")
+
+## Shows the [param Camera2D]'s built-in limit border.[br]
+## The [param PhantomCamera2D] and [param Camera2D] can move around anywhere within it.
+@export var draw_limits: bool = false:
+ set(value):
+ _draw_limits = value
+ if Engine.is_editor_hint():
+ _draw_camera_2d_limit()
+ get:
+ return _draw_limits
+
+## Defines the left side of the [param Camera2D] limit.
+## The camera will not be able to move past this point.
+@export var limit_left: int = -10000000:
+ set = set_limit_left,
+ get = get_limit_left
+## Defines the top side of the [param Camera2D] limit.
+## The camera will not be able to move past this point.
+@export var limit_top: int = -10000000:
+ set = set_limit_top,
+ get = get_limit_top
+## Defines the right side of the [param Camera2D] limit.
+## The camera will not be able to move past this point.
+@export var limit_right: int = 10000000:
+ set = set_limit_right,
+ get = get_limit_right
+## Defines the bottom side of the [param Camera2D] limit.
+## The camera will not be able to move past this point.
+@export var limit_bottom: int = 10000000:
+ set = set_limit_bottom,
+ get = get_limit_bottom
+
+## Allows for setting either a [TileMap], [TileMapLayer] or [CollisionShape2D] node to
+## automatically apply a limit size instead of manually adjusting the Left,
+## Top, Right and Left properties.[br][br]
+## [b]TileMap / TileMapLayer[/b][br]
+## The Limit will update after the [TileSet] of the [TileMap] / [TileMapLayer] has changed.[br]
+## [b]Note:[/b] The limit size will only update after closing the TileMap editor
+## bottom panel.
+## [br][br]
+## [b]CollisionShape2D[/b][br]
+## The limit will update in realtime as the Shape2D changes its size.
+## Note: For performance reasons, resizing the [Shape2D] during runtime will not change the Limits sides.
+@export_node_path("TileMap", "Node2D", "CollisionShape2D") var limit_target: NodePath = NodePath(""):
+ set = set_limit_target,
+ get = get_limit_target
+
+## Applies an offset to the [TileMap]/[TileMapLayer] Limit or [Shape2D] Limit.
+## The values goes from [param Left], [param Top], [param Right]
+## and [param Bottom].
+@export var limit_margin: Vector4i = Vector4.ZERO:
+ set = set_limit_margin,
+ get = get_limit_margin
+#@export var limit_smoothed: bool = false: # TODO - Needs proper support
+ #set = set_limit_smoothing,
+ #get = get_limit_smoothing
+
+@export_group("Noise")
+## Applies a noise, or shake, to a [Camera2D].[br]
+## Once set, the noise will run continuously after the tween to the [PhantomCamera2D] is complete.
+@export var noise: PhantomCameraNoise2D = null:
+ set = set_noise,
+ get = get_noise
+
+## If true, will trigger the noise while in the editor.[br]
+## Useful in cases where you want to temporarily disable the noise in the editor without removing
+## the resource.[br][br]
+## [b]Note:[/b] This property has no effect on runtime behaviour.
+@export var _preview_noise: bool = true:
+ set(value):
+ _preview_noise = value
+ if not value:
+ _transform_noise = Transform2D()
+
+## Enable a corresponding layer for a [member PhantomCameraNoiseEmitter2D.noise_emitter_layer]
+## to make this [PhantomCamera2D] be affect by it.
+@export_flags_2d_render var noise_emitter_layer: int = 0:
+ set = set_noise_emitter_layer,
+ get = get_noise_emitter_layer
+
+#region Private Variables
+
+var _is_active: bool = false
+
+var _should_follow: bool = false
+var _follow_framed_offset: Vector2 = Vector2.ZERO
+var _follow_target_physics_based: bool = false
+var _physics_interpolation_enabled: bool = false # NOTE - Enable for Godot 4.3 and when PhysicsInterpolationMode bug is resolved
+
+var _has_multiple_follow_targets: bool = false
+var _follow_targets_single_target_index: int = 0
+var _follow_targets: Array[Node2D] = []
+
+var _follow_velocity_ref: Vector2 = Vector2.ZERO # Stores and applies the velocity of the movement
+
+var _has_follow_path: bool = false
+
+var _tween_skip: bool = false
+
+## Defines the position of the [member follow_target] within the viewport.[br]
+## This is only used for when [member follow_mode] is set to [param Framed].
+var _follow_framed_initial_set: bool = false
+
+static var _draw_limits: bool = false
+
+var _limit_sides: Vector4i = _limit_sides_default
+var _limit_sides_default: Vector4i = Vector4i(-10000000, -10000000, 10000000, 10000000)
+
+var _limit_node: Node2D = null
+var _tile_size_perspective_scaler: Vector2 = Vector2.ONE
+
+var _limit_inactive_pcam: bool = false
+
+var _target_transform: Transform2D = Transform2D()
+
+var _transform_output: Transform2D = Transform2D()
+var _transform_noise: Transform2D = Transform2D()
+
+var _has_noise_resource: bool = false
+
+# NOTE - Temp solution until Godot has better plugin autoload recognition out-of-the-box.
+var _phantom_camera_manager: Node = null
+
+#endregion
+
+#region Public Variables
+
+var tween_duration: float:
+ set = set_tween_duration,
+ get = get_tween_duration
+var tween_transition: PhantomCameraTween.TransitionType:
+ set = set_tween_transition,
+ get = get_tween_transition
+var tween_ease: PhantomCameraTween.EaseType:
+ set = set_tween_ease,
+ get = get_tween_ease
+
+var viewport_position: Vector2
+
+#endregion
+
+#region Private Functions
+
+func _validate_property(property: Dictionary) -> void:
+ ################
+ ## Follow Target
+ ################
+ if property.name == "follow_target":
+ if follow_mode == FollowMode.NONE or \
+ follow_mode == FollowMode.GROUP:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ elif property.name == "follow_path" and \
+ follow_mode != FollowMode.PATH:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+ ####################
+ ## Follow Parameters
+ ####################
+ if follow_mode == FollowMode.NONE:
+ match property.name:
+ "follow_offset", \
+ "follow_damping", \
+ "follow_damping_value", \
+ "follow_axis_lock":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_offset":
+ if follow_mode == FollowMode.PATH or \
+ follow_mode == FollowMode.GLUED:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_damping_value" and not follow_damping:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ###############
+ ## Follow Group
+ ###############
+ if follow_mode != FollowMode.GROUP:
+ match property.name:
+ "follow_targets", \
+ "auto_zoom":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if not auto_zoom or follow_mode != FollowMode.GROUP:
+ match property.name:
+ "auto_zoom_min", \
+ "auto_zoom_max", \
+ "auto_zoom_margin":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ################
+ ## Follow Framed
+ ################
+ if not follow_mode == FollowMode.FRAMED:
+ match property.name:
+ "dead_zone_width", \
+ "dead_zone_height", \
+ "show_viewfinder_in_play":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ #######
+ ## Zoom
+ #######
+ if property.name == "zoom" and follow_mode == FollowMode.GROUP and auto_zoom:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ########
+ ## Limit
+ ########
+ if is_instance_valid(_limit_node):
+ match property.name:
+ "limit_left", \
+ "limit_top", \
+ "limit_right", \
+ "limit_bottom":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "limit_margin" and not _limit_node:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _enter_tree() -> void:
+ _phantom_camera_manager = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME)
+ _tween_skip = !tween_on_load
+
+ _phantom_camera_manager.pcam_added(self)
+
+ priority_override = false
+
+ _should_follow_checker()
+ if follow_mode == FollowMode.GROUP:
+ _follow_targets_size_check()
+ elif follow_mode == FollowMode.NONE:
+ _is_parents_physics()
+
+ if not visibility_changed.is_connected(_check_visibility):
+ visibility_changed.connect(_check_visibility)
+
+ update_limit_all_sides()
+
+
+
+func _exit_tree() -> void:
+ if not follow_mode == FollowMode.GROUP:
+ follow_targets = []
+
+ if not is_instance_valid(_phantom_camera_manager): return
+ _phantom_camera_manager.pcam_removed(self)
+
+
+func _ready() -> void:
+ if is_instance_valid(follow_target):
+ _transform_output.origin = _get_target_position_offset()
+ else:
+ _transform_output = global_transform
+
+ _phantom_camera_manager.noise_2d_emitted.connect(_noise_emitted)
+
+ if not Engine.is_editor_hint():
+ _preview_noise = true
+
+ if follow_mode == FollowMode.GROUP:
+ _follow_targets_size_check()
+
+
+func _process(delta: float) -> void:
+ if _follow_target_physics_based or _is_active: return
+ process_logic(delta)
+
+
+func _physics_process(delta: float) -> void:
+ if not _follow_target_physics_based or _is_active: return
+ process_logic(delta)
+
+
+func process_logic(delta: float) -> void:
+ if _is_active:
+ if _has_noise_resource and _preview_noise:
+ _transform_noise = noise.get_noise_transform(delta)
+ else:
+ match inactive_update_mode:
+ InactiveUpdateMode.NEVER: return
+ InactiveUpdateMode.ALWAYS:
+ # Only triggers if limit isn't default
+ if _limit_inactive_pcam:
+ global_position = _set_limit_clamp_position(global_position)
+ # InactiveUpdateMode.EXPONENTIALLY:
+ # TODO - Trigger positional updates less frequently as more PCams gets added
+
+ _limit_checker()
+
+ if _should_follow:
+ _follow(delta)
+ else:
+ _transform_output = global_transform
+
+ if _follow_axis_is_locked:
+ match follow_axis_lock:
+ FollowLockAxis.X:
+ _transform_output.origin.x = _follow_axis_lock_value.x
+ FollowLockAxis.Y:
+ _transform_output.origin.y = _follow_axis_lock_value.y
+ FollowLockAxis.XY:
+ _transform_output.origin.x = _follow_axis_lock_value.x
+ _transform_output.origin.y = _follow_axis_lock_value.y
+
+
+func _limit_checker() -> void:
+ ## TODO - Needs to see if this can be triggerd only from CollisionShape2D Transform changes
+ if not Engine.is_editor_hint(): return
+ if draw_limits:
+ update_limit_all_sides()
+
+
+func _follow(delta: float) -> void:
+ _set_follow_position()
+ _interpolate_position(_target_transform.origin, delta)
+
+
+func _set_follow_position() -> void:
+ match follow_mode:
+ FollowMode.GLUED:
+ _target_transform.origin = follow_target.global_position
+
+ FollowMode.SIMPLE:
+ _target_transform.origin = _get_target_position_offset()
+
+ FollowMode.GROUP:
+ if _has_multiple_follow_targets:
+ var rect: Rect2 = Rect2(_follow_targets[0].global_position, Vector2.ZERO)
+ for target in _follow_targets:
+ rect = rect.expand(target.global_position)
+ if auto_zoom:
+ rect = rect.grow_individual(
+ auto_zoom_margin.x,
+ auto_zoom_margin.y,
+ auto_zoom_margin.z,
+ auto_zoom_margin.w
+ )
+
+ if rect.size.x > rect.size.y * _phantom_camera_manager.screen_size.aspect():
+ zoom = clamp(_phantom_camera_manager.screen_size.x / rect.size.x, auto_zoom_min, auto_zoom_max) * Vector2.ONE
+ else:
+ zoom = clamp(_phantom_camera_manager.screen_size.y / rect.size.y, auto_zoom_min, auto_zoom_max) * Vector2.ONE
+ _target_transform.origin = rect.get_center() + follow_offset
+ else:
+ _target_transform.origin = follow_targets[_follow_targets_single_target_index].global_position + follow_offset
+
+ FollowMode.PATH:
+ var path_position: Vector2 = follow_path.global_position
+
+ _target_transform.origin = \
+ follow_path.curve.get_closest_point(
+ _get_target_position_offset() - path_position
+ ) + path_position
+
+ FollowMode.FRAMED:
+ if not Engine.is_editor_hint():
+ if not _is_active:
+ _target_transform.origin = _get_target_position_offset()
+ else:
+ viewport_position = (get_follow_target().get_global_transform_with_canvas().get_origin() + follow_offset) / get_viewport_rect().size
+ var framed_side_offset: Vector2 = _get_framed_side_offset()
+
+ if framed_side_offset != Vector2.ZERO:
+ var glo_pos: Vector2
+ var target_position: Vector2 = _get_target_position_offset() + _follow_framed_offset
+
+ if dead_zone_width == 0 || dead_zone_height == 0:
+ if dead_zone_width == 0 && dead_zone_height != 0:
+ _target_transform.origin = _get_target_position_offset()
+ elif dead_zone_width != 0 && dead_zone_height == 0:
+ glo_pos = _get_target_position_offset()
+ glo_pos.x += target_position.x - global_position.x
+ _target_transform.origin = glo_pos
+ else:
+ _target_transform.origin = _get_target_position_offset()
+
+ # If a horizontal dead zone is reached
+ if framed_side_offset.x != 0 and framed_side_offset.y == 0:
+ _target_transform.origin.y = _transform_output.origin.y
+ _target_transform.origin.x = target_position.x
+ _follow_framed_offset.y = global_position.y - _get_target_position_offset().y
+ dead_zone_reached.emit(Vector2(framed_side_offset.x, 0))
+ # If a vertical dead zone is reached
+ elif framed_side_offset.x == 0 and framed_side_offset.y != 0:
+ _target_transform.origin.x = _transform_output.origin.x
+ _target_transform.origin.y = target_position.y
+ _follow_framed_offset.x = global_position.x - _get_target_position_offset().x
+ dead_zone_reached.emit(Vector2(0, framed_side_offset.y))
+ # If a deadzone corner is reached
+ else:
+ _target_transform.origin = target_position
+ dead_zone_reached.emit(Vector2(framed_side_offset.x, framed_side_offset.y))
+ else:
+ _follow_framed_offset = _transform_output.origin - _get_target_position_offset()
+ return
+ else:
+ _target_transform.origin = _get_target_position_offset()
+
+
+func _set_follow_velocity(index: int, value: float):
+ _follow_velocity_ref[index] = value
+
+
+func _interpolate_position(target_position: Vector2, delta: float) -> void:
+ if _limit_inactive_pcam and not _tween_skip:
+ target_position = _set_limit_clamp_position(target_position)
+
+ global_position = target_position
+ if follow_damping:
+ var output_position: Vector2
+ for i in 2:
+ output_position[i] = _smooth_damp(
+ global_position[i],
+ _transform_output.origin[i],
+ i,
+ _follow_velocity_ref[i],
+ _set_follow_velocity,
+ follow_damping_value[i],
+ delta
+ )
+ _transform_output = Transform2D(global_rotation, output_position)
+ else:
+ _transform_output = Transform2D(global_rotation, target_position)
+
+
+func _smooth_damp(target_axis: float, self_axis: float, index: int, current_velocity: float, set_velocity: Callable, damping_time: float, delta: float) -> float:
+ damping_time = maxf(0.0001, damping_time)
+ var omega: float = 2 / damping_time
+ var x: float = omega * delta
+ var exponential: float = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x)
+ var diff: float = self_axis - target_axis
+ var _target_axis: float = target_axis
+
+ var max_change: float = INF * damping_time
+ diff = clampf(diff, -max_change, max_change)
+ target_axis = self_axis - diff
+
+ var temp: float = (current_velocity + omega * diff) * delta
+ set_velocity.call(index, (current_velocity - omega * temp) * exponential)
+ var output: float = target_axis + (diff + temp) * exponential
+
+ ## To prevent overshooting
+ if (_target_axis - self_axis > 0.0) == (output > _target_axis):
+ output = _target_axis
+ set_velocity.call(index, (output - _target_axis) / delta)
+
+ return output
+
+
+func _set_limit_clamp_position(value: Vector2) -> Vector2:
+ var camera_frame_rect_size: Vector2 = _camera_frame_rect().size
+ value.x = clampf(value.x, _limit_sides.x + camera_frame_rect_size.x / 2, _limit_sides.z - camera_frame_rect_size.x / 2)
+ value.y = clampf(value.y, _limit_sides.y + camera_frame_rect_size.y / 2, _limit_sides.w - camera_frame_rect_size.y / 2)
+ return value
+
+
+func _draw() -> void:
+ if not Engine.is_editor_hint(): return
+
+ if frame_preview and not _is_active:
+ draw_rect(_camera_frame_rect(), Color("3ab99a"), false, 2)
+
+
+func _camera_frame_rect() -> Rect2:
+ var screen_size_zoom: Vector2 = Vector2(_phantom_camera_manager.screen_size.x / get_zoom().x, _phantom_camera_manager.screen_size.y / get_zoom().y)
+
+ return Rect2(-screen_size_zoom / 2, screen_size_zoom)
+
+
+func _on_tile_map_changed() -> void:
+ update_limit_all_sides()
+
+
+func _get_target_position_offset() -> Vector2:
+ return follow_target.global_position + follow_offset
+
+
+func _on_dead_zone_changed() -> void:
+ global_position = _get_target_position_offset()
+
+
+func _get_framed_side_offset() -> Vector2:
+ var frame_out_bounds: Vector2
+
+ if viewport_position.x < 0.5 - dead_zone_width / 2:
+ # Is outside left edge
+ frame_out_bounds.x = -1
+
+ if viewport_position.y < 0.5 - dead_zone_height / 2:
+ # Is outside top edge
+ frame_out_bounds.y = 1
+
+ if viewport_position.x > 0.5 + dead_zone_width / 2:
+ # Is outside right edge
+ frame_out_bounds.x = 1
+
+ if viewport_position.y > 0.5001 + dead_zone_height / 2: # 0.501 to resolve an issue where the bottom vertical Dead Zone never becoming 0 when the Dead Zone Vertical parameter is set to 0
+ # Is outside bottom edge
+ frame_out_bounds.y = -1
+
+ return frame_out_bounds
+
+
+func _draw_camera_2d_limit() -> void:
+ if not is_instance_valid(_phantom_camera_manager): return
+ _phantom_camera_manager.draw_limit_2d.emit(draw_limits)
+
+
+func _check_limit_is_not_default() -> void:
+ if _limit_sides == _limit_sides_default:
+ _limit_inactive_pcam = false
+ else:
+ _limit_inactive_pcam = true
+
+
+func _check_visibility() -> void:
+ _phantom_camera_manager.pcam_visibility_changed.emit(self)
+
+
+func _follow_target_tree_exiting(target: Node) -> void:
+ if target == follow_target:
+ _should_follow = false
+ if _follow_targets.has(target):
+ _follow_targets.erase(target)
+
+
+func _should_follow_checker() -> void:
+ if follow_mode == FollowMode.NONE:
+ _should_follow = false
+ return
+
+ if not follow_mode == FollowMode.GROUP:
+ if is_instance_valid(follow_target):
+ _should_follow = true
+ else:
+ _should_follow = false
+
+
+func _follow_targets_size_check() -> void:
+ var targets_size: int = 0
+ _follow_target_physics_based = false
+ _follow_targets = []
+ for i in follow_targets.size():
+ if follow_targets[i] == null: continue
+ if follow_targets[i].is_inside_tree():
+ _follow_targets.append(follow_targets[i])
+ targets_size += 1
+ _follow_targets_single_target_index = i
+ _check_physics_body(follow_targets[i])
+ if not follow_targets[i].tree_exiting.is_connected(_follow_target_tree_exiting):
+ follow_targets[i].tree_exiting.connect(_follow_target_tree_exiting.bind(follow_targets[i]))
+
+ match targets_size:
+ 0:
+ _should_follow = false
+ _has_multiple_follow_targets = false
+ 1:
+ _should_follow = true
+ _has_multiple_follow_targets = false
+ _:
+ _should_follow = true
+ _has_multiple_follow_targets = true
+
+
+func _noise_emitted(emitter_noise_output: Transform2D, emitter_layer: int) -> void:
+ if noise_emitter_layer & emitter_layer != 0:
+ noise_emitted.emit(emitter_noise_output)
+
+
+func _set_layer(current_layers: int, layer_number: int, value: bool) -> int:
+ var mask: int = current_layers
+
+ # From https://github.com/godotengine/godot/blob/51991e20143a39e9ef0107163eaf283ca0a761ea/scene/3d/camera_3d.cpp#L638
+ if layer_number < 1 or layer_number > 20:
+ printerr("Render layer must be between 1 and 20.")
+ else:
+ if value:
+ mask |= 1 << (layer_number - 1)
+ else:
+ mask &= ~(1 << (layer_number - 1))
+
+ return mask
+
+
+func _check_physics_body(target: Node2D) -> void:
+ if target is PhysicsBody2D:
+ var show_jitter_tips := ProjectSettings.get_setting("phantom_camera/tips/show_jitter_tips")
+ var physics_interpolation_enabled := ProjectSettings.get_setting("physics/common/physics_interpolation")
+
+ ## NOTE - Feature Toggle
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor < 3:
+ if show_jitter_tips == null: # Default value is null when referencing custom Project Setting
+ print_rich("Following a [b]PhysicsBody2D[/b] node will likely result in jitter - on lower physics ticks in particular.")
+ print_rich("If possible, will recommend upgrading to Godot 4.3, as it has built-in support for 2D Physics Interpolation, which will mitigate this issue.")
+ print_rich("Otherwise, try following the guide on the [url=https://phantom-camera.dev/support/faq#i-m-seeing-jitter-what-can-i-do]documentation site[/url] for better results.")
+ print_rich("This tip can be disabled from within [code]Project Settings / Phantom Camera / Tips / Show Jitter Tips[/code]")
+ return
+ ## NOTE - Only supported in Godot 4.3 or above
+ elif not physics_interpolation_enabled and show_jitter_tips == null: # Default value is null when referencing custom Project Setting
+ printerr("Physics Interpolation is disabled in the Project Settings, recommend enabling it to smooth out physics-based camera movement")
+ print_rich("This tip can be disabled from within [code]Project Settings / Phantom Camera / Tips / Show Jitter Tips[/code]")
+ _follow_target_physics_based = true
+ else:
+ _is_parents_physics(target)
+ physics_target_changed.emit()
+
+
+func _is_parents_physics(target: Node = self) -> void:
+ var current_node: Node = target
+ while current_node:
+ current_node = current_node.get_parent()
+ if not current_node is PhysicsBody2D: continue
+ _follow_target_physics_based = true
+
+#endregion
+
+
+#region Public Functions
+
+## Updates the limit sides based what has been set to define it
+## This should be automatic, but can be called manully if need be.
+func update_limit_all_sides() -> void:
+ var limit_rect: Rect2
+
+ if not is_instance_valid(_limit_node):
+ _limit_sides.x = limit_left
+ _limit_sides.y = limit_top
+ _limit_sides.z = limit_right
+ _limit_sides.w = limit_bottom
+ elif _limit_node is TileMap or _limit_node.is_class("TileMapLayer"):
+ var tile_map := _limit_node
+
+ if not tile_map.tile_set: return # TODO: This should be removed once https://github.com/godotengine/godot/issues/96898 is resolved
+
+ var tile_map_size: Vector2 = Vector2(tile_map.get_used_rect().size) * Vector2(tile_map.tile_set.tile_size) * tile_map.get_scale()
+ var tile_map_position: Vector2 = tile_map.global_position + Vector2(tile_map.get_used_rect().position) * Vector2(tile_map.tile_set.tile_size) * tile_map.get_scale()
+
+ ## Calculates the Rect2 based on the Tile Map position and size + margin
+ limit_rect = Rect2(
+ tile_map_position + Vector2(limit_margin.x, limit_margin.y),
+ tile_map_size - Vector2(limit_margin.x, limit_margin.y) - Vector2(limit_margin.z, limit_margin.w)
+ )
+
+ # Left
+ _limit_sides.x = roundi(limit_rect.position.x)
+ # Top
+ _limit_sides.y = roundi(limit_rect.position.y)
+ # Right
+ _limit_sides.z = roundi(limit_rect.position.x + limit_rect.size.x)
+ # Bottom
+ _limit_sides.w = roundi(limit_rect.position.y + limit_rect.size.y)
+ elif _limit_node is CollisionShape2D:
+ var collision_shape_2d: CollisionShape2D = _limit_node as CollisionShape2D
+
+ if not collision_shape_2d.get_shape(): return
+
+ var shape_2d: Shape2D = collision_shape_2d.get_shape()
+ var shape_2d_size: Vector2 = shape_2d.get_rect().size
+ var shape_2d_position: Vector2 = collision_shape_2d.global_position + Vector2(shape_2d.get_rect().position)
+
+ ## Calculates the Rect2 based on the Tile Map position and size
+ limit_rect = Rect2(shape_2d_position, shape_2d_size)
+
+ ## Calculates the Rect2 based on the Tile Map position and size + margin
+ limit_rect = Rect2(
+ limit_rect.position + Vector2(limit_margin.x, limit_margin.y),
+ limit_rect.size - Vector2(limit_margin.x, limit_margin.y) - Vector2(limit_margin.z, limit_margin.w)
+ )
+
+ # Left
+ _limit_sides.x = roundi(limit_rect.position.x)
+ # Top
+ _limit_sides.y = roundi(limit_rect.position.y)
+ # Right
+ _limit_sides.z = roundi(limit_rect.position.x + limit_rect.size.x)
+ # Bottom
+ _limit_sides.w = roundi(limit_rect.position.y + limit_rect.size.y)
+
+ _check_limit_is_not_default()
+ if not _is_active: return
+ if not is_instance_valid(_phantom_camera_manager): return
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_LEFT, _limit_sides.x)
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_TOP, _limit_sides.y)
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_RIGHT, _limit_sides.z)
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_BOTTOM, _limit_sides.w)
+ _phantom_camera_manager.draw_limit_2d.emit(draw_limits)
+
+
+func reset_limit() -> void:
+ if not is_instance_valid(_phantom_camera_manager): return
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_LEFT, _limit_sides_default.x)
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_TOP, _limit_sides_default.y)
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_RIGHT, _limit_sides_default.z)
+ _phantom_camera_manager.limit_2d_changed.emit(SIDE_BOTTOM, _limit_sides_default.w)
+ _phantom_camera_manager.draw_limit_2d.emit(draw_limits)
+
+
+## Assigns the value of the [param has_tweened] property.
+## [b][color=yellow]Important:[/color][/b] This value can only be changed
+## from the [PhantomCameraHost] script.
+func set_tween_skip(caller: Node, value: bool) -> void:
+ if is_instance_of(caller, PhantomCameraHost):
+ _tween_skip = value
+ else:
+ printerr("Can only be called PhantomCameraHost class")
+## Returns the current [param has_tweened] value.
+func get_tween_skip() -> bool:
+ return _tween_skip
+
+## Returns the [Transform3D] value based on the [member follow_mode] / [member look_at_mode] target value.
+func get_transform_output() -> Transform2D:
+ return _transform_output
+
+
+## Returns the noise [Transform3D] value.
+func get_noise_transform() -> Transform2D:
+ return _transform_noise
+
+
+## Emits a noise based on a custom [Transform2D] value.[br]
+## Use this function if you wish to make use of external noise patterns from, for example, other addons.
+func emit_noise(value: Transform2D) -> void:
+ noise_emitted.emit(value)
+
+
+## Teleports the [param PhantomCamera2D] and [Camera2D] to their designated position,
+## bypassing the damping process.
+func teleport_position() -> void:
+ _follow_velocity_ref = Vector2.ZERO
+ _set_follow_position()
+ _transform_output.origin = _target_transform.origin
+ _phantom_camera_manager.pcam_teleport.emit(self)
+
+#endregion
+
+
+#region Setter & Getter Functions
+
+## Assigns new Zoom value.
+func set_zoom(value: Vector2) -> void:
+ zoom = value
+ queue_redraw()
+
+## Gets current Zoom value.
+func get_zoom() -> Vector2:
+ return zoom
+
+
+## Assigns new Priority value.
+func set_priority(value: int) -> void:
+ priority = abs(value)
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).pcam_priority_changed.emit(self)
+
+## Gets current Priority value.
+func get_priority() -> int:
+ return priority
+
+
+## Assigns a new PhantomCameraTween resource to the PhantomCamera2D
+func set_tween_resource(value: PhantomCameraTween) -> void:
+ tween_resource = value
+
+## Gets the PhantomCameraTween resource assigned to the PhantomCamera2D
+## Returns null if there's nothing assigned to it.
+func get_tween_resource() -> PhantomCameraTween:
+ return tween_resource
+
+
+## Assigns a new [param Tween Duration] to the [member tween_resource] value.[br]
+## The duration value is in seconds.
+func set_tween_duration(value: float) -> void:
+ tween_resource.duration = value
+
+## Gets the current [param Tween Duration] value inside the
+## [member tween_resource].[br]
+## The duration value is in seconds.
+func get_tween_duration() -> float:
+ return tween_resource.duration
+
+
+## Assigns a new [param Tween Transition] value inside the
+## [member tween_resource].
+func set_tween_transition(value: int) -> void:
+ tween_resource.transition = value
+
+## Gets the current [param Tween Transition] value inside the
+## [member tween_resource].
+func get_tween_transition() -> int:
+ return tween_resource.transition
+
+
+## Assigns a new [param Tween Ease] value inside the [member tween_resource].
+func set_tween_ease(value: int) -> void:
+ tween_resource.ease = value
+
+## Gets the current [param Tween Ease] value inside the [member tween_resource].
+func get_tween_ease() -> int:
+ return tween_resource.ease
+
+
+## Sets the [param PhantomCamera2D] active state.[br]
+## [b][color=yellow]Important:[/color][/b] This value can only be changed
+## from the [PhantomCameraHost] script.
+func set_is_active(node, value) -> void:
+ if node is PhantomCameraHost:
+ _is_active = value
+ if value:
+ _should_follow_checker()
+ queue_redraw()
+ else:
+ printerr("PCams can only be set from the PhantomCameraHost")
+
+## Gets current active state of the [param PhantomCamera2D].
+## If it returns true, it means the [param PhantomCamera2D] is what the
+## [param Camera2D] is currently following.
+func is_active() -> bool:
+ return _is_active
+
+
+## Enables or disables the [member tween_on_load].
+func set_tween_on_load(value: bool) -> void:
+ tween_on_load = value
+
+## Gets the current [member tween_on_load] value.
+func get_tween_on_load() -> bool:
+ return tween_on_load
+
+## Sets the [member host_layers] value.
+func set_host_layers(value: int) -> void:
+ host_layers = value
+ if is_instance_valid(_phantom_camera_manager):
+ _phantom_camera_manager.pcam_host_layer_changed.emit(self)
+
+## Enables or disables a given layer of [member host_layers].
+func set_host_layers_value(layer: int, value: bool) -> void:
+ host_layers = _set_layer(host_layers, layer, value)
+
+## Gets the current [member host_layers].
+func get_host_layers() -> int:
+ return host_layers
+
+
+## Gets the current follow mode as an enum int based on [enum FollowMode].[br]
+## [b]Note:[/b] Setting [enum FollowMode] purposely not added.
+## A separate PCam should be used instead.
+func get_follow_mode() -> int:
+ return follow_mode
+
+
+## Assigns a new [Node2D] as the [member follow_target].
+func set_follow_target(value: Node2D) -> void:
+ if follow_mode == FollowMode.NONE or follow_mode == FollowMode.GROUP: return
+ if follow_target == value: return
+ follow_target = value
+ _follow_target_physics_based = false
+ if is_instance_valid(value):
+ if follow_mode == FollowMode.PATH:
+ if is_instance_valid(follow_path):
+ _should_follow = true
+ else:
+ _should_follow = false
+ else:
+ _should_follow = true
+ _check_physics_body(value)
+ if not follow_target.tree_exiting.is_connected(_follow_target_tree_exiting):
+ follow_target.tree_exiting.connect(_follow_target_tree_exiting.bind(follow_target))
+ else:
+ _should_follow = false
+ follow_target_changed.emit()
+ notify_property_list_changed()
+
+## Erases the current [member follow_target].
+func erase_follow_target() -> void:
+ if follow_target == null: return
+ _should_follow = false
+ follow_target = null
+ _follow_target_physics_based = false
+ follow_target_changed.emit()
+
+## Gets the current [member follow_target].
+func get_follow_target() -> Node2D:
+ return follow_target
+
+
+## Assigns a new [Path2D] to the [member follow_path].
+func set_follow_path(value: Path2D) -> void:
+ follow_path = value
+ if is_instance_valid(follow_path):
+ _should_follow_checker()
+ else:
+ _should_follow = false
+
+## Erases the current [Path2D] from the [member follow_path] property.
+func erase_follow_path() -> void:
+ follow_path = null
+
+## Gets the current [Path2D] from the [member follow_path].
+func get_follow_path() -> Path2D:
+ return follow_path
+
+
+## Assigns a new [param follow_targets] array value.
+func set_follow_targets(value: Array[Node2D]) -> void:
+ if follow_mode != FollowMode.GROUP: return
+ if follow_targets == value: return
+ follow_targets = value
+ _follow_targets_size_check()
+
+## Appends a single [Node2D] to [member follow_targets].
+func append_follow_targets(value: Node2D) -> void:
+ if not is_instance_valid(value):
+ printerr(value, " is not a valid Node2D instance")
+ return
+
+ if not follow_targets.has(value):
+ follow_targets.append(value)
+ _follow_targets_size_check()
+ else:
+ printerr(value, " is already part of Follow Group")
+
+## Adds an Array of type [Node2D] to [member follow_targets].
+func append_follow_targets_array(value: Array[Node2D]) -> void:
+ for target in value:
+ if not is_instance_valid(target): continue
+ if not follow_targets.has(target):
+ follow_targets.append(target)
+ _follow_targets_size_check()
+ else:
+ printerr(value, " is already part of Follow Group")
+
+## Removes a [Node2D] from [member follow_targets] array.
+func erase_follow_targets(value: Node2D) -> void:
+ follow_targets.erase(value)
+ _follow_targets_size_check()
+
+## Gets all [Node2D] from [member follow_targets] array.
+func get_follow_targets() -> Array[Node2D]:
+ return follow_targets
+
+
+## Assigns a new Vector2 for the Follow Target Offset property.
+func set_follow_offset(value: Vector2) -> void:
+ var temp_offset: Vector2 = follow_offset
+
+ follow_offset = value
+
+ if follow_axis_lock != FollowLockAxis.NONE:
+ temp_offset = temp_offset - value
+ match value:
+ FollowLockAxis.X:
+ _follow_axis_lock_value.x = _transform_output.origin.x + temp_offset.x
+ FollowLockAxis.Y:
+ _follow_axis_lock_value.y = _transform_output.origin.y + temp_offset.y
+ FollowLockAxis.XY:
+ _follow_axis_lock_value.x = _transform_output.origin.x + temp_offset.x
+ _follow_axis_lock_value.y = _transform_output.origin.y + temp_offset.y
+
+
+## Gets the current Vector2 for the Follow Target Offset property.
+func get_follow_offset() -> Vector2:
+ return follow_offset
+
+
+## Enables or disables Follow Damping.
+func set_follow_damping(value: bool) -> void:
+ follow_damping = value
+ notify_property_list_changed()
+
+## Gets the current Follow Damping property.
+func get_follow_damping() -> bool:
+ return follow_damping
+
+
+## Assigns new Damping value.
+func set_follow_damping_value(value: Vector2) -> void:
+ ## TODO - Should be using @export_range once minimum version support is Godot 4.3
+ if value.x < 0: value.x = 0
+ elif value.y < 0: value.y = 0
+ follow_damping_value = value
+
+## Gets the current Follow Damping value.
+func get_follow_damping_value() -> Vector2:
+ return follow_damping_value
+
+## Assigns a new [member follow_axis] member. Value is based on [enum FollowLockAxis] enum.
+func set_lock_axis(value: FollowLockAxis) -> void:
+ follow_axis_lock = value
+
+ # Wait for the node to be ready before setting lock
+ if not is_node_ready(): await ready
+
+ # Prevent axis lock from working in the editor
+ if value != FollowLockAxis.NONE and not Engine.is_editor_hint():
+ _follow_axis_is_locked = true
+ match value:
+ FollowLockAxis.X:
+ _follow_axis_lock_value.x = _transform_output.origin.x
+ FollowLockAxis.Y:
+ _follow_axis_lock_value.y = _transform_output.origin.y
+ FollowLockAxis.XY:
+ _follow_axis_lock_value.x = _transform_output.origin.x
+ _follow_axis_lock_value.y = _transform_output.origin.y
+ else:
+ _follow_axis_is_locked = false
+
+## Gets the current [member follow_axis_lock] value. Value is based on [enum FollowLockAxis] enum.
+func get_lock_axis() -> FollowLockAxis:
+ return follow_axis_lock
+
+
+## Enables or disables [member snap_to_pixel].
+func set_snap_to_pixel(value: bool) -> void:
+ snap_to_pixel = value
+
+## Gets the current [member snap_to_pixel] value.
+func get_snap_to_pixel() -> bool:
+ return snap_to_pixel
+
+
+## Enables or disables Auto zoom when using Group Follow.
+func set_auto_zoom(value: bool) -> void:
+ auto_zoom = value
+ notify_property_list_changed()
+
+## Gets Auto Zoom state.
+func get_auto_zoom() -> bool:
+ return auto_zoom
+
+
+## Assigns new Min Auto Zoom value.
+func set_auto_zoom_min(value: float) -> void:
+ auto_zoom_min = value
+
+## Gets Min Auto Zoom value.
+func get_auto_zoom_min() -> float:
+ return auto_zoom_min
+
+
+## Assigns new Max Auto Zoom value.
+func set_auto_zoom_max(value: float) -> void:
+ auto_zoom_max = value
+
+## Gets Max Auto Zoom value.
+func get_auto_zoom_max() -> float:
+ return auto_zoom_max
+
+
+## Assigns new Zoom Auto Margin value.
+func set_auto_zoom_margin(value: Vector4) -> void:
+ auto_zoom_margin = value
+
+## Gets Zoom Auto Margin value.
+func get_auto_zoom_margin() -> Vector4:
+ return auto_zoom_margin
+
+
+## Sets a limit side based on the side parameter.[br]
+## It's recommended to pass the [enum Side] enum as the sid parameter.
+func set_limit(side: int, value: int) -> void:
+ match side:
+ SIDE_LEFT: limit_left = value
+ SIDE_TOP: limit_top = value
+ SIDE_RIGHT: limit_right = value
+ SIDE_BOTTOM: limit_bottom = value
+ _: printerr("Not a valid Side.")
+
+## Gets the limit side
+func get_limit(value: int) -> int:
+ match value:
+ SIDE_LEFT: return limit_left
+ SIDE_TOP: return limit_top
+ SIDE_RIGHT: return limit_right
+ SIDE_BOTTOM: return limit_bottom
+ _:
+ printerr("Not a valid Side.")
+ return -1
+
+
+## Assign a the Camera2D Left Limit Side value.
+func set_limit_left(value: int) -> void:
+ _limit_target_exist_error()
+ limit_left = value
+ update_limit_all_sides()
+
+## Gets the Camera2D Left Limit value.
+func get_limit_left() -> int:
+ return limit_left
+
+
+## Assign a the Camera2D Top Limit Side value.
+func set_limit_top(value: int) -> void:
+ _limit_target_exist_error()
+ limit_top = value
+ update_limit_all_sides()
+
+## Gets the Camera2D Top Limit value.
+func get_limit_top() -> int:
+ return limit_top
+
+
+## Assign a the Camera2D Right Limit Side value.
+func set_limit_right(value: int) -> void:
+ _limit_target_exist_error()
+ limit_right = value
+ update_limit_all_sides()
+
+## Gets the Camera2D Right Limit value.
+func get_limit_right() -> int:
+ return limit_right
+
+
+## Assign a the Camera2D Bottom Limit Side value.
+func set_limit_bottom(value: int) -> void:
+ _limit_target_exist_error()
+ limit_bottom = value
+ update_limit_all_sides()
+
+## Gets the Camera2D Bottom Limit value.
+func get_limit_bottom() -> int:
+ return limit_bottom
+
+
+func _limit_target_exist_error() -> void:
+ if not limit_target.is_empty():
+ printerr("Unable to set Limit Side due to Limit Target ", _limit_node.name, " being assigned")
+
+
+# Sets a [memeber limit_target] node.
+func set_limit_target(value: NodePath) -> void:
+ limit_target = value
+
+ # Waits for PCam2d's _ready() before trying to validate limit_node_path
+ if not is_node_ready(): await ready
+
+ # Removes signal from existing TileMap node
+ if is_instance_valid(get_node_or_null(value)):
+ var prev_limit_node: Node2D = _limit_node
+ var new_limit_node: Node2D = get_node(value)
+
+ if prev_limit_node:
+ if prev_limit_node is TileMap or prev_limit_node.is_class("TileMapLayer"):
+ if prev_limit_node.changed.is_connected(_on_tile_map_changed):
+ prev_limit_node.changed.disconnect(_on_tile_map_changed)
+
+ if new_limit_node is TileMap or new_limit_node.is_class("TileMapLayer"):
+ if not new_limit_node.changed.is_connected(_on_tile_map_changed):
+ new_limit_node.changed.connect(_on_tile_map_changed)
+ elif new_limit_node is CollisionShape2D:
+ var col_shape: CollisionShape2D = get_node(value)
+
+ if col_shape.shape == null:
+ printerr("No Shape2D in: ", col_shape.name)
+ reset_limit()
+ limit_target = ""
+ return
+ else:
+ printerr("Limit Target is not a TileMap, TileMapLayer or CollisionShape2D node")
+ return
+ elif value == NodePath(""):
+ reset_limit()
+ limit_target = ""
+ else:
+ printerr("Limit Target cannot be found")
+ return
+
+ _limit_node = get_node_or_null(value)
+
+ notify_property_list_changed()
+ update_limit_all_sides()
+
+## Get [member limit_target] node.
+func get_limit_target() -> NodePath:
+ if not limit_target: # TODO - Fixes an spam error if if limit_taret is empty
+ return NodePath("")
+ else:
+ return limit_target
+
+
+## Set Tile Map Limit Margin.
+func set_limit_margin(value: Vector4i) -> void:
+ limit_margin = value
+ update_limit_all_sides()
+## Get Tile Map Limit Margin.
+func get_limit_margin() -> Vector4i:
+ return limit_margin
+
+
+### Enables or disables the Limit Smoothing beaviour.
+#func set_limit_smoothing(value: bool) -> void:
+ #limit_smoothed = value
+### Returns the Limit Smoothing beaviour.
+#func get_limit_smoothing() -> bool:
+ #return limit_smoothed
+
+
+## Sets a [PhantomCameraNoise2D] resource.
+func set_noise(value: PhantomCameraNoise2D) -> void:
+ noise = value
+ if value != null:
+ _has_noise_resource = true
+ noise.set_trauma(1)
+ else:
+ _has_noise_resource = false
+ _transform_noise = Transform2D()
+
+## Returns the [PhantomCameraNoise2D] resource.
+func get_noise() -> PhantomCameraNoise2D:
+ return noise
+
+func has_noise_resource() -> bool:
+ return _has_noise_resource
+
+
+## Sets the [member noise_emitter_layer] value.
+func set_noise_emitter_layer(value: int) -> void:
+ noise_emitter_layer = value
+
+## Enables or disables a given layer of [member noise_emitter_layer].
+func set_noise_emitter_layer_value(value: int, enabled: bool) -> void:
+ noise_emitter_layer = _set_layer(noise_emitter_layer, value, enabled)
+
+## Returns the [member noise_emitter_layer]
+func get_noise_emitter_layer() -> int:
+ return noise_emitter_layer
+
+
+## Sets [member inactive_update_mode] property.
+func set_inactive_update_mode(value: int) -> void:
+ inactive_update_mode = value
+
+## Gets [enum InactiveUpdateMode] value.
+func get_inactive_update_mode() -> int:
+ return inactive_update_mode
+
+
+func get_follow_target_physics_based() -> bool:
+ return _follow_target_physics_based
+
+
+func get_class() -> String:
+ return "PhantomCamera2D"
+
+
+func is_class(value) -> bool:
+ return value == "PhantomCamera2D"
+
+#endregion
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd.uid b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd.uid
new file mode 100644
index 0000000..66fa7de
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_2d.gd.uid
@@ -0,0 +1 @@
+uid://bhexx6mj1xv3q
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd
new file mode 100644
index 0000000..725b4ff
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd
@@ -0,0 +1,2195 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_3d.svg")
+class_name PhantomCamera3D
+extends Node3D
+
+## Controls a scene's [Camera3D] and applies logic to it.
+##
+## The scene's [Camera3D] will follow the position of the
+## [param PhantomCamera3D] with the highest priority.
+## Each instance can have different positional and rotational logic applied
+## to them.
+
+#region Constants
+
+const _constants = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+#endregion
+
+
+#region Signals
+
+## Emitted when the [param PhantomCamera3D] becomes active.
+signal became_active
+
+## Emitted when the [param PhantomCamera3D] becomes inactive.
+signal became_inactive
+
+## Emitted when the follow_mode changes.
+## Note: This is for internal use only
+signal follow_mode_changed
+
+## Emitted when [member follow_target] changes.
+signal follow_target_changed
+
+## Emitted when [member look_at_target] changes.
+signal look_at_target_changed
+
+## Emitted when dead zones changes.[br]
+## [b]Note:[/b] Only applicable in [param Framed] [member FollowMode].
+signal dead_zone_changed
+
+## Emitted when a target touches the edge of the dead zone in [param Framed] [enum FollowMode].
+signal dead_zone_reached
+
+## Emitted when the [param Camera3D] starts to tween to another
+## [param PhantomCamera3D].
+signal tween_started
+
+## Emitted when the [param Camera3D] is to tweening towards another
+## [param PhantomCamera3D].
+signal is_tweening
+
+## Emitted when the tween is interrupted due to another [param PhantomCamera3D]
+## becoming active. The argument is the [param PhantomCamera3D] that
+## interrupted the tween.
+signal tween_interrupted(pcam_3d: PhantomCamera3D)
+
+## Emitted when the [param Camera3D] completes its tween to the
+## [param PhantomCamera3D].
+signal tween_completed
+
+## Emitted when Noise should be applied to the [param Camera3D].
+signal noise_emitted(noise_output: Transform3D)
+
+signal physics_target_changed
+
+signal camera_3d_resource_property_changed(property: StringName, value: Variant)
+signal camera_3d_resource_changed
+
+#endregion
+
+
+#region Enums
+
+## Determines the positional logic for a given [param PhantomCamera3D]
+## [br][br]
+## The different modes have different functionalities and purposes, so choosing
+## the correct one depends on what each [param PhantomCamera3D] is meant to do.
+enum FollowMode {
+ NONE = 0, ## Default - No follow logic is applied.
+ GLUED = 1, ## Sticks to its target.
+ SIMPLE = 2, ## Follows its target with an optional offset.
+ GROUP = 3, ## Follows multiple targets with option to dynamically reframe itself.
+ PATH = 4, ## Follows a target while being positionally confined to a [Path3D] node.
+ FRAMED = 5, ## Applies a dead zone on the frame and only follows its target when it tries to leave it.
+ THIRD_PERSON = 6, ## Applies a [SpringArm3D] node to the target's position and allows for rotating around it.
+}
+
+## Determines the rotational logic for a given [param PhantomCamera3D].[br][br]
+## The different modes has different functionalities and purposes, so
+## choosing the correct mode depends on what each [param PhantomCamera3D]
+## is meant to do.
+enum LookAtMode {
+ NONE = 0, ## Default - No Look At logic is applied.
+ MIMIC = 1, ## Copies its target's rotational value.
+ SIMPLE = 2, ## Looks at its target in a straight line.
+ GROUP = 3, ## Looks at the centre of its targets.
+}
+
+## Determines how often an inactive [param PhantomCamera3D] should update
+## its positional and rotational values. This is meant to reduce the amount
+## of calculations inactive [param PhantomCamera3D] are doing when idling
+## to improve performance.
+enum InactiveUpdateMode {
+ ALWAYS, ## Always updates the [param PhantomCamera3D], even when it's inactive.
+ NEVER, ## Never updates the [param PhantomCamera3D] when it's inactive. Reduces the amount of computational resources when inactive.
+# EXPONENTIALLY,
+}
+
+enum FollowLockAxis {
+ NONE = 0,
+ X = 1,
+ Y = 2,
+ Z = 3,
+ XY = 4,
+ XZ = 5,
+ YZ = 6,
+ XYZ = 7,
+}
+
+#endregion
+
+
+#region Exported Properties
+
+## To quickly preview a [param PhantomCamera3D] without adjusting its
+## [member Priority], this property allows the selected [param PhantomCamera3D]
+## to ignore the Priority system altogether and forcefully become the active
+## one. It's partly designed to work within the [param viewfinder], and will be
+## disabled when running a build export of the game.
+@export var priority_override: bool = false:
+ set(value):
+ priority_override = value
+ if Engine.is_editor_hint():
+ if value:
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).pcam_priority_override.emit(self, priority_override)
+ else:
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).pcam_priority_override.emit(self, priority_override)
+ get:
+ return priority_override
+
+
+## It defines which [param PhantomCamera3D] a scene's [param Camera3D] should
+## be corresponding with and be attached to. This is decided by the
+## [param PhantomCamera3D] with the highest [param priority].
+## [br][br]
+## Changing [param priority] will send an event to the scene's
+## [PhantomCameraHost], which will then determine whether if the
+## [param priority] value is greater than or equal to the currently
+## highest [param PhantomCamera3D]'s in the scene. The
+## [param PhantomCamera3D] with the highest value will then reattach the
+## Camera accordingly.
+@export var priority: int = 0:
+ set = set_priority,
+ get = get_priority
+
+
+## Determines the positional logic for a given [param PhantomCamera3D].
+## The different modes have different functionalities and purposes, so
+## choosing the correct one depends on what each [param PhantomCamera3D]
+## is meant to do.
+@export var follow_mode: FollowMode = FollowMode.NONE:
+ set(value):
+ follow_mode = value
+
+ if follow_mode == FollowMode.NONE:
+ _should_follow = false
+ top_level = false
+ _is_parents_physics()
+ notify_property_list_changed()
+ return
+
+ match follow_mode:
+ FollowMode.PATH:
+ if is_instance_valid(follow_path):
+ _should_follow_checker()
+ FollowMode.GROUP:
+ _follow_targets_size_check()
+ _:
+ _should_follow_checker()
+
+ if follow_mode == FollowMode.FRAMED:
+ if _follow_framed_initial_set and follow_target:
+ _follow_framed_initial_set = false
+ dead_zone_changed.connect(_on_dead_zone_changed)
+ else:
+ if dead_zone_changed.is_connected(_on_dead_zone_changed):
+ dead_zone_changed.disconnect(_on_dead_zone_changed)
+
+ if follow_mode == FollowMode.THIRD_PERSON:
+ top_level = false
+ else:
+ top_level = true
+
+ follow_mode_changed.emit()
+ notify_property_list_changed()
+
+ ## NOTE - Warning that Look At + Follow Mode hasn't been fully tested together yet
+ if look_at_mode != LookAtMode.NONE:
+ print_rich("[color=#EAA15E]Warning: Using both Look At and Follow Mode on the same PCam3D has not been fully tested yet, proceed with caution![/color]")
+ get:
+ return follow_mode
+
+## Determines which target should be followed.
+## The [param Camera3D] will follow the position of the Follow Target based on
+## the [member follow_mode] type and its parameters.
+@export var follow_target: Node3D = null:
+ set = set_follow_target,
+ get = get_follow_target
+
+## Defines the targets that the [param PhantomCamera3D] should be following.
+@export var follow_targets: Array[Node3D] = []:
+ set = set_follow_targets,
+ get = get_follow_targets
+
+## Determines the [Path3D] node the [param PhantomCamera3D]
+## should be bound to.
+## The [param PhantomCamera3D] will follow the position of the
+## [member follow_target] while sticking to the closest point on this path.
+@export var follow_path: Path3D = null:
+ set = set_follow_path,
+ get = get_follow_path
+
+
+## Determines the rotational logic for a given [param PhantomCamera3D].
+## The different modes has different functionalities and purposes,
+## so choosing the correct mode depends on what each
+## [param PhantomCamera3D] is meant to do.
+@export var look_at_mode: LookAtMode = LookAtMode.NONE:
+ set(value):
+ look_at_mode = value
+
+ if look_at_mode == LookAtMode.NONE:
+ _should_look_at = false
+ notify_property_list_changed()
+ return
+
+ if not look_at_mode == LookAtMode.GROUP:
+ if look_at_target is Node3D:
+ _should_look_at = true
+ else: # If Look At Group
+ _look_at_targets_size_check()
+ notify_property_list_changed()
+
+ ## NOTE - Warning that Look At + Follow Mode hasn't been fully tested together yet
+ if follow_mode != FollowMode.NONE:
+ print_rich("[color=#EAA15E]Warning: Using both Look At and Follow Mode on the same PCam3D has not been fully tested yet, proceed with caution![/color]")
+ get:
+ return look_at_mode
+
+## Determines which target should be looked at.
+## The [param PhantomCamera3D] will update its rotational value as the
+## target changes its position.
+@export var look_at_target: Node3D = null:
+ set = set_look_at_target,
+ get = get_look_at_target
+
+## Defines the targets that the camera should looking at.
+## It will be looking at the centre of all the assigned targets.
+@export var look_at_targets: Array[Node3D] = []:
+ set = set_look_at_targets,
+ get = get_look_at_targets
+
+
+## Defines how [param ]PhantomCamera3Ds] transition between one another.
+## Changing the tween values for a given [param PhantomCamera3D]
+## determines how transitioning to that instance will look like.
+## This is a resource type that can be either used for one
+## [param PhantomCamera] or reused across multiple - both 2D and 3D.
+## By default, all [param PhantomCameras] will use a [param linear]
+## transition, [param easeInOut] ease with a [param 1s] duration.
+@export var tween_resource: PhantomCameraTween = PhantomCameraTween.new():
+ set = set_tween_resource,
+ get = get_tween_resource
+
+## If enabled, the moment a [param PhantomCamera3D] is instantiated into
+## a scene, and has the highest priority, it will perform its tween transition.
+## This is most obvious if a [param PhantomCamera3D] has a long duration and
+## is attached to a playable character that can be moved the moment a scene
+## is loaded. Disabling the [param tween_on_load] property will
+## disable this behaviour and skip the tweening entirely when instantiated.
+@export var tween_on_load: bool = true:
+ set = set_tween_on_load,
+ get = get_tween_on_load
+
+
+## Determines how often an inactive [param PhantomCamera3D] should update
+## its positional and rotational values. This is meant to reduce the amount
+## of calculations inactive [param PhantomCamera3Ds] are doing when idling
+## to improve performance.
+@export var inactive_update_mode: InactiveUpdateMode = InactiveUpdateMode.ALWAYS:
+ set = set_inactive_update_mode,
+ get = get_inactive_update_mode
+
+
+## Determines which layers this [param PhantomCamera3D] should be able to communicate with [PhantomCameraHost] nodes.[br]
+## A corresponding layer needs to be set on the [PhantomCameraHost] node.
+@export_flags_3d_render var host_layers: int = 1:
+ set = set_host_layers,
+ get = get_host_layers
+
+
+## A resource type that allows for overriding the [param Camera3D] node's
+## properties.
+@export var camera_3d_resource: Camera3DResource = null:
+ set = set_camera_3d_resource,
+ get = get_camera_3d_resource
+
+
+## Overrides the [member Camera3D.attribuets] resource property.
+@export var attributes: CameraAttributes = null:
+ set = set_attributes,
+ get = get_attributes
+
+
+## Overrides the [member Camera3D.environment] resource property.
+@export var environment: Environment = null:
+ set = set_environment,
+ get = get_environment
+
+
+@export_group("Follow Parameters")
+## Offsets the [member follow_target] position.
+@export var follow_offset: Vector3 = Vector3.ZERO:
+ set = set_follow_offset,
+ get = get_follow_offset
+
+## Applies a damping effect on the camera's movement.
+## Leading to heavier / slower camera movement as the targeted node moves around.
+## This is useful to avoid sharp and rapid camera movement.
+@export var follow_damping: bool = false:
+ set = set_follow_damping,
+ get = get_follow_damping
+
+## Defines the damping amount. The ideal range should be somewhere between 0-1.[br][br]
+## The damping amount can be specified in the individual axis.[br][br]
+## [b]Lower value[/b] = faster / sharper camera movement.[br]
+## [b]Higher value[/b] = slower / heavier camera movement.
+@export var follow_damping_value: Vector3 = Vector3(0.1, 0.1, 0.1):
+ set = set_follow_damping_value,
+ get = get_follow_damping_value
+
+
+## Prevents the [param PhantomCamera2D] from moving in a designated axis.
+## This can be enabled or disabled at runtime or from the editor directly.
+@export var follow_axis_lock: FollowLockAxis = FollowLockAxis.NONE:
+ set = set_follow_axis_lock,
+ get = get_follow_axis_lock
+var _follow_axis_is_locked: bool = false
+var _follow_axis_lock_value: Vector3 = Vector3.ZERO
+
+
+## Sets a distance offset from the centre of the target's position.
+## The distance is applied to the [param PhantomCamera3D]'s local z axis.
+@export var follow_distance: float = 1:
+ set = set_follow_distance,
+ get = get_follow_distance
+
+## Enables the [param PhantomCamera3D] to automatically distance
+## itself as the [param follow targets] move further apart.[br]
+## It looks at the longest axis between the different targets and interpolates
+## the distance length between the [member auto_follow_distance_min] and
+## [member follow_group_distance] properties.[br][br]
+## Note: Enabling this property hides and disables the [member follow_distance]
+## property as this effectively overrides that property.
+@export var auto_follow_distance: bool = false:
+ set = set_auto_follow_distance,
+ get = get_auto_follow_distance
+
+## Sets the minimum distance between the Camera and centre of [AABB].
+## [br][br]
+## Note: This distance will only ever be reached when all the targets are in
+## the exact same [param Vector3] coordinate, which will very unlikely
+## happen, so adjust the value here accordingly.
+## [br][br]
+## If only one follow target is assigned to [member follow_targets], this value will be used as the `follow_distance`.
+@export var auto_follow_distance_min: float = 1:
+ set = set_auto_follow_distance_min,
+ get = get_auto_follow_distance_min
+
+## Sets the maximum distance between the Camera and centre of [AABB].
+@export var auto_follow_distance_max: float = 5:
+ set = set_auto_follow_distance_max,
+ get = get_auto_follow_distance_max
+
+## Determines how fast the [member auto_follow_distance] moves between the
+## maximum and minimum distance. The higher the value, the sooner the
+## maximum distance is reached.[br][br]
+## This value should be based on the sizes of the [member auto_follow_distance_min]
+## and [member auto_follow_distance_max].[br]
+## E.g. if the value between the [member auto_follow_distance_min] and
+## [member auto_follow_distance_max] is small, consider keeping the number low
+## and vice versa.
+@export var auto_follow_distance_divisor: float = 10:
+ set = set_auto_follow_distance_divisor,
+ get = get_auto_follow_distance_divisor
+
+
+@export_subgroup("Dead Zones")
+## Defines the horizontal dead zone area. While the target is within it, the
+## [param PhantomCamera3D] will not move in the horizontal axis.
+## If the targeted node leaves the horizontal bounds, the
+## [param PhantomCamera3D] will follow the target horizontally to keep
+## it within bounds.
+@export_range(0, 1) var dead_zone_width: float = 0:
+ set(value):
+ dead_zone_width = value
+ dead_zone_changed.emit()
+ get:
+ return dead_zone_width
+
+## Defines the vertical dead zone area. While the target is within it, the
+## [param PhantomCamera3D] will not move in the vertical axis.
+## If the targeted node leaves the vertical bounds, the
+## [param PhantomCamera3D] will follow the target horizontally to keep
+## it within bounds.
+@export_range(0, 1) var dead_zone_height: float = 0:
+ set(value):
+ dead_zone_height = value
+ dead_zone_changed.emit()
+ get:
+ return dead_zone_height
+
+## Enables the dead zones to be visible when running the game from the editor.
+## Dead zones will never be visible in build exports.
+@export var show_viewfinder_in_play: bool = false
+
+## Defines the position of the [member follow_target] within the viewport.[br]
+## This is only used for when [member follow_mode] is set to [param Framed].
+
+@export_subgroup("Spring Arm")
+
+## Defines the [member SpringArm3D.spring_length].
+@export var spring_length: float = 1:
+ set = set_spring_length,
+ get = get_spring_length
+
+## Defines the [member SpringArm3D.collision_mask] node's Collision Mask.
+@export_flags_3d_physics var collision_mask: int = 1:
+ set = set_collision_mask,
+ get = get_collision_mask
+
+## Defines the [member SpringArm3D.shape] node's Shape3D.
+@export var shape: Shape3D = null:
+ set = set_shape,
+ get = get_shape
+
+## Defines the [member SpringArm3D.margin] node's Margin.
+@export var margin: float = 0.01:
+ set = set_margin,
+ get = get_margin
+
+
+@export_group("Look At Parameters")
+## Offsets the target's [param Vector3] position that the
+## [param PhantomCamera3D] is looking at.
+@export var look_at_offset: Vector3 = Vector3.ZERO:
+ set = set_look_at_offset,
+ get = get_look_at_offset
+
+## Applies a damping effect on the camera's rotation.
+## Leading to heavier / slower camera movement as the targeted node moves around.
+## This is useful to avoid sharp and rapid camera rotation.
+@export var look_at_damping: bool = false:
+ set = set_look_at_damping,
+ get = get_look_at_damping
+
+## Defines the Rotational damping amount. The ideal range is typically somewhere between 0-1.[br][br]
+## The damping amount can be specified in the individual axis.[br][br]
+## [b]Lower value[/b] = faster / sharper camera rotation.[br]
+## [b]Higher value[/b] = slower / heavier camera rotation.
+@export_range(0.0, 1.0, 0.001, "or_greater") var look_at_damping_value: float = 0.25:
+ set = set_look_at_damping_value,
+ get = get_look_at_damping_value
+
+@export_subgroup("Up Direction")
+
+## Defines the upward direction of the [param PhantomCamera3D] when [member look_at_mode] is set. [br]
+## This value will be overriden if [member up_target] is defined.
+@export var up: Vector3 = Vector3.UP:
+ set = set_up,
+ get = get_up
+
+## Applies and continuously updates the [param up] direction of the [param PhantomCamera3D] based on this target when [member look_at_mode] is set.[br]
+## Setting a value here will override the [member up] value.
+@export var up_target: Node3D = null:
+ set = set_up_target,
+ get = get_up_target
+
+
+@export_group("Noise")
+## Applies a noise, or shake, to a [Camera3D].[br]
+## Once set, the noise will run continuously after the tween to the [PhantomCamera3D] instance is complete.
+@export var noise: PhantomCameraNoise3D = null:
+ set = set_noise,
+ get = get_noise
+
+## If true, will trigger the noise while in the editor.[br]
+## Useful in cases where you want to temporarily disalbe the noise in the editor without removing
+## the resource.[br][br]
+## [b]Note:[/b] This property has no effect on runtime behaviour.
+@export var _preview_noise: bool = true:
+ set(value):
+ _preview_noise = value
+ if not value:
+ _transform_noise = Transform3D()
+
+## Enable a corresponding layer for a [member PhantomCameraNoiseEmitter3D.noise_emitter_layer]
+## to make this [PhantomCamera3D] be affect by it.
+@export_flags_3d_render var noise_emitter_layer: int = 0:
+ set = set_noise_emitter_layer,
+ get = get_noise_emitter_layer
+
+#endregion
+
+#region Private Variables
+
+var _is_active: bool = false
+
+var _is_third_person_follow: bool = false
+var _camera_target: Node3D = self # Calculates the position of the camera in the editor, uses instantiated SpringArm3D node when running the scene
+
+var _should_follow: bool = false
+var _follow_target_physics_based: bool = false
+var _physics_interpolation_enabled: bool = false ## TOOD - Should be enbled once toggling physics_interpolation_mode ON, when previously OFF, works in 3D
+
+var _has_multiple_follow_targets: bool = false
+var _follow_targets_single_target_index: int = 0
+var _follow_targets: Array[Node3D] = []
+
+var _should_look_at: bool = false
+var _look_at_target_physics_based: bool = false
+
+var _has_multiple_look_at_targets: bool = false
+var _look_at_targets_single_target_index: int = 0
+
+var _current_rotation: Vector3 = Vector3.ZERO
+
+var _up: Vector3 = Vector3.UP
+var _has_up_target: bool = false
+
+var _target_transform: Transform3D = Transform3D()
+
+var _transform_output: Transform3D = Transform3D()
+var _transform_noise: Transform3D = Transform3D()
+
+var _tween_skip: bool = false
+
+var _follow_velocity_ref: Vector3 = Vector3.ZERO # Stores and applies the velocity of the movement
+
+var _follow_framed_initial_set: bool = false
+var _follow_framed_offset: Vector3 = Vector3.ZERO
+
+var _follow_spring_arm: SpringArm3D = null
+var _has_follow_spring_arm: bool = false
+
+var _has_noise_resource: bool = false
+
+
+# NOTE - Temp solution until Godot has better plugin autoload recognition out-of-the-box.
+var _phantom_camera_manager: Node = null
+
+#endregion
+
+#region Public Variable
+
+var tween_duration: float:
+ set = set_tween_duration,
+ get = get_tween_duration
+var tween_transition: PhantomCameraTween.TransitionType:
+ set = set_tween_transition,
+ get = get_tween_transition
+var tween_ease: PhantomCameraTween.EaseType:
+ set = set_tween_ease,
+ get = get_tween_ease
+
+var keep_aspect: int:
+ set = set_keep_aspect,
+ get = get_keep_aspect
+var cull_mask: int:
+ set = set_cull_mask,
+ get = get_cull_mask
+var h_offset: float:
+ set = set_h_offset,
+ get = get_h_offset
+var v_offset: float:
+ set = set_v_offset,
+ get = get_v_offset
+var projection: Camera3DResource.ProjectionType:
+ set = set_projection,
+ get = get_projection
+var fov: float:
+ set = set_fov,
+ get = get_fov
+var size: float:
+ set = set_size,
+ get = get_size
+var frustum_offset: Vector2:
+ set = set_frustum_offset,
+ get = get_frustum_offset
+var far: float:
+ set = set_far,
+ get = get_far
+var near: float:
+ set = set_near,
+ get = get_near
+
+var viewport_position: Vector2
+
+#endregion
+
+
+#region Private Functions
+
+func _validate_property(property: Dictionary) -> void:
+ ################
+ ## Follow Target
+ ################
+ if property.name == "follow_target":
+ if follow_mode == FollowMode.NONE or \
+ follow_mode == FollowMode.GROUP:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_path" and \
+ follow_mode != FollowMode.PATH:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ####################
+ ## Follow Parameters
+ ####################
+ if follow_mode == FollowMode.NONE:
+ match property.name:
+ "follow_offset", \
+ "follow_damping", \
+ "follow_damping_value", \
+ "follow_axis_lock":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_offset":
+ if follow_mode == FollowMode.PATH or \
+ follow_mode == FollowMode.GLUED:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_damping_value" and not follow_damping:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_offset":
+ if follow_mode == FollowMode.PATH:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "follow_distance":
+ if not follow_mode == FollowMode.FRAMED:
+ if not follow_mode == FollowMode.GROUP or \
+ auto_follow_distance: \
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ###############
+ ## Group Follow
+ ###############
+ if property.name == "follow_targets" and \
+ not follow_mode == FollowMode.GROUP:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "auto_follow_distance" and \
+ not follow_mode == FollowMode.GROUP:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if not auto_follow_distance or not follow_mode == FollowMode.GROUP:
+ match property.name:
+ "auto_follow_distance_min", \
+ "auto_follow_distance_max", \
+ "auto_follow_distance_divisor":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ###############
+ ## Framed Follow
+ ###############
+ if not follow_mode == FollowMode.FRAMED:
+ match property.name:
+ "dead_zone_width", \
+ "dead_zone_height", \
+ "show_viewfinder_in_play":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ######################
+ ## Third Person Follow
+ ######################
+ if not follow_mode == FollowMode.THIRD_PERSON:
+ match property.name:
+ "spring_length", \
+ "collision_mask", \
+ "shape", \
+ "margin":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ ##########
+ ## Look At
+ ##########
+ if look_at_mode == LookAtMode.NONE:
+ match property.name:
+ "look_at_target", \
+ "look_at_offset" , \
+ "look_at_damping", \
+ "look_at_damping_value", \
+ "up", \
+ "up_target":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+ elif look_at_mode == LookAtMode.GROUP:
+ match property.name:
+ "look_at_target":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "look_at_target":
+ if look_at_mode == LookAtMode.NONE or \
+ look_at_mode == LookAtMode.GROUP:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "look_at_targets" and \
+ not look_at_mode == LookAtMode.GROUP:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "look_at_damping_value" and \
+ not look_at_damping:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "up" and _has_up_target:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _enter_tree() -> void:
+ _phantom_camera_manager = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME)
+ _tween_skip = !tween_on_load
+
+ _phantom_camera_manager.pcam_added(self)
+
+ priority_override = false
+
+ if not visibility_changed.is_connected(_check_visibility):
+ visibility_changed.connect(_check_visibility)
+
+ _should_follow_checker()
+ _should_look_at_checker()
+
+ if follow_mode == FollowMode.GROUP:
+ _follow_targets_size_check()
+ elif follow_mode == FollowMode.NONE:
+ _is_parents_physics()
+
+ if look_at_mode == LookAtMode.GROUP:
+ _look_at_targets_size_check()
+
+ #if not get_parent() is SpringArm3D:
+ #if look_at_target:
+ #_look_at_target_node = look_at_target
+ #elif look_at_targets:
+ #_look_at_group_nodes.clear()
+ #for path in look_at_targets:
+ #if not path.is_empty() and path:
+ #_should_look_at = true
+ #_has_look_at_targets = true
+ #_look_at_group_nodes.append(path)
+
+
+func _exit_tree() -> void:
+ if not follow_mode == FollowMode.GROUP:
+ follow_targets = []
+
+ if not is_instance_valid(_phantom_camera_manager): return
+ _phantom_camera_manager.pcam_removed(self)
+
+
+func _ready():
+ match follow_mode:
+ FollowMode.THIRD_PERSON:
+ _is_third_person_follow = true
+ _transform_output.origin = _get_target_position_offset_distance()
+ if not Engine.is_editor_hint():
+ if not is_instance_valid(_follow_spring_arm):
+ _follow_spring_arm = SpringArm3D.new()
+ _follow_spring_arm.top_level = true
+ _follow_spring_arm.rotation = global_rotation
+ _follow_spring_arm.spring_length = spring_length
+ _follow_spring_arm.collision_mask = collision_mask
+ _follow_spring_arm.shape = shape
+ _follow_spring_arm.margin = margin
+ _follow_spring_arm.add_excluded_object(follow_target)
+ get_parent().add_child.call_deferred(_follow_spring_arm)
+ reparent.call_deferred(_follow_spring_arm)
+
+ # Waits for the SpringArm3D to be ready and then apply rotation
+ # Resolves an issue most prominent in Godot 4.4
+ await _follow_spring_arm.ready
+ _camera_target = _follow_spring_arm
+ _follow_spring_arm.position = _get_target_position_offset() if is_instance_valid(follow_target) else global_position
+ _follow_spring_arm.global_rotation = global_rotation
+ _has_follow_spring_arm = true
+ FollowMode.FRAMED:
+ if not Engine.is_editor_hint():
+ if is_instance_valid(follow_target):
+ _follow_framed_offset = global_position - _get_target_position_offset()
+ _current_rotation = global_rotation
+ FollowMode.GROUP:
+ _follow_targets_size_check()
+ _:
+ if is_instance_valid(follow_target):
+ _transform_output.origin = _get_target_position_offset()
+ else:
+ _transform_output.origin = global_transform.origin
+
+ if not Engine.is_editor_hint():
+ _preview_noise = true
+
+ ## NOTE - Only here to set position for Framed View on startup.
+ ## Should be removed once https://github.com/ramokz/phantom-camera/issues/161 is complete
+ _transform_output = global_transform
+
+ _phantom_camera_manager.noise_3d_emitted.connect(_noise_emitted)
+
+
+func _process(delta: float) -> void:
+ if _follow_target_physics_based or _is_active: return
+ process_logic(delta)
+
+
+func _physics_process(delta: float) -> void:
+ if not _follow_target_physics_based or _is_active: return
+ process_logic(delta)
+
+
+func process_logic(delta: float) -> void:
+ if _is_active:
+ if _has_noise_resource and _preview_noise:
+ _transform_noise = noise.get_noise_transform(delta)
+ else:
+ match inactive_update_mode:
+ InactiveUpdateMode.NEVER: return
+ # InactiveUpdateMode.EXPONENTIALLY:
+ # TODO - Trigger positional updates less frequently as more PCams gets added
+
+ if _should_follow:
+ _follow(delta)
+ else:
+ _transform_output.origin = global_transform.origin
+
+ if _should_look_at:
+ _look_at(delta)
+ else:
+ _transform_output.basis = global_basis
+
+ if _follow_axis_is_locked:
+ match follow_axis_lock:
+ FollowLockAxis.X:
+ _transform_output.origin.x = _follow_axis_lock_value.x
+ FollowLockAxis.Y:
+ _transform_output.origin.y = _follow_axis_lock_value.y
+ FollowLockAxis.Z:
+ _transform_output.origin.z = _follow_axis_lock_value.z
+ FollowLockAxis.XY:
+ _transform_output.origin.x = _follow_axis_lock_value.x
+ _transform_output.origin.y = _follow_axis_lock_value.y
+ FollowLockAxis.XZ:
+ _transform_output.origin.x = _follow_axis_lock_value.x
+ _transform_output.origin.z = _follow_axis_lock_value.z
+ FollowLockAxis.YZ:
+ _transform_output.origin.y = _follow_axis_lock_value.y
+ _transform_output.origin.z = _follow_axis_lock_value.z
+ FollowLockAxis.XYZ:
+ _transform_output.origin.x = _follow_axis_lock_value.x
+ _transform_output.origin.y = _follow_axis_lock_value.y
+ _transform_output.origin.z = _follow_axis_lock_value.z
+
+
+func _follow(delta: float) -> void:
+ _set_follow_position()
+ _interpolate_position(delta)
+
+
+func _set_follow_position() -> void:
+ match follow_mode:
+ FollowMode.GLUED:
+ _target_transform.origin = follow_target.global_position
+
+ FollowMode.SIMPLE:
+ _target_transform.origin = _get_target_position_offset()
+
+ FollowMode.GROUP:
+ if _has_multiple_follow_targets:
+ var bounds: AABB = AABB(_follow_targets[0].global_position, Vector3.ZERO)
+ for target in _follow_targets:
+ bounds = bounds.expand(target.global_position)
+ var distance: float
+ if auto_follow_distance:
+ distance = lerpf(auto_follow_distance_min, auto_follow_distance_max, bounds.get_longest_axis_size() / auto_follow_distance_divisor)
+ distance = clampf(distance, auto_follow_distance_min, auto_follow_distance_max)
+ else:
+ distance = follow_distance
+
+ _target_transform.origin = \
+ bounds.get_center() + \
+ follow_offset + \
+ global_transform.basis.z * \
+ Vector3(distance, distance, distance)
+ else:
+ _target_transform.origin = \
+ follow_targets[_follow_targets_single_target_index].global_position + \
+ follow_offset + \
+ global_transform.basis.z * \
+ Vector3(auto_follow_distance_min, auto_follow_distance_min, auto_follow_distance_min)
+
+ FollowMode.PATH:
+ var path_position: Vector3 = follow_path.global_position
+ _target_transform.origin = \
+ follow_path.curve.get_closest_point(
+ follow_target.global_position - path_position
+ ) + path_position
+
+ FollowMode.FRAMED:
+ if not Engine.is_editor_hint():
+ if not _is_active:
+ _target_transform.origin = _get_target_position_offset_distance()
+ else:
+ viewport_position = get_viewport().get_camera_3d().unproject_position(_get_target_position_offset())
+ var visible_rect_size: Vector2 = get_viewport().get_visible_rect().size
+ viewport_position = viewport_position / visible_rect_size
+ _current_rotation = global_rotation
+
+ if _current_rotation != global_rotation:
+ _target_transform.origin = _get_target_position_offset_distance()
+
+ if _get_framed_side_offset() != Vector2.ZERO:
+ var target_position: Vector3 = _get_target_position_offset() + _follow_framed_offset
+ var glo_pos: Vector3
+
+ if dead_zone_width == 0 || dead_zone_height == 0:
+ if dead_zone_width == 0 && dead_zone_height != 0:
+ glo_pos = _get_target_position_offset_distance()
+ glo_pos.z = target_position.z
+ _target_transform.origin = glo_pos
+ elif dead_zone_width != 0 && dead_zone_height == 0:
+ glo_pos = _get_target_position_offset_distance()
+ glo_pos.x = target_position.x
+ _target_transform.origin = glo_pos
+ else:
+ _target_transform.origin = _get_target_position_offset_distance()
+ else:
+ if _current_rotation != global_rotation:
+ var opposite: float = sin(-global_rotation.x) * follow_distance + _get_target_position_offset().y
+ glo_pos.y = _get_target_position_offset().y + opposite
+ glo_pos.z = sqrt(pow(follow_distance, 2) - pow(opposite, 2)) + _get_target_position_offset().z
+ glo_pos.x = global_position.x
+
+ _target_transform.origin = glo_pos
+ _current_rotation = global_rotation
+ else:
+ dead_zone_reached.emit()
+ _target_transform.origin = target_position
+ else:
+ _follow_framed_offset = global_position - _get_target_position_offset()
+ _current_rotation = global_rotation
+ return
+ else:
+ _target_transform.origin = _get_target_position_offset_distance()
+ var unprojected_position: Vector2 = _get_raw_unprojected_position()
+ var viewport_width: float = get_viewport().size.x
+ var viewport_height: float = get_viewport().size.y
+ var camera_aspect: Camera3D.KeepAspect = get_viewport().get_camera_3d().keep_aspect
+ var visible_rect_size: Vector2 = get_viewport().get_visible_rect().size
+
+ unprojected_position = unprojected_position - visible_rect_size / 2
+ if camera_aspect == Camera3D.KeepAspect.KEEP_HEIGHT:
+ # Landscape View
+ var aspect_ratio_scale: float = viewport_width / viewport_height
+ unprojected_position.x = (unprojected_position.x / aspect_ratio_scale + 1) / 2
+ unprojected_position.y = (unprojected_position.y + 1) / 2
+ else:
+ # Portrait View
+ var aspect_ratio_scale: float = viewport_height / viewport_width
+ unprojected_position.x = (unprojected_position.x + 1) / 2
+ unprojected_position.y = (unprojected_position.y / aspect_ratio_scale + 1) / 2
+
+ viewport_position = unprojected_position
+
+ FollowMode.THIRD_PERSON:
+ if not Engine.is_editor_hint():
+ if not _has_follow_spring_arm: return
+ _target_transform.origin = _get_target_position_offset()
+ else:
+ _target_transform.origin = _get_target_position_offset_distance()
+
+
+func _look_at(delta: float) -> void:
+ match look_at_mode:
+ LookAtMode.MIMIC:
+ _interpolate_rotation(
+ global_position - look_at_target.global_basis.z,
+ delta
+ )
+
+ LookAtMode.SIMPLE:
+ _interpolate_rotation(
+ look_at_target.global_position,
+ delta
+ )
+
+ LookAtMode.GROUP:
+ if not _has_multiple_look_at_targets:
+ _interpolate_rotation(
+ look_at_targets[_look_at_targets_single_target_index].global_position,
+ delta
+ )
+ else:
+ var bounds: AABB = AABB(look_at_targets[0].global_position, Vector3.ZERO)
+ for node in look_at_targets:
+ bounds = bounds.expand(node.global_position)
+ _interpolate_rotation(
+ bounds.get_center(),
+ delta
+ )
+
+
+func _get_target_position_offset() -> Vector3:
+ return follow_target.global_position + follow_offset
+
+
+func _get_target_position_offset_distance() -> Vector3:
+ return _get_target_position_offset() + \
+ transform.basis.z * Vector3(follow_distance, follow_distance, follow_distance)
+
+
+func _set_follow_velocity(index: int, value: float) -> void:
+ _follow_velocity_ref[index] = value
+
+
+func _interpolate_position(delta: float) -> void:
+ if follow_damping:
+ if not _is_third_person_follow:
+ global_position = _target_transform.origin
+ for i in 3:
+ _transform_output.origin[i] = _smooth_damp(
+ global_position[i],
+ _transform_output.origin[i],
+ i,
+ _follow_velocity_ref[i],
+ _set_follow_velocity,
+ follow_damping_value[i],
+ delta
+ )
+ else:
+ for i in 3:
+ _camera_target.global_position[i] = _smooth_damp(
+ _target_transform.origin[i],
+ _camera_target.global_position[i],
+ i,
+ _follow_velocity_ref[i],
+ _set_follow_velocity,
+ follow_damping_value[i],
+ delta
+ )
+ _transform_output.origin = global_position
+ _transform_output.basis = global_basis
+ _camera_target.global_position = _target_transform.origin
+ else:
+ _camera_target.global_position = _target_transform.origin
+ _transform_output.origin = global_position
+
+
+func _interpolate_rotation(target_position: Vector3, delta: float) -> void:
+ var direction: Vector3 = -(target_position - global_position + look_at_offset).normalized()
+
+ if _has_up_target:
+ _up = up_target.get_global_transform().basis.y
+
+ var basis_z: Vector3 = direction.normalized()
+ var basis_x: Vector3 = _up.cross(basis_z)
+ var basis_y: Vector3 = basis_z.cross(basis_x.normalized())
+
+ var target_basis: Basis = Basis(basis_x, basis_y, basis_z)
+
+ if target_basis.determinant() == 0:
+ if target_basis.z == Vector3.UP:
+ global_rotation_degrees.x = -90
+ else:
+ global_rotation_degrees.x = 90
+
+ _transform_output.basis = global_basis
+ return
+
+ var target_quat: Quaternion = target_basis.get_rotation_quaternion().normalized()
+
+ if look_at_damping:
+ var current_quat: Quaternion = quaternion.normalized()
+ var damping_time: float = max(0.0001, look_at_damping_value)
+ var t: float = min(1.0, delta / damping_time)
+
+ var dot: float = current_quat.dot(target_quat)
+
+ if dot < 0.0:
+ target_quat = -target_quat
+ dot = -dot
+
+ dot = clampf(dot, -1.0, 1.0)
+
+ var theta: float = acos(dot) * t
+ var sin_theta: float = sin(theta)
+ var sin_theta_total: float = sin(acos(dot))
+
+ # Stop interpolating once sin_theta_total reaches a very low value or 0
+ if sin_theta_total < 0.00001:
+ return
+
+ var ratio_a: float = cos(theta) - dot * sin_theta / sin_theta_total
+ var ratio_b: float = sin_theta / sin_theta_total
+ var output: Quaternion = current_quat * ratio_a + target_quat * ratio_b
+
+ _transform_output.basis = Basis(output)
+ quaternion = output
+ else:
+ _transform_output.basis = Basis(target_quat)
+ quaternion = target_quat
+
+
+func _smooth_damp(target_axis: float, self_axis: float, index: int, current_velocity: float, set_velocity: Callable, damping_time: float, delta: float) -> float:
+ damping_time = maxf(0.0001, damping_time)
+ var omega: float = 2 / damping_time
+ var x: float = omega * delta
+ var exponential: float = 1 / (1 + x + 0.48 * x * x + 0.235 * x * x * x)
+ var diff: float = self_axis - target_axis
+ var _target_axis: float = target_axis
+
+ var max_change: float = INF * damping_time
+ diff = clampf(diff, -max_change, max_change)
+ target_axis = self_axis - diff
+
+ var temp: float = (current_velocity + omega * diff) * delta
+ set_velocity.call(index, (current_velocity - omega * temp) * exponential)
+ var output: float = target_axis + (diff + temp) * exponential
+
+ ## To prevent overshooting
+ if (_target_axis - self_axis > 0.0) == (output > _target_axis):
+ output = _target_axis
+ set_velocity.call(index, (output - _target_axis) / delta)
+
+ return output
+
+
+func _get_raw_unprojected_position() -> Vector2:
+ return get_viewport().get_camera_3d().unproject_position(follow_target.global_position + follow_offset)
+
+
+func _on_dead_zone_changed() -> void:
+ global_position = _get_target_position_offset_distance()
+
+
+func _get_framed_side_offset() -> Vector2:
+ var frame_out_bounds: Vector2
+
+ if viewport_position.x < 0.5 - dead_zone_width / 2:
+ # Is outside left edge
+ frame_out_bounds.x = -1
+
+ if viewport_position.y < 0.5 - dead_zone_height / 2:
+ # Is outside top edge
+ frame_out_bounds.y = 1
+
+ if viewport_position.x > 0.5 + dead_zone_width / 2:
+ # Is outside right edge
+ frame_out_bounds.x = 1
+
+ if viewport_position.y > 0.5001 + dead_zone_height / 2: # 0.501 to resolve an issue where the bottom vertical Dead Zone never becoming 0 when the Dead Zone Vertical parameter is set to 0
+ # Is outside bottom edge
+ frame_out_bounds.y = -1
+
+ return frame_out_bounds
+
+
+func _set_layer(current_layers: int, layer_number: int, value: bool) -> int:
+ var mask: int = current_layers
+
+ # From https://github.com/godotengine/godot/blob/51991e20143a39e9ef0107163eaf283ca0a761ea/scene/3d/camera_3d.cpp#L638
+ if layer_number < 1 or layer_number > 20:
+ printerr("Render layer must be between 1 and 20.")
+ else:
+ if value:
+ mask |= 1 << (layer_number - 1)
+ else:
+ mask &= ~(1 << (layer_number - 1))
+
+ return mask
+
+
+func _check_visibility() -> void:
+ _phantom_camera_manager.pcam_visibility_changed.emit(self)
+
+
+func _follow_target_tree_exiting(target: Node) -> void:
+ if target == follow_target:
+ _should_follow = false
+ if _follow_targets.has(target):
+ _follow_targets.erase(target)
+
+
+func _should_follow_checker() -> void:
+ if follow_mode == FollowMode.NONE:
+ _should_follow = false
+ return
+
+ if not follow_mode == FollowMode.GROUP:
+ if is_instance_valid(follow_target):
+ _should_follow = true
+ else:
+ _should_follow = false
+
+
+func _follow_targets_size_check() -> void:
+ var targets_size: int = 0
+ _follow_target_physics_based = false
+ _follow_targets = []
+ for i in follow_targets.size():
+ if follow_targets[i] == null: continue
+ if follow_targets[i].is_inside_tree():
+ _follow_targets.append(follow_targets[i])
+ targets_size += 1
+ _follow_targets_single_target_index = i
+ _check_physics_body(follow_targets[i])
+ if not follow_targets[i].tree_exiting.is_connected(_follow_target_tree_exiting):
+ follow_targets[i].tree_exiting.connect(_follow_target_tree_exiting.bind(follow_targets[i]))
+
+ match targets_size:
+ 0:
+ _should_follow = false
+ _has_multiple_follow_targets = false
+ 1:
+ _should_follow = true
+ _has_multiple_follow_targets = false
+ _:
+ _should_follow = true
+ _has_multiple_follow_targets = true
+
+
+func _look_at_target_tree_exiting(target: Node) -> void:
+ if target == look_at_target:
+ _should_look_at = false
+ if look_at_targets.has(target):
+ erase_look_at_targets(target)
+
+func _up_target_tree_exiting() -> void:
+ up_target = null
+
+
+func _should_look_at_checker() -> void:
+ if look_at_mode == LookAtMode.NONE:
+ _should_look_at = false
+ return
+
+ if not look_at_mode == LookAtMode.GROUP:
+ if is_instance_valid(look_at_target):
+ _should_look_at = true
+ else:
+ _should_look_at = false
+
+
+func _look_at_targets_size_check() -> void:
+ var targets_size: int = 0
+ _look_at_target_physics_based = false
+
+ for i in look_at_targets.size():
+ if is_instance_valid(look_at_targets[i]):
+ targets_size += 1
+ _look_at_targets_single_target_index = i
+ _check_physics_body(look_at_targets[i])
+ if not look_at_targets[i].tree_exiting.is_connected(_look_at_target_tree_exiting):
+ look_at_targets[i].tree_exiting.connect(_look_at_target_tree_exiting.bind(look_at_targets[i]))
+
+ match targets_size:
+ 0:
+ _should_look_at = false
+ _has_multiple_look_at_targets = false
+ 1:
+ _should_look_at = true
+ _has_multiple_look_at_targets = false
+ _:
+ _should_look_at = true
+ _has_multiple_look_at_targets = true
+
+
+func _noise_emitted(emitter_noise_output: Transform3D, emitter_layer: int) -> void:
+ if noise_emitter_layer & emitter_layer != 0:
+ noise_emitted.emit(emitter_noise_output)
+
+
+func _check_physics_body(target: Node3D) -> void:
+ if target is PhysicsBody3D:
+ var show_jitter_tips := ProjectSettings.get_setting("phantom_camera/tips/show_jitter_tips")
+ var physics_interpolation_enabled := ProjectSettings.get_setting("physics/common/physics_interpolation")
+
+ ## NOTE - Feature Toggle
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor < 4:
+ if show_jitter_tips == null: # Default value is null when referencing custom Project Setting
+ print_rich("Following or Looking at a [b]PhysicsBody3D[/b] node will likely result in jitter - on lower physics ticks in particular.")
+ print_rich("If possible, will recommend upgrading to Godot 4.4, as it has built-in support for 3D Physics Interpolation, which will mitigate this issue.")
+ print_rich("Until then, try following the guide on the [url=https://phantom-camera.dev/support/faq#i-m-seeing-jitter-what-can-i-do]documentation site[/url] for better results.")
+ print_rich("This tip can be disabled from within [code]Project Settings / Phantom Camera / Tips / Show Jitter Tips[/code]")
+ return
+ ## NOTE - Only supported in Godot 4.4 or above
+ elif not physics_interpolation_enabled and show_jitter_tips == null: # Default value is null when referencing custom Project Setting
+ printerr("Physics Interpolation is disabled in the Project Settings, recommend enabling it to smooth out physics-based camera movement")
+ print_rich("This tip can be disabled from within [code]Project Settings / Phantom Camera / Tips / Show Jitter Tips[/code]")
+ _follow_target_physics_based = true
+ else:
+ _is_parents_physics(target)
+ physics_target_changed.emit()
+
+
+func _is_parents_physics(target: Node = self) -> void:
+ var current_node: Node = target
+ while current_node:
+ current_node = current_node.get_parent()
+ if not current_node is PhysicsBody3D: continue
+ _follow_target_physics_based = true
+
+
+func _camera_resource_changed() -> void:
+ camera_3d_resource_changed.emit()
+
+#endregion
+
+
+# TBD
+#func get_unprojected_position() -> Vector2:
+ #var unprojected_position: Vector2 = _get_raw_unprojected_position()
+ #var viewport_width: float = get_viewport().size.x
+ #var viewport_height: float = get_viewport().size.y
+ #var camera_aspect: Camera3D.KeepAspect = get_viewport().get_camera_3d().keep_aspect
+ #var visible_rect_size: Vector2 = get_viewport().size
+#
+ #unprojected_position = unprojected_position - visible_rect_size / 2
+ #if camera_aspect == Camera3D.KeepAspect.KEEP_HEIGHT:
+## print("Landscape View")
+ #var aspect_ratio_scale: float = viewport_width / viewport_height
+ #unprojected_position.x = (unprojected_position.x / aspect_ratio_scale + 1) / 2
+ #unprojected_position.y = (unprojected_position.y + 1) / 2
+ #else:
+## print("Portrait View")
+ #var aspect_ratio_scale: float = viewport_height / viewport_width
+ #unprojected_position.x = (unprojected_position.x + 1) / 2
+ #unprojected_position.y = (unprojected_position.y / aspect_ratio_scale + 1) / 2
+#
+ #return unprojected_position
+
+
+## Returns the [Transform3D] value based on the [member follow_mode] / [member look_at_mode] target value.
+func get_transform_output() -> Transform3D:
+ return _transform_output
+
+
+## Returns the noise [Transform3D] value.
+func get_noise_transform() -> Transform3D:
+ return _transform_noise
+
+
+## Emits a noise based on a custom [Transform3D] value.[br]
+## Use this function if you wish to make use of external noise patterns from, for example, other addons.
+func emit_noise(value: Transform3D) -> void:
+ noise_emitted.emit(value)
+
+
+## Teleports the [param PhantomCamera3D] and [Camera3D] to their designated position,
+## bypassing the damping process.
+func teleport_position() -> void:
+ _follow_velocity_ref = Vector3.ZERO
+ _set_follow_position()
+ _transform_output.origin = _target_transform.origin
+ _phantom_camera_manager.pcam_teleport.emit(self)
+
+#endregion
+
+
+#region Setter & Getter Functions
+
+## Assigns the value of the [param has_tweened] property.[br]
+## [b][color=yellow]Important:[/color][/b] This value can only be changed
+## from the [PhantomCameraHost] script.
+func set_tween_skip(caller: Node, value: bool) -> void:
+ if is_instance_of(caller, PhantomCameraHost):
+ _tween_skip = value
+ else:
+ printerr("Can only be called PhantomCameraHost class")
+## Returns the current [param has_tweened] value.
+func get_tween_skip() -> bool:
+ return _tween_skip
+
+
+## Assigns new [member priority] value.
+func set_priority(value: int) -> void:
+ priority = abs(value) # TODO - Make any minus values be 0
+ if not Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME): return
+ Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME).pcam_priority_changed.emit(self)
+## Gets current [param Priority] value.
+func get_priority() -> int:
+ return priority
+
+
+## Assigns a new [PhantomCameraTween] resource to the [param PhantomCamera3D].
+func set_tween_resource(value: PhantomCameraTween) -> void:
+ tween_resource = value
+## Gets the [param PhantomCameraTween] resource assigned to the [param PhantomCamera3D].
+## Returns null if there's nothing assigned to it.
+func get_tween_resource() -> PhantomCameraTween:
+ return tween_resource
+
+## Assigns a new [param Tween Duration] to the [member tween_resource] value.[br]
+## The duration value is in seconds.
+func set_tween_duration(value: float) -> void:
+ tween_resource.duration = value
+## Gets the current [param Tween] Duration value. The duration value is in
+## [param seconds].
+func get_tween_duration() -> float:
+ return tween_resource.duration
+
+## Assigns a new [param Tween Transition] to the [member tween_resource] value.[br]
+## The duration value is in seconds.
+func set_tween_transition(value: int) -> void:
+ tween_resource.transition = value
+## Gets the current [param Tween Transition] value.
+func get_tween_transition() -> int:
+ return tween_resource.transition
+
+## Assigns a new [param Tween Ease] to the [member tween_resource] value.[br]
+## The duration value is in seconds.
+func set_tween_ease(value: int) -> void:
+ tween_resource.ease = value
+## Gets the current [param Tween Ease] value.
+func get_tween_ease() -> int:
+ return tween_resource.ease
+
+## Sets the [param PhantomCamera3D] active state[br]
+## [b][color=yellow]Important:[/color][/b] This value can only be changed
+## from the [PhantomCameraHost] script.
+func set_is_active(node: Node, value: bool) -> void:
+ if node is PhantomCameraHost:
+ _is_active = value
+ if value:
+ _should_follow_checker()
+ else:
+ printerr("PCams can only be set from the PhantomCameraHost")
+## Gets current active state of the [param PhantomCamera3D].
+## If it returns true, it means the [param PhantomCamera3D] is what the
+## [param Camera3D] is currently following.
+func is_active() -> bool:
+ return _is_active
+
+
+## Enables or disables the [member tween_on_load].
+func set_tween_on_load(value: bool) -> void:
+ tween_on_load = value
+## Gets the current [member tween_on_load] value.
+func get_tween_on_load() -> bool:
+ return tween_on_load
+
+
+## Sets the [member host_layers] value.
+func set_host_layers(value: int) -> void:
+ host_layers = value
+ if is_instance_valid(_phantom_camera_manager):
+ _phantom_camera_manager.pcam_host_layer_changed.emit(self)
+
+## Enables or disables a given layer of [member host_layers].
+func set_host_layers_value(layer: int, value: bool) -> void:
+ host_layers = _set_layer(host_layers, layer, value)
+
+## Gets the current [member host_layers].
+func get_host_layers() -> int:
+ return host_layers
+
+
+## Gets the current follow mode as an enum int based on [member FollowMode] enum.[br]
+## [b]Note:[/b] Setting [member follow_mode] has purposely not been added.
+## A separate [param PhantomCamera3D] instance should be used instead.
+func get_follow_mode() -> int:
+ return follow_mode
+
+
+## Assigns a new [Node3D] as the [member follow_target].
+func set_follow_target(value: Node3D) -> void:
+ if follow_mode == FollowMode.NONE or follow_mode == FollowMode.GROUP: return
+ if follow_target == value: return
+ follow_target = value
+ _follow_target_physics_based = false
+ if is_instance_valid(value):
+ if follow_mode == FollowMode.PATH:
+ if is_instance_valid(follow_path):
+ _should_follow = true
+ else:
+ _should_follow = false
+ else:
+ _should_follow = true
+ _check_physics_body(value)
+ if not follow_target.tree_exiting.is_connected(_follow_target_tree_exiting):
+ follow_target.tree_exiting.connect(_follow_target_tree_exiting.bind(follow_target))
+ else:
+ if not follow_mode == FollowMode.GROUP:
+ _should_follow = false
+ follow_target_changed.emit()
+ notify_property_list_changed()
+## Removes the current [Node3D] [member follow_target].
+func erase_follow_target() -> void:
+ follow_target = null
+## Gets the current Node3D target.
+func get_follow_target() -> Node3D:
+ return follow_target
+
+
+## Assigns a new [Path3D] to the [member follow_path] property.
+func set_follow_path(value: Path3D) -> void:
+ follow_path = value
+ if is_instance_valid(follow_path):
+ _should_follow_checker()
+ else:
+ _should_follow = false
+
+## Erases the current [Path3D] from [member follow_path] property.
+func erase_follow_path() -> void:
+ follow_path = null
+
+## Gets the current [Path3D] from the [member follow_path] property.
+func get_follow_path() -> Path3D:
+ return follow_path
+
+
+## Assigns a new [param follow_targets] array value.
+func set_follow_targets(value: Array[Node3D]) -> void:
+ if not follow_mode == FollowMode.GROUP: return
+ if follow_targets == value: return
+ follow_targets = value
+ _follow_targets_size_check()
+
+
+## Adds a single [Node3D] to [member follow_targets] array.
+func append_follow_targets(value: Node3D) -> void:
+ if not is_instance_valid(value):
+ printerr(value, " is not a valid Node3D instance")
+ return
+
+ if not follow_targets.has(value):
+ follow_targets.append(value)
+ _follow_targets_size_check()
+ else:
+ printerr(value, " is already part of Follow Group")
+
+## Adds an Array of type [Node3D] to [member follow_targets] array.
+func append_follow_targets_array(value: Array[Node3D]) -> void:
+ for target in value:
+ if not is_instance_valid(target): continue
+ if not follow_targets.has(target):
+ follow_targets.append(target)
+ _follow_targets_size_check()
+ else:
+ printerr(value, " is already part of Follow Group")
+
+## Removes [Node3D] from [member follow_targets].
+func erase_follow_targets(value: Node3D) -> void:
+ follow_targets.erase(value)
+ _follow_targets_size_check()
+
+
+## Gets all [Node3D] from [follow_targets].
+func get_follow_targets() -> Array[Node3D]:
+ return follow_targets
+
+
+## Assigns a new [param Vector3] for the [param follow_offset] property.
+func set_follow_offset(value: Vector3) -> void:
+ var temp_offset: Vector3 = follow_offset
+ follow_offset = value
+
+ if follow_axis_lock != FollowLockAxis.NONE:
+ temp_offset = temp_offset - value
+ match value:
+ FollowLockAxis.X:
+ _follow_axis_lock_value.x = _transform_output.origin.x + temp_offset.x
+ FollowLockAxis.Y:
+ _follow_axis_lock_value.y = _transform_output.origin.y + temp_offset.y
+ FollowLockAxis.Z:
+ _follow_axis_lock_value.z = _transform_output.origin.z + temp_offset.z
+ FollowLockAxis.XY:
+ _follow_axis_lock_value.x = _transform_output.origin.x + temp_offset.x
+ _follow_axis_lock_value.y = _transform_output.origin.y + temp_offset.y
+ FollowLockAxis.XZ:
+ _follow_axis_lock_value.x = _transform_output.origin.x + temp_offset.x
+ _follow_axis_lock_value.z = _transform_output.origin.z + temp_offset.z
+ FollowLockAxis.YZ:
+ _follow_axis_lock_value.y = _transform_output.origin.y + temp_offset.y
+ _follow_axis_lock_value.z = _transform_output.origin.z + temp_offset.z
+ FollowLockAxis.XYZ:
+ _follow_axis_lock_value.x = _transform_output.origin.x + temp_offset.x
+ _follow_axis_lock_value.y = _transform_output.origin.y + temp_offset.y
+ _follow_axis_lock_value.z = _transform_output.origin.z + temp_offset.z
+
+## Gets the current [param Vector3] for the [param follow_offset] property.
+func get_follow_offset() -> Vector3:
+ return follow_offset
+
+
+## Enables or disables [member follow_damping].
+func set_follow_damping(value: bool) -> void:
+ follow_damping = value
+ notify_property_list_changed()
+
+## Gets the currents [member follow_damping] property.
+func get_follow_damping() -> bool:
+ return follow_damping
+
+
+## Assigns new [member follow_damping_value] value.
+func set_follow_damping_value(value: Vector3) -> void:
+ ## TODO - Should be using @export_range once minimum version support is Godot 4.3
+ if value.x < 0: value.x = 0
+ elif value.y < 0: value.y = 0
+ elif value.z < 0: value.z = 0
+ follow_damping_value = value
+
+## Gets the currents [member follow_damping_value] value.
+func get_follow_damping_value() -> Vector3:
+ return follow_damping_value
+
+
+## Assigns a new [member follow_distance] value.
+func set_follow_distance(value: float) -> void:
+ follow_distance = value
+
+## Gets [member follow_distance] value.
+func get_follow_distance() -> float:
+ return follow_distance
+
+
+## Enables or disables [member auto_follow_distance] when using Group Follow.
+func set_auto_follow_distance(value: bool) -> void:
+ auto_follow_distance = value
+ notify_property_list_changed()
+
+## Gets [member auto_follow_distance] state.
+func get_auto_follow_distance() -> bool:
+ return auto_follow_distance
+
+
+## Assigns new [member auto_follow_distance_min] value.
+func set_auto_follow_distance_min(value: float) -> void:
+ auto_follow_distance_min = value
+
+## Gets [member auto_follow_distance_min] value.
+func get_auto_follow_distance_min() -> float:
+ return auto_follow_distance_min
+
+
+## Assigns new [member auto_follow_distance_max] value.
+func set_auto_follow_distance_max(value: float) -> void:
+ auto_follow_distance_max = value
+## Gets [member auto_follow_distance_max] value.
+func get_auto_follow_distance_max() -> float:
+ return auto_follow_distance_max
+
+
+## Assigns new [member auto_follow_distance_divisor] value.
+func set_auto_follow_distance_divisor(value: float) -> void:
+ auto_follow_distance_divisor = value
+
+## Gets [member auto_follow_distance_divisor] value.
+func get_auto_follow_distance_divisor() -> float:
+ return auto_follow_distance_divisor
+
+
+## Assigns new rotation (in radians) value to [SpringArm3D] for
+## [param ThirdPerson] [enum FollowMode].
+func set_third_person_rotation(value: Vector3) -> void:
+ _follow_spring_arm.rotation = value
+
+## Gets the rotation value (in radians) from the [SpringArm3D] for
+## [param ThirdPerson] [enum FollowMode].
+func get_third_person_rotation() -> Vector3:
+ return _follow_spring_arm.rotation
+
+
+## Assigns new rotation (in degrees) value to [SpringArm3D] for
+## [param ThirdPerson] [enum FollowMode].
+func set_third_person_rotation_degrees(value: Vector3) -> void:
+ _follow_spring_arm.rotation_degrees = value
+
+## Gets the rotation value (in degrees) from the [SpringArm3D] for
+## [param ThirdPerson] [enum FollowMode].
+func get_third_person_rotation_degrees() -> Vector3:
+ return _follow_spring_arm.rotation_degrees
+
+
+## Assigns new [Quaternion] value to [SpringArm3D] for [param ThirdPerson]
+## [enum FollowMode].
+func set_third_person_quaternion(value: Quaternion) -> void:
+ _follow_spring_arm.quaternion = value
+
+## Gets the [Quaternion] value of the [SpringArm3D] for [param ThirdPerson]
+## [enum Follow mode].
+func get_third_person_quaternion() -> Quaternion:
+ return _follow_spring_arm.quaternion
+
+
+## Assigns a new ThirdPerson [member SpringArm3D.length] value.
+func set_spring_length(value: float) -> void:
+ follow_distance = value
+ if not is_instance_valid(_follow_spring_arm): return
+ _follow_spring_arm.spring_length = value
+
+## Gets the [member SpringArm3D.length]
+## from a [param ThirdPerson] [enum follow_mode] instance.
+func get_spring_length() -> float:
+ return follow_distance
+
+
+## Assigns a new [member collision_mask] to the [SpringArm3D] when [enum FollowMode]
+## is set to [param ThirdPerson].
+func set_collision_mask(value: int) -> void:
+ collision_mask = value
+ if not is_instance_valid(_follow_spring_arm): return
+ _follow_spring_arm.collision_mask = collision_mask
+
+## Enables or disables a specific [member collision_mask] layer for the
+## [SpringArm3D] when [enum FollowMode] is set to [param ThirdPerson].
+func set_collision_mask_value(value: int, enabled: bool) -> void:
+ collision_mask = _set_layer(collision_mask, value, enabled)
+ if not is_instance_valid(_follow_spring_arm): return
+ _follow_spring_arm.collision_mask = collision_mask
+
+## Gets [member collision_mask] from the [SpringArm3D] when [enum FollowMode]
+## is set to [param ThirdPerson].
+func get_collision_mask() -> int:
+ return collision_mask
+
+
+## Assigns a new [SpringArm3D.shape] when [enum FollowMode]
+## is set to [param ThirdPerson].
+func set_shape(value: Shape3D) -> void:
+ shape = value
+ if not is_instance_valid(_follow_spring_arm): return
+ _follow_spring_arm.shape = shape
+
+## Gets [param ThirdPerson] [member SpringArm3D.shape] value.
+func get_shape() -> Shape3D:
+ return shape
+
+
+## Assigns a new [member SpringArm3D.margin] value when [enum FollowMode]
+## is set to [param ThirdPerson].
+func set_margin(value: float) -> void:
+ margin = value
+ if not is_instance_valid(_follow_spring_arm): return
+ _follow_spring_arm.margin = margin
+
+## Gets the [SpringArm3D.margin] when [enum FollowMode] is set to
+## [param ThirdPerson].
+func get_margin() -> float:
+ return margin
+
+
+## Gets the current [member look_at_mode]. Value is based on [enum LookAtMode]
+## enum.[br]
+## Note: To set a new [member look_at_mode], a separate [param PhantomCamera3D] should be used.
+func get_look_at_mode() -> int:
+ return look_at_mode
+
+
+## Assigns new [Node3D] as [member look_at_target].
+func set_look_at_target(value: Node3D) -> void:
+ if look_at_mode == LookAtMode.NONE: return
+ if look_at_target == value: return
+ look_at_target = value
+ if not look_at_mode == LookAtMode.GROUP:
+ if is_instance_valid(look_at_target):
+ _should_look_at = true
+ _check_physics_body(value)
+ if not look_at_target.tree_exiting.is_connected(_look_at_target_tree_exiting):
+ look_at_target.tree_exiting.connect(_look_at_target_tree_exiting.bind(look_at_target))
+ else:
+ _should_look_at = false
+ elif look_at_targets.size() == 0:
+ _should_look_at = false
+
+ look_at_target_changed.emit()
+ notify_property_list_changed()
+
+## Gets current [Node3D] from [member look_at_target] property.
+func get_look_at_target() -> Node3D:
+ return look_at_target
+
+
+## Sets an array of type [Node3D] to [member set_look_at_targets].
+func set_look_at_targets(value: Array[Node3D]) -> void:
+ if not look_at_mode == LookAtMode.GROUP: return
+ if look_at_targets == value: return
+ look_at_targets = value
+
+ _look_at_targets_size_check()
+ notify_property_list_changed()
+
+## Appends a [Node3D] to [member look_at_targets] array.
+func append_look_at_target(value: Node3D) -> void:
+ if not is_instance_valid(value):
+ printerr(value, "is an invalid Node3D instance")
+ return
+
+ if not look_at_targets.has(value):
+ look_at_targets.append(value)
+ _look_at_targets_size_check()
+ else:
+ printerr(value, " is already part of Look At Group")
+
+
+## Appends an array of type [Node3D] to [member look_at_targets] array.
+func append_look_at_targets_array(value: Array[Node3D]) -> void:
+ for val in value:
+ if not is_instance_valid(val): continue
+ if not look_at_targets.has(val):
+ look_at_targets.append(val)
+ _look_at_targets_size_check()
+ else:
+ printerr(val, " is already part of Look At Group")
+
+## Removes [Node3D] from [member look_at_targets] array.
+func erase_look_at_targets(value: Node3D) -> void:
+ if look_at_targets.has(value):
+ look_at_targets.erase(value)
+ _look_at_targets_size_check()
+ else:
+ printerr(value, " is not part of Look At Group")
+
+
+## Removes [Node3D] from [member look_at_targets] array. [br]
+## @deprecated: Use [member erase_look_at_targets] instead.
+func erase_look_at_targets_member(value: Node3D) -> void:
+ printerr("erase_look_at_targets_member is deprecated, use erase_look_at_targets instead")
+ erase_look_at_targets(value)
+
+## Gets all the [Node3D] instances in [member look_at_targets].
+func get_look_at_targets() -> Array[Node3D]:
+ return look_at_targets
+
+
+## Assigns a new [Vector3] to the [member look_at_offset] value.
+func set_look_at_offset(value: Vector3) -> void:
+ look_at_offset = value
+
+## Gets the current [member look_at_offset] value.
+func get_look_at_offset() -> Vector3:
+ return look_at_offset
+
+
+## Enables or disables [member look_at_damping].
+func set_look_at_damping(value: bool) -> void:
+ look_at_damping = value
+ notify_property_list_changed()
+
+## Gets the currents [member look_at_damping] property.
+func get_look_at_damping() -> bool:
+ return look_at_damping
+
+
+## Assigns new [member look_at_damping_value] value.
+func set_look_at_damping_value(value: float) -> void:
+ look_at_damping_value = value
+
+## Gets the currents [member look_at_damping_value] value.
+func get_look_at_damping_value() -> float:
+ return look_at_damping_value
+
+## Assigns the Follow Axis.
+func set_follow_axis_lock(value: FollowLockAxis) -> void:
+ follow_axis_lock = value
+
+ # Wait for the node to be ready before setting lock
+ if not is_node_ready(): await ready
+
+ # Prevent axis lock from working in the editor
+ if value != FollowLockAxis.NONE and not Engine.is_editor_hint():
+ _follow_axis_is_locked = true
+ match value:
+ FollowLockAxis.X:
+ _follow_axis_lock_value.x = _transform_output.origin.x
+ FollowLockAxis.Y:
+ _follow_axis_lock_value.y = _transform_output.origin.y
+ FollowLockAxis.Z:
+ _follow_axis_lock_value.z = _transform_output.origin.z
+ FollowLockAxis.XY:
+ _follow_axis_lock_value.x = _transform_output.origin.x
+ _follow_axis_lock_value.y = _transform_output.origin.y
+ FollowLockAxis.XZ:
+ _follow_axis_lock_value.x = _transform_output.origin.x
+ _follow_axis_lock_value.z = _transform_output.origin.z
+ FollowLockAxis.YZ:
+ _follow_axis_lock_value.y = _transform_output.origin.y
+ _follow_axis_lock_value.z = _transform_output.origin.z
+ FollowLockAxis.XYZ:
+ _follow_axis_lock_value.x = _transform_output.origin.x
+ _follow_axis_lock_value.y = _transform_output.origin.y
+ _follow_axis_lock_value.z = _transform_output.origin.z
+ else:
+ _follow_axis_is_locked = false
+
+## Gets the current [member follow_axis_lock] property. Value is based on [enum FollowLockAxis] enum.
+func get_follow_axis_lock() -> FollowLockAxis:
+ return follow_axis_lock
+
+
+## Sets the [member up] value.
+func set_up(value: Vector3) -> void:
+ if value == Vector3.ZERO:
+ value = Vector3.UP
+ push_warning("Up value cannot be (0, 0, 0), resetting to (0, 1, 0).")
+
+ up = value
+ if not _has_up_target:
+ _up = value
+
+## Gets the [member up] value.
+func get_up() -> Vector3:
+ return up
+
+
+## Sets the [member up_target].
+func set_up_target(value: Node3D) -> void:
+ up_target = value
+ if is_instance_valid(value):
+ _has_up_target = true
+ if not value.tree_exiting.is_connected(_up_target_tree_exiting):
+ value.tree_exiting.connect(_up_target_tree_exiting)
+ else:
+ _has_up_target = false
+ _up = up
+ notify_property_list_changed()
+
+## Gets the [member up_target].
+func get_up_target() -> Node3D:
+ return up_target
+
+
+## Sets a [PhantomCameraNoise3D] resource.
+func set_noise(value: PhantomCameraNoise3D) -> void:
+ noise = value
+ if value != null:
+ _has_noise_resource = true
+ noise.set_trauma(1)
+ else:
+ _has_noise_resource = false
+ _transform_noise = Transform3D()
+
+func get_noise() -> PhantomCameraNoise3D:
+ return noise
+
+func has_noise_resource() -> bool:
+ return _has_noise_resource
+
+
+## Sets the [member noise_emitter_layer] value.
+func set_noise_emitter_layer(value: int) -> void:
+ noise_emitter_layer = value
+
+## Enables or disables a given layer of [member noise_emitter_layer].
+func set_noise_emitter_layer_value(value: int, enabled: bool) -> void:
+ noise_emitter_layer = _set_layer(noise_emitter_layer, value, enabled)
+
+## Returns the [member noise_emitter_layer].
+func get_noise_emitter_layer() -> int:
+ return noise_emitter_layer
+
+
+## Sets [member inactive_update_mode] property.
+func set_inactive_update_mode(value: int) -> void:
+ inactive_update_mode = value
+
+## Gets [member inactive_update_mode] property.
+func get_inactive_update_mode() -> int:
+ return inactive_update_mode
+
+
+## Assigns a [Camera3DResource].
+func set_camera_3d_resource(value: Camera3DResource) -> void:
+ camera_3d_resource = value
+ camera_3d_resource_changed.emit()
+ if value:
+ if not camera_3d_resource.changed.is_connected(_camera_resource_changed):
+ camera_3d_resource.changed.connect(_camera_resource_changed)
+
+## Gets the [Camera3DResource].
+func get_camera_3d_resource() -> Camera3DResource:
+ return camera_3d_resource
+
+
+func set_keep_aspect(value: int) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a keep_aspect value. No Camera3DResource assigned to ", name)
+ return
+ keep_aspect = value
+ camera_3d_resource_property_changed.emit("keep_aspect", value)
+
+func get_keep_aspect() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.keep_aspect
+
+
+## Assigns a new [member Camera3D.cull_mask] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_cull_mask(value: int) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a cull_mask value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.cull_mask = value
+ camera_3d_resource_property_changed.emit("cull_mask", value)
+
+## Enables or disables a specific [member Camera3D.cull_mask] layer.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_cull_mask_value(layer_number: int, value: bool) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a cull_mask value. No Camera3DResource assigned to ", name)
+ return
+ var mask: int = _set_layer(get_cull_mask(), layer_number, value)
+ camera_3d_resource.cull_mask = mask
+ camera_3d_resource_property_changed.emit("cull_mask", mask)
+
+## Gets the [member Camera3D.cull_mask] value assigned to the [Camera3DResource].
+func get_cull_mask() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.cull_mask
+
+
+## Assigns a new [Environment] resource to the [Camera3DResource].
+func set_environment(value: Environment) -> void:
+ environment = value
+ camera_3d_resource_property_changed.emit("environment", value)
+
+## Gets the [Camera3D.environment] value assigned to the [Camera3DResource].
+func get_environment() -> Environment:
+ return environment
+
+
+## Assigns a new [CameraAttributes] resource to the [Camera3DResource].
+func set_attributes(value: CameraAttributes) -> void:
+ attributes = value
+ camera_3d_resource_property_changed.emit("attributes", value)
+
+## Gets the [Camera3D.attributes] value assigned to the [Camera3DResource].
+func get_attributes() -> CameraAttributes:
+ return attributes
+
+
+## Assigns a new [member Camera3D.h_offset] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_h_offset(value: float) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a h_offset value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.h_offset = value
+ camera_3d_resource_property_changed.emit("h_offset", value)
+
+## Gets the [member Camera3D.h_offset] value assigned to the [param Camera3DResource].
+func get_h_offset() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.h_offset
+
+
+## Assigns a new [Camera3D.v_offset] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_v_offset(value: float) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a v_offset value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.v_offset = value
+ camera_3d_resource_property_changed.emit("v_offset", value)
+
+## Gets the [member Camera3D.v_offset] value assigned to the [param Camera3DResource].
+func get_v_offset() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.v_offset
+
+
+## Assigns a new [Camera3D.projection] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_projection(value: int) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a projection value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.projection = value
+ camera_3d_resource_property_changed.emit("projection", value)
+
+## Gets the [member Camera3D.projection] value assigned to the [param Camera3DResource].
+func get_projection() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.projection
+
+
+## Assigns a new [member Camera3D.fov] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_fov(value: float) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a fov value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.fov = value
+ camera_3d_resource_property_changed.emit("fov", value)
+
+## Gets the [member Camera3D.fov] value assigned to the [param Camera3DResource].
+func get_fov() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.fov
+
+
+## Assigns a new [member Camera3D.size] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_size(value: float) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a size value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.size = value
+ camera_3d_resource_property_changed.emit("size", value)
+
+## Gets the [member Camera3D.size] value assigned to the [param Camera3DResource].
+func get_size() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.size
+
+
+## Assigns a new [member Camera3D.frustum_offset] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_frustum_offset(value: Vector2) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a frustum_offset value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.frustum_offset = value
+ camera_3d_resource_property_changed.emit("frustum_offset", value)
+
+## Gets the [member Camera3D.frustum_offset] value assigned to the [param Camera3DResource].
+func get_frustum_offset() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.frustum_offset
+
+
+## Assigns a new [member Camera3D.near] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_near(value: float) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a near value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.near = value
+ camera_3d_resource_property_changed.emit("near", value)
+
+## Gets the [member Camera3D.near] value assigned to the [param Camera3DResource].
+func get_near() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.near
+
+
+## Assigns a new [member Camera3D.far] value.[br]
+## [b]Note:[/b] This will override and make the [param Camera3DResource] unique to
+## this [param PhantomCamera3D].
+func set_far(value: float) -> void:
+ if not camera_3d_resource:
+ printerr("Can't assign a far value. No Camera3DResource assigned to ", name)
+ return
+ camera_3d_resource.far = value
+ camera_3d_resource_property_changed.emit("far", value)
+
+## Gets the [member Camera3D.far] value assigned to the [param Camera3DResource].
+func get_far() -> Variant:
+ if not camera_3d_resource: return null
+ return camera_3d_resource.far
+
+
+func get_follow_target_physics_based() -> bool:
+ return _follow_target_physics_based
+
+
+func get_class() -> String:
+ return "PhantomCamera3D"
+
+
+func is_class(value) -> bool:
+ return value == "PhantomCamera3D"
+
+#endregion
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd.uid b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd.uid
new file mode 100644
index 0000000..31a2fc9
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_3d.gd.uid
@@ -0,0 +1 @@
+uid://csjccrhj5wnx7
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd
new file mode 100644
index 0000000..43f6cf4
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd
@@ -0,0 +1,29 @@
+@tool
+extends RefCounted
+
+#region Constants
+
+#const PhantomCameraHost: Script = preload("res://addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd")
+
+const CAMERA_2D_NODE_NAME: StringName = "Camera2D"
+const CAMERA_3D_NODE_NAME: StringName = "Camera3D"
+const PCAM_HOST_NODE_NAME: StringName = "PhantomCameraHost"
+const PCAM_MANAGER_NODE_NAME: String = "PhantomCameraManager" # TODO - Convert to StringName once https://github.com/godotengine/godot/pull/72702 is merged
+const PCAM_2D_NODE_NAME: StringName = "PhantomCamera2D"
+const PCAM_3D_NODE_NAME: StringName = "PhantomCamera3D"
+const PCAM_HOST: StringName = "phantom_camera_host"
+
+const COLOR_2D: Color = Color("8DA5F3")
+const COLOR_3D: Color = Color("FC7F7F")
+const COLOR_PCAM: Color = Color("3AB99A")
+const COLOR_PCAM_33: Color = Color("3ab99a33")
+const PCAM_HOST_COLOR: Color = Color("E0E0E0")
+
+#endregion
+
+#region Group Names
+
+const PCAM_GROUP_NAME: StringName = "phantom_camera_group"
+const PCAM_HOST_GROUP_NAME: StringName = "phantom_camera_host_group"
+
+#endregion
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd.uid b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd.uid
new file mode 100644
index 0000000..b1e3789
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd.uid
@@ -0,0 +1 @@
+uid://dn74j5b5hdxu
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd
new file mode 100644
index 0000000..7306810
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd
@@ -0,0 +1,264 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_noise_emitter_2d.svg")
+class_name PhantomCameraNoiseEmitter2D
+extends Node2D
+
+## Emits positional and rotational noise to active [PhantomCamera2D]s and its corresponding [Camera2D].
+##
+## Is a node meant to apply positional and rotational noise, also referred to as shake, to the [Camera2D].
+## It is designed for use cases such as when hitting or when being hit, earthquakes or to add a
+## bit of slight movement to the camera to make it feel less static.
+## The emitter can affect multiple [PhantomCamera2D] in a given scene based on which [member noise_emitter_layer]
+## are enabled by calling its [method emit] function. At least one corresponding layer has to be
+## set on the [PhantomCamera2D] and the emitter node.
+
+const _constants = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+#region Exported Proerpties
+
+## The [PhantomCameraNoise2D] resource that defines the noise pattern.
+@export var noise: PhantomCameraNoise2D = null:
+ set = set_noise,
+ get = get_noise
+
+## If true, previews the noise in the editor - can be seen in the viewfinder.
+@export var preview: bool = false:
+ set(value):
+ preview = value
+ _play = value
+ get:
+ return preview
+
+## If true, repeats the noise indefinitely once started. Otherwise, it will only be triggered once. [br]
+@export var continuous: bool = false:
+ set = set_continuous,
+ get = get_continuous
+
+## Determines how long the noise should take to reach full [member intensity] once started.[br]
+## The value is set in [b]seconds[/b].
+@export_exp_easing("positive_only", "suffix: s") var growth_time: float = 0:
+ set = set_growth_time,
+ get = get_growth_time
+
+## Sets the duration for the camera noise if [member continuous] is set to [b]false[/b].[br][br]
+## The value is set in [b]seconds[/b].
+@export_range(0, 10, 0.001, "or_greater", "suffix: s") var duration: float = 1.0:
+ set = set_duration,
+ get = get_duration
+
+## Determines how long the noise should take to come to a full stop.[br]
+## The value is set in [b]seconds[/b].
+@export_exp_easing("attenuation", "positive_only", "suffix: s") var decay_time: float = 0:
+ set = set_decay_time,
+ get = get_decay_time
+
+## Enabled layers will affect [PhantomCamera2D] nodes with at least one corresponding layer enabled.[br]
+## Enabling multiple corresponding layers on the same [PhantomCamera2D] causes no additional effect.
+@export_flags_2d_render var noise_emitter_layer: int = 1:
+ set = set_noise_emitter_layer,
+ get = get_noise_emitter_layer
+
+#endregion
+
+
+#region Private Variables
+
+var _play: bool = false:
+ set(value):
+ _play = value
+ if value:
+ _elasped_play_time = 0
+ _decay_countdown = 0
+ _play = true
+ _should_grow = true
+ _start_duration_countdown = false
+ _should_decay = false
+ else:
+ _should_decay = true
+ if noise.randomize_noise_seed:
+ noise.noise_seed = randi() & 1000
+ else:
+ noise.reset_noise_time()
+ get:
+ return _play
+
+var _start_duration_countdown: bool = false
+
+var _decay_countdown: float = 0
+
+var _should_grow: bool = false
+
+var _should_decay: bool = false
+
+var _elasped_play_time: float = 0
+
+var _noise_output: Transform2D = Transform2D()
+
+# NOTE - Temp solution until Godot has better plugin autoload recognition out-of-the-box.
+var _phantom_camera_manager: Node
+
+#endregion
+
+#region Private Functions
+
+func _get_configuration_warnings() -> PackedStringArray:
+ if noise == null:
+ return ["Noise resource is required in order to trigger emitter."]
+ else:
+ return []
+
+
+func _validate_property(property) -> void:
+ if property.name == "duration" and continuous:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _enter_tree() -> void:
+ _phantom_camera_manager = get_tree().root.get_node(_constants.PCAM_MANAGER_NODE_NAME)
+
+
+func _process(delta: float) -> void:
+ if not _play and not _should_decay: return
+ if noise == null:
+ printerr("Noise resource missing in ", name)
+ _play = false
+ return
+
+ _elasped_play_time += delta
+
+ if _should_grow:
+ noise.set_trauma(minf(_elasped_play_time / growth_time, 1))
+ if _elasped_play_time >= growth_time:
+ _should_grow = false
+ _start_duration_countdown = true
+ noise.set_trauma(1)
+ else:
+ noise.set_trauma(1)
+
+ if not continuous:
+ if _start_duration_countdown:
+ if _elasped_play_time >= duration + growth_time:
+ _should_decay = true
+ _start_duration_countdown = false
+
+ if _should_decay:
+ _decay_countdown += delta
+ noise.set_trauma(maxf(1 - (_decay_countdown / decay_time), 0))
+ if _decay_countdown >= decay_time:
+ noise.set_trauma(0)
+ _play = false
+ preview = false
+ _should_decay = false
+ _elasped_play_time = 0
+ _decay_countdown = 0
+
+ _noise_output = noise.get_noise_transform(delta)
+ _phantom_camera_manager.noise_2d_emitted.emit(_noise_output, noise_emitter_layer)
+
+
+func _set_layer(current_layers: int, layer_number: int, value: bool) -> int:
+ var mask: int = current_layers
+
+ # From https://github.com/godotengine/godot/blob/51991e20143a39e9ef0107163eaf283ca0a761ea/scene/3d/camera_3d.cpp#L638
+ if layer_number < 1 or layer_number > 20:
+ printerr("Layer must be between 1 and 20.")
+ else:
+ if value:
+ mask |= 1 << (layer_number - 1)
+ else:
+ mask &= ~(1 << (layer_number - 1))
+
+ return mask
+
+#endregion
+
+
+#region Public Functions
+
+## Emits noise to the [PhantomCamera2D]s that has at least one matching layers.
+func emit() -> void:
+ if _play: _play = false
+ _play = true
+
+## Returns the state for the emitter. If true, the emitter is currently emitting.
+func is_emitting() -> bool:
+ return _play
+
+## Stops the emitter from emitting noise.
+func stop(should_decay: bool = true) -> void:
+ if should_decay:
+ _should_decay = true
+ else:
+ _play = false
+
+## Toggles the emitter on and off.
+func toggle() -> void:
+ _play = !_play
+
+#endregion
+
+
+#region Setter & Getter Functions
+
+## Sets the [member noise] resource.
+func set_noise(value: PhantomCameraNoise2D) -> void:
+ noise = value
+ update_configuration_warnings()
+
+## Returns the [member noise] resource.
+func get_noise() -> PhantomCameraNoise2D:
+ return noise
+
+
+## Sets the [member continous] value.
+func set_continuous(value: bool) -> void:
+ continuous = value
+ notify_property_list_changed()
+
+## Gets the [member continous] value.
+func get_continuous() -> bool:
+ return continuous
+
+
+## Sets the [member growth_time] value.
+func set_growth_time(value: float) -> void:
+ growth_time = value
+
+## Returns the [member growth_time] value.
+func get_growth_time() -> float:
+ return growth_time
+
+
+## Sets the [member duration] value.
+func set_duration(value: float) -> void:
+ duration = value
+ if duration == 0:
+ duration = 0.001
+
+## Returns the [member duration] value.
+func get_duration() -> float:
+ return duration
+
+
+## Sets the [member decay_time] value.
+func set_decay_time(value: float) -> void:
+ decay_time = value
+
+## Returns the [member decay_time] value.
+func get_decay_time() -> float:
+ return decay_time
+
+
+## Sets the [member noise_emitter_layer] value.
+func set_noise_emitter_layer(value: int) -> void:
+ noise_emitter_layer = value
+
+## Enables or disables a given layer of [member noise_emitter_layer].
+func set_noise_emitter_value(value: int, enabled: bool) -> void:
+ noise_emitter_layer = _set_layer(noise_emitter_layer, value, enabled)
+
+## Returns the [member noise_emitter_layer] value.
+func get_noise_emitter_layer() -> int:
+ return noise_emitter_layer
+
+#endregion
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd.uid b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd.uid
new file mode 100644
index 0000000..19dbc96
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_2d.gd.uid
@@ -0,0 +1 @@
+uid://bhd4nuiu23e7l
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd
new file mode 100644
index 0000000..cd6c634
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd
@@ -0,0 +1,265 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_noise_emitter_3d.svg")
+class_name PhantomCameraNoiseEmitter3D
+extends Node3D
+
+## Emits positional and rotational noise to active [PhantomCamera3D]s and its corresponding [Camera3D].
+##
+## Is a node meant to apply positional and rotational noise, also referred to as shake, to the [Camera3D].
+## It is designed for use cases such as when hitting or when being hit, earthquakes or to add a
+## bit of slight movement to the camera to make it feel less static.
+## The emitter can affect multiple [PhantomCamera3D] in a given scene based on which [member noise_emitter_layer]
+## are enabled by calling its [method emit] function. At least one corresponding layer has to be
+## set on the [PhantomCamera3D] and the emitter node.
+
+const _constants = preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+#region Exported Properties
+
+## The [PhantomCameraNoise3D] resource that defines the noise pattern.
+@export var noise: PhantomCameraNoise3D = null:
+ set = set_noise,
+ get = get_noise
+
+## If true, previews the noise in the Viewfinder.
+@export var preview: bool = false:
+ set(value):
+ preview = value
+ _play = value
+ get:
+ return preview
+
+## If true, repeats the noise indefinitely once started.Otherwise, it will only be triggered once. [br]
+## [b]Note:[/b] This will always be enabled if the resource is assigned the the [PhantomCamera3D]'s
+## [member PhantomCamera3D.noise] property.
+@export var continuous: bool = false:
+ set = set_continuous,
+ get = get_continuous
+
+## Determines how long the noise should take to reach full [member intensity] once started.[br]
+## The value is set in [b]seconds[/b].
+@export_exp_easing("positive_only", "suffix: s") var growth_time: float = 0:
+ set = set_growth_time,
+ get = get_growth_time
+
+## Sets the duration for the camera noise if [member loop] is set to false.[br]
+## If the duration is [param 0] then [member continous] becomes enabled.[br]
+## The value is set in [b]seconds[/b].
+@export_range(0, 10, 0.001, "or_greater", "suffix: s") var duration: float = 1.0:
+ set = set_duration,
+ get = get_duration
+
+## Determines how long the noise should take to come to a full stop.[br]
+## The value is set in [b]seconds[/b].
+@export_exp_easing("attenuation", "positive_only", "suffix: s") var decay_time: float = 0:
+ set = set_decay_time,
+ get = get_decay_time
+
+## Enabled layers will affect [PhantomCamera3D] nodes with at least one corresponding layer enabled.[br]
+## Enabling multiple corresponding layers on the same [PhantomCamera3D] causes no additional effect.
+@export_flags_3d_render var noise_emitter_layer: int = 1:
+ set = set_noise_emitter_layer,
+ get = get_noise_emitter_layer
+
+#endregion
+
+#region Private Variables
+
+var _play: bool = false:
+ set(value):
+ _play = value
+ if value:
+ _elasped_play_time = 0
+ _decay_countdown = 0
+ _play = true
+ _should_grow = true
+ _start_duration_countdown = false
+ _should_decay = false
+ else:
+ _should_decay = true
+ if noise.randomize_noise_seed:
+ noise.noise_seed = randi() & 1000
+ else:
+ noise.reset_noise_time()
+ get:
+ return _play
+
+var _start_duration_countdown: bool = false
+
+var _decay_countdown: float = 0
+
+var _should_grow: bool = false
+
+var _should_decay: bool = false
+
+var _elasped_play_time: float = 0
+
+var _noise_output: Transform3D = Transform3D()
+
+# NOTE - Temp solution until Godot has better plugin autoload recognition out-of-the-box.
+var _phantom_camera_manager: Node
+
+#endregion
+
+#region Private Functions
+
+func _get_configuration_warnings() -> PackedStringArray:
+ if noise == null:
+ return ["Noise resource is required in order to trigger emitter."]
+ else:
+ return []
+
+
+func _validate_property(property) -> void:
+ if property.name == "duration" and continuous:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _enter_tree() -> void:
+ _phantom_camera_manager = get_tree().root.get_node(_constants.PCAM_MANAGER_NODE_NAME)
+
+
+func _process(delta: float) -> void:
+ if not _play and not _should_decay: return
+ if noise == null:
+ printerr("Noise resource missing in ", name)
+ _play = false
+ return
+
+ _elasped_play_time += delta
+
+ if _should_grow:
+ noise.set_trauma(minf(_elasped_play_time / growth_time, 1))
+ if _elasped_play_time >= growth_time:
+ _should_grow = false
+ _start_duration_countdown = true
+ noise.set_trauma(1)
+
+ if not continuous:
+ if _start_duration_countdown:
+ if _elasped_play_time >= duration + growth_time:
+ _should_decay = true
+ _start_duration_countdown = false
+
+ if _should_decay:
+ _decay_countdown += delta
+ noise.set_trauma(maxf(1 - (_decay_countdown / decay_time), 0))
+ if _decay_countdown >= decay_time:
+ noise.set_trauma(0)
+ _play = false
+ preview = false
+ _should_decay = false
+ _elasped_play_time = 0
+ _decay_countdown = 0
+
+ _noise_output = noise.get_noise_transform(delta)
+ _phantom_camera_manager.noise_3d_emitted.emit(_noise_output, noise_emitter_layer)
+
+
+func _set_layer(current_layers: int, layer_number: int, value: bool) -> int:
+ var mask: int = current_layers
+
+ # From https://github.com/godotengine/godot/blob/51991e20143a39e9ef0107163eaf283ca0a761ea/scene/3d/camera_3d.cpp#L638
+ if layer_number < 1 or layer_number > 20:
+ printerr("Layer must be between 1 and 20.")
+ else:
+ if value:
+ mask |= 1 << (layer_number - 1)
+ else:
+ mask &= ~(1 << (layer_number - 1))
+
+ return mask
+
+#endregion
+
+#region Public Functions
+
+## Emits noise to the [PhantomCamera3D]s that has at least one matching layers.
+func emit() -> void:
+ if _play: _play = false
+ _play = true
+
+
+## Returns the state for the emitter. If true, the emitter is currently emitting.
+func is_emitting() -> bool:
+ return _play
+
+
+## Stops the emitter from emitting noise.
+func stop(should_decay: bool = true) -> void:
+ if should_decay:
+ _should_decay = true
+ else:
+ _play = false
+
+
+## Toggles the emitter on and off.[br]
+func toggle() -> void:
+ _play = !_play
+
+#endregion
+
+#region Setter & Getter Functions
+
+## Sets the [member noise] resource.
+func set_noise(value: PhantomCameraNoise3D) -> void:
+ noise = value
+ update_configuration_warnings()
+
+## Returns the [member noise] resource.
+func get_noise() -> PhantomCameraNoise3D:
+ return noise
+
+
+## Sets the [member continous] value.
+func set_continuous(value: bool) -> void:
+ continuous = value
+ notify_property_list_changed()
+
+## Gets the [member continous] value.
+func get_continuous() -> bool:
+ return continuous
+
+
+## Sets the [member growth_time] value.
+func set_growth_time(value: float) -> void:
+ growth_time = value
+
+## Returns the [member growth_time] value.
+func get_growth_time() -> float:
+ return growth_time
+
+
+## Sets the [member duration] value.
+func set_duration(value: float) -> void:
+ duration = value
+ if duration == 0:
+ duration = 0.001
+
+## Returns the [member duration] value.
+func get_duration() -> float:
+ return duration
+
+
+## Sets the [member decay_time] value.
+func set_decay_time(value: float) -> void:
+ decay_time = value
+
+## Returns the [member decay_time] value.
+func get_decay_time() -> float:
+ return decay_time
+
+
+## Sets the [member noise_emitter_layer] value.
+func set_noise_emitter_layer(value: int) -> void:
+ noise_emitter_layer = value
+
+## Enables or disables a given layer of [member noise_emitter_layer].
+func set_noise_emitter_value(value: int, enabled: bool) -> void:
+ noise_emitter_layer = _set_layer(noise_emitter_layer, value, enabled)
+
+## Returns the [member noise_emitter_layer] value.
+func get_noise_emitter_layer() -> int:
+ return noise_emitter_layer
+
+ #endregion
diff --git a/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd.uid b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd.uid
new file mode 100644
index 0000000..aa7e880
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera/phantom_camera_noise_emitter_3d.gd.uid
@@ -0,0 +1 @@
+uid://ccmiitq0sdh7j
diff --git a/addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd b/addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd
new file mode 100644
index 0000000..476130c
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd
@@ -0,0 +1,1428 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_host.svg")
+class_name PhantomCameraHost
+extends Node
+
+## Controls a scene's [Camera2D] (2D scenes) and [Camera3D] (3D scenes).
+##
+## All instantiated [param PhantomCameras] in a scene are assigned to a specific layer, where a
+## PhantomCameraHost will react to those that corresponds. It is what determines which [param PhantomCamera] should
+## be active.
+
+#region Signals
+
+
+#endregion
+
+#region Constants
+
+const _constants := preload("res://addons/phantom_camera/scripts/phantom_camera/phantom_camera_constants.gd")
+
+#endregion
+
+#region Signals
+
+## Updates the viewfinder [param dead zones] sizes.[br]
+## [b]Note:[/b] This is only being used in the editor viewfinder UI.
+#signal update_editor_viewfinder
+signal viewfinder_update(check_framed_view: bool)
+signal viewfinder_disable_dead_zone
+
+## Used internally to check if the [param PhantomCameraHost] is valid.
+## The result will be visible in the viewfinder when multiple instances are present.
+signal has_error()
+
+#endregion
+
+
+#region Enums
+
+enum InterpolationMode {
+ AUTO = 0,
+ IDLE = 1,
+ PHYSICS = 2,
+}
+
+#endregion
+
+
+#region Public Variables
+
+## Determines which [PhantomCamera2D] / [PhantomCamera3D] nodes this [param PhantomCameraHost] should recognise.
+## At least one corresponding layer needs to be set on the [param PhantomCamera] for the [param PhantomCameraHost] node to work.
+@export_flags_2d_render var host_layers: int = 1:
+ set = set_host_layers,
+ get = get_host_layers
+
+## TBD - For when Godot 4.3 becomes the minimum version
+#@export var interpolation_mode: InterpolationMode = InterpolationMode.AUTO:
+ #set = set_interpolation_mode,
+ #get = get_interpolation_mode
+
+#endregion
+
+
+#region Private Variables
+
+var _active_pcam_2d: PhantomCamera2D = null
+var _active_pcam_3d: Node = null ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+var _active_pcam_priority: int = -1
+var _active_pcam_missing: bool = true
+var _active_pcam_has_damping: bool = false
+var _follow_target_physics_based: bool = false
+
+var _prev_active_pcam_2d_transform: Transform2D = Transform2D()
+var _prev_active_pcam_3d_transform: Transform3D = Transform3D()
+
+var _trigger_pcam_tween: bool = false
+var _tween_elapsed_time: float = 0
+var _tween_duration: float = 0
+var _tween_is_instant: bool = false
+
+var _multiple_pcam_hosts: bool = false
+
+var _is_child_of_camera: bool = false
+var _is_2d: bool = false
+
+var _viewfinder_node: Control = null
+var _viewfinder_needed_check: bool = true
+
+var _camera_zoom: Vector2 = Vector2.ONE
+
+#region Camera3DResource
+
+var _prev_cam_attributes: CameraAttributes = null
+var _cam_attribute_type: int = 0 # 0 = CameraAttributesPractical, 1 = CameraAttributesPhysical
+var _cam_attribute_changed: bool = false
+var _cam_attribute_assigned: bool = false
+
+#region CameraAttributes
+var _prev_cam_auto_exposure_scale: float = 0.4
+var _cam_auto_exposure_scale_changed: bool = false
+
+var _prev_cam_auto_exposure_speed: float = 0.5
+var _cam_auto_exposure_speed_changed: bool = false
+
+var _prev_cam_exposure_multiplier: float = 1.0
+var _cam_exposure_multiplier_changed: bool = false
+
+var _prev_cam_exposure_sensitivity: float = 100.0
+var _cam_exposure_sensitivity_changed: bool = false
+
+#region CameraAttributesPractical
+var _prev_cam_exposure_min_sensitivity: float = 0.0
+var _cam_exposure_min_sensitivity_changed: bool = false
+
+var _prev_cam_exposure_max_sensitivity: float = 800.0
+var _cam_exposure_max_sensitivity_changed: bool = false
+
+var _prev_cam_dof_blur_amount: float = 0.1
+var _cam_dof_blur_amount_changed: bool = false
+
+var _cam_dof_blur_far_distance_default: float = 10
+var _prev_cam_dof_blur_far_distance: float = _cam_dof_blur_far_distance_default
+var _cam_dof_blur_far_distance_changed: bool = false
+
+var _cam_dof_blur_far_transition_default: float = 5
+var _prev_cam_dof_blur_far_transition: float = _cam_dof_blur_far_transition_default
+var _cam_dof_blur_far_transition_changed: bool = false
+
+var _cam_dof_blur_near_distance_default: float = 2
+var _prev_cam_dof_blur_near_distance: float = _cam_dof_blur_near_distance_default
+var _cam_dof_blur_near_distance_changed: bool = false
+
+var _cam_dof_blur_near_transition_default: float = 1
+var _prev_cam_dof_blur_near_transition: float = _cam_dof_blur_near_transition_default
+var _cam_dof_blur_near_transition_changed: bool = false
+#endregion
+
+#region CameraAttributesPhysical
+var _prev_cam_exposure_min_exposure_value: float = 10.0
+var _cam_exposure_min_exposure_value_changed: bool = false
+
+var _prev_cam_exposure_max_exposure_value: float = -8.0
+var _cam_exposure_max_exposure_value_changed: bool = false
+
+var _prev_cam_exposure_aperture: float = 16.0
+var _cam_exposure_aperture_changed: bool = false
+
+var _prev_cam_exposure_shutter_speed: float = 100.0
+var _cam_exposure_shutter_speed_changed: bool = false
+
+var _prev_cam_frustum_far: float = 4000.0
+var _cam_frustum_far_changed: bool = false
+
+var _prev_cam_frustum_focal_length: float = 35.0
+var _cam_frustum_focal_length_changed: bool = false
+
+var _prev_cam_frustum_near: float = 0.05
+var _cam_frustum_near_changed: bool = false
+
+var _prev_cam_frustum_focus_distance: float = 10.0
+var _cam_frustum_focus_distance_changed: bool = false
+
+#endregion
+
+var _prev_cam_h_offset: float = 0
+var _cam_h_offset_changed: bool = false
+
+var _prev_cam_v_offset: float = 0
+var _cam_v_offset_changed: bool = false
+
+var _prev_cam_fov: float = 75
+var _cam_fov_changed: bool = false
+
+var _prev_cam_size: float = 1
+var _cam_size_changed: bool = false
+
+var _prev_cam_frustum_offset: Vector2 = Vector2.ZERO
+var _cam_frustum_offset_changed: bool = false
+
+var _prev_cam_near: float = 0.05
+var _cam_near_changed: bool = false
+
+var _prev_cam_far: float = 4000
+var _cam_far_changed: bool = false
+
+#endregion
+
+var _active_pcam_2d_glob_transform: Transform2D = Transform2D()
+var _active_pcam_3d_glob_transform: Transform3D = Transform3D()
+
+var _has_noise_emitted: bool = false
+var _reset_noise_offset_2d: bool = false
+var _noise_emitted_output_2d: Transform2D = Transform2D()
+var _noise_emitted_output_3d: Transform3D = Transform3D()
+
+#endregion
+
+# NOTE - Temp solution until Godot has better plugin autoload recognition out-of-the-box.
+var _phantom_camera_manager: Node = null
+
+#region Public Variables
+
+var show_warning: bool = false
+
+## For 2D scenes, is the [Camera2D] instance the [param PhantomCameraHost] controls.
+var camera_2d: Camera2D = null
+
+## For 3D scenes, is the [Camera3D] instance the [param PhantomCameraHost] controls.
+var camera_3d: Node = null ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+
+#endregion
+
+#region Private Functions
+
+## TBD - For when Godot 4.3 becomes a minimum version
+#func _validate_property(property: Dictionary) -> void:
+ #if property.name == "interpolation_mode" and get_parent() is Node3D:
+ #property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _get_configuration_warnings() -> PackedStringArray:
+ var parent: Node = get_parent()
+ var first_pcam_host_child: PhantomCameraHost
+
+ if _is_2d:
+ if not parent is Camera2D:
+ show_warning = true
+ has_error.emit()
+ return["Needs to be a child of a Camera2D in order to work."]
+ else:
+ if not parent.is_class("Camera3D"): ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+ show_warning = true
+ has_error.emit()
+ return["Needs to be a child of a Camera3D in order to work."]
+
+ for child in parent.get_children():
+ if not child is PhantomCameraHost: continue
+ if not is_instance_valid(first_pcam_host_child):
+ first_pcam_host_child = child
+ continue
+ elif not first_pcam_host_child == self:
+ show_warning = true
+ has_error.emit()
+ return["Only the first PhantomCameraHost child will be used."]
+ child.update_configuration_warnings()
+
+ show_warning = false
+ has_error.emit()
+ return[]
+
+
+func _enter_tree() -> void:
+ var parent: Node = get_parent()
+ if parent is Camera2D or parent.is_class("Camera3D"): ## Note: To support disable_3d export templates for 2D projects, this is purposely not strongly typed.
+ _phantom_camera_manager = get_tree().root.get_node(_constants.PCAM_MANAGER_NODE_NAME)
+ _phantom_camera_manager.pcam_host_added(self)
+
+ _is_child_of_camera = true
+ if parent is Camera2D:
+ _is_2d = true
+ camera_2d = parent
+ ## Force applies position smoothing to be disabled
+ ## This is to prevent overlap with the interpolation of the PCam2D.
+ camera_2d.set_position_smoothing_enabled(false)
+ else:
+ _is_2d = false
+ camera_3d = parent
+
+ if _is_2d:
+ if not _phantom_camera_manager.get_phantom_camera_2ds().is_empty():
+ for pcam in _phantom_camera_manager.get_phantom_camera_2ds():
+ _pcam_added_to_scene(pcam)
+
+ if not _phantom_camera_manager.limit_2d_changed.is_connected(_update_limit_2d):
+ _phantom_camera_manager.limit_2d_changed.connect(_update_limit_2d)
+ if not _phantom_camera_manager.draw_limit_2d.is_connected(_draw_limit_2d):
+ _phantom_camera_manager.draw_limit_2d.connect(_draw_limit_2d)
+
+ else:
+ if not _phantom_camera_manager.get_phantom_camera_3ds().is_empty():
+ for pcam in _phantom_camera_manager.get_phantom_camera_3ds():
+ _pcam_added_to_scene(pcam)
+
+
+func _exit_tree() -> void:
+ if is_instance_valid(_phantom_camera_manager):
+ _phantom_camera_manager.pcam_host_removed(self)
+
+
+func _ready() -> void:
+ # Waits for the first process tick to finish before initializing any logic
+ # This should help with avoiding ocassional erratic camera movement upon running a scene
+ await get_tree().process_frame
+
+ process_priority = 300
+ process_physics_priority = 300
+
+ # PCam Host Signals
+ if Engine.has_singleton(_constants.PCAM_MANAGER_NODE_NAME):
+ _phantom_camera_manager = Engine.get_singleton(_constants.PCAM_MANAGER_NODE_NAME)
+ _phantom_camera_manager.pcam_host_layer_changed.connect(_pcam_host_layer_changed)
+
+ # PCam Signals
+ _phantom_camera_manager.pcam_added_to_scene.connect(_pcam_added_to_scene)
+ _phantom_camera_manager.pcam_removed_from_scene.connect(_pcam_removed_from_scene)
+
+ _phantom_camera_manager.pcam_priority_changed.connect(pcam_priority_updated)
+ _phantom_camera_manager.pcam_priority_override.connect(_pcam_priority_override)
+
+ _phantom_camera_manager.pcam_visibility_changed.connect(_pcam_visibility_changed)
+
+ _phantom_camera_manager.pcam_teleport.connect(_pcam_teleported)
+
+ if _is_2d:
+ if not _phantom_camera_manager.limit_2d_changed.is_connected(_update_limit_2d):
+ _phantom_camera_manager.limit_2d_changed.connect(_update_limit_2d)
+ if not _phantom_camera_manager.draw_limit_2d.is_connected(_draw_limit_2d):
+ _phantom_camera_manager.draw_limit_2d.connect(_draw_limit_2d)
+ else:
+ printerr("Could not find Phantom Camera Manager singleton")
+ printerr("Make sure the addon is enable or that the singleton hasn't been disabled inside Project Settings / Globals")
+
+ _find_pcam_with_highest_priority()
+
+ if _is_2d:
+ camera_2d.offset = Vector2.ZERO
+ if not is_instance_valid(_active_pcam_2d): return
+ _active_pcam_2d_glob_transform = _active_pcam_2d.get_transform_output()
+ else:
+ if not is_instance_valid(_active_pcam_3d): return
+ _active_pcam_3d_glob_transform = _active_pcam_3d.get_transform_output()
+
+
+func _pcam_host_layer_changed(pcam: Node) -> void:
+ if _pcam_is_in_host_layer(pcam):
+ _check_pcam_priority(pcam)
+ else:
+ if _is_2d:
+ if _active_pcam_2d == pcam:
+ _active_pcam_missing = true
+ _active_pcam_2d = null
+ _active_pcam_priority = -1
+ pcam.set_is_active(self, false)
+ else:
+ if _active_pcam_3d == pcam:
+ _active_pcam_missing = true
+ _active_pcam_3d = null
+ _active_pcam_priority = -1
+ pcam.set_is_active(self, false)
+ _find_pcam_with_highest_priority()
+
+
+func _pcam_is_in_host_layer(pcam: Node) -> bool:
+ if pcam.host_layers & host_layers != 0: return true
+ return false
+
+
+func _find_pcam_with_highest_priority() -> void:
+ var pcam_list: Array
+ if _is_2d:
+ pcam_list = _phantom_camera_manager.phantom_camera_2ds
+ else:
+ pcam_list = _phantom_camera_manager.phantom_camera_3ds
+
+ for pcam in pcam_list:
+ _check_pcam_priority(pcam)
+
+
+func _check_pcam_priority(pcam: Node) -> void:
+ if not _pcam_is_in_host_layer(pcam): return
+ if not pcam.visible: return # Prevents hidden PCams from becoming active
+ if pcam.get_priority() > _active_pcam_priority:
+ _assign_new_active_pcam(pcam)
+ _active_pcam_missing = false
+ else:
+ pcam.set_tween_skip(self, false)
+
+
+func _assign_new_active_pcam(pcam: Node) -> void:
+ # Only checks if the scene tree is still present.
+ # Prevents a few errors and checks from happening if the scene is exited.
+ if not is_inside_tree(): return
+ var no_previous_pcam: bool
+ if is_instance_valid(_active_pcam_2d) or is_instance_valid(_active_pcam_3d):
+ if OS.has_feature("debug"):
+ viewfinder_disable_dead_zone.emit()
+
+ if _is_2d:
+ _prev_active_pcam_2d_transform = camera_2d.global_transform
+ _active_pcam_2d.queue_redraw()
+ _active_pcam_2d.set_is_active(self, false)
+ _active_pcam_2d.became_inactive.emit()
+
+ if _active_pcam_2d.physics_target_changed.is_connected(_check_pcam_physics):
+ _active_pcam_2d.physics_target_changed.disconnect(_check_pcam_physics)
+
+ if _active_pcam_2d.noise_emitted.is_connected(_noise_emitted_2d):
+ _active_pcam_2d.noise_emitted.disconnect(_noise_emitted_2d)
+
+ if _trigger_pcam_tween:
+ _active_pcam_2d.tween_interrupted.emit(pcam)
+ else:
+ _prev_active_pcam_3d_transform = camera_3d.global_transform
+ _active_pcam_3d.set_is_active(self, false)
+ _active_pcam_3d.became_inactive.emit()
+
+ if _active_pcam_3d.physics_target_changed.is_connected(_check_pcam_physics):
+ _active_pcam_3d.physics_target_changed.disconnect(_check_pcam_physics)
+
+ if _active_pcam_3d.noise_emitted.is_connected(_noise_emitted_3d):
+ _active_pcam_3d.noise_emitted.disconnect(_noise_emitted_3d)
+
+ if _active_pcam_3d.camera_3d_resource_changed.is_connected(_camera_3d_resource_changed):
+ _active_pcam_3d.camera_3d_resource_changed.disconnect(_camera_3d_resource_changed)
+
+ if _active_pcam_3d.camera_3d_resource_property_changed.is_connected(_camera_3d_resource_property_changed):
+ _active_pcam_3d.camera_3d_resource_property_changed.disconnect(_camera_3d_resource_property_changed)
+
+ if _trigger_pcam_tween:
+ _active_pcam_3d.tween_interrupted.emit(pcam)
+
+ if camera_3d.attributes != null:
+ var _attributes: CameraAttributes = camera_3d.attributes
+
+ _prev_cam_exposure_multiplier = _attributes.exposure_multiplier
+ _prev_cam_auto_exposure_scale = _attributes.auto_exposure_scale
+ _prev_cam_auto_exposure_speed = _attributes.auto_exposure_speed
+
+ if camera_3d.attributes is CameraAttributesPractical:
+ _attributes = _attributes as CameraAttributesPractical
+
+ _prev_cam_dof_blur_amount = _attributes.dof_blur_amount
+
+ if _attributes.dof_blur_far_enabled:
+ _prev_cam_dof_blur_far_distance = _attributes.dof_blur_far_distance
+ _prev_cam_dof_blur_far_transition = _attributes.dof_blur_far_transition
+ else:
+ _prev_cam_dof_blur_far_distance = _cam_dof_blur_far_distance_default
+ _prev_cam_dof_blur_far_transition = _cam_dof_blur_far_transition_default
+
+ if _attributes.dof_blur_near_enabled:
+ _prev_cam_dof_blur_near_distance = _attributes.dof_blur_near_distance
+ _prev_cam_dof_blur_near_transition = _attributes.dof_blur_near_transition
+ else:
+ _prev_cam_dof_blur_near_distance = _cam_dof_blur_near_distance_default
+ _prev_cam_dof_blur_near_transition = _cam_dof_blur_near_transition_default
+
+ if _attributes.auto_exposure_enabled:
+ _prev_cam_exposure_max_sensitivity = _attributes.auto_exposure_max_sensitivity
+ _prev_cam_exposure_min_sensitivity = _attributes.auto_exposure_min_sensitivity
+
+ elif camera_3d.attributes is CameraAttributesPhysical:
+ _attributes = _attributes as CameraAttributesPhysical
+
+ _prev_cam_frustum_focus_distance = _attributes.frustum_focus_distance
+ _prev_cam_frustum_focal_length = _attributes.frustum_focal_length
+ _prev_cam_frustum_far = _attributes.frustum_far
+ _prev_cam_frustum_near = _attributes.frustum_near
+ _prev_cam_exposure_aperture = _attributes.exposure_aperture
+ _prev_cam_exposure_shutter_speed = _attributes.exposure_shutter_speed
+
+ if _attributes.auto_exposure_enabled:
+ _prev_cam_exposure_min_exposure_value = _attributes.auto_exposure_min_exposure_value
+ _prev_cam_exposure_max_exposure_value = _attributes.auto_exposure_max_exposure_value
+
+ _prev_cam_h_offset = camera_3d.h_offset
+ _prev_cam_v_offset = camera_3d.v_offset
+ _prev_cam_fov = camera_3d.fov
+ _prev_cam_size = camera_3d.size
+ _prev_cam_frustum_offset = camera_3d.frustum_offset
+ _prev_cam_near = camera_3d.near
+ _prev_cam_far = camera_3d.far
+
+ else:
+ no_previous_pcam = true
+
+ ## Assign newly active pcam
+ if _is_2d:
+ _active_pcam_2d = pcam
+ _active_pcam_priority = _active_pcam_2d.priority
+ _active_pcam_has_damping = _active_pcam_2d.follow_damping
+ _tween_duration = _active_pcam_2d.tween_duration
+
+ if not _active_pcam_2d.physics_target_changed.is_connected(_check_pcam_physics):
+ _active_pcam_2d.physics_target_changed.connect(_check_pcam_physics)
+
+ if not _active_pcam_2d.noise_emitted.is_connected(_noise_emitted_2d):
+ _active_pcam_2d.noise_emitted.connect(_noise_emitted_2d)
+ else:
+ _active_pcam_3d = pcam
+ _active_pcam_priority = _active_pcam_3d.priority
+ _active_pcam_has_damping = _active_pcam_3d.follow_damping
+ _tween_duration = _active_pcam_3d.tween_duration
+
+ if not Engine.is_editor_hint():
+ # Assigns a default shape to SpringArm3D node is none is supplied
+ if _active_pcam_3d.follow_mode == _active_pcam_3d.FollowMode.THIRD_PERSON:
+ if not _active_pcam_3d.shape:
+
+ var pyramid_shape_data = Engine.get_singleton("PhysicsServer3D").call("shape_get_data",
+ camera_3d.get_pyramid_shape_rid()
+ )
+ var shape = ClassDB.instantiate("ConvexPolygonShape3D")
+ shape.points = pyramid_shape_data
+ _active_pcam_3d.shape = shape
+
+ if not _active_pcam_3d.physics_target_changed.is_connected(_check_pcam_physics):
+ _active_pcam_3d.physics_target_changed.connect(_check_pcam_physics)
+
+ if not _active_pcam_3d.noise_emitted.is_connected(_noise_emitted_3d):
+ _active_pcam_3d.noise_emitted.connect(_noise_emitted_3d)
+
+ if not _active_pcam_3d.camera_3d_resource_changed.is_connected(_camera_3d_resource_changed):
+ _active_pcam_3d.camera_3d_resource_changed.connect(_camera_3d_resource_changed)
+
+ if not _active_pcam_3d.camera_3d_resource_property_changed.is_connected(_camera_3d_resource_property_changed):
+ _active_pcam_3d.camera_3d_resource_property_changed.connect(_camera_3d_resource_property_changed)
+
+ # Checks if the Camera3DResource has changed from the previous active PCam3D
+ if _active_pcam_3d.camera_3d_resource:
+ # Signal to detect if the Camera3D properties are being changed in the inspector
+ # This is to prevent accidential misalignment between the Camera3D and Camera3DResource
+ if Engine.is_editor_hint():
+ if not Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.is_connected(_camera_3d_edited):
+ Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.connect(_camera_3d_edited)
+ if _prev_cam_h_offset != _active_pcam_3d.h_offset:
+ _cam_h_offset_changed = true
+ if _prev_cam_v_offset != _active_pcam_3d.v_offset:
+ _cam_v_offset_changed = true
+ if _prev_cam_fov != _active_pcam_3d.fov:
+ _cam_fov_changed = true
+ if _prev_cam_size != _active_pcam_3d.size:
+ _cam_size_changed = true
+ if _prev_cam_frustum_offset != _active_pcam_3d.frustum_offset:
+ _cam_frustum_offset_changed = true
+ if _prev_cam_near != _active_pcam_3d.near:
+ _cam_near_changed = true
+ if _prev_cam_far != _active_pcam_3d.far:
+ _cam_far_changed = true
+ else:
+ _cam_h_offset_changed = false
+ _cam_v_offset_changed = false
+ _cam_fov_changed = false
+ _cam_size_changed = false
+ _cam_frustum_offset_changed = false
+ _cam_near_changed = false
+ _cam_far_changed = false
+ _cam_attribute_changed = false
+ if Engine.is_editor_hint():
+ if Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.is_connected(_camera_3d_edited):
+ Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.disconnect(_camera_3d_edited)
+
+ if _active_pcam_3d.attributes == null:
+ _cam_attribute_changed = false
+ else:
+ if _prev_cam_attributes != _active_pcam_3d.attributes:
+ _prev_cam_attributes = _active_pcam_3d.attributes
+ _cam_attribute_changed = true
+ var _attributes: CameraAttributes = _active_pcam_3d.attributes
+
+ if _prev_cam_auto_exposure_scale != _attributes.auto_exposure_scale:
+ _cam_auto_exposure_scale_changed = true
+ if _prev_cam_auto_exposure_speed != _attributes.auto_exposure_speed:
+ _cam_auto_exposure_speed_changed = true
+ if _prev_cam_exposure_multiplier != _attributes.exposure_multiplier:
+ _cam_exposure_multiplier_changed = true
+ if _prev_cam_exposure_sensitivity != _attributes.exposure_sensitivity:
+ _cam_exposure_sensitivity_changed = true
+
+ if _attributes is CameraAttributesPractical:
+ _cam_attribute_type = 0
+
+ if camera_3d.attributes == null:
+ camera_3d.attributes = CameraAttributesPractical.new()
+ camera_3d.attributes = _active_pcam_3d.attributes.duplicate()
+ _cam_attribute_assigned = true
+
+ if _prev_cam_exposure_min_sensitivity != _attributes.auto_exposure_min_sensitivity:
+ _cam_exposure_min_sensitivity_changed = true
+ if _prev_cam_exposure_max_sensitivity != _attributes.auto_exposure_max_sensitivity:
+ _cam_exposure_max_sensitivity_changed = true
+
+ if _prev_cam_dof_blur_amount != _attributes.dof_blur_amount:
+ _cam_dof_blur_amount_changed = true
+
+ if _prev_cam_dof_blur_far_distance != _attributes.dof_blur_far_distance:
+ _cam_dof_blur_far_distance_changed = true
+ camera_3d.attributes.dof_blur_far_enabled = true
+ if _prev_cam_dof_blur_far_transition != _attributes.dof_blur_far_transition:
+ _cam_dof_blur_far_transition_changed = true
+ camera_3d.attributes.dof_blur_far_enabled = true
+
+ if _prev_cam_dof_blur_near_distance != _attributes.dof_blur_near_distance:
+ _cam_dof_blur_near_distance_changed = true
+ camera_3d.attributes.dof_blur_near_enabled = true
+ if _prev_cam_dof_blur_near_transition != _attributes.dof_blur_near_transition:
+ _cam_dof_blur_near_transition_changed = true
+ camera_3d.attributes.dof_blur_near_enabled = true
+ elif _attributes is CameraAttributesPhysical:
+ _cam_attribute_type = 1
+
+ if camera_3d.attributes == null:
+ camera_3d.attributes = CameraAttributesPhysical.new()
+ camera_3d.attributes = _active_pcam_3d.attributes.duplicate()
+
+ if _prev_cam_exposure_min_exposure_value != _attributes.auto_exposure_min_exposure_value:
+ _cam_exposure_min_exposure_value_changed = true
+ if _prev_cam_exposure_max_exposure_value != _attributes.auto_exposure_max_exposure_value:
+ _cam_exposure_max_exposure_value_changed = true
+
+ if _prev_cam_exposure_aperture != _attributes.exposure_aperture:
+ _cam_exposure_aperture_changed = true
+ if _prev_cam_exposure_shutter_speed != _attributes.exposure_shutter_speed:
+ _cam_exposure_shutter_speed_changed = true
+
+ if _prev_cam_frustum_far != _attributes.frustum_far:
+ _cam_frustum_far_changed = true
+
+ if _prev_cam_frustum_focal_length != _attributes.frustum_focal_length:
+ _cam_frustum_focal_length_changed = true
+
+ if _prev_cam_frustum_focus_distance != _attributes.frustum_focus_distance:
+ _cam_frustum_focus_distance_changed = true
+
+ if _prev_cam_frustum_near != _attributes.frustum_near:
+ _cam_frustum_near_changed = true
+
+ if OS.has_feature("debug"):
+ viewfinder_update.emit(false)
+
+ if _is_2d:
+ if _active_pcam_2d.show_viewfinder_in_play:
+ _viewfinder_needed_check = true
+
+ _active_pcam_2d.set_is_active(self, true)
+ _active_pcam_2d.became_active.emit()
+ _camera_zoom = camera_2d.zoom
+ else:
+ if _active_pcam_3d.show_viewfinder_in_play:
+ _viewfinder_needed_check = true
+
+ _active_pcam_3d.set_is_active(self, true)
+ _active_pcam_3d.became_active.emit()
+ if _active_pcam_3d.camera_3d_resource:
+ camera_3d.keep_aspect = _active_pcam_3d.keep_aspect
+ camera_3d.cull_mask = _active_pcam_3d.cull_mask
+ camera_3d.projection = _active_pcam_3d.projection
+
+ if no_previous_pcam:
+ if _is_2d:
+ _prev_active_pcam_2d_transform = _active_pcam_2d.get_transform_output()
+ else:
+ _prev_active_pcam_3d_transform = _active_pcam_3d.get_transform_output()
+
+ if pcam.get_tween_skip() or pcam.tween_duration == 0:
+ _tween_elapsed_time = pcam.tween_duration
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor >= 3:
+ _tween_is_instant = true
+ else:
+ _tween_elapsed_time = 0
+
+ _check_pcam_physics()
+
+ _trigger_pcam_tween = true
+
+
+func _check_pcam_physics() -> void:
+ if _is_2d:
+ ## NOTE - Only supported in Godot 4.3 or later
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor >= 3:
+ if _active_pcam_2d.get_follow_target_physics_based():
+ _follow_target_physics_based = true
+ ## TODO - Temporary solution to support Godot 4.2
+ ## Remove line below and uncomment the following once Godot 4.3 is min verison.
+ camera_2d.call("reset_physics_interpolation")
+ camera_2d.set("physics_interpolation_mode", 1)
+ #camera_2d.reset_physics_interpolation()
+ #camera_2d.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_ON
+ if ProjectSettings.get_setting("physics/common/physics_interpolation"):
+ camera_2d.process_callback = Camera2D.CAMERA2D_PROCESS_PHYSICS # Prevents a warning
+ else:
+ camera_2d.process_callback = Camera2D.CAMERA2D_PROCESS_IDLE
+ else:
+ _follow_target_physics_based = false
+ ## TODO - Temporary solution to support Godot 4.2
+ ## Remove line below and uncomment the following once Godot 4.3 is min verison.
+ camera_2d.set("physics_interpolation_mode", 0)
+ #camera_2d.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_INHERIT
+ if get_tree().physics_interpolation:
+ camera_2d.process_callback = Camera2D.CAMERA2D_PROCESS_PHYSICS # Prevents a warning
+ else:
+ camera_2d.process_callback = Camera2D.CAMERA2D_PROCESS_IDLE
+ else:
+ ## NOTE - Only supported in Godot 4.4 or later
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor >= 4:
+ if get_tree().physics_interpolation or _active_pcam_3d.get_follow_target_physics_based():
+ #if get_tree().physics_interpolation or _active_pcam_3d.get_follow_target_physics_based():
+ _follow_target_physics_based = true
+ ## TODO - Temporary solution to support Godot 4.2
+ ## Remove line below and uncomment the following once Godot 4.3 is min verison.
+ camera_3d.call("reset_physics_interpolation")
+ camera_3d.set("physics_interpolation_mode", 1)
+ #camera_3d.reset_physics_interpolation()
+ #camera_3d.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_ON
+ else:
+ _follow_target_physics_based = false
+ ## TODO - Temporary solution to support Godot 4.2
+ ## Remove line below and uncomment the following once Godot 4.3 is min verison.
+ camera_3d.set("physics_interpolation_mode", 0)
+
+
+## TODO - For 0.8 release
+#func _find_pcam_with_highest_priority() -> void:
+ #var highest_priority_pcam: Node
+ #for pcam in _pcam_list:
+ #if not pcam.visible: continue # Prevents hidden PCams from becoming active
+ #if pcam.priority > _active_pcam_priority:
+ #_active_pcam_priority = pcam.priority
+ #highest_priority_pcam = pcam
+ #pcam.set_has_tweened(self, false)
+#
+ #_active_pcam_missing = false
+#
+ #if is_instance_valid(highest_priority_pcam):
+ #_assign_new_active_pcam(highest_priority_pcam)
+ #else:
+ #_active_pcam_missing = true
+
+
+func _process(delta: float) -> void:
+ if _active_pcam_missing: return
+
+ if not _follow_target_physics_based: _tween_follow_checker(delta)
+
+
+func _physics_process(delta: float) -> void:
+ if _active_pcam_missing or not _follow_target_physics_based: return
+ _tween_follow_checker(delta)
+
+
+func _tween_follow_checker(delta: float) -> void:
+ if _is_2d:
+ if not is_instance_valid(_active_pcam_2d):
+ _active_pcam_missing = true
+ return
+
+ _active_pcam_2d.process_logic(delta)
+ _active_pcam_2d_glob_transform = _active_pcam_2d.get_transform_output()
+
+ if _reset_noise_offset_2d:
+ camera_2d.offset = Vector2.ZERO # Resets noise position
+ _reset_noise_offset_2d = false
+ else:
+ if not is_instance_valid(_active_pcam_3d):
+ _active_pcam_missing = true
+ return
+
+ _active_pcam_3d.process_logic(delta)
+ _active_pcam_3d_glob_transform = _active_pcam_3d.get_transform_output()
+
+ if not _trigger_pcam_tween:
+ # Rechecks physics target if PCam transitioned with an instant tween
+ if _tween_is_instant:
+ _check_pcam_physics()
+ _tween_is_instant = false
+ _pcam_follow(delta)
+ else:
+ _pcam_tween(delta)
+
+ # Camera Noise
+ if _is_2d:
+ if not _has_noise_emitted and not _active_pcam_2d.has_noise_resource(): return
+ camera_2d.offset += _active_pcam_2d.get_noise_transform().origin + _noise_emitted_output_2d.origin
+ if camera_2d.ignore_rotation and _noise_emitted_output_2d.get_rotation() != 0:
+ push_warning(camera_2d.name, " has ignore_rotation enabled. Uncheck the property if you want to apply rotational noise.")
+ else:
+ camera_2d.rotation += _active_pcam_2d.get_noise_transform().get_rotation() + _noise_emitted_output_2d.get_rotation()
+ _has_noise_emitted = false
+ _reset_noise_offset_2d = true
+ else:
+ if not _has_noise_emitted and not _active_pcam_3d.has_noise_resource(): return
+ camera_3d.global_transform *= _active_pcam_3d.get_noise_transform() * _noise_emitted_output_3d
+ _has_noise_emitted = false
+
+
+func _pcam_follow(_delta: float) -> void:
+ if _active_pcam_missing or not _is_child_of_camera: return
+
+ if _is_2d:
+ if _active_pcam_2d.snap_to_pixel:
+ var snap_to_pixel_glob_transform: Transform2D = _active_pcam_2d_glob_transform
+ snap_to_pixel_glob_transform.origin = snap_to_pixel_glob_transform.origin.round()
+ camera_2d.global_transform = snap_to_pixel_glob_transform
+ else:
+ camera_2d.global_transform = _active_pcam_2d_glob_transform
+ camera_2d.zoom = _active_pcam_2d.zoom
+ else:
+ camera_3d.global_transform = _active_pcam_3d_glob_transform
+
+ if _viewfinder_needed_check:
+ _show_viewfinder_in_play()
+ _viewfinder_needed_check = false
+
+ if Engine.is_editor_hint():
+ if not _is_2d:
+ # TODO - Signal-based solution pending merge of: https://github.com/godotengine/godot/pull/99729
+ if _active_pcam_3d.attributes != null:
+ camera_3d.attributes = _active_pcam_3d.attributes.duplicate()
+
+ # TODO - Signal-based solution pending merge of: https://github.com/godotengine/godot/pull/99873
+ if _active_pcam_3d.environment != null:
+ camera_3d.environment = _active_pcam_3d.environment.duplicate()
+
+
+func _noise_emitted_2d(noise_output: Transform2D) -> void:
+ _noise_emitted_output_2d = noise_output
+ _has_noise_emitted = true
+
+
+func _noise_emitted_3d(noise_output: Transform3D) -> void:
+ _noise_emitted_output_3d = noise_output
+ _has_noise_emitted = true
+
+
+func _camera_3d_resource_changed() -> void:
+ if _active_pcam_3d.camera_3d_resource:
+ if Engine.is_editor_hint():
+ if not Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.is_connected(_camera_3d_edited):
+ Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.connect(_camera_3d_edited)
+ camera_3d.keep_aspect = _active_pcam_3d.keep_aspect
+ camera_3d.cull_mask = _active_pcam_3d.cull_mask
+ camera_3d.h_offset = _active_pcam_3d.h_offset
+ camera_3d.v_offset = _active_pcam_3d.v_offset
+ camera_3d.projection = _active_pcam_3d.projection
+ camera_3d.fov = _active_pcam_3d.fov
+ camera_3d.size = _active_pcam_3d.size
+ camera_3d.frustum_offset = _active_pcam_3d.frustum_offset
+ camera_3d.near = _active_pcam_3d.near
+ camera_3d.far = _active_pcam_3d.far
+ else:
+ if Engine.is_editor_hint():
+ if Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.is_connected(_camera_3d_edited):
+ Engine.get_singleton(&"EditorInterface").get_inspector().property_edited.disconnect(_camera_3d_edited)
+
+func _camera_3d_edited(value: String) -> void:
+ if not Engine.get_singleton(&"EditorInterface").get_inspector().get_edited_object() == camera_3d: return
+ camera_3d.set(value, _active_pcam_3d.camera_3d_resource.get(value))
+ push_warning("Camera3D properties are being overridden by ", _active_pcam_3d.name, "'s Camera3DResource")
+
+func _camera_3d_resource_property_changed(property: StringName, value: Variant) -> void:
+ camera_3d.set(property, value)
+
+
+func _pcam_tween(delta: float) -> void:
+ # TODO - Should be optimised
+ # Run at the first tween frame
+ if _tween_elapsed_time == 0:
+ if _is_2d:
+ _active_pcam_2d.tween_started.emit()
+ _active_pcam_2d.reset_limit()
+ else:
+ _active_pcam_3d.tween_started.emit()
+
+ _tween_elapsed_time = min(_tween_duration, _tween_elapsed_time + delta)
+
+ if _is_2d:
+ _active_pcam_2d.is_tweening.emit()
+ var interpolation_destination: Vector2 = _tween_interpolate_value(
+ _prev_active_pcam_2d_transform.origin,
+ _active_pcam_2d_glob_transform.origin,
+ _active_pcam_2d.tween_duration,
+ _active_pcam_2d.tween_transition,
+ _active_pcam_2d.tween_ease
+ )
+
+ if _active_pcam_2d.snap_to_pixel:
+ camera_2d.global_position = interpolation_destination.round()
+ else:
+ camera_2d.global_position = interpolation_destination
+
+ camera_2d.rotation = _tween_interpolate_value(
+ _prev_active_pcam_2d_transform.get_rotation(),
+ _active_pcam_2d_glob_transform.get_rotation(),
+ _active_pcam_2d.tween_duration,
+ _active_pcam_2d.tween_transition,
+ _active_pcam_2d.tween_ease
+ )
+ camera_2d.zoom = _tween_interpolate_value(
+ _camera_zoom,
+ _active_pcam_2d.zoom,
+ _active_pcam_2d.tween_duration,
+ _active_pcam_2d.tween_transition,
+ _active_pcam_2d.tween_ease
+ )
+ else:
+ _active_pcam_3d.is_tweening.emit()
+ camera_3d.global_position = _tween_interpolate_value(
+ _prev_active_pcam_3d_transform.origin,
+ _active_pcam_3d_glob_transform.origin,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ var prev_active_pcam_3d_quat: Quaternion = Quaternion(_prev_active_pcam_3d_transform.basis.orthonormalized())
+ camera_3d.quaternion = \
+ Tween.interpolate_value(
+ prev_active_pcam_3d_quat, \
+ prev_active_pcam_3d_quat.inverse() * Quaternion(_active_pcam_3d_glob_transform.basis.orthonormalized()),
+ _tween_elapsed_time, \
+ _active_pcam_3d.tween_duration, \
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_attribute_changed:
+ if _active_pcam_3d.attributes.auto_exposure_enabled:
+ if _cam_auto_exposure_scale_changed:
+ camera_3d.attributes.auto_exposure_scale = \
+ _tween_interpolate_value(
+ _prev_cam_auto_exposure_scale,
+ _active_pcam_3d.attributes.auto_exposure_scale,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_auto_exposure_speed_changed:
+ camera_3d.attributes.auto_exposure_speed = \
+ _tween_interpolate_value(
+ _prev_cam_auto_exposure_scale,
+ _active_pcam_3d.attributes.auto_exposure_scale,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_attribute_type == 0: # CameraAttributePractical
+ if _active_pcam_3d.attributes.auto_exposure_enabled:
+ if _cam_exposure_min_sensitivity_changed:
+ camera_3d.attributes.auto_exposure_min_sensitivity = \
+ _tween_interpolate_value(
+ _prev_cam_exposure_min_sensitivity,
+ _active_pcam_3d.attributes.auto_exposure_min_sensitivity,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_exposure_max_sensitivity_changed:
+ camera_3d.attributes.auto_exposure_max_sensitivity = \
+ _tween_interpolate_value(
+ _prev_cam_exposure_max_sensitivity,
+ _active_pcam_3d.attributes.auto_exposure_max_sensitivity,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_dof_blur_amount_changed:
+ camera_3d.attributes.dof_blur_amount = \
+ _tween_interpolate_value(
+ _prev_cam_dof_blur_amount,
+ _active_pcam_3d.attributes.dof_blur_amount,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_dof_blur_far_distance_changed:
+ camera_3d.attributes.dof_blur_far_distance = \
+ _tween_interpolate_value(
+ _prev_cam_dof_blur_far_distance,
+ _active_pcam_3d.attributes.dof_blur_far_distance,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_dof_blur_far_transition_changed:
+ camera_3d.attributes.dof_blur_far_transition = \
+ _tween_interpolate_value(
+ _prev_cam_dof_blur_far_transition,
+ _active_pcam_3d.attributes.dof_blur_far_transition,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_dof_blur_near_distance_changed:
+ camera_3d.attributes.dof_blur_near_distance = \
+ _tween_interpolate_value(
+ _prev_cam_dof_blur_near_distance,
+ _active_pcam_3d.attributes.dof_blur_near_distance,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_dof_blur_near_transition_changed:
+ camera_3d.attributes.dof_blur_near_transition = \
+ _tween_interpolate_value(
+ _prev_cam_dof_blur_near_transition,
+ _active_pcam_3d.attributes.dof_blur_near_transition,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ elif _cam_attribute_type == 1: # CameraAttributePhysical
+ if _cam_dof_blur_near_transition_changed:
+ camera_3d.attributes.auto_exposure_max_exposure_value = \
+ _tween_interpolate_value(
+ _prev_cam_exposure_max_exposure_value,
+ _active_pcam_3d.attributes.auto_exposure_max_exposure_value,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_exposure_min_exposure_value_changed:
+ camera_3d.attributes.auto_exposure_min_exposure_value = \
+ _tween_interpolate_value(
+ _prev_cam_exposure_min_exposure_value,
+ _active_pcam_3d.attributes.auto_exposure_min_exposure_value,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_exposure_aperture_changed:
+ camera_3d.attributes.exposure_aperture = \
+ _tween_interpolate_value(
+ _prev_cam_exposure_aperture,
+ _active_pcam_3d.attributes.exposure_aperture,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_exposure_shutter_speed_changed:
+ camera_3d.attributes.exposure_shutter_speed = \
+ _tween_interpolate_value(
+ _prev_cam_exposure_shutter_speed,
+ _active_pcam_3d.attributes.exposure_shutter_speed,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_frustum_far_changed:
+ camera_3d.attributes.frustum_far = \
+ _tween_interpolate_value(
+ _prev_cam_frustum_far,
+ _active_pcam_3d.attributes.frustum_far,
+ _active_pcam_3d.tween_duration(),
+ _active_pcam_3d.tween_transition(),
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_frustum_near_changed:
+ camera_3d.attributes.frustum_near = \
+ _tween_interpolate_value(
+ _prev_cam_frustum_far,
+ _active_pcam_3d.attributes.frustum_near,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_frustum_focal_length_changed:
+ camera_3d.attributes.frustum_focal_length = \
+ _tween_interpolate_value(
+ _prev_cam_frustum_focal_length,
+ _active_pcam_3d.attributes.frustum_focal_length,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+ if _cam_frustum_focus_distance_changed:
+ camera_3d.attributes.frustum_focus_distance = \
+ _tween_interpolate_value(
+ _prev_cam_frustum_focus_distance,
+ _active_pcam_3d.attributes.frustum_focus_distance,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_h_offset_changed:
+ camera_3d.h_offset = \
+ _tween_interpolate_value(
+ _prev_cam_h_offset,
+ _active_pcam_3d.h_offset,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_v_offset_changed:
+ camera_3d.v_offset = \
+ _tween_interpolate_value(
+ _prev_cam_v_offset,
+ _active_pcam_3d.v_offset,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_fov_changed:
+ camera_3d.fov = \
+ _tween_interpolate_value(
+ _prev_cam_fov,
+ _active_pcam_3d.fov,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_size_changed:
+ camera_3d.size = \
+ _tween_interpolate_value(
+ _prev_cam_size,
+ _active_pcam_3d.size,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_frustum_offset_changed:
+ camera_3d.frustum_offset = \
+ _tween_interpolate_value(
+ _prev_cam_frustum_offset,
+ _active_pcam_3d.frustum_offset,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_near_changed:
+ camera_3d.near = \
+ _tween_interpolate_value(
+ _prev_cam_near,
+ _active_pcam_3d.near,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ if _cam_far_changed:
+ camera_3d.far = \
+ _tween_interpolate_value(
+ _prev_cam_far,
+ _active_pcam_3d.far,
+ _active_pcam_3d.tween_duration,
+ _active_pcam_3d.tween_transition,
+ _active_pcam_3d.tween_ease
+ )
+
+ # Forcefully disables physics interpolation when tweens are instant
+ if _tween_is_instant:
+ if _is_2d:
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor >= 3:
+ camera_2d.set("physics_interpolation_mode", 2)
+ camera_2d.call("reset_physics_interpolation")
+ else:
+ if Engine.get_version_info().major == 4 and \
+ Engine.get_version_info().minor >= 4:
+ camera_3d.set("physics_interpolation_mode", 2)
+ camera_3d.call("reset_physics_interpolation")
+
+ if _tween_elapsed_time < _tween_duration: return
+
+ _trigger_pcam_tween = false
+ _tween_elapsed_time = 0
+ viewfinder_update.emit(true)
+
+ if _is_2d:
+ _active_pcam_2d.update_limit_all_sides()
+ _active_pcam_2d.tween_completed.emit()
+ _active_pcam_2d.set_tween_skip(self, false)
+ if Engine.is_editor_hint():
+ _active_pcam_2d.queue_redraw()
+ else:
+ if _active_pcam_3d.camera_3d_resource and _active_pcam_3d.attributes != null:
+ if _cam_attribute_type == 0:
+ if not _active_pcam_3d.attributes.dof_blur_far_enabled:
+ camera_3d.attributes.dof_blur_far_enabled = false
+ if not _active_pcam_3d.attributes.dof_blur_near_enabled:
+ camera_3d.attributes.dof_blur_near_enabled = false
+ _cam_h_offset_changed = false
+ _cam_v_offset_changed = false
+ _cam_fov_changed = false
+ _cam_size_changed = false
+ _cam_frustum_offset_changed = false
+ _cam_near_changed = false
+ _cam_far_changed = false
+ _cam_attribute_changed = false
+
+ _active_pcam_3d.set_tween_skip(self, false)
+ _active_pcam_3d.tween_completed.emit()
+
+
+func _tween_interpolate_value(from: Variant, to: Variant, duration: float, transition_type: int, ease_type: int) -> Variant:
+ return Tween.interpolate_value(
+ from, \
+ to - from,
+ _tween_elapsed_time, \
+ duration, \
+ transition_type,
+ ease_type,
+ )
+
+
+func _show_viewfinder_in_play() -> void:
+ # Don't show the viewfinder in the actual editor or project builds
+ if Engine.is_editor_hint() or !OS.has_feature("editor"): return
+
+ # Default the viewfinder node to be hidden
+ if is_instance_valid(_viewfinder_node):
+ _viewfinder_node.visible = false
+
+ if _is_2d:
+ if not _active_pcam_2d.show_viewfinder_in_play: return
+ if _active_pcam_2d.follow_mode != _active_pcam_2d.FollowMode.FRAMED: return
+ else:
+ if not _active_pcam_3d.show_viewfinder_in_play: return
+ if _active_pcam_3d.follow_mode != _active_pcam_2d.FollowMode.FRAMED: return
+
+ var canvas_layer: CanvasLayer = CanvasLayer.new()
+ get_tree().get_root().add_child(canvas_layer)
+
+ # Instantiate the viewfinder scene if it isn't already
+ if not is_instance_valid(_viewfinder_node):
+ var _viewfinder_scene := load("res://addons/phantom_camera/panel/viewfinder/viewfinder_panel.tscn")
+ _viewfinder_node = _viewfinder_scene.instantiate()
+ canvas_layer.add_child(_viewfinder_node)
+
+ _viewfinder_node.visible = true
+ _viewfinder_node.update_dead_zone()
+
+
+func _update_limit_2d(side: int, limit: int) -> void:
+ if is_instance_valid(camera_2d):
+ camera_2d.set_limit(side, limit)
+
+func _draw_limit_2d(enabled: bool) -> void:
+ camera_2d.set_limit_drawing_enabled(enabled)
+
+
+## Called when a [param PhantomCamera] is added to the scene.[br]
+## [b]Note:[/b] This can only be called internally from a [param PhantomCamera] node.
+func _pcam_added_to_scene(pcam: Node) -> void:
+ if not pcam.is_node_ready(): await pcam.ready
+ _check_pcam_priority(pcam)
+
+
+## Called when a [param PhantomCamera] is removed from the scene.[br]
+## [b]Note:[/b] This can only be called internally from a
+## [param PhantomCamera] node.
+func _pcam_removed_from_scene(pcam: Node) -> void:
+ if _is_2d:
+ if pcam == _active_pcam_2d:
+ _active_pcam_2d = null
+ _active_pcam_missing = true
+ _active_pcam_priority = -1
+ _find_pcam_with_highest_priority()
+ else:
+ if pcam == _active_pcam_3d:
+ _active_pcam_3d = null
+ _active_pcam_missing = true
+ _active_pcam_priority = -1
+ _find_pcam_with_highest_priority()
+
+
+func _pcam_visibility_changed(pcam: Node) -> void:
+ if pcam == _active_pcam_2d or pcam == _active_pcam_3d:
+ _active_pcam_priority = -1
+ _find_pcam_with_highest_priority()
+ return
+ _check_pcam_priority(pcam)
+
+
+func _pcam_teleported(pcam: Node) -> void:
+ if _is_2d:
+ if not pcam == _active_pcam_2d: return
+ if not is_instance_valid(camera_2d): return
+ camera_2d.global_position = _active_pcam_2d.get_transform_output().origin
+ camera_2d.call("reset_physics_interpolation")
+# camera_2d.reset_physics_interpolation() # TODO - For when Godot 4.3 becomes the minimum version
+ else:
+ if not pcam == _active_pcam_3d: return
+ if not is_instance_valid(camera_3d): return
+ camera_3d.global_position = _active_pcam_3d.get_transform_output().origin
+ camera_3d.call("reset_physics_interpolation")
+# camera_3d.reset_physics_interpolation() # TODO - For when Godot 4.3 becomes the minimum version
+
+
+func _set_layer(current_layers: int, layer_number: int, value: bool) -> int:
+ var mask: int = current_layers
+
+ # From https://github.com/godotengine/godot/blob/51991e20143a39e9ef0107163eaf283ca0a761ea/scene/3d/camera_3d.cpp#L638
+ if layer_number < 1 or layer_number > 20:
+ printerr("Render layer must be between 1 and 20.")
+ else:
+ if value:
+ mask |= 1 << (layer_number - 1)
+ else:
+ mask &= ~(1 << (layer_number - 1))
+
+ return mask
+
+#endregion
+
+#region Public Functions
+
+## Triggers a recalculation to determine which PhantomCamera has the highest priority.
+func pcam_priority_updated(pcam: Node) -> void:
+ if not is_instance_valid(pcam): return
+ if not _pcam_is_in_host_layer(pcam): return
+
+ if pcam == _active_pcam_2d or pcam == _active_pcam_3d:
+ if not pcam.visible:
+ refresh_pcam_list_priorty()
+
+ if Engine.is_editor_hint():
+ if _is_2d:
+ if not is_instance_valid(_active_pcam_2d): return
+ if _active_pcam_2d.priority_override: return
+ else:
+ if not is_instance_valid(_active_pcam_3d): return
+ if _active_pcam_3d.priority_override: return
+
+ var current_pcam_priority: int = pcam.priority
+
+ if current_pcam_priority >= _active_pcam_priority:
+ if _is_2d:
+ if pcam != _active_pcam_2d:
+ _assign_new_active_pcam(pcam)
+ else:
+ if pcam != _active_pcam_3d:
+ _assign_new_active_pcam(pcam)
+ pcam.set_tween_skip(self, false)
+ _active_pcam_missing = false
+
+ if pcam == _active_pcam_2d or pcam == _active_pcam_3d:
+ if current_pcam_priority <= _active_pcam_priority:
+ _active_pcam_priority = current_pcam_priority
+ _find_pcam_with_highest_priority()
+ else:
+ _active_pcam_priority = current_pcam_priority
+
+
+## Updates the viewfinder when a [param PhantomCamera] has its
+## [param priority_ovrride] enabled.[br]
+## [b]Note:[/b] This only affects the editor.
+func _pcam_priority_override(pcam: Node, should_override: bool) -> void:
+ if not Engine.is_editor_hint(): return
+ if not _pcam_is_in_host_layer(pcam): return
+ if should_override:
+ if _is_2d:
+ if is_instance_valid(_active_pcam_2d):
+ if _active_pcam_2d.priority_override:
+ _active_pcam_2d.priority_override = false
+ else:
+ if is_instance_valid(_active_pcam_3d):
+ if _active_pcam_3d.priority_override:
+ _active_pcam_3d.priority_override = false
+ _assign_new_active_pcam(pcam)
+ else:
+ _find_pcam_with_highest_priority()
+
+ viewfinder_update.emit(false)
+
+
+## Updates the viewfinder when a [param PhantomCamera] has its
+## [param priority_ovrride] disabled.[br]
+## [b]Note:[/b] This only affects the editor.
+func pcam_priority_override_disabled() -> void:
+ viewfinder_update.emit(false)
+
+
+## Returns the currently active [param PhantomCamera]
+func get_active_pcam() -> Node:
+ if _is_2d:
+ return _active_pcam_2d
+ else:
+ return _active_pcam_3d
+
+
+## Returns whether if a [param PhantomCamera] should tween when it becomes
+## active. If it's already active, the value will always be false.
+## [b]Note:[/b] This can only be called internally from a
+## [param PhantomCamera] node.
+func get_trigger_pcam_tween() -> bool:
+ return _trigger_pcam_tween
+
+
+## Refreshes the [param PhantomCamera] list and checks for the highest priority. [br]
+## [b]Note:[/b] This should [b]not[/b] be necessary to call manually.
+func refresh_pcam_list_priorty() -> void:
+ _active_pcam_priority = -1
+ _find_pcam_with_highest_priority()
+
+
+#func set_interpolation_mode(value: int) -> void:
+ #interpolation_mode = value
+#func get_interpolation_mode() -> int:
+ #return interpolation_mode
+
+#endregion
+
+##region Setters / Getters
+
+## Sets the [member host_layers] value.
+func set_host_layers(value: int) -> void:
+ host_layers = value
+
+ if not _is_child_of_camera: return
+
+ if not _active_pcam_missing:
+ if _is_2d:
+ _pcam_host_layer_changed(_active_pcam_2d)
+ else:
+ _pcam_host_layer_changed(_active_pcam_3d)
+ else:
+ _find_pcam_with_highest_priority()
+
+## Enables or disables a given layer of [member host_layers].
+func set_host_layers_value(layer: int, value: bool) -> void:
+ host_layers = _set_layer(host_layers, layer, value)
+
+## Returns the [member host_layers] value.
+func get_host_layers() -> int:
+ return host_layers
+
+##endregion
diff --git a/addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd.uid b/addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd.uid
new file mode 100644
index 0000000..e96433d
--- /dev/null
+++ b/addons/phantom_camera/scripts/phantom_camera_host/phantom_camera_host.gd.uid
@@ -0,0 +1 @@
+uid://bd046eokvcnu2
diff --git a/addons/phantom_camera/scripts/resources/camera_3d_resource.gd b/addons/phantom_camera/scripts/resources/camera_3d_resource.gd
new file mode 100644
index 0000000..c630e16
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/camera_3d_resource.gd
@@ -0,0 +1,110 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_camera_3d_resource.svg")
+class_name Camera3DResource
+extends Resource
+
+## Resource for [PhantomCamera3D] to override various [Camera3D] properties.
+##
+## The overrides defined here will be applied to the [Camera3D] upon the
+## [PhantomCamera3D] becoming active.
+
+enum KeepAspect {
+ KEEP_WIDTH = 0, ## Preserves the horizontal aspect ratio; also known as Vert- scaling. This is usually the best option for projects running in portrait mode, as taller aspect ratios will benefit from a wider vertical FOV.
+ KEEP_HEIGHT = 1, ## Preserves the vertical aspect ratio; also known as Hor+ scaling. This is usually the best option for projects running in landscape mode, as wider aspect ratios will automatically benefit from a wider horizontal FOV.
+}
+
+enum ProjectionType {
+ PERSPECTIVE = 0, ## Perspective projection. Objects on the screen becomes smaller when they are far away.
+ ORTHOGONAL = 1, ## Orthogonal projection, also known as orthographic projection. Objects remain the same size on the screen no matter how far away they are.
+ FRUSTUM = 2, ## Frustum projection. This mode allows adjusting frustum_offset to create "tilted frustum" effects.
+}
+
+## Overrides [member Camera3D.keep_aspect].
+@export var keep_aspect: KeepAspect = KeepAspect.KEEP_HEIGHT:
+ set(value):
+ keep_aspect = value
+ emit_changed()
+ get:
+ return keep_aspect
+
+## Overrides [member Camera3D.cull_mask].
+@export_flags_3d_render var cull_mask: int = 1048575:
+ set(value):
+ cull_mask = value
+ emit_changed()
+ get:
+ return cull_mask
+
+## Overrides [member Camera3D.h_offset].
+@export_range(0, 1, 0.001, "or_greater", "or_less", "hide_slider", "suffix:m") var h_offset: float = 0:
+ set(value):
+ h_offset = value
+ emit_changed()
+ get:
+ return h_offset
+
+## Overrides [member Camera3D.v_offset].
+@export_range(0, 1, 0.001, "or_greater", "or_less", "hide_slider", "suffix:m") var v_offset: float = 0:
+ set(value):
+ v_offset = value
+ emit_changed()
+
+## Overrides [member Camera3D.projection].
+@export var projection: ProjectionType = ProjectionType.PERSPECTIVE:
+ set(value):
+ projection = value
+ notify_property_list_changed()
+ emit_changed()
+ get:
+ return projection
+
+## Overrides [member Camera3D.fov].
+@export_range(1, 179, 0.1, "degrees") var fov: float = 75:
+ set(value):
+ fov = value
+ emit_changed()
+ get:
+ return fov
+
+## Overrides [member Camera3D.size].
+@export_range(0.001, 100, 0.001, "suffix:m", "or_greater") var size: float = 1:
+ set(value):
+ size = value
+ emit_changed()
+ get:
+ return size
+
+## Overrides [member Camera3d.frustum_offset].
+@export var frustum_offset: Vector2 = Vector2.ZERO:
+ set(value):
+ frustum_offset = value
+ emit_changed()
+ get:
+ return frustum_offset
+
+## Overrides [member Camera3D.near].
+@export_range(0.001, 10, 0.001, "suffix:m", "or_greater") var near: float = 0.05:
+ set(value):
+ near = value
+ emit_changed()
+ get:
+ return near
+
+## Overrides [member Camera3D.far].
+@export_range(0.01, 4000, 0.001, "suffix:m","or_greater") var far: float = 4000:
+ set(value):
+ far = value
+ emit_changed()
+ get:
+ return far
+
+
+func _validate_property(property: Dictionary) -> void:
+ if property.name == "fov" and not projection == ProjectionType.PERSPECTIVE:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "size" and projection == ProjectionType.PERSPECTIVE:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if property.name == "frustum_offset" and not projection == ProjectionType.FRUSTUM:
+ property.usage = PROPERTY_USAGE_NO_EDITOR
diff --git a/addons/phantom_camera/scripts/resources/camera_3d_resource.gd.uid b/addons/phantom_camera/scripts/resources/camera_3d_resource.gd.uid
new file mode 100644
index 0000000..e8378eb
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/camera_3d_resource.gd.uid
@@ -0,0 +1 @@
+uid://bc2tn187qiatpcheck
diff --git a/addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd b/addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd
new file mode 100644
index 0000000..cc87dba
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd
@@ -0,0 +1,228 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_noise_resource.svg")
+class_name PhantomCameraNoise2D
+extends Resource
+
+## A resource type used to apply noise, or shake, to [Camera2D]s that have a [PhantomCameraHost] as a child.
+##
+## Is a resource type that defines, calculates and outputs the noise values to a [Camera2D] through active
+## [PhantomCamera3D].[br]
+## It can be applied to either [PhantomCameraNoiseEmitter2D] or a [PhantomCamera2D] noise property directly
+
+#region Exported Properties
+
+## Defines the size of the noise pattern.[br]
+## Higher values will increase the range the noise can reach.
+@export_range(0, 1000, 0.001, "or_greater") var amplitude: float = 10:
+ set = set_amplitude,
+ get = get_amplitude
+
+## Sets the density of the noise pattern.[br]
+## Higher values will result in more erratic noise.
+@export_range(0, 10, 0.001, "or_greater") var frequency: float = 0.5:
+ set = set_frequency,
+ get = get_frequency
+
+## If true, randomizes the noise pattern every time the noise is run.[br]
+## If disabled, [member seed] can be used to define a fixed noise pattern.
+@export var randomize_noise_seed: bool = true:
+ set = set_randomize_noise_seed,
+ get = get_randomize_noise_seed
+
+## Sets a predetermined seed noise value.[br]
+## Useful if wanting to achieve a persistent noise pattern every time the noise is emitted.
+@export var noise_seed: int = 0:
+ set = set_noise_seed,
+ get = get_noise_seed
+
+## Enables noise changes to the [member Camera2D.offset] position.
+@export var positional_noise: bool = true:
+ set = set_positional_noise,
+ get = get_positional_noise
+
+## Enables noise changes to the [Camera2D]'s rotation.
+@export var rotational_noise: bool = false:
+ set = set_rotational_noise,
+ get = get_rotational_noise
+
+@export_group("Positional Multiplier")
+## Multiplies positional noise amount in the X-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.
+@export_range(0, 1, 0.001, "or_greater") var positional_multiplier_x: float = 1:
+ set = set_positional_multiplier_x,
+ get = get_positional_multiplier_x
+
+## Multiplies positional noise amount in the Y-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.
+@export_range(0, 1, 0.001, "or_greater") var positional_multiplier_y: float = 1:
+ set = set_positional_multiplier_y,
+ get = get_positional_multiplier_y
+
+@export_group("Rotational Multiplier")
+## Multiplies rotational noise amount.
+@export_range(0, 1, 0.001, "or_greater") var rotational_multiplier: float = 1:
+ set = set_rotational_multiplier,
+ get = get_rotational_multiplier
+
+#endregion
+
+#region Private Variables
+
+var _noise_algorithm: FastNoiseLite = FastNoiseLite.new()
+
+var _noise_positional_multiplier: Vector2 = Vector2(
+ positional_multiplier_x,
+ positional_multiplier_y
+)
+
+var _trauma: float = 0.0:
+ set(value):
+ _trauma = value
+
+var _noise_time: float = 0.0
+
+#endregion
+
+#region Private Functions
+
+func _init():
+ _noise_algorithm.noise_type = FastNoiseLite.TYPE_PERLIN
+ if randomize_noise_seed: _noise_algorithm.seed = randi()
+ _noise_algorithm.frequency = frequency
+
+
+func _validate_property(property: Dictionary) -> void:
+ if randomize_noise_seed and property.name == "noise_seed":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if not rotational_noise and property.name == "rotational_multiplier":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if not positional_noise:
+ match property.name:
+ "positional_multiplier_x", \
+ "positional_multiplier_y":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _get_noise_from_seed(noise_seed: int) -> float:
+ return _noise_algorithm.get_noise_2d(noise_seed, _noise_time) * amplitude
+
+
+func set_trauma(value: float) -> void:
+ _trauma = value
+
+#endregion
+
+#region Public Functions
+
+func get_noise_transform(delta: float) -> Transform2D:
+ var output_position: Vector2 = Vector2.ZERO
+ var output_rotation: float = 0.0
+ _noise_time += delta
+ _trauma = maxf(_trauma, 0.0)
+
+ if positional_noise:
+ for i in 2:
+ output_position[i] = _noise_positional_multiplier[i] * pow(_trauma, 2) * _get_noise_from_seed(i + noise_seed)
+ if rotational_noise:
+ output_rotation = rotational_multiplier / 100 * pow(_trauma, 2) * _get_noise_from_seed(noise_seed)
+
+ return Transform2D(output_rotation, output_position)
+
+
+func reset_noise_time() -> void:
+ _noise_time = 0
+
+#endregion
+
+#region Setters & Getters
+
+## Sets the [member amplitude] value.
+func set_amplitude(value: float) -> void:
+ amplitude =value
+
+## Returns the [member amplitude] value.
+func get_amplitude() -> float:
+ return amplitude
+
+
+## Sets the [member frequency] value.
+func set_frequency(value: float) -> void:
+ frequency = value
+ _noise_algorithm.frequency = value
+
+## Returns the [member frequency] value.
+func get_frequency() -> float:
+ return frequency
+
+
+## Sets the [member randomize_seed] value.
+func set_randomize_noise_seed(value: int) -> void:
+ randomize_noise_seed = value
+ if value: _noise_algorithm.seed = randi()
+ notify_property_list_changed()
+
+## Returns the [member randomize_seed] value.
+func get_randomize_noise_seed() -> int:
+ return randomize_noise_seed
+
+
+## Sets the [member randomize_seed] value.
+func set_noise_seed(value: int) -> void:
+ noise_seed = value
+
+## Returns the [member seed] value.
+func get_noise_seed() -> int:
+ return noise_seed
+
+
+## Sets the [member positional_noise] value.
+func set_positional_noise(value: bool) -> void:
+ positional_noise = value
+ notify_property_list_changed()
+
+## Returns the [member positional_noise] value.
+func get_positional_noise() -> bool:
+ return positional_noise
+
+
+## Sets the [member rotational_noise] value.
+func set_rotational_noise(value: bool) -> void:
+ rotational_noise = value
+ notify_property_list_changed()
+
+## Returns the [member rotational_noise] value.
+func get_rotational_noise() -> bool:
+ return rotational_noise
+
+
+## Sets the [member positional_multiplier_x] value.
+func set_positional_multiplier_x(value: float) -> void:
+ positional_multiplier_x = value
+ _noise_positional_multiplier.x = value
+
+## Returns the [member positional_multiplier_x] value.
+func get_positional_multiplier_x() -> float:
+ return positional_multiplier_x
+
+
+## Sets the [member positional_multiplier_y] value.
+func set_positional_multiplier_y(value: float) -> void:
+ positional_multiplier_y = value
+ _noise_positional_multiplier.y = value
+
+## Returns the [member positional_multiplier_y] value.
+func get_positional_multiplier_y() -> float:
+ return positional_multiplier_y
+
+
+## Sets the [member rotational_multiplier] value.
+func set_rotational_multiplier(value: float) -> void:
+ rotational_multiplier = value
+
+## Returns the [member rotational_multiplier] value.
+func get_rotational_multiplier() -> float:
+ return rotational_multiplier
+
+#endregion
diff --git a/addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd.uid b/addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd.uid
new file mode 100644
index 0000000..45ae480
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/phantom_camera_noise_2d.gd.uid
@@ -0,0 +1 @@
+uid://dimvdouy8g0sv
diff --git a/addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd b/addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd
new file mode 100644
index 0000000..6cf840f
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd
@@ -0,0 +1,301 @@
+@tool
+@icon("res://addons/phantom_camera/icons/phantom_camera_noise_resource.svg")
+class_name PhantomCameraNoise3D
+extends Resource
+
+## A resource type used to apply noise, or shake, to [Camera3D]s that have a [PhantomCameraHost] as a child.
+##
+## Is a resource type that defines, calculates and outputs the noise values to a [Camera3D] through active
+## [PhantomCamera3D].[br]
+## It can be applied to either [PhantomCameraNoiseEmitter3D] or a [PhantomCamera3D] noise property directly
+
+#region Exported Properties
+
+## Defines the size of the noise pattern.[br]
+## Higher values will increase the range the noise can reach.
+@export_range(0, 100, 0.001, "or_greater") var amplitude: float = 10:
+ set = set_amplitude,
+ get = get_amplitude
+
+## Sets the density of the noise pattern.[br]
+## Higher values will result in more erratic noise.
+@export_range(0, 10, 0.001, "or_greater") var frequency: float = 0.2:
+ set = set_frequency,
+ get = get_frequency
+
+## If true, randomizes the noise pattern every time the noise is run.[br]
+## If disabled, [member seed] can be used to define a fixed noise pattern.
+@export var randomize_noise_seed: bool = true:
+ set = set_randomize_noise_seed,
+ get = get_randomize_noise_seed
+
+## Sets a predetermined seed noise value.[br]
+## Useful if wanting to achieve a persistent noise pattern every time the noise is emitted.
+@export var noise_seed: int = 0:
+ set = set_noise_seed,
+ get = get_noise_seed
+
+## Enables noise changes to the [Camera3D]'s rotation.
+@export var rotational_noise: bool = true:
+ set = set_rotational_noise,
+ get = get_rotational_noise
+
+## Enables noise changes to the camera's position.[br][br]
+## [b]Important[/b][br]This can cause geometry clipping if the camera gets too close while this is active.
+@export var positional_noise: bool = false:
+ set = set_positional_noise,
+ get = get_positional_noise
+
+@export_group("Rotational Multiplier")
+## Multiplies rotational noise amount in the X-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.
+@export_range(0, 1, 0.001, "or_greater") var rotational_multiplier_x: float = 1:
+ set = set_rotational_multiplier_x,
+ get = get_rotational_multiplier_x
+
+## Multiplies rotational noise amount in the Y-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.
+@export_range(0, 1, 0.001, "or_greater") var rotational_multiplier_y: float = 1:
+ set = set_rotational_multiplier_y,
+ get = get_rotational_multiplier_y
+
+## Multiplies rotational noise amount in the Z-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.
+@export_range(0, 1, 0.001, "or_greater") var rotational_multiplier_z: float = 1:
+ set = set_rotational_multiplier_z,
+ get = get_rotational_multiplier_z
+
+@export_group("Positional Multiplier")
+## Multiplies positional noise amount in the X-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.[br]
+## [b]Note:[/b] Rotational Offset is recommended to avoid potential camera clipping with the environment.
+@export_range(0, 1, 0.001, "or_greater") var positional_multiplier_x: float = 1:
+ set = set_positional_multiplier_x,
+ get = get_positional_multiplier_x
+
+## Multiplies positional noise amount in the Y-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.[br]
+## [b]Note:[/b] Rotational Offset is recommended to avoid potential camera clipping with the environment.
+@export_range(0, 1, 0.001, "or_greater") var positional_multiplier_y: float = 1:
+ set = set_positional_multiplier_y,
+ get = get_positional_multiplier_y
+
+## Multiplies positional noise amount in the Z-axis.[br]
+## Set the value to [param 0] to disable noise in the axis.[br]
+## [b]Note:[/b] Rotational Offset is recommended to avoid potential camera clipping with the environment.
+@export_range(0, 1, 0.001, "or_greater") var positional_multiplier_z: float = 1:
+ set = set_positional_multiplier_z,
+ get = get_positional_multiplier_z
+
+#endregion
+
+#region Private Variables
+
+var _noise_algorithm: FastNoiseLite = FastNoiseLite.new()
+
+var _noise_rotational_multiplier: Vector3 = Vector3(
+ rotational_multiplier_x,
+ rotational_multiplier_y,
+ rotational_multiplier_z,
+)
+
+var _noise_positional_multiplier: Vector3 = Vector3(
+ positional_multiplier_x,
+ positional_multiplier_y,
+ positional_multiplier_z,
+)
+
+var _trauma: float = 0.0:
+ set(value):
+ _trauma = value
+ if _trauma == 0.0:
+ _noise_time = 0.0
+
+var _noise_time: float = 0.0
+
+#endregion
+
+#region Private Functions
+
+func _init():
+ _noise_algorithm.noise_type = FastNoiseLite.TYPE_PERLIN
+
+ if randomize_noise_seed: _noise_algorithm.seed = randi()
+ _noise_algorithm.frequency = frequency
+
+
+func _validate_property(property: Dictionary) -> void:
+ if randomize_noise_seed and property.name == "noise_seed":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if not rotational_noise:
+ match property.name:
+ "rotational_multiplier_x", \
+ "rotational_multiplier_y", \
+ "rotational_multiplier_z":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+ if not positional_noise:
+ match property.name:
+ "positional_multiplier_x", \
+ "positional_multiplier_y", \
+ "positional_multiplier_z":
+ property.usage = PROPERTY_USAGE_NO_EDITOR
+
+
+func _get_noise_from_seed(noise_seed: int) -> float:
+ return _noise_algorithm.get_noise_2d(noise_seed, _noise_time) * amplitude
+
+
+func set_trauma(value: float) -> void:
+ _trauma = value
+
+#endregion
+
+#region Public Functions
+
+func get_noise_transform(delta: float) -> Transform3D:
+ var output_rotation: Vector3 = Vector3.ZERO
+ var output_position: Vector3 = Vector3.ZERO
+ _noise_time += delta
+ _trauma = maxf(_trauma, 0.0)
+
+ for i in 3:
+ if rotational_noise:
+ output_rotation[i] = deg_to_rad(
+ _noise_rotational_multiplier[i] * pow(_trauma, 2) * _get_noise_from_seed(i + noise_seed)
+ )
+
+ if positional_noise:
+ output_position[i] += _noise_positional_multiplier[i] / 10 * \
+ pow(_trauma, 2) * _get_noise_from_seed(i + noise_seed)
+
+ return Transform3D(Quaternion.from_euler(output_rotation), output_position)
+
+
+func reset_noise_time() -> void:
+ _noise_time = 0
+
+#endregion
+
+#region Setters & Getters
+
+## Sets the [member amplitude] value.
+func set_amplitude(value: float) -> void:
+ amplitude =value
+
+## Returns the [member amplitude] value.
+func get_amplitude() -> float:
+ return amplitude
+
+
+## Sets the [member frequency] value.
+func set_frequency(value: float) -> void:
+ frequency = value
+ _noise_algorithm.frequency = value
+
+## Returns the [member frequency] value.
+func get_frequency() -> float:
+ return frequency
+
+
+## Sets the [member randomize_seed] value.
+func set_randomize_noise_seed(value: int) -> void:
+ randomize_noise_seed = value
+ if value: _noise_algorithm.seed = randi()
+ notify_property_list_changed()
+
+## Returns the [member randomize_seed] value.
+func get_randomize_noise_seed() -> int:
+ return randomize_noise_seed
+
+
+## Sets the [member randomize_seed] value.
+func set_noise_seed(value: int) -> void:
+ noise_seed = value
+
+## Returns the [member seed] value.
+func get_noise_seed() -> int:
+ return noise_seed
+
+
+## Sets the [member positional_noise] value.
+func set_positional_noise(value: bool) -> void:
+ positional_noise = value
+ notify_property_list_changed()
+
+## Returns the [member positional_noise] value.
+func get_positional_noise() -> bool:
+ return positional_noise
+
+
+## Sets the [member rotational_noise] value.
+func set_rotational_noise(value: bool) -> void:
+ rotational_noise = value
+ notify_property_list_changed()
+
+## Returns the [member rotational_noise] value.
+func get_rotational_noise() -> bool:
+ return rotational_noise
+
+
+## Sets the [member positional_multiplier_x] value.
+func set_positional_multiplier_x(value: float) -> void:
+ positional_multiplier_x = value
+ _noise_positional_multiplier.x = value
+
+## Returns the [member positional_multiplier_x] value.
+func get_positional_multiplier_x() -> float:
+ return positional_multiplier_x
+
+
+## Sets the [member positional_multiplier_y] value.
+func set_positional_multiplier_y(value: float) -> void:
+ positional_multiplier_y = value
+ _noise_positional_multiplier.y = value
+
+## Returns the [member positional_multiplier_y] value.
+func get_positional_multiplier_y() -> float:
+ return positional_multiplier_y
+
+
+## Sets the [member positional_multiplier_z] value.
+func set_positional_multiplier_z(value: float) -> void:
+ positional_multiplier_z = value
+ _noise_positional_multiplier.z = value
+
+## Returns the [member positional_multiplier_z] value.
+func get_positional_multiplier_z() -> float:
+ return positional_multiplier_z
+
+
+## Sets the [member rotational_multiplier_x] value.
+func set_rotational_multiplier_x(value: float) -> void:
+ rotational_multiplier_x = value
+ _noise_rotational_multiplier.x = value
+
+## Returns the [member rotational_multiplier_x] value.
+func get_rotational_multiplier_x() -> float:
+ return rotational_multiplier_x
+
+
+## Sets the [member rotational_multiplier_y] value.
+func set_rotational_multiplier_y(value: float) -> void:
+ rotational_multiplier_y = value
+ _noise_rotational_multiplier.y = value
+
+## Returns the [member rotational_multiplier_y] value.
+func get_rotational_multiplier_y() -> float:
+ return rotational_multiplier_y
+
+
+## Sets the [member rotational_multiplier_z] value.
+func set_rotational_multiplier_z(value: float) -> void:
+ rotational_multiplier_z = value
+ _noise_rotational_multiplier.z = value
+
+## Returns the [member rotational_multiplier_z] value.
+func get_rotational_multiplier_z() -> float:
+ return rotational_multiplier_z
+
+ #endregion
diff --git a/addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd.uid b/addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd.uid
new file mode 100644
index 0000000..42a0694
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/phantom_camera_noise_3d.gd.uid
@@ -0,0 +1 @@
+uid://cuffvge5ad4aa
diff --git a/addons/phantom_camera/scripts/resources/tween_resource.gd b/addons/phantom_camera/scripts/resources/tween_resource.gd
new file mode 100644
index 0000000..0a3b46f
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/tween_resource.gd
@@ -0,0 +1,41 @@
+@icon("res://addons/phantom_camera/icons/phantom_camera_tween.svg")
+class_name PhantomCameraTween
+extends Resource
+
+## Tweening resource for [PhantomCamera2D] and [PhantomCamera3D].
+##
+## Defines how [param PhantomCameras] transition between one another.
+## Changing the tween values for a given [param PhantomCamera] determines how
+## transitioning to that instance will look like.
+
+enum TransitionType {
+ LINEAR = 0, ## The animation is interpolated linearly.
+ SINE = 1, ## The animation is interpolated using a sine function.
+ QUINT = 2, ## The animation is interpolated with a quintic (to the power of 5) function.
+ QUART = 3, ## The animation is interpolated with a quartic (to the power of 4) function.
+ QUAD = 4, ## The animation is interpolated with a quadratic (to the power of 2) function.
+ EXPO = 5, ## The animation is interpolated with an exponential (to the power of x) function.
+ ELASTIC = 6, ## The animation is interpolated with elasticity, wiggling around the edges.
+ CUBIC = 7, ## The animation is interpolated with a cubic (to the power of 3) function.
+ CIRC = 8, ## The animation is interpolated with a function using square roots.
+ BOUNCE = 9, ## The animation is interpolated by bouncing at the end.
+ BACK = 10, ## The animation is interpolated backing out at ends.
+# CUSTOM = 11,
+# NONE = 12,
+}
+
+enum EaseType {
+ EASE_IN = 0, ## The interpolation starts slowly and speeds up towards the end.
+ EASE_OUT = 1, ## The interpolation starts quickly and slows down towards the end.
+ EASE_IN_OUT = 2, ## A combination of EASE_IN and EASE_OUT. The interpolation is slowest at both ends.
+ EASE_OUT_IN = 3, ## A combination of EASE_IN and EASE_OUT. The interpolation is fastest at both ends.
+}
+
+## The time it takes to tween to this PhantomCamera in [param seconds].
+@export var duration: float = 1.0
+
+## The transition bezier type for the tween. The options are defined in the [enum TransitionType].
+@export var transition: TransitionType = TransitionType.LINEAR
+
+## The ease type for the tween. The options are defined in the [enum EaseType].
+@export var ease: EaseType = EaseType.EASE_IN_OUT
diff --git a/addons/phantom_camera/scripts/resources/tween_resource.gd.uid b/addons/phantom_camera/scripts/resources/tween_resource.gd.uid
new file mode 100644
index 0000000..a0f2cf1
--- /dev/null
+++ b/addons/phantom_camera/scripts/resources/tween_resource.gd.uid
@@ -0,0 +1 @@
+uid://8umksf8e80fw
diff --git a/addons/phantom_camera/themes/button_focus.tres b/addons/phantom_camera/themes/button_focus.tres
new file mode 100644
index 0000000..e6fcc45
--- /dev/null
+++ b/addons/phantom_camera/themes/button_focus.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://p058hmj3uut0"]
+
+[resource]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/addons/phantom_camera/themes/button_hover.tres b/addons/phantom_camera/themes/button_hover.tres
new file mode 100644
index 0000000..9d37a86
--- /dev/null
+++ b/addons/phantom_camera/themes/button_hover.tres
@@ -0,0 +1,13 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://5weqvkjsfso3"]
+
+[resource]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.960784, 0.960784, 0.960784, 1)
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/addons/phantom_camera/themes/button_normal.tres b/addons/phantom_camera/themes/button_normal.tres
new file mode 100644
index 0000000..4eae33d
--- /dev/null
+++ b/addons/phantom_camera/themes/button_normal.tres
@@ -0,0 +1,17 @@
+[gd_resource type="StyleBoxFlat" format=3 uid="uid://bclbwo3xrdat0"]
+
+[resource]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
diff --git a/addons/phantom_camera/themes/theme.tres b/addons/phantom_camera/themes/theme.tres
new file mode 100644
index 0000000..7ce53d8
--- /dev/null
+++ b/addons/phantom_camera/themes/theme.tres
@@ -0,0 +1,102 @@
+[gd_resource type="Theme" load_steps=12 format=3 uid="uid://bhppejri5dbsf"]
+
+[ext_resource type="FontFile" uid="uid://dve7mgsjik4dg" path="res://addons/phantom_camera/fonts/Nunito-Regular.ttf" id="1_5rtjh"]
+[ext_resource type="StyleBox" uid="uid://5weqvkjsfso3" path="res://addons/phantom_camera/themes/button_hover.tres" id="2_du6h5"]
+[ext_resource type="StyleBox" uid="uid://bclbwo3xrdat0" path="res://addons/phantom_camera/themes/button_normal.tres" id="3_a8j1f"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ek0y3"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rjkuq"]
+content_margin_left = 8.0
+content_margin_top = 4.0
+content_margin_right = 8.0
+content_margin_bottom = 4.0
+bg_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x7u0w"]
+content_margin_top = 2.0
+content_margin_right = 8.0
+bg_color = Color(0.0784314, 0.109804, 0.129412, 1)
+border_width_top = 2
+border_width_right = 2
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_right = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dln2q"]
+content_margin_top = 8.0
+content_margin_bottom = 8.0
+draw_center = false
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wk7ot"]
+bg_color = Color(0.227451, 0.72549, 0.603922, 1)
+border_color = Color(0.227451, 0.72549, 0.603922, 1)
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_jidrt"]
+bg_color = Color(0.960784, 0.960784, 0.960784, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_o2xwc"]
+bg_color = Color(0.960784, 0.960784, 0.960784, 1)
+border_width_left = 2
+border_width_top = 2
+border_width_right = 2
+border_width_bottom = 2
+corner_radius_top_left = 8
+corner_radius_top_right = 8
+corner_radius_bottom_right = 8
+corner_radius_bottom_left = 8
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ul127"]
+draw_center = false
+border_width_left = 4
+border_width_right = 4
+border_color = Color(0.8, 0.8, 0.8, 0)
+
+[resource]
+default_font = ExtResource("1_5rtjh")
+Button/colors/font_color = Color(0.227451, 0.72549, 0.603922, 1)
+Button/colors/font_focus_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/font_hover_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/font_hover_pressed_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/font_pressed_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/icon_focus_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/icon_hover_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/icon_hover_pressed_color = Color(0.227451, 0.72549, 0.603922, 1)
+Button/colors/icon_normal_color = Color(0.0784314, 0.109804, 0.129412, 1)
+Button/colors/icon_pressed_color = Color(0.227451, 0.72549, 0.603922, 1)
+Button/styles/focus = SubResource("StyleBoxFlat_ek0y3")
+Button/styles/hover = ExtResource("2_du6h5")
+Button/styles/hover_pressed = null
+Button/styles/normal = ExtResource("3_a8j1f")
+Button/styles/pressed = SubResource("StyleBoxFlat_rjkuq")
+PanelContainer/styles/panel = SubResource("StyleBoxFlat_x7u0w")
+ScrollContainer/styles/panel = SubResource("StyleBoxFlat_dln2q")
+VBoxContainer/constants/separation = 8
+VScrollBar/styles/grabber = SubResource("StyleBoxFlat_wk7ot")
+VScrollBar/styles/grabber_highlight = SubResource("StyleBoxFlat_jidrt")
+VScrollBar/styles/grabber_pressed = SubResource("StyleBoxFlat_o2xwc")
+VScrollBar/styles/scroll = SubResource("StyleBoxFlat_ul127")
diff --git a/assets/sprites/characters/README.txt b/assets/sprites/characters/README.txt
deleted file mode 100644
index edebaa5..0000000
--- a/assets/sprites/characters/README.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-Player and skeleton sprites are on a 48x48 grid.
-Slime is on a 32x32 grid.
-
-Flip right facing sprites to get the left facing sprites.
-
-Animations [rows (0 based for us programmers)]
-Player:
-[0 - 2] idle
-[3 - 5] move
-[6 - 8] attack
-[9] death
-
-Enemies:
-[0 - 2] idle
-[3 - 5] move
-[6 - 8] attack
-[9 - 11] damaged
-[12] death
\ No newline at end of file
diff --git a/assets/sprites/characters/pink/Double_Jump_Dust_5.png b/assets/sprites/characters/pink/Double_Jump_Dust_5.png
new file mode 100644
index 0000000..1558c8d
Binary files /dev/null and b/assets/sprites/characters/pink/Double_Jump_Dust_5.png differ
diff --git a/assets/sprites/characters/pink/Double_Jump_Dust_5.png.import b/assets/sprites/characters/pink/Double_Jump_Dust_5.png.import
new file mode 100644
index 0000000..f1ed659
--- /dev/null
+++ b/assets/sprites/characters/pink/Double_Jump_Dust_5.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dpq3v6syrdgdb"
+path="res://.godot/imported/Double_Jump_Dust_5.png-28ec70513306b715131157e8ecde3739.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Double_Jump_Dust_5.png"
+dest_files=["res://.godot/imported/Double_Jump_Dust_5.png-28ec70513306b715131157e8ecde3739.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
diff --git a/assets/sprites/characters/pink/Pink_Monster.png b/assets/sprites/characters/pink/Pink_Monster.png
new file mode 100644
index 0000000..36ad589
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster.png differ
diff --git a/assets/sprites/tilesets/walls/wooden_door_b.png.import b/assets/sprites/characters/pink/Pink_Monster.png.import
similarity index 66%
rename from assets/sprites/tilesets/walls/wooden_door_b.png.import
rename to assets/sprites/characters/pink/Pink_Monster.png.import
index 494c5a3..7f115bc 100644
--- a/assets/sprites/tilesets/walls/wooden_door_b.png.import
+++ b/assets/sprites/characters/pink/Pink_Monster.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://c5tf6626uiegu"
-path="res://.godot/imported/wooden_door_b.png-53fdfb6a481c3305596bb5f693299829.ctex"
+uid="uid://d3k2ffc1lo0y0"
+path="res://.godot/imported/Pink_Monster.png-7769a9462e0dc238cae11f75feee9309.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/tilesets/walls/wooden_door_b.png"
-dest_files=["res://.godot/imported/wooden_door_b.png-53fdfb6a481c3305596bb5f693299829.ctex"]
+source_file="res://assets/sprites/characters/pink/Pink_Monster.png"
+dest_files=["res://.godot/imported/Pink_Monster.png-7769a9462e0dc238cae11f75feee9309.ctex"]
[params]
diff --git a/assets/sprites/characters/pink/Pink_Monster_Attack1_4.png b/assets/sprites/characters/pink/Pink_Monster_Attack1_4.png
new file mode 100644
index 0000000..e55eacd
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Attack1_4.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Attack1_4.png.import b/assets/sprites/characters/pink/Pink_Monster_Attack1_4.png.import
new file mode 100644
index 0000000..b430269
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Attack1_4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dqgq2c1h6yk3k"
+path="res://.godot/imported/Pink_Monster_Attack1_4.png-ed4bee20b8fbdf8ee56a7188f0d0cf96.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Attack1_4.png"
+dest_files=["res://.godot/imported/Pink_Monster_Attack1_4.png-ed4bee20b8fbdf8ee56a7188f0d0cf96.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Attack2_6.png b/assets/sprites/characters/pink/Pink_Monster_Attack2_6.png
new file mode 100644
index 0000000..e76abe4
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Attack2_6.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Attack2_6.png.import b/assets/sprites/characters/pink/Pink_Monster_Attack2_6.png.import
new file mode 100644
index 0000000..fe13dc1
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Attack2_6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dinwwco5v2km5"
+path="res://.godot/imported/Pink_Monster_Attack2_6.png-bd22f7486ca80b59ae6f4995ee2da60f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Attack2_6.png"
+dest_files=["res://.godot/imported/Pink_Monster_Attack2_6.png-bd22f7486ca80b59ae6f4995ee2da60f.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Climb_4.png b/assets/sprites/characters/pink/Pink_Monster_Climb_4.png
new file mode 100644
index 0000000..b7b8d05
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Climb_4.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Climb_4.png.import b/assets/sprites/characters/pink/Pink_Monster_Climb_4.png.import
new file mode 100644
index 0000000..3242fc3
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Climb_4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bx6nst3hqsww0"
+path="res://.godot/imported/Pink_Monster_Climb_4.png-2f4294abce1f51e361240895922b21cc.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Climb_4.png"
+dest_files=["res://.godot/imported/Pink_Monster_Climb_4.png-2f4294abce1f51e361240895922b21cc.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Death_8.png b/assets/sprites/characters/pink/Pink_Monster_Death_8.png
new file mode 100644
index 0000000..413f6ff
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Death_8.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Death_8.png.import b/assets/sprites/characters/pink/Pink_Monster_Death_8.png.import
new file mode 100644
index 0000000..1cb3fee
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Death_8.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0muqa26ngic7"
+path="res://.godot/imported/Pink_Monster_Death_8.png-4307e6abd1a5f542411af53a5b4c1de8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Death_8.png"
+dest_files=["res://.godot/imported/Pink_Monster_Death_8.png-4307e6abd1a5f542411af53a5b4c1de8.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Hurt_4.png b/assets/sprites/characters/pink/Pink_Monster_Hurt_4.png
new file mode 100644
index 0000000..7e2590f
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Hurt_4.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Hurt_4.png.import b/assets/sprites/characters/pink/Pink_Monster_Hurt_4.png.import
new file mode 100644
index 0000000..312522e
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Hurt_4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://nyfqe1b4k8sx"
+path="res://.godot/imported/Pink_Monster_Hurt_4.png-6ada15ba4c6edac01963b288ff67f7e6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Hurt_4.png"
+dest_files=["res://.godot/imported/Pink_Monster_Hurt_4.png-6ada15ba4c6edac01963b288ff67f7e6.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Idle_4.png b/assets/sprites/characters/pink/Pink_Monster_Idle_4.png
new file mode 100644
index 0000000..6d49c46
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Idle_4.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Idle_4.png.import b/assets/sprites/characters/pink/Pink_Monster_Idle_4.png.import
new file mode 100644
index 0000000..9a6bfc2
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Idle_4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b6ihjsu8qhm2x"
+path="res://.godot/imported/Pink_Monster_Idle_4.png-3796b6d5aa27e068e67d2304a03bdcbe.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Idle_4.png"
+dest_files=["res://.godot/imported/Pink_Monster_Idle_4.png-3796b6d5aa27e068e67d2304a03bdcbe.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Jump_8.png b/assets/sprites/characters/pink/Pink_Monster_Jump_8.png
new file mode 100644
index 0000000..1403e79
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Jump_8.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Jump_8.png.import b/assets/sprites/characters/pink/Pink_Monster_Jump_8.png.import
new file mode 100644
index 0000000..66b61f0
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Jump_8.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://hnw46ye6vusn"
+path="res://.godot/imported/Pink_Monster_Jump_8.png-e77cf80516a5fd71dff26c350d8cdc8a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Jump_8.png"
+dest_files=["res://.godot/imported/Pink_Monster_Jump_8.png-e77cf80516a5fd71dff26c350d8cdc8a.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Push_6.png b/assets/sprites/characters/pink/Pink_Monster_Push_6.png
new file mode 100644
index 0000000..e23fd81
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Push_6.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Push_6.png.import b/assets/sprites/characters/pink/Pink_Monster_Push_6.png.import
new file mode 100644
index 0000000..2861637
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Push_6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dun0f0rkhaew5"
+path="res://.godot/imported/Pink_Monster_Push_6.png-3e4515e253d9f1a42715b4a733fcb27d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Push_6.png"
+dest_files=["res://.godot/imported/Pink_Monster_Push_6.png-3e4515e253d9f1a42715b4a733fcb27d.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Run_6.png b/assets/sprites/characters/pink/Pink_Monster_Run_6.png
new file mode 100644
index 0000000..bc2beda
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Run_6.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Run_6.png.import b/assets/sprites/characters/pink/Pink_Monster_Run_6.png.import
new file mode 100644
index 0000000..79a1501
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Run_6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cx2x6p0dnnm6y"
+path="res://.godot/imported/Pink_Monster_Run_6.png-0fd9d38acb5b631cec6fcb0878023b16.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Run_6.png"
+dest_files=["res://.godot/imported/Pink_Monster_Run_6.png-0fd9d38acb5b631cec6fcb0878023b16.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Throw_4.png b/assets/sprites/characters/pink/Pink_Monster_Throw_4.png
new file mode 100644
index 0000000..b830a81
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Throw_4.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Throw_4.png.import b/assets/sprites/characters/pink/Pink_Monster_Throw_4.png.import
new file mode 100644
index 0000000..0650510
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Throw_4.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://538sc3bsdell"
+path="res://.godot/imported/Pink_Monster_Throw_4.png-153cd77a251b8f2d3f59fdb64c106944.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Throw_4.png"
+dest_files=["res://.godot/imported/Pink_Monster_Throw_4.png-153cd77a251b8f2d3f59fdb64c106944.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png b/assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png
new file mode 100644
index 0000000..39c545a
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png.import b/assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png.import
new file mode 100644
index 0000000..1acfb04
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cyfq0x0h2qeof"
+path="res://.godot/imported/Pink_Monster_Walk+Attack_6.png-78419941eebf906a7eea98871d563b0e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png"
+dest_files=["res://.godot/imported/Pink_Monster_Walk+Attack_6.png-78419941eebf906a7eea98871d563b0e.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
diff --git a/assets/sprites/characters/pink/Pink_Monster_Walk_6.png b/assets/sprites/characters/pink/Pink_Monster_Walk_6.png
new file mode 100644
index 0000000..30b1fc1
Binary files /dev/null and b/assets/sprites/characters/pink/Pink_Monster_Walk_6.png differ
diff --git a/assets/sprites/characters/pink/Pink_Monster_Walk_6.png.import b/assets/sprites/characters/pink/Pink_Monster_Walk_6.png.import
new file mode 100644
index 0000000..350518d
--- /dev/null
+++ b/assets/sprites/characters/pink/Pink_Monster_Walk_6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://efnfh4mf0ia2"
+path="res://.godot/imported/Pink_Monster_Walk_6.png-b7a91566720e04fae01efb6043df67ab.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Pink_Monster_Walk_6.png"
+dest_files=["res://.godot/imported/Pink_Monster_Walk_6.png-b7a91566720e04fae01efb6043df67ab.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
diff --git a/assets/sprites/characters/pink/Rock1.png b/assets/sprites/characters/pink/Rock1.png
new file mode 100644
index 0000000..eed143b
Binary files /dev/null and b/assets/sprites/characters/pink/Rock1.png differ
diff --git a/assets/sprites/objects/objects.png.import b/assets/sprites/characters/pink/Rock1.png.import
similarity index 68%
rename from assets/sprites/objects/objects.png.import
rename to assets/sprites/characters/pink/Rock1.png.import
index 7813e92..6b48308 100644
--- a/assets/sprites/objects/objects.png.import
+++ b/assets/sprites/characters/pink/Rock1.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://dpumvq8hurh2k"
-path="res://.godot/imported/objects.png-d2d4a55345a132e9a8ff3dd2e52d6372.ctex"
+uid="uid://b82eovf1htp4i"
+path="res://.godot/imported/Rock1.png-ba89fc56a2a75848b726a2843d6128b3.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/objects/objects.png"
-dest_files=["res://.godot/imported/objects.png-d2d4a55345a132e9a8ff3dd2e52d6372.ctex"]
+source_file="res://assets/sprites/characters/pink/Rock1.png"
+dest_files=["res://.godot/imported/Rock1.png-ba89fc56a2a75848b726a2843d6128b3.ctex"]
[params]
diff --git a/assets/sprites/characters/pink/Rock2.png b/assets/sprites/characters/pink/Rock2.png
new file mode 100644
index 0000000..0167f1b
Binary files /dev/null and b/assets/sprites/characters/pink/Rock2.png differ
diff --git a/assets/sprites/tilesets/fences.png.import b/assets/sprites/characters/pink/Rock2.png.import
similarity index 68%
rename from assets/sprites/tilesets/fences.png.import
rename to assets/sprites/characters/pink/Rock2.png.import
index 82970dd..61a9982 100644
--- a/assets/sprites/tilesets/fences.png.import
+++ b/assets/sprites/characters/pink/Rock2.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://cb3b4l32gyimg"
-path="res://.godot/imported/fences.png-d23fafb51f1291954243acd82ff27e91.ctex"
+uid="uid://bqqp0fx48hk0f"
+path="res://.godot/imported/Rock2.png-1bc44a91d6435cee0fa76a8be96f43d0.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/tilesets/fences.png"
-dest_files=["res://.godot/imported/fences.png-d23fafb51f1291954243acd82ff27e91.ctex"]
+source_file="res://assets/sprites/characters/pink/Rock2.png"
+dest_files=["res://.godot/imported/Rock2.png-1bc44a91d6435cee0fa76a8be96f43d0.ctex"]
[params]
diff --git a/assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png b/assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png
new file mode 100644
index 0000000..3da6cc5
Binary files /dev/null and b/assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png differ
diff --git a/assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png.import b/assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png.import
new file mode 100644
index 0000000..174800b
--- /dev/null
+++ b/assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ciufookwmay82"
+path="res://.godot/imported/Walk_Run_Push_Dust_6.png-e7eb7b875e19c92ffb1d526d1f3eeb65.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/characters/pink/Walk_Run_Push_Dust_6.png"
+dest_files=["res://.godot/imported/Walk_Run_Push_Dust_6.png-e7eb7b875e19c92ffb1d526d1f3eeb65.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
diff --git a/assets/sprites/characters/player.png b/assets/sprites/characters/player.png
deleted file mode 100644
index 2a9ac86..0000000
Binary files a/assets/sprites/characters/player.png and /dev/null differ
diff --git a/assets/sprites/characters/skeleton.png b/assets/sprites/characters/skeleton.png
deleted file mode 100644
index 14a64b4..0000000
Binary files a/assets/sprites/characters/skeleton.png and /dev/null differ
diff --git a/assets/sprites/characters/skeleton_swordless.png b/assets/sprites/characters/skeleton_swordless.png
deleted file mode 100644
index db7640e..0000000
Binary files a/assets/sprites/characters/skeleton_swordless.png and /dev/null differ
diff --git a/assets/sprites/characters/slime.png b/assets/sprites/characters/slime.png
deleted file mode 100644
index 5de1aed..0000000
Binary files a/assets/sprites/characters/slime.png and /dev/null differ
diff --git a/assets/sprites/objects/chest_01.png b/assets/sprites/objects/chest_01.png
deleted file mode 100644
index 5f9051e..0000000
Binary files a/assets/sprites/objects/chest_01.png and /dev/null differ
diff --git a/assets/sprites/objects/chest_02.png b/assets/sprites/objects/chest_02.png
deleted file mode 100644
index a98db11..0000000
Binary files a/assets/sprites/objects/chest_02.png and /dev/null differ
diff --git a/assets/sprites/objects/chest_02.png.import b/assets/sprites/objects/chest_02.png.import
deleted file mode 100644
index 37f1c29..0000000
--- a/assets/sprites/objects/chest_02.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://b3h3f0j4ft3xe"
-path="res://.godot/imported/chest_02.png-c36076d515c87cecb82a5a31dca4ff5f.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/chest_02.png"
-dest_files=["res://.godot/imported/chest_02.png-c36076d515c87cecb82a5a31dca4ff5f.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
diff --git a/assets/sprites/objects/objects.png b/assets/sprites/objects/objects.png
deleted file mode 100644
index cb230a3..0000000
Binary files a/assets/sprites/objects/objects.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_01-sheet.png b/assets/sprites/objects/rock_in_water_01-sheet.png
deleted file mode 100644
index abda63a..0000000
Binary files a/assets/sprites/objects/rock_in_water_01-sheet.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_01-sheet.png.import b/assets/sprites/objects/rock_in_water_01-sheet.png.import
deleted file mode 100644
index 2aed9ea..0000000
--- a/assets/sprites/objects/rock_in_water_01-sheet.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://bemqlej7o4t4n"
-path="res://.godot/imported/rock_in_water_01-sheet.png-3620ca531764c61ef4f85db7254bee8b.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_01-sheet.png"
-dest_files=["res://.godot/imported/rock_in_water_01-sheet.png-3620ca531764c61ef4f85db7254bee8b.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
diff --git a/assets/sprites/objects/rock_in_water_01.png b/assets/sprites/objects/rock_in_water_01.png
deleted file mode 100644
index 84f3985..0000000
Binary files a/assets/sprites/objects/rock_in_water_01.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_01.png.import b/assets/sprites/objects/rock_in_water_01.png.import
deleted file mode 100644
index 007c094..0000000
--- a/assets/sprites/objects/rock_in_water_01.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://b3ig0r6ifmc7k"
-path="res://.godot/imported/rock_in_water_01.png-fb2518225a198de2e9e0673a500511a8.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_01.png"
-dest_files=["res://.godot/imported/rock_in_water_01.png-fb2518225a198de2e9e0673a500511a8.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
diff --git a/assets/sprites/objects/rock_in_water_02.png b/assets/sprites/objects/rock_in_water_02.png
deleted file mode 100644
index 277cb6e..0000000
Binary files a/assets/sprites/objects/rock_in_water_02.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_02.png.import b/assets/sprites/objects/rock_in_water_02.png.import
deleted file mode 100644
index cb006de..0000000
--- a/assets/sprites/objects/rock_in_water_02.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://b04ancuu7mg5o"
-path="res://.godot/imported/rock_in_water_02.png-a8f3bbce00b5dd46c3a26feb452894e8.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_02.png"
-dest_files=["res://.godot/imported/rock_in_water_02.png-a8f3bbce00b5dd46c3a26feb452894e8.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
diff --git a/assets/sprites/objects/rock_in_water_03.png b/assets/sprites/objects/rock_in_water_03.png
deleted file mode 100644
index 7418d13..0000000
Binary files a/assets/sprites/objects/rock_in_water_03.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_03.png.import b/assets/sprites/objects/rock_in_water_03.png.import
deleted file mode 100644
index 98baa89..0000000
--- a/assets/sprites/objects/rock_in_water_03.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://dfljec7o2o5vu"
-path="res://.godot/imported/rock_in_water_03.png-6be3f6f42c0da7cd9a013bf18a1aaf83.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_03.png"
-dest_files=["res://.godot/imported/rock_in_water_03.png-6be3f6f42c0da7cd9a013bf18a1aaf83.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
diff --git a/assets/sprites/objects/rock_in_water_04.png b/assets/sprites/objects/rock_in_water_04.png
deleted file mode 100644
index 36116a7..0000000
Binary files a/assets/sprites/objects/rock_in_water_04.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_04.png.import b/assets/sprites/objects/rock_in_water_04.png.import
deleted file mode 100644
index c922398..0000000
--- a/assets/sprites/objects/rock_in_water_04.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://dbn6gtau8l7mt"
-path="res://.godot/imported/rock_in_water_04.png-56d7d053f05d3af179eabd417470e2d9.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_04.png"
-dest_files=["res://.godot/imported/rock_in_water_04.png-56d7d053f05d3af179eabd417470e2d9.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
diff --git a/assets/sprites/objects/rock_in_water_05.png b/assets/sprites/objects/rock_in_water_05.png
deleted file mode 100644
index 5ce6c88..0000000
Binary files a/assets/sprites/objects/rock_in_water_05.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_05.png.import b/assets/sprites/objects/rock_in_water_05.png.import
deleted file mode 100644
index 4447159..0000000
--- a/assets/sprites/objects/rock_in_water_05.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://bbx4frs3mlf16"
-path="res://.godot/imported/rock_in_water_05.png-e977fe5c63a94bd2a4ca5a972ee7d985.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_05.png"
-dest_files=["res://.godot/imported/rock_in_water_05.png-e977fe5c63a94bd2a4ca5a972ee7d985.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
diff --git a/assets/sprites/objects/rock_in_water_06.png b/assets/sprites/objects/rock_in_water_06.png
deleted file mode 100644
index 277cb6e..0000000
Binary files a/assets/sprites/objects/rock_in_water_06.png and /dev/null differ
diff --git a/assets/sprites/objects/rock_in_water_06.png.import b/assets/sprites/objects/rock_in_water_06.png.import
deleted file mode 100644
index 81d0138..0000000
--- a/assets/sprites/objects/rock_in_water_06.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://bb110k2gvul25"
-path="res://.godot/imported/rock_in_water_06.png-9b93aa1d4044f6968b94b1cd78d9a1ea.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/objects/rock_in_water_06.png"
-dest_files=["res://.godot/imported/rock_in_water_06.png-9b93aa1d4044f6968b94b1cd78d9a1ea.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
diff --git a/assets/sprites/tilesets/Sprites/foreground_trees.png b/assets/sprites/tilesets/Sprites/foreground_trees.png
new file mode 100644
index 0000000..0f1ab87
Binary files /dev/null and b/assets/sprites/tilesets/Sprites/foreground_trees.png differ
diff --git a/assets/sprites/characters/skeleton_swordless.png.import b/assets/sprites/tilesets/Sprites/foreground_trees.png.import
similarity index 65%
rename from assets/sprites/characters/skeleton_swordless.png.import
rename to assets/sprites/tilesets/Sprites/foreground_trees.png.import
index 66321ee..f797321 100644
--- a/assets/sprites/characters/skeleton_swordless.png.import
+++ b/assets/sprites/tilesets/Sprites/foreground_trees.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://dd4x5oeym48l5"
-path="res://.godot/imported/skeleton_swordless.png-95add0301a56da4e5c440a853795f167.ctex"
+uid="uid://bjxmo55titd4x"
+path="res://.godot/imported/foreground_trees.png-82ac9c507bbc53c68ff4d089c72143af.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/characters/skeleton_swordless.png"
-dest_files=["res://.godot/imported/skeleton_swordless.png-95add0301a56da4e5c440a853795f167.ctex"]
+source_file="res://assets/sprites/tilesets/Sprites/foreground_trees.png"
+dest_files=["res://.godot/imported/foreground_trees.png-82ac9c507bbc53c68ff4d089c72143af.ctex"]
[params]
diff --git a/assets/sprites/tilesets/Sprites/hexedforest_props.png b/assets/sprites/tilesets/Sprites/hexedforest_props.png
new file mode 100644
index 0000000..b18baaf
Binary files /dev/null and b/assets/sprites/tilesets/Sprites/hexedforest_props.png differ
diff --git a/assets/sprites/characters/slime.png.import b/assets/sprites/tilesets/Sprites/hexedforest_props.png.import
similarity index 65%
rename from assets/sprites/characters/slime.png.import
rename to assets/sprites/tilesets/Sprites/hexedforest_props.png.import
index ef9b820..3e0b4b5 100644
--- a/assets/sprites/characters/slime.png.import
+++ b/assets/sprites/tilesets/Sprites/hexedforest_props.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://cs7pbhq71yuvy"
-path="res://.godot/imported/slime.png-a3f8ad08e9951dcfc89db77a5b728bd5.ctex"
+uid="uid://wxpdjbrlnij"
+path="res://.godot/imported/hexedforest_props.png-97a9c3125f2edce3161c01f017a2c509.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/characters/slime.png"
-dest_files=["res://.godot/imported/slime.png-a3f8ad08e9951dcfc89db77a5b728bd5.ctex"]
+source_file="res://assets/sprites/tilesets/Sprites/hexedforest_props.png"
+dest_files=["res://.godot/imported/hexedforest_props.png-97a9c3125f2edce3161c01f017a2c509.ctex"]
[params]
diff --git a/assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png b/assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png
new file mode 100644
index 0000000..4b56df0
Binary files /dev/null and b/assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png differ
diff --git a/assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png.import b/assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png.import
new file mode 100644
index 0000000..0326091
--- /dev/null
+++ b/assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bdmvs4fivkkwl"
+path="res://.godot/imported/hexedforest_props_noshadow.png-93e5ecb03facac489293d9b79a185008.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/tilesets/Sprites/hexedforest_props_noshadow.png"
+dest_files=["res://.godot/imported/hexedforest_props_noshadow.png-93e5ecb03facac489293d9b79a185008.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
diff --git a/assets/sprites/tilesets/Sprites/hexedforest_tileset.png b/assets/sprites/tilesets/Sprites/hexedforest_tileset.png
new file mode 100644
index 0000000..1ed3603
Binary files /dev/null and b/assets/sprites/tilesets/Sprites/hexedforest_tileset.png differ
diff --git a/assets/sprites/tilesets/Sprites/hexedforest_tileset.png.import b/assets/sprites/tilesets/Sprites/hexedforest_tileset.png.import
new file mode 100644
index 0000000..9c11def
--- /dev/null
+++ b/assets/sprites/tilesets/Sprites/hexedforest_tileset.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://buwkb3nded8w4"
+path="res://.godot/imported/hexedforest_tileset.png-2a6865e3ab8b13d5236db845b1ed8df9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/sprites/tilesets/Sprites/hexedforest_tileset.png"
+dest_files=["res://.godot/imported/hexedforest_tileset.png-2a6865e3ab8b13d5236db845b1ed8df9.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
diff --git a/assets/sprites/tilesets/decor_16x16.png b/assets/sprites/tilesets/decor_16x16.png
deleted file mode 100644
index 692df2b..0000000
Binary files a/assets/sprites/tilesets/decor_16x16.png and /dev/null differ
diff --git a/assets/sprites/tilesets/decor_16x16.png.import b/assets/sprites/tilesets/decor_16x16.png.import
deleted file mode 100644
index b3f2935..0000000
--- a/assets/sprites/tilesets/decor_16x16.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://fjqqc0lba16n"
-path="res://.godot/imported/decor_16x16.png-a6671d8eaa9d39a3ab3991c9b40c2479.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/decor_16x16.png"
-dest_files=["res://.godot/imported/decor_16x16.png-a6671d8eaa9d39a3ab3991c9b40c2479.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
diff --git a/assets/sprites/tilesets/decor_8x8.png b/assets/sprites/tilesets/decor_8x8.png
deleted file mode 100644
index b3e9d1e..0000000
Binary files a/assets/sprites/tilesets/decor_8x8.png and /dev/null differ
diff --git a/assets/sprites/tilesets/decor_8x8.png.import b/assets/sprites/tilesets/decor_8x8.png.import
deleted file mode 100644
index c8f8f2e..0000000
--- a/assets/sprites/tilesets/decor_8x8.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://i0w5e3cn4n46"
-path="res://.godot/imported/decor_8x8.png-4934eeb97f0762ec0d99dd4564802fea.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/decor_8x8.png"
-dest_files=["res://.godot/imported/decor_8x8.png-4934eeb97f0762ec0d99dd4564802fea.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
diff --git a/assets/sprites/tilesets/fences.png b/assets/sprites/tilesets/fences.png
deleted file mode 100644
index 808ec4d..0000000
Binary files a/assets/sprites/tilesets/fences.png and /dev/null differ
diff --git a/assets/sprites/tilesets/floors/carpet.png b/assets/sprites/tilesets/floors/carpet.png
deleted file mode 100644
index 2e97e4d..0000000
Binary files a/assets/sprites/tilesets/floors/carpet.png and /dev/null differ
diff --git a/assets/sprites/tilesets/floors/carpet.png.import b/assets/sprites/tilesets/floors/carpet.png.import
deleted file mode 100644
index e64ec02..0000000
--- a/assets/sprites/tilesets/floors/carpet.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://f2k4x0c17w1g"
-path="res://.godot/imported/carpet.png-e8b23c3dd0dfa76142c349d59f207122.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/floors/carpet.png"
-dest_files=["res://.godot/imported/carpet.png-e8b23c3dd0dfa76142c349d59f207122.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
diff --git a/assets/sprites/tilesets/floors/flooring.png b/assets/sprites/tilesets/floors/flooring.png
deleted file mode 100644
index 74a61bd..0000000
Binary files a/assets/sprites/tilesets/floors/flooring.png and /dev/null differ
diff --git a/assets/sprites/tilesets/floors/flooring.png.import b/assets/sprites/tilesets/floors/flooring.png.import
deleted file mode 100644
index c9659fb..0000000
--- a/assets/sprites/tilesets/floors/flooring.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://b51a0hwypuu47"
-path="res://.godot/imported/flooring.png-26145fa990ec148278aa695adca94be5.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/floors/flooring.png"
-dest_files=["res://.godot/imported/flooring.png-26145fa990ec148278aa695adca94be5.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
diff --git a/assets/sprites/tilesets/floors/wooden.png b/assets/sprites/tilesets/floors/wooden.png
deleted file mode 100644
index 43de340..0000000
Binary files a/assets/sprites/tilesets/floors/wooden.png and /dev/null differ
diff --git a/assets/sprites/tilesets/floors/wooden.png.import b/assets/sprites/tilesets/floors/wooden.png.import
deleted file mode 100644
index 805765d..0000000
--- a/assets/sprites/tilesets/floors/wooden.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://7cdq70veqnye"
-path="res://.godot/imported/wooden.png-de6adc573ce9cb65f7ce8eea97dce428.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/floors/wooden.png"
-dest_files=["res://.godot/imported/wooden.png-de6adc573ce9cb65f7ce8eea97dce428.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
diff --git a/assets/sprites/tilesets/forest.tres b/assets/sprites/tilesets/forest.tres
new file mode 100644
index 0000000..09ff695
--- /dev/null
+++ b/assets/sprites/tilesets/forest.tres
@@ -0,0 +1,1141 @@
+[gd_resource type="TileSet" load_steps=5 format=3 uid="uid://c66l102pgntht"]
+
+[ext_resource type="Texture2D" uid="uid://buwkb3nded8w4" path="res://assets/sprites/tilesets/Sprites/hexedforest_tileset.png" id="1_t7y2e"]
+[ext_resource type="Texture2D" uid="uid://wxpdjbrlnij" path="res://assets/sprites/tilesets/Sprites/hexedforest_props.png" id="2_f3rn6"]
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_kgifk"]
+texture = ExtResource("1_t7y2e")
+texture_region_size = Vector2i(32, 32)
+0:0/0 = 0
+1:0/0 = 0
+2:0/0 = 0
+3:0/0 = 0
+4:0/0 = 0
+5:0/0 = 0
+6:0/0 = 0
+7:0/0 = 0
+8:0/0 = 0
+9:0/0 = 0
+10:0/0 = 0
+10:0/0/terrain_set = 0
+10:0/0/terrain = 1
+10:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-2.48804, -2.79904, 16, -8.39713, 16, 16, -1.24402, 16)
+10:0/0/terrains_peering_bit/right_side = 1
+10:0/0/terrains_peering_bit/bottom_right_corner = 1
+10:0/0/terrains_peering_bit/bottom_side = 1
+11:0/0 = 0
+11:0/0/terrain_set = 0
+11:0/0/terrain = 1
+11:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -8.39713, 16, -11.5072, 16, 16, -16, 16)
+11:0/0/terrains_peering_bit/right_side = 1
+11:0/0/terrains_peering_bit/bottom_right_corner = 1
+11:0/0/terrains_peering_bit/bottom_side = 1
+11:0/0/terrains_peering_bit/bottom_left_corner = 1
+11:0/0/terrains_peering_bit/left_side = 1
+12:0/0 = 0
+12:0/0/terrain_set = 0
+12:0/0/terrain = 1
+12:0/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -9.33014, -0.311005, -2.79904, 4.97608, 16, -16, 16)
+12:0/0/terrains_peering_bit/bottom_side = 1
+12:0/0/terrains_peering_bit/bottom_left_corner = 1
+12:0/0/terrains_peering_bit/left_side = 1
+13:0/0 = 0
+13:0/0/terrain_set = 0
+13:0/0/terrain = 1
+13:0/0/terrains_peering_bit/right_side = 1
+13:0/0/terrains_peering_bit/bottom_right_corner = 1
+13:0/0/terrains_peering_bit/bottom_side = 1
+13:0/0/terrains_peering_bit/bottom_left_corner = 1
+13:0/0/terrains_peering_bit/left_side = 1
+13:0/0/terrains_peering_bit/top_side = 1
+13:0/0/terrains_peering_bit/top_right_corner = 1
+14:0/0 = 0
+14:0/0/terrain_set = 0
+14:0/0/terrain = 1
+14:0/0/terrains_peering_bit/right_side = 1
+14:0/0/terrains_peering_bit/bottom_right_corner = 1
+14:0/0/terrains_peering_bit/bottom_side = 1
+14:0/0/terrains_peering_bit/bottom_left_corner = 1
+14:0/0/terrains_peering_bit/left_side = 1
+14:0/0/terrains_peering_bit/top_left_corner = 1
+14:0/0/terrains_peering_bit/top_side = 1
+15:0/0 = 0
+19:0/0 = 0
+20:0/0 = 0
+21:0/0 = 0
+22:0/0 = 0
+23:0/0 = 0
+24:0/0 = 0
+25:0/0 = 0
+0:1/0 = 0
+1:1/0 = 0
+2:1/0 = 0
+3:1/0 = 0
+4:1/0 = 0
+5:1/0 = 0
+6:1/0 = 0
+7:1/0 = 0
+8:1/0 = 0
+9:1/0 = 0
+10:1/0 = 0
+10:1/0/terrain_set = 0
+10:1/0/terrain = 1
+10:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-0.311005, -16, 16, -16, 16, 16, -2.17703, 16)
+10:1/0/terrains_peering_bit/right_side = 1
+10:1/0/terrains_peering_bit/bottom_right_corner = 1
+10:1/0/terrains_peering_bit/bottom_side = 1
+10:1/0/terrains_peering_bit/top_side = 1
+10:1/0/terrains_peering_bit/top_right_corner = 1
+11:1/0 = 0
+11:1/0/terrain_set = 0
+11:1/0/terrain = 1
+11:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 16, -16, 16)
+11:1/0/terrains_peering_bit/right_side = 1
+11:1/0/terrains_peering_bit/bottom_right_corner = 1
+11:1/0/terrains_peering_bit/bottom_side = 1
+11:1/0/terrains_peering_bit/bottom_left_corner = 1
+11:1/0/terrains_peering_bit/left_side = 1
+11:1/0/terrains_peering_bit/top_left_corner = 1
+11:1/0/terrains_peering_bit/top_side = 1
+11:1/0/terrains_peering_bit/top_right_corner = 1
+12:1/0 = 0
+12:1/0/terrain_set = 0
+12:1/0/terrain = 1
+12:1/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 5.59809, -16, 6.2201, 16, -16, 16)
+12:1/0/terrains_peering_bit/bottom_side = 1
+12:1/0/terrains_peering_bit/bottom_left_corner = 1
+12:1/0/terrains_peering_bit/left_side = 1
+12:1/0/terrains_peering_bit/top_left_corner = 1
+12:1/0/terrains_peering_bit/top_side = 1
+13:1/0 = 0
+13:1/0/terrain_set = 0
+13:1/0/terrain = 1
+13:1/0/terrains_peering_bit/right_side = 1
+13:1/0/terrains_peering_bit/bottom_right_corner = 1
+13:1/0/terrains_peering_bit/bottom_side = 1
+13:1/0/terrains_peering_bit/left_side = 1
+13:1/0/terrains_peering_bit/top_left_corner = 1
+13:1/0/terrains_peering_bit/top_side = 1
+13:1/0/terrains_peering_bit/top_right_corner = 1
+14:1/0 = 0
+14:1/0/terrain_set = 0
+14:1/0/terrain = 1
+14:1/0/terrains_peering_bit/right_side = 1
+14:1/0/terrains_peering_bit/bottom_side = 1
+14:1/0/terrains_peering_bit/bottom_left_corner = 1
+14:1/0/terrains_peering_bit/left_side = 1
+14:1/0/terrains_peering_bit/top_left_corner = 1
+14:1/0/terrains_peering_bit/top_side = 1
+14:1/0/terrains_peering_bit/top_right_corner = 1
+15:1/0 = 0
+16:1/0 = 0
+18:1/0 = 0
+19:1/0 = 0
+20:1/0 = 0
+21:1/0 = 0
+22:1/0 = 0
+23:1/0 = 0
+24:1/0 = 0
+25:1/0 = 0
+0:2/0 = 0
+1:2/0 = 0
+2:2/0 = 0
+3:2/0 = 0
+3:2/0/terrain_set = 0
+3:2/0/terrain = 0
+3:2/0/terrains_peering_bit/right_side = 0
+3:2/0/terrains_peering_bit/bottom_right_corner = 0
+3:2/0/terrains_peering_bit/bottom_side = 0
+3:2/0/terrains_peering_bit/bottom_left_corner = 0
+3:2/0/terrains_peering_bit/left_side = 0
+3:2/0/terrains_peering_bit/top_left_corner = 0
+3:2/0/terrains_peering_bit/top_side = 0
+3:2/0/terrains_peering_bit/top_right_corner = 0
+5:2/0 = 0
+6:2/0 = 0
+7:2/0 = 0
+10:2/0 = 0
+10:2/0/terrain_set = 0
+10:2/0/terrain = 1
+10:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-2.17703, -16, 16, -16, 16, 4.97608, 2.48804, -2.17703)
+10:2/0/terrains_peering_bit/right_side = 1
+10:2/0/terrains_peering_bit/top_side = 1
+10:2/0/terrains_peering_bit/top_right_corner = 1
+11:2/0 = 0
+11:2/0/terrain_set = 0
+11:2/0/terrain = 1
+11:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, 7.15311, -16, 4.97608)
+11:2/0/terrains_peering_bit/right_side = 1
+11:2/0/terrains_peering_bit/left_side = 1
+11:2/0/terrains_peering_bit/top_left_corner = 1
+11:2/0/terrains_peering_bit/top_side = 1
+11:2/0/terrains_peering_bit/top_right_corner = 1
+12:2/0 = 0
+12:2/0/terrain_set = 0
+12:2/0/terrain = 1
+12:2/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 6.84211, -16, 2.48804, 3.42105, -16, 6.2201)
+12:2/0/terrains_peering_bit/left_side = 1
+12:2/0/terrains_peering_bit/top_left_corner = 1
+12:2/0/terrains_peering_bit/top_side = 1
+13:2/0 = 0
+13:2/0/terrain_set = 0
+14:2/0 = 0
+14:2/0/terrain_set = 0
+15:2/0 = 0
+16:2/0 = 0
+17:2/0 = 0
+18:2/0 = 0
+19:2/0 = 0
+20:2/0 = 0
+21:2/0 = 0
+22:2/0 = 0
+23:2/0 = 0
+24:2/0 = 0
+25:2/0 = 0
+
+[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_lcnnk"]
+texture = ExtResource("2_f3rn6")
+texture_region_size = Vector2i(32, 32)
+1:0/0 = 0
+1:0/0/z_index = 10
+2:0/0 = 0
+2:0/0/z_index = 10
+3:0/0 = 0
+3:0/0/z_index = 10
+4:0/0 = 0
+4:0/0/z_index = 10
+7:0/0 = 0
+7:0/0/z_index = 10
+8:0/0 = 0
+8:0/0/z_index = 10
+9:0/0 = 0
+9:0/0/z_index = 10
+0:1/0 = 0
+0:1/0/z_index = 10
+1:1/0 = 0
+1:1/0/z_index = 10
+2:1/0 = 0
+2:1/0/z_index = 10
+3:1/0 = 0
+3:1/0/z_index = 10
+4:1/0 = 0
+4:1/0/z_index = 10
+5:1/0 = 0
+5:1/0/z_index = 10
+6:1/0 = 0
+6:1/0/z_index = 10
+7:1/0 = 0
+7:1/0/z_index = 10
+8:1/0 = 0
+8:1/0/z_index = 10
+9:1/0 = 0
+9:1/0/z_index = 10
+10:1/0 = 0
+10:1/0/z_index = 10
+12:1/0 = 0
+12:1/0/z_index = 10
+13:1/0 = 0
+13:1/0/z_index = 10
+14:1/0 = 0
+14:1/0/z_index = 10
+16:1/0 = 0
+17:1/0 = 0
+18:1/0 = 0
+19:1/0 = 0
+20:1/0 = 0
+21:1/0 = 0
+22:1/0 = 0
+23:1/0 = 0
+24:1/0 = 0
+26:1/0 = 0
+27:1/0 = 0
+28:1/0 = 0
+29:1/0 = 0
+30:1/0 = 0
+31:1/0 = 0
+0:2/0 = 0
+0:2/0/z_index = 10
+1:2/0 = 0
+1:2/0/z_index = 10
+2:2/0 = 0
+2:2/0/z_index = 10
+3:2/0 = 0
+3:2/0/z_index = 10
+4:2/0 = 0
+4:2/0/z_index = 10
+5:2/0 = 0
+5:2/0/z_index = 10
+6:2/0 = 0
+6:2/0/z_index = 10
+7:2/0 = 0
+7:2/0/z_index = 10
+8:2/0 = 0
+8:2/0/z_index = 10
+9:2/0 = 0
+9:2/0/z_index = 10
+10:2/0 = 0
+10:2/0/z_index = 10
+11:2/0 = 0
+11:2/0/z_index = 10
+12:2/0 = 0
+12:2/0/z_index = 10
+13:2/0 = 0
+13:2/0/z_index = 10
+14:2/0 = 0
+14:2/0/z_index = 10
+15:2/0 = 0
+15:2/0/z_index = 10
+16:2/0 = 0
+17:2/0 = 0
+20:2/0 = 0
+21:2/0 = 0
+22:2/0 = 0
+23:2/0 = 0
+24:2/0 = 0
+25:2/0 = 0
+26:2/0 = 0
+27:2/0 = 0
+28:2/0 = 0
+29:2/0 = 0
+30:2/0 = 0
+31:2/0 = 0
+0:3/0 = 0
+0:3/0/z_index = 10
+1:3/0 = 0
+1:3/0/z_index = 10
+2:3/0 = 0
+2:3/0/z_index = 10
+3:3/0 = 0
+3:3/0/z_index = 10
+4:3/0 = 0
+4:3/0/z_index = 10
+5:3/0 = 0
+5:3/0/z_index = 10
+6:3/0 = 0
+6:3/0/z_index = 10
+7:3/0 = 0
+7:3/0/z_index = 10
+8:3/0 = 0
+8:3/0/z_index = 10
+9:3/0 = 0
+9:3/0/z_index = 10
+10:3/0 = 0
+10:3/0/z_index = 10
+11:3/0 = 0
+11:3/0/z_index = 10
+12:3/0 = 0
+12:3/0/z_index = 10
+13:3/0 = 0
+13:3/0/z_index = 10
+14:3/0 = 0
+14:3/0/z_index = 10
+15:3/0 = 0
+15:3/0/z_index = 10
+16:3/0 = 0
+17:3/0 = 0
+18:3/0 = 0
+20:3/0 = 0
+21:3/0 = 0
+22:3/0 = 0
+23:3/0 = 0
+24:3/0 = 0
+25:3/0 = 0
+26:3/0 = 0
+27:3/0 = 0
+28:3/0 = 0
+29:3/0 = 0
+30:3/0 = 0
+31:3/0 = 0
+0:4/0 = 0
+0:4/0/z_index = 10
+1:4/0 = 0
+1:4/0/z_index = 10
+2:4/0 = 0
+2:4/0/z_index = 10
+3:4/0 = 0
+3:4/0/z_index = 10
+4:4/0 = 0
+4:4/0/z_index = 10
+5:4/0 = 0
+5:4/0/z_index = 10
+6:4/0 = 0
+6:4/0/z_index = 10
+7:4/0 = 0
+7:4/0/z_index = 10
+8:4/0 = 0
+8:4/0/z_index = 10
+9:4/0 = 0
+9:4/0/z_index = 10
+10:4/0 = 0
+10:4/0/z_index = 10
+11:4/0 = 0
+11:4/0/z_index = 10
+12:4/0 = 0
+12:4/0/z_index = 10
+13:4/0 = 0
+13:4/0/z_index = 10
+14:4/0 = 0
+14:4/0/z_index = 10
+15:4/0 = 0
+15:4/0/z_index = 10
+16:4/0 = 0
+17:4/0 = 0
+18:4/0 = 0
+19:4/0 = 0
+20:4/0 = 0
+21:4/0 = 0
+22:4/0 = 0
+23:4/0 = 0
+24:4/0 = 0
+25:4/0 = 0
+26:4/0 = 0
+27:4/0 = 0
+28:4/0 = 0
+29:4/0 = 0
+30:4/0 = 0
+31:4/0 = 0
+2:5/0 = 0
+2:5/0/z_index = 10
+3:5/0 = 0
+3:5/0/z_index = 10
+4:5/0 = 0
+4:5/0/z_index = 10
+7:5/0 = 0
+7:5/0/z_index = 10
+8:5/0 = 0
+8:5/0/z_index = 10
+9:5/0 = 0
+9:5/0/z_index = 10
+12:5/0 = 0
+12:5/0/z_index = 10
+13:5/0 = 0
+13:5/0/z_index = 10
+14:5/0 = 0
+14:5/0/z_index = 10
+20:5/0 = 0
+21:5/0 = 0
+22:5/0 = 0
+23:5/0 = 0
+26:5/0 = 0
+27:5/0 = 0
+28:5/0 = 0
+29:5/0 = 0
+30:5/0 = 0
+31:5/0 = 0
+2:6/0 = 0
+2:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-8.08612, -16, 16, -16, 16, 16, -16, 16)
+3:6/0 = 0
+3:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 9.95216, -16, 16, -2.17703, 16, 16, -16, 16)
+4:6/0 = 0
+4:6/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, -9.95215, -16, -4.82057, 13.3732, -16, 16)
+7:6/0 = 0
+8:6/0 = 0
+9:6/0 = 0
+13:6/0 = 0
+14:6/0 = 0
+16:6/0 = 0
+17:6/0 = 0
+21:6/0 = 0
+26:6/0 = 0
+27:6/0 = 0
+28:6/0 = 0
+29:6/0 = 0
+30:6/0 = 0
+31:6/0 = 0
+2:7/0 = 0
+2:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, -5.59809, -8.39713, -7.77512)
+3:7/0 = 0
+3:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, 16, -16, 16, -0.622009, -16, -3.11005)
+4:7/0 = 0
+4:7/0/physics_layer_0/polygon_0/points = PackedVector2Array(-16, -16, -6.06459, -16, -6.6866, -6.2201, -16, -4.04306)
+7:7/0 = 0
+8:7/0 = 0
+9:7/0 = 0
+13:7/0 = 0
+14:7/0 = 0
+16:7/0 = 0
+17:7/0 = 0
+18:7/0 = 0
+21:7/0 = 0
+22:7/0 = 0
+23:7/0 = 0
+26:7/0 = 0
+27:7/0 = 0
+28:7/0 = 0
+29:7/0 = 0
+30:7/0 = 0
+31:7/0 = 0
+1:8/0 = 0
+1:8/0/z_index = 10
+2:8/0 = 0
+2:8/0/z_index = 10
+3:8/0 = 0
+3:8/0/z_index = 10
+4:8/0 = 0
+4:8/0/z_index = 10
+7:8/0 = 0
+7:8/0/z_index = 10
+8:8/0 = 0
+8:8/0/z_index = 10
+9:8/0 = 0
+9:8/0/z_index = 10
+16:8/0 = 0
+17:8/0 = 0
+18:8/0 = 0
+19:8/0 = 0
+21:8/0 = 0
+22:8/0 = 0
+23:8/0 = 0
+24:8/0 = 0
+26:8/0 = 0
+27:8/0 = 0
+28:8/0 = 0
+29:8/0 = 0
+30:8/0 = 0
+31:8/0 = 0
+0:9/0 = 0
+0:9/0/z_index = 10
+1:9/0 = 0
+1:9/0/z_index = 10
+2:9/0 = 0
+2:9/0/z_index = 10
+3:9/0 = 0
+3:9/0/z_index = 10
+4:9/0 = 0
+4:9/0/z_index = 10
+5:9/0 = 0
+5:9/0/z_index = 10
+6:9/0 = 0
+6:9/0/z_index = 10
+7:9/0 = 0
+7:9/0/z_index = 10
+8:9/0 = 0
+8:9/0/z_index = 10
+9:9/0 = 0
+9:9/0/z_index = 10
+10:9/0 = 0
+10:9/0/z_index = 10
+12:9/0 = 0
+12:9/0/z_index = 10
+13:9/0 = 0
+13:9/0/z_index = 10
+14:9/0 = 0
+14:9/0/z_index = 10
+16:9/0 = 0
+17:9/0 = 0
+21:9/0 = 0
+26:9/0 = 0
+27:9/0 = 0
+28:9/0 = 0
+29:9/0 = 0
+30:9/0 = 0
+31:9/0 = 0
+0:10/0 = 0
+0:10/0/z_index = 10
+1:10/0 = 0
+1:10/0/z_index = 10
+2:10/0 = 0
+2:10/0/z_index = 10
+3:10/0 = 0
+3:10/0/z_index = 10
+4:10/0 = 0
+4:10/0/z_index = 10
+5:10/0 = 0
+5:10/0/z_index = 10
+6:10/0 = 0
+6:10/0/z_index = 10
+7:10/0 = 0
+7:10/0/z_index = 10
+8:10/0 = 0
+8:10/0/z_index = 10
+9:10/0 = 0
+9:10/0/z_index = 10
+10:10/0 = 0
+10:10/0/z_index = 10
+11:10/0 = 0
+11:10/0/z_index = 10
+12:10/0 = 0
+12:10/0/z_index = 10
+13:10/0 = 0
+13:10/0/z_index = 10
+14:10/0 = 0
+14:10/0/z_index = 10
+15:10/0 = 0
+15:10/0/z_index = 10
+16:10/0 = 0
+17:10/0 = 0
+18:10/0 = 0
+21:10/0 = 0
+22:10/0 = 0
+23:10/0 = 0
+26:10/0 = 0
+27:10/0 = 0
+28:10/0 = 0
+29:10/0 = 0
+30:10/0 = 0
+31:10/0 = 0
+0:11/0 = 0
+0:11/0/z_index = 10
+1:11/0 = 0
+1:11/0/z_index = 10
+2:11/0 = 0
+2:11/0/z_index = 10
+3:11/0 = 0
+3:11/0/z_index = 10
+4:11/0 = 0
+4:11/0/z_index = 10
+5:11/0 = 0
+5:11/0/z_index = 10
+6:11/0 = 0
+6:11/0/z_index = 10
+7:11/0 = 0
+7:11/0/z_index = 10
+8:11/0 = 0
+8:11/0/z_index = 10
+9:11/0 = 0
+9:11/0/z_index = 10
+10:11/0 = 0
+10:11/0/z_index = 10
+11:11/0 = 0
+11:11/0/z_index = 10
+12:11/0 = 0
+12:11/0/z_index = 10
+13:11/0 = 0
+13:11/0/z_index = 10
+14:11/0 = 0
+14:11/0/z_index = 10
+15:11/0 = 0
+15:11/0/z_index = 10
+16:11/0 = 0
+17:11/0 = 0
+18:11/0 = 0
+19:11/0 = 0
+21:11/0 = 0
+22:11/0 = 0
+23:11/0 = 0
+24:11/0 = 0
+26:11/0 = 0
+27:11/0 = 0
+28:11/0 = 0
+29:11/0 = 0
+30:11/0 = 0
+31:11/0 = 0
+0:12/0 = 0
+0:12/0/z_index = 10
+1:12/0 = 0
+1:12/0/z_index = 10
+2:12/0 = 0
+2:12/0/z_index = 10
+3:12/0 = 0
+3:12/0/z_index = 10
+4:12/0 = 0
+4:12/0/z_index = 10
+5:12/0 = 0
+5:12/0/z_index = 10
+6:12/0 = 0
+6:12/0/z_index = 10
+7:12/0 = 0
+7:12/0/z_index = 10
+8:12/0 = 0
+8:12/0/z_index = 10
+9:12/0 = 0
+9:12/0/z_index = 10
+10:12/0 = 0
+10:12/0/z_index = 10
+11:12/0 = 0
+11:12/0/z_index = 10
+12:12/0 = 0
+12:12/0/z_index = 10
+13:12/0 = 0
+13:12/0/z_index = 10
+14:12/0 = 0
+14:12/0/z_index = 10
+15:12/0 = 0
+15:12/0/z_index = 10
+16:12/0 = 0
+17:12/0 = 0
+26:12/0 = 0
+27:12/0 = 0
+28:12/0 = 0
+29:12/0 = 0
+30:12/0 = 0
+31:12/0 = 0
+2:13/0 = 0
+2:13/0/z_index = 10
+3:13/0 = 0
+3:13/0/z_index = 10
+4:13/0 = 0
+4:13/0/z_index = 10
+7:13/0 = 0
+7:13/0/z_index = 10
+8:13/0 = 0
+8:13/0/z_index = 10
+9:13/0 = 0
+9:13/0/z_index = 10
+12:13/0 = 0
+12:13/0/z_index = 10
+13:13/0 = 0
+13:13/0/z_index = 10
+14:13/0 = 0
+14:13/0/z_index = 10
+16:13/0 = 0
+17:13/0 = 0
+18:13/0 = 0
+21:13/0 = 0
+21:13/0/z_index = 10
+22:13/0 = 0
+22:13/0/z_index = 10
+23:13/0 = 0
+24:13/0 = 0
+26:13/0 = 0
+27:13/0 = 0
+28:13/0 = 0
+29:13/0 = 0
+30:13/0 = 0
+31:13/0 = 0
+32:13/0 = 0
+33:13/0 = 0
+34:13/0 = 0
+35:13/0 = 0
+36:13/0 = 0
+37:13/0 = 0
+2:14/0 = 0
+3:14/0 = 0
+4:14/0 = 0
+7:14/0 = 0
+8:14/0 = 0
+9:14/0 = 0
+13:14/0 = 0
+14:14/0 = 0
+16:14/0 = 0
+17:14/0 = 0
+18:14/0 = 0
+19:14/0 = 0
+21:14/0 = 0
+22:14/0 = 0
+23:14/0 = 0
+24:14/0 = 0
+25:14/0 = 0
+26:14/0 = 0
+29:14/0 = 0
+30:14/0 = 0
+31:14/0 = 0
+32:14/0 = 0
+33:14/0 = 0
+34:14/0 = 0
+35:14/0 = 0
+36:14/0 = 0
+37:14/0 = 0
+2:15/0 = 0
+3:15/0 = 0
+4:15/0 = 0
+7:15/0 = 0
+8:15/0 = 0
+9:15/0 = 0
+13:15/0 = 0
+14:15/0 = 0
+16:15/0 = 0
+21:15/0 = 0
+22:15/0 = 0
+23:15/0 = 0
+26:15/0 = 0
+27:15/0 = 0
+28:15/0 = 0
+29:15/0 = 0
+30:15/0 = 0
+31:15/0 = 0
+32:15/0 = 0
+33:15/0 = 0
+34:15/0 = 0
+35:15/0 = 0
+36:15/0 = 0
+37:15/0 = 0
+2:16/0 = 0
+3:16/0 = 0
+7:16/0 = 0
+8:16/0 = 0
+9:16/0 = 0
+13:16/0 = 0
+14:16/0 = 0
+16:16/0 = 0
+17:16/0 = 0
+18:16/0 = 0
+19:16/0 = 0
+21:16/0 = 0
+22:16/0 = 0
+23:16/0 = 0
+26:16/0 = 0
+27:16/0 = 0
+28:16/0 = 0
+29:16/0 = 0
+30:16/0 = 0
+31:16/0 = 0
+32:16/0 = 0
+33:16/0 = 0
+34:16/0 = 0
+35:16/0 = 0
+36:16/0 = 0
+37:16/0 = 0
+2:17/0 = 0
+3:17/0 = 0
+4:17/0 = 0
+7:17/0 = 0
+8:17/0 = 0
+9:17/0 = 0
+13:17/0 = 0
+14:17/0 = 0
+16:17/0 = 0
+26:17/0 = 0
+29:17/0 = 0
+30:17/0 = 0
+33:17/0 = 0
+34:17/0 = 0
+37:17/0 = 0
+2:18/0 = 0
+3:18/0 = 0
+4:18/0 = 0
+7:18/0 = 0
+8:18/0 = 0
+9:18/0 = 0
+13:18/0 = 0
+14:18/0 = 0
+16:18/0 = 0
+17:18/0 = 0
+18:18/0 = 0
+19:18/0 = 0
+26:18/0 = 0
+27:18/0 = 0
+28:18/0 = 0
+29:18/0 = 0
+30:18/0 = 0
+31:18/0 = 0
+32:18/0 = 0
+33:18/0 = 0
+34:18/0 = 0
+35:18/0 = 0
+36:18/0 = 0
+37:18/0 = 0
+2:19/0 = 0
+3:19/0 = 0
+15:19/0 = 0
+15:19/0/z_index = 10
+16:19/0 = 0
+16:19/0/z_index = 10
+26:19/0 = 0
+27:19/0 = 0
+28:19/0 = 0
+29:19/0 = 0
+30:19/0 = 0
+31:19/0 = 0
+32:19/0 = 0
+33:19/0 = 0
+34:19/0 = 0
+35:19/0 = 0
+2:20/0 = 0
+2:20/0/z_index = 10
+3:20/0 = 0
+3:20/0/z_index = 10
+6:20/0 = 0
+6:20/0/z_index = 10
+7:20/0 = 0
+7:20/0/z_index = 10
+15:20/0 = 0
+15:20/0/z_index = 10
+16:20/0 = 0
+16:20/0/z_index = 10
+19:20/0 = 0
+19:20/0/z_index = 10
+20:20/0 = 0
+20:20/0/z_index = 10
+26:20/0 = 0
+28:20/0 = 0
+29:20/0 = 0
+30:20/0 = 0
+31:20/0 = 0
+32:20/0 = 0
+33:20/0 = 0
+34:20/0 = 0
+35:20/0 = 0
+1:21/0 = 0
+1:21/0/z_index = 10
+2:21/0 = 0
+2:21/0/z_index = 10
+3:21/0 = 0
+3:21/0/z_index = 10
+4:21/0 = 0
+4:21/0/z_index = 10
+6:21/0 = 0
+6:21/0/z_index = 10
+7:21/0 = 0
+7:21/0/z_index = 10
+10:21/0 = 0
+10:21/0/z_index = 10
+11:21/0 = 0
+11:21/0/z_index = 10
+14:21/0 = 0
+14:21/0/z_index = 10
+15:21/0 = 0
+15:21/0/z_index = 10
+16:21/0 = 0
+16:21/0/z_index = 10
+17:21/0 = 0
+17:21/0/z_index = 10
+19:21/0 = 0
+19:21/0/z_index = 10
+20:21/0 = 0
+20:21/0/z_index = 10
+23:21/0 = 0
+23:21/0/z_index = 10
+24:21/0 = 0
+24:21/0/z_index = 10
+26:21/0 = 0
+28:21/0 = 0
+29:21/0 = 0
+30:21/0 = 0
+31:21/0 = 0
+32:21/0 = 0
+33:21/0 = 0
+34:21/0 = 0
+35:21/0 = 0
+1:22/0 = 0
+1:22/0/z_index = 10
+2:22/0 = 0
+2:22/0/z_index = 10
+3:22/0 = 0
+3:22/0/z_index = 10
+4:22/0 = 0
+4:22/0/z_index = 10
+5:22/0 = 0
+5:22/0/z_index = 10
+6:22/0 = 0
+6:22/0/z_index = 10
+7:22/0 = 0
+7:22/0/z_index = 10
+8:22/0 = 0
+8:22/0/z_index = 10
+10:22/0 = 0
+10:22/0/z_index = 10
+11:22/0 = 0
+11:22/0/z_index = 10
+14:22/0 = 0
+14:22/0/z_index = 10
+15:22/0 = 0
+15:22/0/z_index = 10
+16:22/0 = 0
+16:22/0/z_index = 10
+17:22/0 = 0
+17:22/0/z_index = 10
+18:22/0 = 0
+18:22/0/z_index = 10
+19:22/0 = 0
+19:22/0/z_index = 10
+20:22/0 = 0
+20:22/0/z_index = 10
+21:22/0 = 0
+21:22/0/z_index = 10
+23:22/0 = 0
+23:22/0/z_index = 10
+24:22/0 = 0
+24:22/0/z_index = 10
+26:22/0 = 0
+27:22/0 = 0
+28:22/0 = 0
+29:22/0 = 0
+30:22/0 = 0
+31:22/0 = 0
+32:22/0 = 0
+33:22/0 = 0
+34:22/0 = 0
+35:22/0 = 0
+1:23/0 = 0
+1:23/0/z_index = 10
+2:23/0 = 0
+2:23/0/z_index = 10
+3:23/0 = 0
+3:23/0/z_index = 10
+4:23/0 = 0
+4:23/0/z_index = 10
+5:23/0 = 0
+5:23/0/z_index = 10
+6:23/0 = 0
+6:23/0/z_index = 10
+7:23/0 = 0
+7:23/0/z_index = 10
+8:23/0 = 0
+8:23/0/z_index = 10
+9:23/0 = 0
+9:23/0/z_index = 10
+10:23/0 = 0
+10:23/0/z_index = 10
+11:23/0 = 0
+11:23/0/z_index = 10
+12:23/0 = 0
+12:23/0/z_index = 10
+14:23/0 = 0
+14:23/0/z_index = 10
+15:23/0 = 0
+15:23/0/z_index = 10
+16:23/0 = 0
+16:23/0/z_index = 10
+17:23/0 = 0
+17:23/0/z_index = 10
+18:23/0 = 0
+18:23/0/z_index = 10
+19:23/0 = 0
+19:23/0/z_index = 10
+20:23/0 = 0
+20:23/0/z_index = 10
+21:23/0 = 0
+21:23/0/z_index = 10
+22:23/0 = 0
+22:23/0/z_index = 10
+23:23/0 = 0
+23:23/0/z_index = 10
+24:23/0 = 0
+24:23/0/z_index = 10
+25:23/0 = 0
+25:23/0/z_index = 10
+26:23/0 = 0
+27:23/0 = 0
+28:23/0 = 0
+29:23/0 = 0
+30:23/0 = 0
+31:23/0 = 0
+32:23/0 = 0
+33:23/0 = 0
+34:23/0 = 0
+1:24/0 = 0
+1:24/0/z_index = 10
+2:24/0 = 0
+2:24/0/z_index = 10
+3:24/0 = 0
+3:24/0/z_index = 10
+4:24/0 = 0
+4:24/0/z_index = 10
+5:24/0 = 0
+5:24/0/z_index = 10
+6:24/0 = 0
+6:24/0/z_index = 10
+7:24/0 = 0
+7:24/0/z_index = 10
+8:24/0 = 0
+8:24/0/z_index = 10
+9:24/0 = 0
+9:24/0/z_index = 10
+10:24/0 = 0
+10:24/0/z_index = 10
+11:24/0 = 0
+11:24/0/z_index = 10
+12:24/0 = 0
+12:24/0/z_index = 10
+14:24/0 = 0
+14:24/0/z_index = 10
+15:24/0 = 0
+15:24/0/z_index = 10
+16:24/0 = 0
+16:24/0/z_index = 10
+17:24/0 = 0
+17:24/0/z_index = 10
+18:24/0 = 0
+18:24/0/z_index = 10
+19:24/0 = 0
+19:24/0/z_index = 10
+20:24/0 = 0
+20:24/0/z_index = 10
+21:24/0 = 0
+21:24/0/z_index = 10
+22:24/0 = 0
+22:24/0/z_index = 10
+23:24/0 = 0
+23:24/0/z_index = 10
+24:24/0 = 0
+24:24/0/z_index = 10
+25:24/0 = 0
+25:24/0/z_index = 10
+26:24/0 = 0
+28:24/0 = 0
+29:24/0 = 0
+31:24/0 = 0
+32:24/0 = 0
+34:24/0 = 0
+2:25/0 = 0
+2:25/0/z_index = 10
+3:25/0 = 0
+3:25/0/z_index = 10
+6:25/0 = 0
+6:25/0/z_index = 10
+7:25/0 = 0
+7:25/0/z_index = 10
+9:25/0 = 0
+9:25/0/z_index = 10
+10:25/0 = 0
+10:25/0/z_index = 10
+11:25/0 = 0
+11:25/0/z_index = 10
+12:25/0 = 0
+12:25/0/z_index = 10
+15:25/0 = 0
+15:25/0/z_index = 10
+16:25/0 = 0
+16:25/0/z_index = 10
+19:25/0 = 0
+19:25/0/z_index = 10
+20:25/0 = 0
+20:25/0/z_index = 10
+22:25/0 = 0
+22:25/0/z_index = 10
+23:25/0 = 0
+23:25/0/z_index = 10
+24:25/0 = 0
+24:25/0/z_index = 10
+25:25/0 = 0
+25:25/0/z_index = 10
+26:25/0 = 0
+27:25/0 = 0
+28:25/0 = 0
+29:25/0 = 0
+30:25/0 = 0
+31:25/0 = 0
+32:25/0 = 0
+33:25/0 = 0
+34:25/0 = 0
+2:26/0 = 0
+3:26/0 = 0
+6:26/0 = 0
+7:26/0 = 0
+10:26/0 = 0
+11:26/0 = 0
+15:26/0 = 0
+16:26/0 = 0
+19:26/0 = 0
+20:26/0 = 0
+23:26/0 = 0
+24:26/0 = 0
+30:26/0 = 0
+2:27/0 = 0
+3:27/0 = 0
+6:27/0 = 0
+7:27/0 = 0
+10:27/0 = 0
+11:27/0 = 0
+2:28/0 = 0
+3:28/0 = 0
+6:28/0 = 0
+7:28/0 = 0
+10:28/0 = 0
+11:28/0 = 0
+0:8/0 = 0
+0:8/0/z_index = 10
+5:13/0 = 0
+5:13/0/z_index = 10
+
+[resource]
+tile_size = Vector2i(32, 32)
+physics_layer_0/collision_layer = 4
+terrain_set_0/mode = 0
+terrain_set_0/terrain_0/name = "Floor"
+terrain_set_0/terrain_0/color = Color(0.5, 0.4375, 0.25, 1)
+terrain_set_0/terrain_1/name = "Water"
+terrain_set_0/terrain_1/color = Color(0.00998942, 0.470085, 2.88785e-07, 1)
+sources/0 = SubResource("TileSetAtlasSource_kgifk")
+sources/1 = SubResource("TileSetAtlasSource_lcnnk")
diff --git a/assets/sprites/tilesets/grass.png b/assets/sprites/tilesets/grass.png
deleted file mode 100644
index 146c7b6..0000000
Binary files a/assets/sprites/tilesets/grass.png and /dev/null differ
diff --git a/assets/sprites/tilesets/grass.png.import b/assets/sprites/tilesets/grass.png.import
deleted file mode 100644
index e3406d9..0000000
--- a/assets/sprites/tilesets/grass.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://kp2ka2v6xul5"
-path="res://.godot/imported/grass.png-a8ad71a7b62094379d8ccf591977f6b3.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/grass.png"
-dest_files=["res://.godot/imported/grass.png-a8ad71a7b62094379d8ccf591977f6b3.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
diff --git a/assets/sprites/tilesets/grass_scatter.png b/assets/sprites/tilesets/grass_scatter.png
deleted file mode 100644
index 22b81a5..0000000
Binary files a/assets/sprites/tilesets/grass_scatter.png and /dev/null differ
diff --git a/assets/sprites/tilesets/grass_scatter.png.import b/assets/sprites/tilesets/grass_scatter.png.import
deleted file mode 100644
index 6aebee2..0000000
--- a/assets/sprites/tilesets/grass_scatter.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://dpm7scfhii1fp"
-path="res://.godot/imported/grass_scatter.png-2424db367f1961afed01950380b66fcf.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/grass_scatter.png"
-dest_files=["res://.godot/imported/grass_scatter.png-2424db367f1961afed01950380b66fcf.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
diff --git a/assets/sprites/tilesets/plains.png b/assets/sprites/tilesets/plains.png
deleted file mode 100644
index 551aec1..0000000
Binary files a/assets/sprites/tilesets/plains.png and /dev/null differ
diff --git a/assets/sprites/tilesets/plains.png.import b/assets/sprites/tilesets/plains.png.import
deleted file mode 100644
index 5c31285..0000000
--- a/assets/sprites/tilesets/plains.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://caehri3cruwp8"
-path="res://.godot/imported/plains.png-b63aac4e7bfff1d2302cf7e7e3910af6.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/plains.png"
-dest_files=["res://.godot/imported/plains.png-b63aac4e7bfff1d2302cf7e7e3910af6.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
diff --git a/assets/sprites/tilesets/walls/walls.png b/assets/sprites/tilesets/walls/walls.png
deleted file mode 100644
index 3cd3023..0000000
Binary files a/assets/sprites/tilesets/walls/walls.png and /dev/null differ
diff --git a/assets/sprites/tilesets/walls/walls.png.import b/assets/sprites/tilesets/walls/walls.png.import
deleted file mode 100644
index eea0214..0000000
--- a/assets/sprites/tilesets/walls/walls.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://bvrwvvj0kyfxo"
-path="res://.godot/imported/walls.png-2ed5512c462efc1c7833a4bb9877458f.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/walls/walls.png"
-dest_files=["res://.godot/imported/walls.png-2ed5512c462efc1c7833a4bb9877458f.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
diff --git a/assets/sprites/tilesets/walls/wooden_door.png b/assets/sprites/tilesets/walls/wooden_door.png
deleted file mode 100644
index 8b3d41a..0000000
Binary files a/assets/sprites/tilesets/walls/wooden_door.png and /dev/null differ
diff --git a/assets/sprites/tilesets/walls/wooden_door.png.import b/assets/sprites/tilesets/walls/wooden_door.png.import
deleted file mode 100644
index 980969e..0000000
--- a/assets/sprites/tilesets/walls/wooden_door.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://cyxtwwu84auaf"
-path="res://.godot/imported/wooden_door.png-f43348d65798d1769e951f50da4575c8.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/walls/wooden_door.png"
-dest_files=["res://.godot/imported/wooden_door.png-f43348d65798d1769e951f50da4575c8.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
diff --git a/assets/sprites/tilesets/walls/wooden_door_b.png b/assets/sprites/tilesets/walls/wooden_door_b.png
deleted file mode 100644
index ad9fdfc..0000000
Binary files a/assets/sprites/tilesets/walls/wooden_door_b.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water-sheet.png b/assets/sprites/tilesets/water-sheet.png
deleted file mode 100644
index 1ffdcd0..0000000
Binary files a/assets/sprites/tilesets/water-sheet.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water-sheet.png.import b/assets/sprites/tilesets/water-sheet.png.import
deleted file mode 100644
index 6c279c7..0000000
--- a/assets/sprites/tilesets/water-sheet.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://cqndfths68nrm"
-path="res://.godot/imported/water-sheet.png-0762cf9cba22ea2c17ae1cdd6bde8ae6.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water-sheet.png"
-dest_files=["res://.godot/imported/water-sheet.png-0762cf9cba22ea2c17ae1cdd6bde8ae6.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
diff --git a/assets/sprites/tilesets/water1.png b/assets/sprites/tilesets/water1.png
deleted file mode 100644
index c0d5adb..0000000
Binary files a/assets/sprites/tilesets/water1.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water1.png.import b/assets/sprites/tilesets/water1.png.import
deleted file mode 100644
index dc06e41..0000000
--- a/assets/sprites/tilesets/water1.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://bld4c50nqblnf"
-path="res://.godot/imported/water1.png-f00586e264cceabee23e2616b95419e7.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water1.png"
-dest_files=["res://.godot/imported/water1.png-f00586e264cceabee23e2616b95419e7.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
diff --git a/assets/sprites/tilesets/water2.png b/assets/sprites/tilesets/water2.png
deleted file mode 100644
index 1625608..0000000
Binary files a/assets/sprites/tilesets/water2.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water2.png.import b/assets/sprites/tilesets/water2.png.import
deleted file mode 100644
index 643f35f..0000000
--- a/assets/sprites/tilesets/water2.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://c6vdpb3jw82wa"
-path="res://.godot/imported/water2.png-8c1520366c726d04b5cc3acadff0b900.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water2.png"
-dest_files=["res://.godot/imported/water2.png-8c1520366c726d04b5cc3acadff0b900.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
diff --git a/assets/sprites/tilesets/water3.png b/assets/sprites/tilesets/water3.png
deleted file mode 100644
index 795eb6c..0000000
Binary files a/assets/sprites/tilesets/water3.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water3.png.import b/assets/sprites/tilesets/water3.png.import
deleted file mode 100644
index fba56ca..0000000
--- a/assets/sprites/tilesets/water3.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://cwnjta2mi1c0h"
-path="res://.godot/imported/water3.png-b344b2b3c1be5db582f7c8eea243cb0f.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water3.png"
-dest_files=["res://.godot/imported/water3.png-b344b2b3c1be5db582f7c8eea243cb0f.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
diff --git a/assets/sprites/tilesets/water4.png b/assets/sprites/tilesets/water4.png
deleted file mode 100644
index 01585fe..0000000
Binary files a/assets/sprites/tilesets/water4.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water4.png.import b/assets/sprites/tilesets/water4.png.import
deleted file mode 100644
index 26ecf92..0000000
--- a/assets/sprites/tilesets/water4.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://bahrovkle7n8c"
-path="res://.godot/imported/water4.png-7e2a40803f048ca90fe06df19feead67.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water4.png"
-dest_files=["res://.godot/imported/water4.png-7e2a40803f048ca90fe06df19feead67.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
diff --git a/assets/sprites/tilesets/water5.png b/assets/sprites/tilesets/water5.png
deleted file mode 100644
index 1625608..0000000
Binary files a/assets/sprites/tilesets/water5.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water5.png.import b/assets/sprites/tilesets/water5.png.import
deleted file mode 100644
index 0aac8c2..0000000
--- a/assets/sprites/tilesets/water5.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://b5q52dhjhtyql"
-path="res://.godot/imported/water5.png-b971537c1b970f980c158571c19d8471.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water5.png"
-dest_files=["res://.godot/imported/water5.png-b971537c1b970f980c158571c19d8471.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
diff --git a/assets/sprites/tilesets/water6.png b/assets/sprites/tilesets/water6.png
deleted file mode 100644
index 37c4047..0000000
Binary files a/assets/sprites/tilesets/water6.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water6.png.import b/assets/sprites/tilesets/water6.png.import
deleted file mode 100644
index 4208400..0000000
--- a/assets/sprites/tilesets/water6.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://clxco4yisxynw"
-path="res://.godot/imported/water6.png-13756df334cb3af9e273d20b2ffc8479.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water6.png"
-dest_files=["res://.godot/imported/water6.png-13756df334cb3af9e273d20b2ffc8479.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
diff --git a/assets/sprites/tilesets/water_decorations.png b/assets/sprites/tilesets/water_decorations.png
deleted file mode 100644
index ce29682..0000000
Binary files a/assets/sprites/tilesets/water_decorations.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water_decorations.png.import b/assets/sprites/tilesets/water_decorations.png.import
deleted file mode 100644
index 250adf8..0000000
--- a/assets/sprites/tilesets/water_decorations.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://b35pi8tvj5c8r"
-path="res://.godot/imported/water_decorations.png-d3f68aed14cd428253b7445c3919d27d.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water_decorations.png"
-dest_files=["res://.godot/imported/water_decorations.png-d3f68aed14cd428253b7445c3919d27d.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
diff --git a/assets/sprites/tilesets/water_lillies.png b/assets/sprites/tilesets/water_lillies.png
deleted file mode 100644
index 104474b..0000000
Binary files a/assets/sprites/tilesets/water_lillies.png and /dev/null differ
diff --git a/assets/sprites/tilesets/water_lillies.png.import b/assets/sprites/tilesets/water_lillies.png.import
deleted file mode 100644
index 61cb702..0000000
--- a/assets/sprites/tilesets/water_lillies.png.import
+++ /dev/null
@@ -1,34 +0,0 @@
-[remap]
-
-importer="texture"
-type="CompressedTexture2D"
-uid="uid://l2dfvg5ve47g"
-path="res://.godot/imported/water_lillies.png-1ab68f1f500dad1ccee0d3f1379aea7b.ctex"
-metadata={
-"vram_texture": false
-}
-
-[deps]
-
-source_file="res://assets/sprites/tilesets/water_lillies.png"
-dest_files=["res://.godot/imported/water_lillies.png-1ab68f1f500dad1ccee0d3f1379aea7b.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
diff --git a/assets/tileset/basic.tres b/assets/tileset/basic.tres
deleted file mode 100644
index 6811ae8..0000000
--- a/assets/tileset/basic.tres
+++ /dev/null
@@ -1,926 +0,0 @@
-[gd_resource type="TileSet" load_steps=53 format=3 uid="uid://eucffgw5x26k"]
-
-[ext_resource type="Texture2D" uid="uid://i0w5e3cn4n46" path="res://assets/sprites/tilesets/decor_8x8.png" id="1_7kedk"]
-[ext_resource type="Texture2D" uid="uid://clxco4yisxynw" path="res://assets/sprites/tilesets/water6.png" id="3_tuen1"]
-[ext_resource type="Texture2D" uid="uid://cqndfths68nrm" path="res://assets/sprites/tilesets/water-sheet.png" id="4_xqmy8"]
-[ext_resource type="Texture2D" uid="uid://b35pi8tvj5c8r" path="res://assets/sprites/tilesets/water_decorations.png" id="5_mu317"]
-[ext_resource type="Texture2D" uid="uid://l2dfvg5ve47g" path="res://assets/sprites/tilesets/water_lillies.png" id="6_mu2t0"]
-[ext_resource type="Texture2D" uid="uid://f2k4x0c17w1g" path="res://assets/sprites/tilesets/floors/carpet.png" id="7_r6dyw"]
-[ext_resource type="Texture2D" uid="uid://b51a0hwypuu47" path="res://assets/sprites/tilesets/floors/flooring.png" id="8_l7s4e"]
-[ext_resource type="Texture2D" uid="uid://7cdq70veqnye" path="res://assets/sprites/tilesets/floors/wooden.png" id="9_h4pic"]
-[ext_resource type="Texture2D" uid="uid://cqe5b8whdo7qi" path="res://assets/sprites/objects/chest_01.png" id="10_tiq8g"]
-[ext_resource type="Texture2D" uid="uid://b3h3f0j4ft3xe" path="res://assets/sprites/objects/chest_02.png" id="11_8js75"]
-[ext_resource type="Texture2D" uid="uid://dpumvq8hurh2k" path="res://assets/sprites/objects/objects.png" id="12_q4c6y"]
-[ext_resource type="Texture2D" uid="uid://cb3b4l32gyimg" path="res://assets/sprites/tilesets/fences.png" id="13_54pxl"]
-[ext_resource type="Texture2D" uid="uid://bemqlej7o4t4n" path="res://assets/sprites/objects/rock_in_water_01-sheet.png" id="14_fgkc1"]
-[ext_resource type="Texture2D" uid="uid://b3ig0r6ifmc7k" path="res://assets/sprites/objects/rock_in_water_01.png" id="15_8nsc3"]
-[ext_resource type="Texture2D" uid="uid://b04ancuu7mg5o" path="res://assets/sprites/objects/rock_in_water_02.png" id="16_ptgc6"]
-[ext_resource type="Texture2D" uid="uid://dfljec7o2o5vu" path="res://assets/sprites/objects/rock_in_water_03.png" id="17_mvgdg"]
-[ext_resource type="Texture2D" uid="uid://dbn6gtau8l7mt" path="res://assets/sprites/objects/rock_in_water_04.png" id="18_42cgq"]
-[ext_resource type="Texture2D" uid="uid://bbx4frs3mlf16" path="res://assets/sprites/objects/rock_in_water_05.png" id="19_5gmqf"]
-[ext_resource type="Texture2D" uid="uid://bb110k2gvul25" path="res://assets/sprites/objects/rock_in_water_06.png" id="20_6kawu"]
-[ext_resource type="Texture2D" uid="uid://dpm7scfhii1fp" path="res://assets/sprites/tilesets/grass_scatter.png" id="20_ayjk1"]
-[ext_resource type="Texture2D" uid="uid://caehri3cruwp8" path="res://assets/sprites/tilesets/plains.png" id="22_ii7ir"]
-[ext_resource type="Texture2D" uid="uid://bld4c50nqblnf" path="res://assets/sprites/tilesets/water1.png" id="23_rpgtf"]
-[ext_resource type="Texture2D" uid="uid://c6vdpb3jw82wa" path="res://assets/sprites/tilesets/water2.png" id="24_xk4xi"]
-[ext_resource type="Texture2D" uid="uid://cwnjta2mi1c0h" path="res://assets/sprites/tilesets/water3.png" id="25_aasd6"]
-[ext_resource type="Texture2D" uid="uid://bahrovkle7n8c" path="res://assets/sprites/tilesets/water4.png" id="26_uxew8"]
-[ext_resource type="Texture2D" uid="uid://b5q52dhjhtyql" path="res://assets/sprites/tilesets/water5.png" id="27_ah31x"]
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_sr222"]
-texture = ExtResource("1_7kedk")
-0:0/0 = 0
-1:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_lfpr5"]
-texture = ExtResource("3_tuen1")
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_qut18"]
-texture = ExtResource("4_xqmy8")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-6:0/0 = 0
-7:0/0 = 0
-8:0/0 = 0
-9:0/0 = 0
-10:0/0 = 0
-11:0/0 = 0
-12:0/0 = 0
-13:0/0 = 0
-14:0/0 = 0
-15:0/0 = 0
-16:0/0 = 0
-17:0/0 = 0
-18:0/0 = 0
-19:0/0 = 0
-20:0/0 = 0
-21:0/0 = 0
-22:0/0 = 0
-23:0/0 = 0
-24:0/0 = 0
-25:0/0 = 0
-26:0/0 = 0
-27:0/0 = 0
-28:0/0 = 0
-29:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-6:1/0 = 0
-7:1/0 = 0
-8:1/0 = 0
-9:1/0 = 0
-10:1/0 = 0
-11:1/0 = 0
-12:1/0 = 0
-13:1/0 = 0
-14:1/0 = 0
-15:1/0 = 0
-16:1/0 = 0
-17:1/0 = 0
-18:1/0 = 0
-19:1/0 = 0
-20:1/0 = 0
-21:1/0 = 0
-22:1/0 = 0
-23:1/0 = 0
-24:1/0 = 0
-25:1/0 = 0
-26:1/0 = 0
-27:1/0 = 0
-28:1/0 = 0
-29:1/0 = 0
-0:2/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-5:2/0 = 0
-6:2/0 = 0
-7:2/0 = 0
-10:2/0 = 0
-11:2/0 = 0
-12:2/0 = 0
-15:2/0 = 0
-16:2/0 = 0
-17:2/0 = 0
-20:2/0 = 0
-21:2/0 = 0
-22:2/0 = 0
-25:2/0 = 0
-26:2/0 = 0
-27:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_djg5w"]
-texture = ExtResource("5_mu317")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_dfx05"]
-texture = ExtResource("6_mu2t0")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_pc77a"]
-texture = ExtResource("7_r6dyw")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-0:2/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-4:2/0 = 0
-5:2/0 = 0
-0:3/0 = 0
-1:3/0 = 0
-2:3/0 = 0
-3:3/0 = 0
-0:4/0 = 0
-1:4/0 = 0
-2:4/0 = 0
-3:4/0 = 0
-4:4/0 = 0
-5:4/0 = 0
-0:5/0 = 0
-1:5/0 = 0
-2:5/0 = 0
-3:5/0 = 0
-4:5/0 = 0
-5:5/0 = 0
-0:6/0 = 0
-1:6/0 = 0
-2:6/0 = 0
-3:6/0 = 0
-4:6/0 = 0
-5:6/0 = 0
-0:7/0 = 0
-1:7/0 = 0
-2:7/0 = 0
-3:7/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_cyw70"]
-texture = ExtResource("8_l7s4e")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-0:2/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_rrj7c"]
-texture = ExtResource("9_h4pic")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_2hwhy"]
-texture = ExtResource("10_tiq8g")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_1s51s"]
-texture = ExtResource("11_8js75")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_etldj"]
-texture = ExtResource("12_q4c6y")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-6:0/0 = 0
-7:0/0 = 0
-8:0/0 = 0
-9:0/0 = 0
-10:0/0 = 0
-11:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-8:1/0 = 0
-9:1/0 = 0
-10:1/0 = 0
-11:1/0 = 0
-8:2/0 = 0
-9:2/0 = 0
-10:2/0 = 0
-11:2/0 = 0
-12:2/0 = 0
-9:3/0 = 0
-10:3/0 = 0
-11:3/0 = 0
-12:3/0 = 0
-9:4/0 = 0
-10:4/0 = 0
-11:4/0 = 0
-0:5/0 = 0
-1:5/0 = 0
-2:5/0 = 0
-3:5/0 = 0
-4:5/0 = 0
-5:5/0 = 0
-6:5/0 = 0
-7:5/0 = 0
-8:5/0 = 0
-9:5/0 = 0
-10:5/0 = 0
-11:5/0 = 0
-0:6/0 = 0
-1:6/0 = 0
-2:6/0 = 0
-3:6/0 = 0
-4:6/0 = 0
-5:6/0 = 0
-6:6/0 = 0
-7:6/0 = 0
-8:6/0 = 0
-9:6/0 = 0
-10:6/0 = 0
-11:6/0 = 0
-0:7/0 = 0
-1:7/0 = 0
-2:7/0 = 0
-3:7/0 = 0
-4:7/0 = 0
-5:7/0 = 0
-6:7/0 = 0
-7:7/0 = 0
-8:7/0 = 0
-9:7/0 = 0
-10:7/0 = 0
-11:7/0 = 0
-0:8/0 = 0
-1:8/0 = 0
-2:8/0 = 0
-3:8/0 = 0
-4:8/0 = 0
-5:8/0 = 0
-6:8/0 = 0
-7:8/0 = 0
-8:8/0 = 0
-9:8/0 = 0
-10:8/0 = 0
-11:8/0 = 0
-0:9/0 = 0
-1:9/0 = 0
-2:9/0 = 0
-3:9/0 = 0
-4:9/0 = 0
-5:9/0 = 0
-6:9/0 = 0
-7:9/0 = 0
-8:9/0 = 0
-0:10/0 = 0
-1:10/0 = 0
-2:10/0 = 0
-3:10/0 = 0
-4:10/0 = 0
-5:10/0 = 0
-0:11/0 = 0
-1:11/0 = 0
-2:11/0 = 0
-3:11/0 = 0
-4:11/0 = 0
-5:11/0 = 0
-0:12/0 = 0
-1:12/0 = 0
-2:12/0 = 0
-3:12/0 = 0
-4:12/0 = 0
-5:12/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_h4wes"]
-texture = ExtResource("13_54pxl")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-0:2/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-0:3/0 = 0
-1:3/0 = 0
-2:3/0 = 0
-3:3/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_xgkax"]
-texture = ExtResource("14_fgkc1")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_ruajh"]
-texture = ExtResource("15_8nsc3")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_53gqo"]
-texture = ExtResource("16_ptgc6")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_y44q6"]
-texture = ExtResource("17_mvgdg")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_v5rde"]
-texture = ExtResource("18_42cgq")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_h63s7"]
-texture = ExtResource("19_5gmqf")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_jn4cm"]
-texture = ExtResource("20_6kawu")
-0:0/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_7kedk"]
-texture = ExtResource("20_ayjk1")
-0:0/0 = 0
-0:0/0/terrain_set = 0
-0:0/0/terrain = 1
-0:0/0/probability = 0.02
-0:0/0/terrains_peering_bit/right_side = 1
-0:0/0/terrains_peering_bit/bottom_right_corner = 1
-0:0/0/terrains_peering_bit/bottom_side = 1
-0:0/0/terrains_peering_bit/bottom_left_corner = 1
-0:0/0/terrains_peering_bit/left_side = 1
-0:0/0/terrains_peering_bit/top_left_corner = 1
-0:0/0/terrains_peering_bit/top_side = 1
-0:0/0/terrains_peering_bit/top_right_corner = 1
-0:1/0 = 0
-0:1/0/terrain_set = 0
-0:1/0/terrain = 1
-0:1/0/probability = 0.01
-0:1/0/terrains_peering_bit/right_side = 1
-0:1/0/terrains_peering_bit/bottom_right_corner = 1
-0:1/0/terrains_peering_bit/bottom_side = 1
-0:1/0/terrains_peering_bit/bottom_left_corner = 1
-0:1/0/terrains_peering_bit/left_side = 1
-0:1/0/terrains_peering_bit/top_left_corner = 1
-0:1/0/terrains_peering_bit/top_side = 1
-0:1/0/terrains_peering_bit/top_right_corner = 1
-0:2/0 = 0
-0:2/0/terrain_set = 0
-0:2/0/terrain = 1
-0:2/0/probability = 0.02
-0:2/0/terrains_peering_bit/right_side = 1
-0:2/0/terrains_peering_bit/bottom_right_corner = 1
-0:2/0/terrains_peering_bit/bottom_side = 1
-0:2/0/terrains_peering_bit/bottom_left_corner = 1
-0:2/0/terrains_peering_bit/left_side = 1
-0:2/0/terrains_peering_bit/top_left_corner = 1
-0:2/0/terrains_peering_bit/top_side = 1
-0:2/0/terrains_peering_bit/top_right_corner = 1
-0:3/0 = 0
-0:3/0/terrain_set = 0
-0:3/0/terrain = 1
-0:3/0/probability = 0.005
-0:3/0/terrains_peering_bit/right_side = 1
-0:3/0/terrains_peering_bit/bottom_right_corner = 1
-0:3/0/terrains_peering_bit/bottom_side = 1
-0:3/0/terrains_peering_bit/bottom_left_corner = 1
-0:3/0/terrains_peering_bit/left_side = 1
-0:3/0/terrains_peering_bit/top_left_corner = 1
-0:3/0/terrains_peering_bit/top_side = 1
-0:3/0/terrains_peering_bit/top_right_corner = 1
-0:4/0 = 0
-1:0/0 = 0
-1:0/0/terrain_set = 0
-1:0/0/terrain = 1
-1:0/0/probability = 0.02
-1:0/0/terrains_peering_bit/right_side = 1
-1:0/0/terrains_peering_bit/bottom_right_corner = 1
-1:0/0/terrains_peering_bit/bottom_side = 1
-1:0/0/terrains_peering_bit/bottom_left_corner = 1
-1:0/0/terrains_peering_bit/left_side = 1
-1:0/0/terrains_peering_bit/top_left_corner = 1
-1:0/0/terrains_peering_bit/top_side = 1
-1:0/0/terrains_peering_bit/top_right_corner = 1
-1:1/0 = 0
-1:1/0/terrain_set = 0
-1:1/0/terrain = 1
-1:1/0/probability = 0.01
-1:1/0/terrains_peering_bit/right_side = 1
-1:1/0/terrains_peering_bit/bottom_right_corner = 1
-1:1/0/terrains_peering_bit/bottom_side = 1
-1:1/0/terrains_peering_bit/bottom_left_corner = 1
-1:1/0/terrains_peering_bit/left_side = 1
-1:1/0/terrains_peering_bit/top_left_corner = 1
-1:1/0/terrains_peering_bit/top_side = 1
-1:1/0/terrains_peering_bit/top_right_corner = 1
-1:2/0 = 0
-1:2/0/terrain_set = 0
-1:2/0/terrain = 1
-1:2/0/probability = 0.02
-1:2/0/terrains_peering_bit/right_side = 1
-1:2/0/terrains_peering_bit/bottom_right_corner = 1
-1:2/0/terrains_peering_bit/bottom_side = 1
-1:2/0/terrains_peering_bit/bottom_left_corner = 1
-1:2/0/terrains_peering_bit/left_side = 1
-1:2/0/terrains_peering_bit/top_left_corner = 1
-1:2/0/terrains_peering_bit/top_side = 1
-1:2/0/terrains_peering_bit/top_right_corner = 1
-1:3/0 = 0
-1:3/0/terrain_set = 0
-1:3/0/terrain = 1
-1:3/0/probability = 0.005
-1:3/0/terrains_peering_bit/right_side = 1
-1:3/0/terrains_peering_bit/bottom_right_corner = 1
-1:3/0/terrains_peering_bit/bottom_side = 1
-1:3/0/terrains_peering_bit/bottom_left_corner = 1
-1:3/0/terrains_peering_bit/left_side = 1
-1:3/0/terrains_peering_bit/top_left_corner = 1
-1:3/0/terrains_peering_bit/top_side = 1
-1:3/0/terrains_peering_bit/top_right_corner = 1
-1:4/0 = 0
-2:0/0 = 0
-2:0/0/terrain_set = 0
-2:0/0/terrain = 1
-2:0/0/probability = 0.02
-2:0/0/terrains_peering_bit/right_side = 1
-2:0/0/terrains_peering_bit/bottom_right_corner = 1
-2:0/0/terrains_peering_bit/bottom_side = 1
-2:0/0/terrains_peering_bit/bottom_left_corner = 1
-2:0/0/terrains_peering_bit/left_side = 1
-2:0/0/terrains_peering_bit/top_left_corner = 1
-2:0/0/terrains_peering_bit/top_side = 1
-2:0/0/terrains_peering_bit/top_right_corner = 1
-2:1/0 = 0
-2:1/0/terrain_set = 0
-2:1/0/terrain = 1
-2:1/0/probability = 0.01
-2:1/0/terrains_peering_bit/right_side = 1
-2:1/0/terrains_peering_bit/bottom_right_corner = 1
-2:1/0/terrains_peering_bit/bottom_side = 1
-2:1/0/terrains_peering_bit/bottom_left_corner = 1
-2:1/0/terrains_peering_bit/left_side = 1
-2:1/0/terrains_peering_bit/top_left_corner = 1
-2:1/0/terrains_peering_bit/top_side = 1
-2:1/0/terrains_peering_bit/top_right_corner = 1
-2:2/0 = 0
-2:2/0/terrain_set = 0
-2:2/0/terrain = 1
-2:2/0/probability = 0.02
-2:2/0/terrains_peering_bit/right_side = 1
-2:2/0/terrains_peering_bit/bottom_right_corner = 1
-2:2/0/terrains_peering_bit/bottom_side = 1
-2:2/0/terrains_peering_bit/bottom_left_corner = 1
-2:2/0/terrains_peering_bit/left_side = 1
-2:2/0/terrains_peering_bit/top_left_corner = 1
-2:2/0/terrains_peering_bit/top_side = 1
-2:2/0/terrains_peering_bit/top_right_corner = 1
-2:3/0 = 0
-2:3/0/terrain_set = 0
-2:3/0/terrain = 1
-2:3/0/probability = 0.005
-2:3/0/terrains_peering_bit/right_side = 1
-2:3/0/terrains_peering_bit/bottom_right_corner = 1
-2:3/0/terrains_peering_bit/bottom_side = 1
-2:3/0/terrains_peering_bit/bottom_left_corner = 1
-2:3/0/terrains_peering_bit/left_side = 1
-2:3/0/terrains_peering_bit/top_left_corner = 1
-2:3/0/terrains_peering_bit/top_side = 1
-2:3/0/terrains_peering_bit/top_right_corner = 1
-2:4/0 = 0
-3:0/0 = 0
-3:0/0/terrain_set = 0
-3:0/0/terrain = 1
-3:0/0/probability = 0.02
-3:0/0/terrains_peering_bit/right_side = 1
-3:0/0/terrains_peering_bit/bottom_right_corner = 1
-3:0/0/terrains_peering_bit/bottom_side = 1
-3:0/0/terrains_peering_bit/bottom_left_corner = 1
-3:0/0/terrains_peering_bit/left_side = 1
-3:0/0/terrains_peering_bit/top_left_corner = 1
-3:0/0/terrains_peering_bit/top_side = 1
-3:0/0/terrains_peering_bit/top_right_corner = 1
-3:1/0 = 0
-3:1/0/terrain_set = 0
-3:1/0/terrain = 1
-3:1/0/probability = 0.01
-3:1/0/terrains_peering_bit/right_side = 1
-3:1/0/terrains_peering_bit/bottom_right_corner = 1
-3:1/0/terrains_peering_bit/bottom_side = 1
-3:1/0/terrains_peering_bit/bottom_left_corner = 1
-3:1/0/terrains_peering_bit/left_side = 1
-3:1/0/terrains_peering_bit/top_left_corner = 1
-3:1/0/terrains_peering_bit/top_side = 1
-3:1/0/terrains_peering_bit/top_right_corner = 1
-3:2/0 = 0
-3:2/0/terrain_set = 0
-3:2/0/terrain = 1
-3:2/0/probability = 0.02
-3:2/0/terrains_peering_bit/right_side = 1
-3:2/0/terrains_peering_bit/bottom_right_corner = 1
-3:2/0/terrains_peering_bit/bottom_side = 1
-3:2/0/terrains_peering_bit/bottom_left_corner = 1
-3:2/0/terrains_peering_bit/left_side = 1
-3:2/0/terrains_peering_bit/top_left_corner = 1
-3:2/0/terrains_peering_bit/top_side = 1
-3:2/0/terrains_peering_bit/top_right_corner = 1
-3:3/0 = 0
-3:3/0/terrain_set = 0
-3:3/0/terrain = 1
-3:3/0/probability = 0.005
-3:3/0/terrains_peering_bit/right_side = 1
-3:3/0/terrains_peering_bit/bottom_right_corner = 1
-3:3/0/terrains_peering_bit/bottom_side = 1
-3:3/0/terrains_peering_bit/bottom_left_corner = 1
-3:3/0/terrains_peering_bit/left_side = 1
-3:3/0/terrains_peering_bit/top_left_corner = 1
-3:3/0/terrains_peering_bit/top_side = 1
-3:3/0/terrains_peering_bit/top_right_corner = 1
-3:4/0 = 0
-4:0/next_alternative_id = 4
-4:0/0 = 0
-4:0/0/terrain_set = 0
-4:0/0/terrain = 1
-4:0/0/terrains_peering_bit/right_side = 1
-4:0/0/terrains_peering_bit/bottom_right_corner = 1
-4:0/0/terrains_peering_bit/bottom_side = 1
-4:0/0/terrains_peering_bit/bottom_left_corner = 1
-4:0/0/terrains_peering_bit/left_side = 1
-4:0/0/terrains_peering_bit/top_left_corner = 1
-4:0/0/terrains_peering_bit/top_side = 1
-4:0/0/terrains_peering_bit/top_right_corner = 1
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_h21i8"]
-texture = ExtResource("22_ii7ir")
-0:0/0 = 0
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-0:1/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-0:2/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-4:2/0 = 0
-5:2/0 = 0
-0:3/0 = 0
-1:3/0 = 0
-2:3/0 = 0
-3:3/0 = 0
-0:4/0 = 0
-0:4/0/terrain_set = 0
-0:4/0/terrain = 0
-0:4/0/terrains_peering_bit/bottom_side = 0
-1:4/0 = 0
-1:4/0/terrain_set = 0
-1:4/0/terrain = 0
-1:4/0/terrains_peering_bit/right_side = 0
-1:4/0/terrains_peering_bit/bottom_right_corner = 0
-1:4/0/terrains_peering_bit/bottom_side = 0
-2:4/0 = 0
-2:4/0/terrain_set = 0
-2:4/0/terrain = 0
-2:4/0/terrains_peering_bit/right_side = 0
-2:4/0/terrains_peering_bit/bottom_right_corner = 0
-2:4/0/terrains_peering_bit/bottom_side = 0
-2:4/0/terrains_peering_bit/bottom_left_corner = 0
-2:4/0/terrains_peering_bit/left_side = 0
-3:4/0 = 0
-3:4/0/terrain_set = 0
-3:4/0/terrain = 0
-3:4/0/terrains_peering_bit/bottom_side = 0
-3:4/0/terrains_peering_bit/bottom_left_corner = 0
-3:4/0/terrains_peering_bit/left_side = 0
-4:4/0 = 0
-4:4/0/terrain_set = 0
-4:4/0/terrain = 0
-4:4/0/terrains_peering_bit/right_side = 0
-4:4/0/terrains_peering_bit/bottom_side = 0
-4:4/0/terrains_peering_bit/bottom_left_corner = 0
-4:4/0/terrains_peering_bit/left_side = 0
-4:4/0/terrains_peering_bit/top_left_corner = 0
-4:4/0/terrains_peering_bit/top_side = 0
-4:4/0/terrains_peering_bit/top_right_corner = 0
-5:4/0 = 0
-5:4/0/terrain_set = 0
-5:4/0/terrain = 0
-5:4/0/terrains_peering_bit/right_side = 0
-5:4/0/terrains_peering_bit/bottom_right_corner = 0
-5:4/0/terrains_peering_bit/bottom_side = 0
-5:4/0/terrains_peering_bit/left_side = 0
-5:4/0/terrains_peering_bit/top_left_corner = 0
-5:4/0/terrains_peering_bit/top_side = 0
-5:4/0/terrains_peering_bit/top_right_corner = 0
-0:5/0 = 0
-0:5/0/terrain_set = 0
-0:5/0/terrain = 0
-0:5/0/terrains_peering_bit/bottom_side = 0
-0:5/0/terrains_peering_bit/top_side = 0
-1:5/0 = 0
-1:5/0/terrain_set = 0
-1:5/0/terrain = 0
-1:5/0/terrains_peering_bit/right_side = 0
-1:5/0/terrains_peering_bit/bottom_right_corner = 0
-1:5/0/terrains_peering_bit/bottom_side = 0
-1:5/0/terrains_peering_bit/top_side = 0
-1:5/0/terrains_peering_bit/top_right_corner = 0
-2:5/0 = 0
-2:5/0/terrain_set = 0
-2:5/0/terrain = 0
-2:5/0/terrains_peering_bit/right_side = 0
-2:5/0/terrains_peering_bit/bottom_right_corner = 0
-2:5/0/terrains_peering_bit/bottom_side = 0
-2:5/0/terrains_peering_bit/bottom_left_corner = 0
-2:5/0/terrains_peering_bit/left_side = 0
-2:5/0/terrains_peering_bit/top_left_corner = 0
-2:5/0/terrains_peering_bit/top_side = 0
-2:5/0/terrains_peering_bit/top_right_corner = 0
-3:5/0 = 0
-3:5/0/terrain_set = 0
-3:5/0/terrain = 0
-3:5/0/terrains_peering_bit/bottom_side = 0
-3:5/0/terrains_peering_bit/bottom_left_corner = 0
-3:5/0/terrains_peering_bit/left_side = 0
-3:5/0/terrains_peering_bit/top_left_corner = 0
-3:5/0/terrains_peering_bit/top_side = 0
-4:5/0 = 0
-4:5/0/terrain_set = 0
-4:5/0/terrain = 0
-4:5/0/terrains_peering_bit/right_side = 0
-4:5/0/terrains_peering_bit/bottom_right_corner = 0
-4:5/0/terrains_peering_bit/bottom_side = 0
-4:5/0/terrains_peering_bit/bottom_left_corner = 0
-4:5/0/terrains_peering_bit/left_side = 0
-4:5/0/terrains_peering_bit/top_left_corner = 0
-4:5/0/terrains_peering_bit/top_side = 0
-5:5/0 = 0
-5:5/0/terrain_set = 0
-5:5/0/terrain = 0
-5:5/0/terrains_peering_bit/right_side = 0
-5:5/0/terrains_peering_bit/bottom_right_corner = 0
-5:5/0/terrains_peering_bit/bottom_side = 0
-5:5/0/terrains_peering_bit/bottom_left_corner = 0
-5:5/0/terrains_peering_bit/left_side = 0
-5:5/0/terrains_peering_bit/top_side = 0
-5:5/0/terrains_peering_bit/top_right_corner = 0
-0:6/0 = 0
-0:6/0/terrain_set = 0
-0:6/0/terrain = 0
-0:6/0/terrains_peering_bit/top_side = 0
-1:6/0 = 0
-1:6/0/terrain_set = 0
-1:6/0/terrain = 0
-1:6/0/terrains_peering_bit/right_side = 0
-1:6/0/terrains_peering_bit/top_side = 0
-1:6/0/terrains_peering_bit/top_right_corner = 0
-2:6/0 = 0
-2:6/0/terrain_set = 0
-2:6/0/terrain = 0
-2:6/0/terrains_peering_bit/right_side = 0
-2:6/0/terrains_peering_bit/left_side = 0
-2:6/0/terrains_peering_bit/top_left_corner = 0
-2:6/0/terrains_peering_bit/top_side = 0
-2:6/0/terrains_peering_bit/top_right_corner = 0
-3:6/0 = 0
-3:6/0/terrain_set = 0
-3:6/0/terrain = 0
-3:6/0/terrains_peering_bit/left_side = 0
-3:6/0/terrains_peering_bit/top_left_corner = 0
-3:6/0/terrains_peering_bit/top_side = 0
-4:6/0 = 0
-4:6/0/terrain_set = 0
-4:6/0/terrain = 0
-4:6/0/terrains_peering_bit/right_side = 0
-4:6/0/terrains_peering_bit/bottom_side = 0
-4:6/0/terrains_peering_bit/bottom_left_corner = 0
-4:6/0/terrains_peering_bit/left_side = 0
-4:6/0/terrains_peering_bit/top_side = 0
-4:6/0/terrains_peering_bit/top_right_corner = 0
-5:6/0 = 0
-5:6/0/terrain_set = 0
-5:6/0/terrain = 0
-5:6/0/terrains_peering_bit/right_side = 0
-5:6/0/terrains_peering_bit/bottom_right_corner = 0
-5:6/0/terrains_peering_bit/bottom_side = 0
-5:6/0/terrains_peering_bit/left_side = 0
-5:6/0/terrains_peering_bit/top_left_corner = 0
-5:6/0/terrains_peering_bit/top_side = 0
-0:7/0 = 0
-0:7/0/terrain_set = 0
-0:7/0/terrain = 0
-1:7/0 = 0
-1:7/0/terrain_set = 0
-1:7/0/terrain = 0
-1:7/0/terrains_peering_bit/right_side = 0
-2:7/0 = 0
-2:7/0/terrain_set = 0
-2:7/0/terrain = 0
-2:7/0/terrains_peering_bit/right_side = 0
-2:7/0/terrains_peering_bit/left_side = 0
-3:7/0 = 0
-3:7/0/terrain_set = 0
-3:7/0/terrain = 0
-3:7/0/terrains_peering_bit/left_side = 0
-0:8/0 = 0
-1:8/0 = 0
-2:8/0 = 0
-3:8/0 = 0
-4:8/0 = 0
-5:8/0 = 0
-0:9/0 = 0
-1:9/0 = 0
-2:9/0 = 0
-3:9/0 = 0
-4:9/0 = 0
-5:9/0 = 0
-0:10/0 = 0
-1:10/0 = 0
-2:10/0 = 0
-3:10/0 = 0
-4:10/0 = 0
-5:10/0 = 0
-0:11/0 = 0
-1:11/0 = 0
-2:11/0 = 0
-3:11/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_ufsgn"]
-texture = ExtResource("23_rpgtf")
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_l2gsh"]
-texture = ExtResource("24_xk4xi")
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_4t521"]
-texture = ExtResource("25_aasd6")
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_xd5qh"]
-texture = ExtResource("26_uxew8")
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-
-[sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_t5ap8"]
-texture = ExtResource("27_ah31x")
-1:0/0 = 0
-2:0/0 = 0
-3:0/0 = 0
-4:0/0 = 0
-5:0/0 = 0
-1:1/0 = 0
-2:1/0 = 0
-3:1/0 = 0
-4:1/0 = 0
-5:1/0 = 0
-1:2/0 = 0
-2:2/0 = 0
-3:2/0 = 0
-
-[resource]
-terrain_set_0/mode = 0
-terrain_set_0/terrain_0/name = "Walls"
-terrain_set_0/terrain_0/color = Color(0.20321, 0.41277, 0.51279, 1)
-terrain_set_0/terrain_1/name = "Grass"
-terrain_set_0/terrain_1/color = Color(0.5, 0.4375, 0.25, 1)
-sources/0 = SubResource("TileSetAtlasSource_sr222")
-sources/2 = SubResource("TileSetAtlasSource_h4wes")
-sources/4 = SubResource("TileSetAtlasSource_h21i8")
-sources/5 = SubResource("TileSetAtlasSource_ufsgn")
-sources/6 = SubResource("TileSetAtlasSource_l2gsh")
-sources/7 = SubResource("TileSetAtlasSource_4t521")
-sources/8 = SubResource("TileSetAtlasSource_xd5qh")
-sources/9 = SubResource("TileSetAtlasSource_t5ap8")
-sources/10 = SubResource("TileSetAtlasSource_lfpr5")
-sources/11 = SubResource("TileSetAtlasSource_qut18")
-sources/12 = SubResource("TileSetAtlasSource_djg5w")
-sources/13 = SubResource("TileSetAtlasSource_dfx05")
-sources/14 = SubResource("TileSetAtlasSource_pc77a")
-sources/15 = SubResource("TileSetAtlasSource_cyw70")
-sources/16 = SubResource("TileSetAtlasSource_rrj7c")
-sources/17 = SubResource("TileSetAtlasSource_2hwhy")
-sources/18 = SubResource("TileSetAtlasSource_1s51s")
-sources/19 = SubResource("TileSetAtlasSource_etldj")
-sources/20 = SubResource("TileSetAtlasSource_xgkax")
-sources/21 = SubResource("TileSetAtlasSource_ruajh")
-sources/22 = SubResource("TileSetAtlasSource_53gqo")
-sources/23 = SubResource("TileSetAtlasSource_y44q6")
-sources/24 = SubResource("TileSetAtlasSource_v5rde")
-sources/25 = SubResource("TileSetAtlasSource_h63s7")
-sources/26 = SubResource("TileSetAtlasSource_jn4cm")
-sources/27 = SubResource("TileSetAtlasSource_7kedk")
-tile_proxies/coords_level = [[1, Vector2i(0, 0)], [27, Vector2i(0, 0)], [1, Vector2i(0, 1)], [27, Vector2i(0, 1)], [1, Vector2i(0, 2)], [27, Vector2i(0, 2)], [1, Vector2i(0, 3)], [27, Vector2i(0, 3)], [1, Vector2i(0, 4)], [27, Vector2i(0, 4)], [1, Vector2i(1, 0)], [27, Vector2i(1, 0)], [1, Vector2i(1, 1)], [27, Vector2i(1, 1)], [1, Vector2i(1, 2)], [27, Vector2i(1, 2)], [1, Vector2i(1, 3)], [27, Vector2i(1, 3)], [1, Vector2i(1, 4)], [27, Vector2i(1, 4)], [1, Vector2i(2, 0)], [27, Vector2i(2, 0)], [1, Vector2i(2, 1)], [27, Vector2i(2, 1)], [1, Vector2i(2, 2)], [27, Vector2i(2, 2)], [1, Vector2i(2, 3)], [27, Vector2i(2, 3)], [1, Vector2i(2, 4)], [27, Vector2i(2, 4)], [1, Vector2i(3, 0)], [27, Vector2i(3, 0)], [1, Vector2i(3, 1)], [27, Vector2i(3, 1)], [1, Vector2i(3, 2)], [27, Vector2i(3, 2)], [1, Vector2i(3, 3)], [27, Vector2i(3, 3)], [1, Vector2i(3, 4)], [27, Vector2i(3, 4)], [3, Vector2i(0, 0)], [27, Vector2i(4, 0)]]
diff --git a/assets/ui/items/RA_Item_Icons.png b/assets/ui/items/RA_Item_Icons.png
new file mode 100644
index 0000000..05a47e4
Binary files /dev/null and b/assets/ui/items/RA_Item_Icons.png differ
diff --git a/assets/sprites/characters/skeleton.png.import b/assets/ui/items/RA_Item_Icons.png.import
similarity index 67%
rename from assets/sprites/characters/skeleton.png.import
rename to assets/ui/items/RA_Item_Icons.png.import
index c6bf432..2297b59 100644
--- a/assets/sprites/characters/skeleton.png.import
+++ b/assets/ui/items/RA_Item_Icons.png.import
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
-uid="uid://csw18iu762nof"
-path="res://.godot/imported/skeleton.png-d8aa7afaf8eaf6f264fc0a343e305765.ctex"
+uid="uid://66p8cxahto41"
+path="res://.godot/imported/RA_Item_Icons.png-8faa9d60987733bb88370795d5b17825.ctex"
metadata={
"vram_texture": false
}
[deps]
-source_file="res://assets/sprites/characters/skeleton.png"
-dest_files=["res://.godot/imported/skeleton.png-d8aa7afaf8eaf6f264fc0a343e305765.ctex"]
+source_file="res://assets/ui/items/RA_Item_Icons.png"
+dest_files=["res://.godot/imported/RA_Item_Icons.png-8faa9d60987733bb88370795d5b17825.ctex"]
[params]
diff --git a/assets/ui/items/RA_Item_Icons_Background.png b/assets/ui/items/RA_Item_Icons_Background.png
new file mode 100644
index 0000000..e5e4443
Binary files /dev/null and b/assets/ui/items/RA_Item_Icons_Background.png differ
diff --git a/assets/ui/items/RA_Item_Icons_Background.png.import b/assets/ui/items/RA_Item_Icons_Background.png.import
new file mode 100644
index 0000000..465a7c2
--- /dev/null
+++ b/assets/ui/items/RA_Item_Icons_Background.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://g6dn4ukiy60l"
+path="res://.godot/imported/RA_Item_Icons_Background.png-9ba88c6492d283edcfdac3592d021c4a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://assets/ui/items/RA_Item_Icons_Background.png"
+dest_files=["res://.godot/imported/RA_Item_Icons_Background.png-9ba88c6492d283edcfdac3592d021c4a.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
diff --git a/components/FreeCameraComponent.tscn b/components/FreeCameraComponent.tscn
new file mode 100644
index 0000000..84227bf
--- /dev/null
+++ b/components/FreeCameraComponent.tscn
@@ -0,0 +1,13 @@
+[gd_scene load_steps=3 format=3 uid="uid://4ajjlg37ssja"]
+
+[ext_resource type="Script" uid="uid://dy08nb2aotcf7" path="res://components/scripts/free_camera_component.gd" id="1_ang1s"]
+[ext_resource type="Script" uid="uid://ox4lxm83ciu" path="res://components/scripts/free_camera_game_camera_component.gd" id="2_3cl4c"]
+
+[node name="FreeCameraComponent" type="Node2D"]
+script = ExtResource("1_ang1s")
+
+[node name="GameCamera" type="Camera2D" parent="."]
+script = ExtResource("2_3cl4c")
+key = false
+edge = true
+wheel = false
diff --git a/components/scripts/free_camera_component.gd b/components/scripts/free_camera_component.gd
new file mode 100644
index 0000000..1de5d3e
--- /dev/null
+++ b/components/scripts/free_camera_component.gd
@@ -0,0 +1,17 @@
+extends Node2D
+class_name FreeCameraComponent
+
+@onready var camera: GameCamera = $GameCamera
+
+func _ready() -> void:
+ var map_size_x: int = Global.TILE_SIZE * (Global.ROOM_WIDTH * Global.GRID_WIDTH)
+ var map_size_y: int = Global.TILE_SIZE * (Global.ROOM_HEIGHT * Global.GRID_HEIGHT)
+
+ Log.pr("Map size: ", map_size_x, map_size_y)
+
+ camera.set_limit(SIDE_LEFT, -100)
+ camera.set_limit(SIDE_TOP, -100)
+ camera.set_limit(SIDE_RIGHT, map_size_x + 100)
+ camera.set_limit(SIDE_BOTTOM, map_size_y + 100)
+
+ pass
diff --git a/components/scripts/free_camera_component.gd.uid b/components/scripts/free_camera_component.gd.uid
new file mode 100644
index 0000000..f0bba64
--- /dev/null
+++ b/components/scripts/free_camera_component.gd.uid
@@ -0,0 +1 @@
+uid://dy08nb2aotcf7
diff --git a/components/scripts/free_camera_game_camera_component.gd b/components/scripts/free_camera_game_camera_component.gd
new file mode 100644
index 0000000..c6a2577
--- /dev/null
+++ b/components/scripts/free_camera_game_camera_component.gd
@@ -0,0 +1,118 @@
+extends Camera2D
+class_name GameCamera
+
+@export var key: bool = true
+@export var drag: bool = true
+@export var edge: bool = false
+@export var wheel: bool = true
+
+@export var zoom_in_limit: float = 2.5 # The max vector2 zoom value
+
+@export var camera_speed: int = 450 # Camera speed in px/s
+@export var camera_margin: int = 50 # Pixels around edge of screen where the mouse will start moving the camera
+
+# Initial zoom value taken from Editor.
+var camera_zoom: Vector2 = zoom
+
+const camera_zoom_speed = Vector2(0.5, 0.5)
+var camera_movement: Vector2 = Vector2.ZERO
+var _prev_mouse_pos: Vector2 = Vector2.ZERO
+
+# INPUTS
+
+# Right mouse button was or is pressed.
+var __rmbk: bool = false
+# Move camera by keys: left, top, right, bottom.
+var __keys: Array[bool] = [false, false, false, false]
+
+func _ready() -> void:
+ set_drag_horizontal_enabled(false)
+ set_drag_vertical_enabled(false)
+ set_position_smoothing_enabled(true)
+ set_position_smoothing_speed(4)
+
+func _physics_process(delta: float) -> void:
+ # Move camera by keys defined in InputMap (ui_left/top/right/bottom).
+ if key:
+ if __keys[0]:
+ camera_movement.x -= camera_speed * delta
+ if __keys[1]:
+ camera_movement.y -= camera_speed * delta
+ if __keys[2]:
+ camera_movement.x += camera_speed * delta
+ if __keys[3]:
+ camera_movement.y += camera_speed * delta
+
+ # Move camera by mouse, when it's on the margin (defined by camera_margin).
+ if edge:
+ var rec: Rect2 = get_viewport().get_visible_rect()
+ var v: Vector2 = get_local_mouse_position() + rec.size / 2
+ if rec.size.x - v.x <= camera_margin:
+ camera_movement.x += camera_speed * delta
+ if v.x <= camera_margin:
+ camera_movement.x -= camera_speed * delta
+ if rec.size.y - v.y <= camera_margin:
+ camera_movement.y += camera_speed * delta
+ if v.y <= camera_margin:
+ camera_movement.y -= camera_speed * delta
+
+ # When RMB is pressed, move camera by difference of mouse position
+ if drag and __rmbk:
+ camera_movement = _prev_mouse_pos - get_local_mouse_position()
+
+ # If this movement would take us out of clamp we don't want to do it:
+ var position_change: Vector2 = camera_movement * get_zoom()
+
+ if (position.x + position_change.x) < limit_left or \
+ (position.x + position_change.x) > limit_right:
+ camera_movement.x = 0
+
+ if (position.y + position_change.y) < limit_top or \
+ (position.y + position_change.y) > limit_bottom:
+ camera_movement.y = 0
+
+ if camera_movement.y != 0 or camera_movement.x != 0:
+ # Update position of the camera.
+ position += camera_movement * get_zoom()
+
+ # Set camera movement to zero, update old mouse position.
+ camera_movement = Vector2(0, 0)
+ _prev_mouse_pos = get_local_mouse_position()
+
+func _unhandled_input(event: InputEvent) -> void:
+ if event is InputEventMouseButton:
+ var mouse_event: InputEventMouseButton = event as InputEventMouseButton
+ if drag and \
+ mouse_event.button_index == MOUSE_BUTTON_RIGHT:
+ # Control by right mouse button.
+ if mouse_event.pressed: __rmbk = true
+ else: __rmbk = false
+ # Check if mouse wheel was used. Not handled by InputMap!
+ if wheel:
+ if mouse_event.button_index == MOUSE_BUTTON_WHEEL_DOWN and \
+ camera_zoom.x - camera_zoom_speed.x > 0 and \
+ camera_zoom.y - camera_zoom_speed.y > 0:
+ camera_zoom -= camera_zoom_speed
+ set_zoom(camera_zoom)
+ if mouse_event.button_index == MOUSE_BUTTON_WHEEL_UP and \
+ camera_zoom.x + camera_zoom_speed.x < zoom_in_limit and \
+ camera_zoom.y + camera_zoom_speed.y < zoom_in_limit:
+ camera_zoom += camera_zoom_speed
+ set_zoom(camera_zoom)
+ # Control by keyboard handled by InpuMap.
+ if event.is_action_pressed("ui_left"):
+ __keys[0] = true
+ if event.is_action_pressed("ui_up"):
+ __keys[1] = true
+ if event.is_action_pressed("ui_right"):
+ __keys[2] = true
+ if event.is_action_pressed("ui_down"):
+ __keys[3] = true
+ if event.is_action_released("ui_left"):
+ __keys[0] = false
+ if event.is_action_released("ui_up"):
+ __keys[1] = false
+ if event.is_action_released("ui_right"):
+ __keys[2] = false
+ if event.is_action_released("ui_down"):
+ __keys[3] = false
diff --git a/components/scripts/free_camera_game_camera_component.gd.uid b/components/scripts/free_camera_game_camera_component.gd.uid
new file mode 100644
index 0000000..ed61a8e
--- /dev/null
+++ b/components/scripts/free_camera_game_camera_component.gd.uid
@@ -0,0 +1 @@
+uid://ox4lxm83ciu
diff --git a/map/Map.tscn b/map/Map.tscn
new file mode 100644
index 0000000..b4071fe
--- /dev/null
+++ b/map/Map.tscn
@@ -0,0 +1,19 @@
+[gd_scene load_steps=4 format=3 uid="uid://cfkusqucsap26"]
+
+[ext_resource type="Script" uid="uid://begwu0icmrxyw" path="res://map/map.gd" id="1_l804v"]
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="2_3nv2f"]
+[ext_resource type="PackedScene" uid="uid://bo5aw2cad3akl" path="res://player/player.tscn" id="5_3nv2f"]
+
+[node name="Map" type="Node2D"]
+script = ExtResource("1_l804v")
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_set = ExtResource("2_3nv2f")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_set = ExtResource("2_3nv2f")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_set = ExtResource("2_3nv2f")
+
+[node name="Player" parent="." instance=ExtResource("5_3nv2f")]
diff --git a/map/map.gd b/map/map.gd
new file mode 100644
index 0000000..d473480
--- /dev/null
+++ b/map/map.gd
@@ -0,0 +1,105 @@
+extends Node2D
+
+## MAP VARIABLES
+var GRID_WIDTH = Global.GRID_WIDTH
+var GRID_HEIGHT = Global.GRID_HEIGHT
+var EMPTY = Global.MAP_EMPTY
+var PATH = Global.MAP_PATH
+var START = Global.MAP_START
+var FINISH = Global.MAP_FINISH
+var UP_CELL = Global.MAP_UP_CELL
+
+## MAP LAYERS
+var ground: TileMapLayer
+var water: TileMapLayer
+var walls: TileMapLayer
+var scatter: TileMapLayer
+var cells: Array
+
+## MAP
+var map: Array
+
+var map_width: int
+var map_height: int
+
+func _ready() -> void:
+ Log.pr("Level ready")
+
+ map_width = Global.GRID_WIDTH * Global.ROOM_WIDTH
+ map_height = Global.GRID_HEIGHT * Global.ROOM_HEIGHT
+
+ ground = $Ground
+ water = $Water
+ #walls = $Walls
+ scatter = $Scatter
+
+ cells = CoordUtil.all_cells(Vector2i(-1, -1), Vector2i(map_width + 1, map_height + 1))
+
+ #RNG.set_seed(123456789)
+ Log.pr(RNG.get_seed())
+ Log.pr(RNG.randf())
+ Log.pr(RNG.randf())
+ Log.pr(RNG.randf())
+ Log.pr(RNG.randf())
+ #RNG.set_seed(123456789)
+ Log.pr(RNG.get_seed())
+ Log.pr(RNG.randf())
+ Log.pr(RNG.randf())
+ Log.pr(RNG.randf())
+ Log.pr(RNG.randf())
+ generateMap()
+
+func generateMap() -> void:
+ Log.pr("Generating map")
+
+ var map_generator = MapGenerator.new()
+ map_generator.generate_map()
+ map_generator.print_map()
+
+ map = map_generator.get_map()
+ Log.pr(map)
+
+ drawGround()
+ drawPerimiter()
+ populateMapCells()
+
+func drawGround() -> void:
+ Log.pr("Drawing ground")
+ ground.clear()
+ ground.set_cells_terrain_connect(cells, 0, 0, false)
+
+func drawPerimiter() -> void:
+ Log.pr("Drawing perimeter")
+ var perimeter_cells: Array = CoordUtil.perimeter_cells(Vector2i(0, 0), Vector2i(map_width - 1, map_height - 1), false, 2)
+ water.set_cells_terrain_connect(perimeter_cells, 0, 1, false)
+
+func populateMapCells() -> void:
+ # Loop through the map and check which type of cell it is
+ Log.pr("Populating map cells")
+ for y in range(GRID_HEIGHT):
+ for x in range(GRID_WIDTH):
+ var cell_value = map[y][x]
+
+ var scene = null
+
+ match cell_value:
+ EMPTY:
+ scene = SceneSelector.get_random_scene("res://mapgen/layout/misc")
+ Log.pr("Empty cell ", y, x, scene)
+ PATH:
+ scene = SceneSelector.get_random_scene("res://mapgen/layout/path")
+ Log.pr("Path cell", y, x, scene)
+ START:
+ scene = SceneSelector.get_random_scene("res://mapgen/layout/start")
+ Log.pr("Start cell", y, x, scene)
+ FINISH:
+ scene = SceneSelector.get_random_scene("res://mapgen/layout/finish")
+ Log.pr("Finish cell", y, x, scene)
+ UP_CELL:
+ scene = SceneSelector.get_random_scene("res://mapgen/layout/link")
+ Log.pr("Up cell", y, x, scene)
+
+ MapBuilder.copy_tilemap_to_target(scene, water, "Water", Vector2i(x, y))
+ MapBuilder.copy_tilemap_to_target(scene, scatter, "Scatter", Vector2i(x, y))
+ #walls.set_cells_terrain_connect(cells, 0, 0, true)
+ MapBuilder.redraw_terrain(cells, water, 0, 1)
diff --git a/map/map.gd.uid b/map/map.gd.uid
new file mode 100644
index 0000000..24d32b5
--- /dev/null
+++ b/map/map.gd.uid
@@ -0,0 +1 @@
+uid://begwu0icmrxyw
diff --git a/mapgen/layout/finish/001.tscn b/mapgen/layout/finish/001.tscn
new file mode 100644
index 0000000..ea6f636
--- /dev/null
+++ b/mapgen/layout/finish/001.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=2 format=4 uid="uid://bojsp6ifotdwm"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_xrr0o"]
+
+[node name="Finish-001" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAA8AAAADAAIAAAAAAA4AAAADAAIAAAAAAA0AAAADAAIAAAAAAAwAAAADAAIAAAAAAAsAAAADAAIAAAAAAAoAAAADAAIAAAAAAAkAAAADAAIAAAAAAAgAAAADAAIAAAAAAAcAAAADAAIAAAAAAAYAAAADAAIAAAAAAAUAAAADAAIAAAAAAAQAAAADAAIAAAAAAAMAAAADAAIAAAAAAAIAAAADAAIAAAAAAAEAAAADAAIAAAAAAAAAAAADAAIAAAAJAAAAAAADAAIAAAAIAAAAAAADAAIAAAAHAAAAAAADAAIAAAAGAAAAAAADAAIAAAAFAAAAAAADAAIAAAAEAAAAAAADAAIAAAADAAAAAAADAAIAAAACAAAAAAADAAIAAAABAAAAAAADAAIAAAATAAAAAAADAAIAAAASAAAAAAADAAIAAAARAAAAAAADAAIAAAAQAAAAAAADAAIAAAAPAAAAAAADAAIAAAAOAAAAAAADAAIAAAANAAAAAAADAAIAAAAMAAAAAAADAAIAAAALAAAAAAADAAIAAAAKAAAAAAADAAIAAAATAA8AAAADAAIAAAATAA4AAAADAAIAAAATAA0AAAADAAIAAAATAAwAAAADAAIAAAATAAsAAAADAAIAAAATAAoAAAADAAIAAAATAAkAAAADAAIAAAATAAgAAAADAAIAAAATAAcAAAADAAIAAAATAAYAAAADAAIAAAATAAUAAAADAAIAAAATAAQAAAADAAIAAAATAAMAAAADAAIAAAATAAIAAAADAAIAAAATAAEAAAADAAIAAAASAA8AAAADAAIAAAASAA4AAAADAAIAAAASAA0AAAADAAIAAAASAAwAAAADAAIAAAASAAsAAAADAAIAAAASAAoAAAADAAIAAAASAAkAAAADAAIAAAASAAgAAAADAAIAAAASAAcAAAADAAIAAAASAAYAAAADAAIAAAASAAUAAAADAAIAAAASAAQAAAADAAIAAAASAAMAAAADAAIAAAASAAIAAAADAAIAAAASAAEAAAADAAIAAAARAA8AAAADAAIAAAARAA4AAAADAAIAAAARAA0AAAADAAIAAAARAAwAAAADAAIAAAARAAsAAAADAAIAAAARAAoAAAADAAIAAAARAAkAAAADAAIAAAARAAgAAAADAAIAAAARAAcAAAADAAIAAAARAAYAAAADAAIAAAARAAUAAAADAAIAAAARAAQAAAADAAIAAAARAAMAAAADAAIAAAARAAIAAAADAAIAAAARAAEAAAADAAIAAAAQAA8AAAADAAIAAAAQAA4AAAADAAIAAAAQAA0AAAADAAIAAAAQAAwAAAADAAIAAAAQAAsAAAADAAIAAAAQAAoAAAADAAIAAAAQAAkAAAADAAIAAAAQAAgAAAADAAIAAAAQAAcAAAADAAIAAAAQAAYAAAADAAIAAAAQAAUAAAADAAIAAAAQAAQAAAADAAIAAAAQAAMAAAADAAIAAAAQAAIAAAADAAIAAAAQAAEAAAADAAIAAAAPAA8AAAADAAIAAAAPAA4AAAADAAIAAAAPAA0AAAADAAIAAAAPAAwAAAADAAIAAAAPAAsAAAADAAIAAAAPAAoAAAADAAIAAAAPAAkAAAADAAIAAAAPAAgAAAADAAIAAAAPAAcAAAADAAIAAAAPAAYAAAADAAIAAAAPAAUAAAADAAIAAAAPAAQAAAADAAIAAAAPAAMAAAADAAIAAAAPAAIAAAADAAIAAAAPAAEAAAADAAIAAAAOAA8AAAADAAIAAAAOAA4AAAADAAIAAAAOAA0AAAADAAIAAAAOAAwAAAADAAIAAAAOAAsAAAADAAIAAAAOAAoAAAADAAIAAAAOAAkAAAADAAIAAAAOAAgAAAADAAIAAAAOAAcAAAADAAIAAAAOAAYAAAADAAIAAAAOAAUAAAADAAIAAAAOAAQAAAADAAIAAAAOAAMAAAADAAIAAAAOAAIAAAADAAIAAAAOAAEAAAADAAIAAAANAA8AAAADAAIAAAANAA4AAAADAAIAAAANAA0AAAADAAIAAAANAAwAAAADAAIAAAANAAsAAAADAAIAAAANAAoAAAADAAIAAAANAAkAAAADAAIAAAANAAgAAAADAAIAAAANAAcAAAADAAIAAAANAAYAAAADAAIAAAANAAUAAAADAAIAAAANAAQAAAADAAIAAAANAAMAAAADAAIAAAANAAIAAAADAAIAAAANAAEAAAADAAIAAAAMAA8AAAADAAIAAAAMAA4AAAADAAIAAAAMAA0AAAADAAIAAAAMAAwAAAADAAIAAAAMAAsAAAADAAIAAAAMAAoAAAADAAIAAAAMAAkAAAADAAIAAAAMAAgAAAADAAIAAAAMAAcAAAADAAIAAAAMAAYAAAADAAIAAAAMAAUAAAADAAIAAAAMAAQAAAADAAIAAAAMAAMAAAADAAIAAAAMAAIAAAADAAIAAAAMAAEAAAADAAIAAAALAA8AAAADAAIAAAALAA4AAAADAAIAAAALAA0AAAADAAIAAAALAAwAAAADAAIAAAALAAsAAAADAAIAAAALAAoAAAADAAIAAAALAAkAAAADAAIAAAALAAgAAAADAAIAAAALAAcAAAADAAIAAAALAAYAAAADAAIAAAALAAUAAAADAAIAAAALAAQAAAADAAIAAAALAAMAAAADAAIAAAALAAIAAAADAAIAAAALAAEAAAADAAIAAAAKAA8AAAADAAIAAAAKAA4AAAADAAIAAAAKAA0AAAADAAIAAAAKAAwAAAADAAIAAAAKAAsAAAADAAIAAAAKAAoAAAADAAIAAAAKAAkAAAADAAIAAAAKAAgAAAADAAIAAAAKAAcAAAADAAIAAAAKAAYAAAADAAIAAAAKAAUAAAADAAIAAAAKAAQAAAADAAIAAAAKAAMAAAADAAIAAAAKAAIAAAADAAIAAAAKAAEAAAADAAIAAAAJAA8AAAADAAIAAAAJAA4AAAADAAIAAAAJAA0AAAADAAIAAAAJAAwAAAADAAIAAAAJAAsAAAADAAIAAAAJAAoAAAADAAIAAAAJAAkAAAADAAIAAAAJAAgAAAADAAIAAAAJAAcAAAADAAIAAAAJAAYAAAADAAIAAAAJAAUAAAADAAIAAAAJAAQAAAADAAIAAAAJAAMAAAADAAIAAAAJAAIAAAADAAIAAAAJAAEAAAADAAIAAAAIAA8AAAADAAIAAAAIAA4AAAADAAIAAAAIAA0AAAADAAIAAAAIAAwAAAADAAIAAAAIAAsAAAADAAIAAAAIAAoAAAADAAIAAAAIAAkAAAADAAIAAAAIAAgAAAADAAIAAAAIAAcAAAADAAIAAAAIAAYAAAADAAIAAAAIAAUAAAADAAIAAAAIAAQAAAADAAIAAAAIAAMAAAADAAIAAAAIAAIAAAADAAIAAAAIAAEAAAADAAIAAAAHAA8AAAADAAIAAAAHAA4AAAADAAIAAAAHAA0AAAADAAIAAAAHAAwAAAADAAIAAAAHAAsAAAADAAIAAAAHAAoAAAADAAIAAAAHAAkAAAADAAIAAAAHAAgAAAADAAIAAAAHAAcAAAADAAIAAAAHAAYAAAADAAIAAAAHAAUAAAADAAIAAAAHAAQAAAADAAIAAAAHAAMAAAADAAIAAAAHAAIAAAADAAIAAAAHAAEAAAADAAIAAAAGAA8AAAADAAIAAAAGAA4AAAADAAIAAAAGAA0AAAADAAIAAAAGAAwAAAADAAIAAAAGAAsAAAADAAIAAAAGAAoAAAADAAIAAAAGAAkAAAADAAIAAAAGAAgAAAADAAIAAAAGAAcAAAADAAIAAAAGAAYAAAADAAIAAAAGAAUAAAADAAIAAAAGAAQAAAADAAIAAAAGAAMAAAADAAIAAAAGAAIAAAADAAIAAAAGAAEAAAADAAIAAAAFAA8AAAADAAIAAAAFAA4AAAADAAIAAAAFAA0AAAADAAIAAAAFAAwAAAADAAIAAAAFAAsAAAADAAIAAAAFAAoAAAADAAIAAAAFAAkAAAADAAIAAAAFAAgAAAADAAIAAAAFAAcAAAADAAIAAAAFAAYAAAADAAIAAAAFAAUAAAADAAIAAAAFAAQAAAADAAIAAAAFAAMAAAADAAIAAAAFAAIAAAADAAIAAAAFAAEAAAADAAIAAAAEAA8AAAADAAIAAAAEAA4AAAADAAIAAAAEAA0AAAADAAIAAAAEAAwAAAADAAIAAAAEAAsAAAADAAIAAAAEAAoAAAADAAIAAAAEAAkAAAADAAIAAAAEAAgAAAADAAIAAAAEAAcAAAADAAIAAAAEAAYAAAADAAIAAAAEAAUAAAADAAIAAAAEAAQAAAADAAIAAAAEAAMAAAADAAIAAAAEAAIAAAADAAIAAAAEAAEAAAADAAIAAAADAA8AAAADAAIAAAADAA4AAAADAAIAAAADAA0AAAADAAIAAAADAAwAAAADAAIAAAADAAsAAAADAAIAAAADAAoAAAADAAIAAAADAAkAAAADAAIAAAADAAgAAAADAAIAAAADAAcAAAADAAIAAAADAAYAAAADAAIAAAADAAUAAAADAAIAAAADAAQAAAADAAIAAAADAAMAAAADAAIAAAADAAIAAAADAAIAAAADAAEAAAADAAIAAAACAA8AAAADAAIAAAACAA4AAAADAAIAAAACAA0AAAADAAIAAAACAAwAAAADAAIAAAACAAsAAAADAAIAAAACAAoAAAADAAIAAAACAAkAAAADAAIAAAACAAgAAAADAAIAAAACAAcAAAADAAIAAAACAAYAAAADAAIAAAACAAUAAAADAAIAAAACAAQAAAADAAIAAAACAAMAAAADAAIAAAACAAIAAAADAAIAAAACAAEAAAADAAIAAAABAA8AAAADAAIAAAABAA4AAAADAAIAAAABAA0AAAADAAIAAAABAAwAAAADAAIAAAABAAsAAAADAAIAAAABAAoAAAADAAIAAAABAAkAAAADAAIAAAABAAgAAAADAAIAAAABAAcAAAADAAIAAAABAAYAAAADAAIAAAABAAUAAAADAAIAAAABAAQAAAADAAIAAAABAAMAAAADAAIAAAABAAIAAAADAAIAAAABAAEAAAADAAIAAAA=")
+tile_set = ExtResource("1_xrr0o")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAEAAAAMAAIAAAAEAAAAAAAMAAAAAAADAAEAAAALAAIAAAADAAAAAAALAAAAAAACAAEAAAALAAIAAAACAAAAAAALAAAAAAABAAEAAAAOAAEAAAABAAAAAAALAAAAAAAAAAEAAAAKAAEAAAAAAAAAAAAKAAAAAAABAAMAAAAMAAIAAAABAAIAAAAMAAEAAAAAAAMAAAAKAAIAAAAAAAIAAAAKAAEAAAATAAEAAAAMAAEAAAATAAAAAAAMAAAAAAASAAEAAAANAAEAAAASAAAAAAALAAAAAAARAAEAAAALAAIAAAARAAAAAAALAAAAAAAQAAEAAAAKAAIAAAAQAAAAAAAKAAAAAAATAAMAAAAMAAIAAAATAAIAAAAMAAEAAAASAAMAAAAKAAIAAAASAAIAAAAKAAEAAAATAA8AAAAMAAIAAAATAA4AAAAMAAEAAAASAA8AAAALAAIAAAASAA4AAAANAAAAAAARAA8AAAALAAIAAAARAA4AAAALAAAAAAAQAA8AAAAKAAIAAAAQAA4AAAAKAAAAAAATAA0AAAAMAAAAAAASAA0AAAAKAAAAAAADAA8AAAAMAAIAAAADAA4AAAAMAAAAAAACAA8AAAALAAIAAAACAA4AAAALAAAAAAABAA8AAAALAAIAAAABAA4AAAAOAAAAAAAAAA8AAAAKAAIAAAAAAA4AAAAKAAEAAAABAA0AAAAMAAEAAAABAAwAAAAMAAAAAAAAAA0AAAAKAAEAAAAAAAwAAAAKAAAAAAAKAAQAAAAMAAIAAAAIAAQAAAALAAIAAAAHAAQAAAALAAIAAAAGAAQAAAAOAAEAAAAGAAsAAAAMAAIAAAAGAAoAAAAMAAEAAAAGAAkAAAAMAAEAAAAGAAgAAAAOAAEAAAAGAAcAAAAOAAAAAAAGAAYAAAAMAAEAAAAGAAUAAAAMAAEAAAAJAAgAAAAMAAIAAAAIAAgAAAALAAIAAAAHAAgAAAALAAIAAAAKAAMAAAAMAAAAAAAJAAMAAAALAAAAAAAIAAMAAAALAAAAAAAHAAMAAAALAAAAAAAGAAMAAAALAAAAAAAFAAMAAAAKAAAAAAAFAAsAAAAKAAIAAAAFAAoAAAAKAAEAAAAFAAkAAAAKAAEAAAAFAAgAAAAKAAEAAAAFAAcAAAAKAAEAAAAFAAYAAAAKAAEAAAAFAAUAAAAKAAEAAAAFAAQAAAAKAAEAAAAIAAcAAAALAAAAAAAHAAcAAAALAAAAAAAJAAcAAAAMAAAAAAAJAAQAAAALAAIAAAAMAAsAAAAMAAIAAAAMAAoAAAAMAAEAAAAMAAkAAAAMAAEAAAAMAAgAAAAMAAEAAAAMAAcAAAAMAAAAAAALAAsAAAAKAAIAAAALAAoAAAAKAAEAAAALAAkAAAAKAAEAAAALAAgAAAAKAAEAAAALAAcAAAAKAAAAAAAPAAsAAAAMAAIAAAAPAAoAAAAMAAEAAAAPAAkAAAAOAAEAAAAPAAgAAAALAAAAAAAOAAsAAAAKAAIAAAAOAAoAAAAKAAEAAAAOAAkAAAAKAAEAAAAOAAgAAAAKAAAAAAARAAkAAAANAAEAAAARAAgAAAALAAEAAAAQAAkAAAALAAIAAAAQAAgAAAANAAAAAAASAAoAAAAMAAEAAAASAAkAAAAMAAEAAAASAAgAAAAMAAEAAAARAAoAAAAKAAEAAAASAAsAAAAMAAIAAAARAAsAAAAKAAIAAAASAAcAAAAMAAAAAAARAAcAAAALAAAAAAAQAAcAAAAKAAAAAAAMAAUAAAAKAAIAAAAMAAQAAAAKAAAAAAANAAQAAAAMAAAAAAANAAUAAAAMAAIAAAA=")
+tile_set = ExtResource("1_xrr0o")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAMAEwADAAEAAAAFAAMAEwAEAAEAAAA=")
+tile_set = ExtResource("1_xrr0o")
diff --git a/mapgen/layout/link/001.tscn b/mapgen/layout/link/001.tscn
new file mode 100644
index 0000000..ff7e06f
--- /dev/null
+++ b/mapgen/layout/link/001.tscn
@@ -0,0 +1,21 @@
+[gd_scene load_steps=2 format=4 uid="uid://cn5l7ev6eesln"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_8ayb0"]
+
+[node name="Link-001" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAAAAAAADAAIAAAAAAAEAAAADAAIAAAAAAAIAAAADAAIAAAAAAAMAAAADAAIAAAAAAAQAAAADAAIAAAAAAAUAAAADAAIAAAAAAAYAAAADAAIAAAAAAAcAAAADAAIAAAAAAAgAAAADAAIAAAAAAAkAAAADAAIAAAAAAAoAAAADAAIAAAAAAAsAAAADAAIAAAAAAAwAAAADAAIAAAAAAA0AAAADAAIAAAAAAA4AAAADAAIAAAAAAA8AAAADAAIAAAABAAAAAAADAAIAAAABAAEAAAADAAIAAAABAAIAAAADAAIAAAABAAMAAAADAAIAAAABAAQAAAADAAIAAAABAAUAAAADAAIAAAABAAYAAAADAAIAAAABAAcAAAADAAIAAAABAAgAAAADAAIAAAABAAkAAAADAAIAAAABAAoAAAADAAIAAAABAAsAAAADAAIAAAABAAwAAAADAAIAAAABAA0AAAADAAIAAAABAA4AAAADAAIAAAABAA8AAAADAAIAAAACAAAAAAADAAIAAAACAAEAAAADAAIAAAACAAIAAAADAAIAAAACAAMAAAADAAIAAAACAAQAAAADAAIAAAACAAUAAAADAAIAAAACAAYAAAADAAIAAAACAAcAAAADAAIAAAACAAgAAAADAAIAAAACAAkAAAADAAIAAAACAAoAAAADAAIAAAACAAsAAAADAAIAAAACAAwAAAADAAIAAAACAA0AAAADAAIAAAACAA4AAAADAAIAAAACAA8AAAADAAIAAAADAAAAAAADAAIAAAADAAEAAAADAAIAAAADAAIAAAADAAIAAAADAAMAAAADAAIAAAADAAQAAAADAAIAAAADAAUAAAADAAIAAAADAAYAAAADAAIAAAADAAcAAAADAAIAAAADAAgAAAADAAIAAAADAAkAAAADAAIAAAADAAoAAAADAAIAAAADAAsAAAADAAIAAAADAAwAAAADAAIAAAADAA0AAAADAAIAAAADAA4AAAADAAIAAAADAA8AAAADAAIAAAAEAAAAAAADAAIAAAAEAAEAAAADAAIAAAAEAAIAAAADAAIAAAAEAAMAAAADAAIAAAAEAAQAAAADAAIAAAAEAAUAAAADAAIAAAAEAAYAAAADAAIAAAAEAAcAAAADAAIAAAAEAAgAAAADAAIAAAAEAAkAAAADAAIAAAAEAAoAAAADAAIAAAAEAAsAAAADAAIAAAAEAAwAAAADAAIAAAAEAA0AAAADAAIAAAAEAA4AAAADAAIAAAAEAA8AAAADAAIAAAAFAAAAAAADAAIAAAAFAAEAAAADAAIAAAAFAAIAAAADAAIAAAAFAAMAAAADAAIAAAAFAAQAAAADAAIAAAAFAAUAAAADAAIAAAAFAAYAAAADAAIAAAAFAAcAAAADAAIAAAAFAAgAAAADAAIAAAAFAAkAAAADAAIAAAAFAAoAAAADAAIAAAAFAAsAAAADAAIAAAAFAAwAAAADAAIAAAAFAA0AAAADAAIAAAAFAA4AAAADAAIAAAAFAA8AAAADAAIAAAAGAAAAAAADAAIAAAAGAAEAAAADAAIAAAAGAAIAAAADAAIAAAAGAAMAAAADAAIAAAAGAAQAAAADAAIAAAAGAAUAAAADAAIAAAAGAAYAAAADAAIAAAAGAAcAAAADAAIAAAAGAAgAAAADAAIAAAAGAAkAAAADAAIAAAAGAAoAAAADAAIAAAAGAAsAAAADAAIAAAAGAAwAAAADAAIAAAAGAA0AAAADAAIAAAAGAA4AAAADAAIAAAAGAA8AAAADAAIAAAAHAAAAAAADAAIAAAAHAAEAAAADAAIAAAAHAAIAAAADAAIAAAAHAAMAAAADAAIAAAAHAAQAAAADAAIAAAAHAAUAAAADAAIAAAAHAAYAAAADAAIAAAAHAAcAAAADAAIAAAAHAAgAAAADAAIAAAAHAAkAAAADAAIAAAAHAAoAAAADAAIAAAAHAAsAAAADAAIAAAAHAAwAAAADAAIAAAAHAA0AAAADAAIAAAAHAA4AAAADAAIAAAAHAA8AAAADAAIAAAAIAAAAAAADAAIAAAAIAAEAAAADAAIAAAAIAAIAAAADAAIAAAAIAAMAAAADAAIAAAAIAAQAAAADAAIAAAAIAAUAAAADAAIAAAAIAAYAAAADAAIAAAAIAAcAAAADAAIAAAAIAAgAAAADAAIAAAAIAAkAAAADAAIAAAAIAAoAAAADAAIAAAAIAAsAAAADAAIAAAAIAAwAAAADAAIAAAAIAA0AAAADAAIAAAAIAA4AAAADAAIAAAAIAA8AAAADAAIAAAAJAAAAAAADAAIAAAAJAAEAAAADAAIAAAAJAAIAAAADAAIAAAAJAAMAAAADAAIAAAAJAAQAAAADAAIAAAAJAAUAAAADAAIAAAAJAAYAAAADAAIAAAAJAAcAAAADAAIAAAAJAAgAAAADAAIAAAAJAAkAAAADAAIAAAAJAAoAAAADAAIAAAAJAAsAAAADAAIAAAAJAAwAAAADAAIAAAAJAA0AAAADAAIAAAAJAA4AAAADAAIAAAAJAA8AAAADAAIAAAAKAAAAAAADAAIAAAAKAAEAAAADAAIAAAAKAAIAAAADAAIAAAAKAAMAAAADAAIAAAAKAAQAAAADAAIAAAAKAAUAAAADAAIAAAAKAAYAAAADAAIAAAAKAAcAAAADAAIAAAAKAAgAAAADAAIAAAAKAAkAAAADAAIAAAAKAAoAAAADAAIAAAAKAAsAAAADAAIAAAAKAAwAAAADAAIAAAAKAA0AAAADAAIAAAAKAA4AAAADAAIAAAAKAA8AAAADAAIAAAALAAAAAAADAAIAAAALAAEAAAADAAIAAAALAAIAAAADAAIAAAALAAMAAAADAAIAAAALAAQAAAADAAIAAAALAAUAAAADAAIAAAALAAYAAAADAAIAAAALAAcAAAADAAIAAAALAAgAAAADAAIAAAALAAkAAAADAAIAAAALAAoAAAADAAIAAAALAAsAAAADAAIAAAALAAwAAAADAAIAAAALAA0AAAADAAIAAAALAA4AAAADAAIAAAALAA8AAAADAAIAAAAMAAAAAAADAAIAAAAMAAEAAAADAAIAAAAMAAIAAAADAAIAAAAMAAMAAAADAAIAAAAMAAQAAAADAAIAAAAMAAUAAAADAAIAAAAMAAYAAAADAAIAAAAMAAcAAAADAAIAAAAMAAgAAAADAAIAAAAMAAkAAAADAAIAAAAMAAoAAAADAAIAAAAMAAsAAAADAAIAAAAMAAwAAAADAAIAAAAMAA0AAAADAAIAAAAMAA4AAAADAAIAAAAMAA8AAAADAAIAAAANAAAAAAADAAIAAAANAAEAAAADAAIAAAANAAIAAAADAAIAAAANAAMAAAADAAIAAAANAAQAAAADAAIAAAANAAUAAAADAAIAAAANAAYAAAADAAIAAAANAAcAAAADAAIAAAANAAgAAAADAAIAAAANAAkAAAADAAIAAAANAAoAAAADAAIAAAANAAsAAAADAAIAAAANAAwAAAADAAIAAAANAA0AAAADAAIAAAANAA4AAAADAAIAAAANAA8AAAADAAIAAAAOAAAAAAADAAIAAAAOAAEAAAADAAIAAAAOAAIAAAADAAIAAAAOAAMAAAADAAIAAAAOAAQAAAADAAIAAAAOAAUAAAADAAIAAAAOAAYAAAADAAIAAAAOAAcAAAADAAIAAAAOAAgAAAADAAIAAAAOAAkAAAADAAIAAAAOAAoAAAADAAIAAAAOAAsAAAADAAIAAAAOAAwAAAADAAIAAAAOAA0AAAADAAIAAAAOAA4AAAADAAIAAAAOAA8AAAADAAIAAAAPAAAAAAADAAIAAAAPAAEAAAADAAIAAAAPAAIAAAADAAIAAAAPAAMAAAADAAIAAAAPAAQAAAADAAIAAAAPAAUAAAADAAIAAAAPAAYAAAADAAIAAAAPAAcAAAADAAIAAAAPAAgAAAADAAIAAAAPAAkAAAADAAIAAAAPAAoAAAADAAIAAAAPAAsAAAADAAIAAAAPAAwAAAADAAIAAAAPAA0AAAADAAIAAAAPAA4AAAADAAIAAAAPAA8AAAADAAIAAAAQAAAAAAADAAIAAAAQAAEAAAADAAIAAAAQAAIAAAADAAIAAAAQAAMAAAADAAIAAAAQAAQAAAADAAIAAAAQAAUAAAADAAIAAAAQAAYAAAADAAIAAAAQAAcAAAADAAIAAAAQAAgAAAADAAIAAAAQAAkAAAADAAIAAAAQAAoAAAADAAIAAAAQAAsAAAADAAIAAAAQAAwAAAADAAIAAAAQAA0AAAADAAIAAAAQAA4AAAADAAIAAAAQAA8AAAADAAIAAAARAAAAAAADAAIAAAARAAEAAAADAAIAAAARAAIAAAADAAIAAAARAAMAAAADAAIAAAARAAQAAAADAAIAAAARAAUAAAADAAIAAAARAAYAAAADAAIAAAARAAcAAAADAAIAAAARAAgAAAADAAIAAAARAAkAAAADAAIAAAARAAoAAAADAAIAAAARAAsAAAADAAIAAAARAAwAAAADAAIAAAARAA0AAAADAAIAAAARAA4AAAADAAIAAAARAA8AAAADAAIAAAASAAAAAAADAAIAAAASAAEAAAADAAIAAAASAAIAAAADAAIAAAASAAMAAAADAAIAAAASAAQAAAADAAIAAAASAAUAAAADAAIAAAASAAYAAAADAAIAAAASAAcAAAADAAIAAAASAAgAAAADAAIAAAASAAkAAAADAAIAAAASAAoAAAADAAIAAAASAAsAAAADAAIAAAASAAwAAAADAAIAAAASAA0AAAADAAIAAAASAA4AAAADAAIAAAASAA8AAAADAAIAAAATAAAAAAADAAIAAAATAAEAAAADAAIAAAATAAIAAAADAAIAAAATAAMAAAADAAIAAAATAAQAAAADAAIAAAATAAUAAAADAAIAAAATAAYAAAADAAIAAAATAAcAAAADAAIAAAATAAgAAAADAAIAAAATAAkAAAADAAIAAAATAAoAAAADAAIAAAATAAsAAAADAAIAAAATAAwAAAADAAIAAAATAA0AAAADAAIAAAATAA4AAAADAAIAAAATAA8AAAADAAIAAAA=")
+tile_set = ExtResource("1_8ayb0")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAIAAUAAAALAAEAAAAIAAQAAAALAAEAAAAIAAMAAAALAAAAAAAHAAUAAAALAAEAAAAHAAQAAAALAAEAAAAHAAMAAAALAAAAAAAGAAUAAAALAAEAAAAGAAQAAAALAAEAAAAGAAMAAAALAAAAAAAFAAUAAAALAAEAAAAFAAQAAAALAAEAAAAFAAMAAAALAAAAAAAEAAUAAAALAAEAAAAEAAQAAAANAAAAAAAEAAMAAAAKAAAAAAALAAcAAAALAAEAAAALAAYAAAALAAEAAAALAAUAAAALAAEAAAALAAQAAAALAAAAAAAKAAcAAAANAAEAAAAKAAYAAAALAAEAAAAKAAUAAAALAAEAAAAKAAQAAAAOAAAAAAAJAAcAAAALAAIAAAAJAAYAAAALAAEAAAAJAAUAAAALAAEAAAAJAAQAAAALAAEAAAAIAAcAAAALAAIAAAAIAAYAAAALAAEAAAAHAAcAAAALAAIAAAAHAAYAAAALAAEAAAAGAAgAAAAMAAIAAAAGAAcAAAAOAAEAAAAGAAYAAAALAAEAAAAFAAgAAAAKAAIAAAAFAAcAAAANAAEAAAAFAAYAAAALAAEAAAAOAAkAAAAMAAIAAAAOAAgAAAAMAAEAAAAOAAcAAAAMAAAAAAANAAkAAAALAAIAAAANAAgAAAALAAEAAAANAAcAAAAOAAAAAAAMAAkAAAALAAIAAAAMAAgAAAALAAEAAAAMAAcAAAALAAEAAAALAAkAAAALAAIAAAALAAgAAAALAAEAAAAKAAkAAAAKAAIAAAAKAAgAAAAKAAEAAAANAAYAAAAMAAEAAAANAAUAAAAMAAEAAAANAAQAAAAMAAAAAAAMAAYAAAALAAEAAAAMAAUAAAALAAEAAAAMAAQAAAALAAAAAAAKAAMAAAAMAAAAAAAJAAMAAAALAAAAAAAEAAcAAAALAAIAAAAEAAYAAAALAAEAAAADAAcAAAAKAAIAAAADAAYAAAAKAAEAAAADAAUAAAAKAAEAAAADAAQAAAAKAAAAAAA=")
+tile_set = ExtResource("1_8ayb0")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAGAAYAAQAQAAIAAAAGAAcAAQAQAAMAAAAGAAgAAQAQAAQAAAAHAAYAAQARAAIAAAAHAAcAAQARAAMAAAAHAAgAAQARAAQAAAAHAAoAAQAQAAEAAAALAAcAAQAWAAIAAAALAAgAAQAWAAMAAAAMAAcAAQAXAAIAAAAMAAgAAQAXAAMAAAAJAAQAAQAaAAEAAAAJAAUAAQAaAAIAAAAKAAQAAQAbAAEAAAAKAAUAAQAbAAIAAAAEAAYAAQAeAAEAAAACAAMAAQATAAQAAAAFAAEAAQAWAAcAAAAFAAIAAQAWAAgAAAANAAcAAQAAAAEAAAANAAgAAQAAAAIAAAANAAkAAQAAAAMAAAANAAoAAQAAAAQAAAAOAAYAAQABAAAAAAAOAAcAAQABAAEAAAAOAAgAAQABAAIAAAAOAAkAAQABAAMAAAAOAAoAAQABAAQAAAAPAAYAAQACAAAAAAAPAAcAAQACAAEAAAAPAAgAAQACAAIAAAAPAAkAAQACAAMAAAAPAAoAAQACAAQAAAAPAAsAAQACAAUAAAAPAAwAAQACAAYAAAAPAA0AAQACAAcAAAAQAAYAAQADAAAAAAAQAAcAAQADAAEAAAAQAAgAAQADAAIAAAAQAAkAAQADAAMAAAAQAAoAAQADAAQAAAAQAAsAAQADAAUAAAAQAAwAAQADAAYAAAAQAA0AAQADAAcAAAARAAYAAQAEAAAAAAARAAcAAQAEAAEAAAARAAgAAQAEAAIAAAARAAkAAQAEAAMAAAARAAoAAQAEAAQAAAARAAsAAQAEAAUAAAARAAwAAQAEAAYAAAARAA0AAQAEAAcAAAASAAcAAQAFAAEAAAASAAgAAQAFAAIAAAASAAkAAQAFAAMAAAASAAoAAQAFAAQAAAA=")
+tile_set = ExtResource("1_8ayb0")
+
+[node name="Scatter" type="TileMapLayer" parent="Scatter"]
+tile_map_data = PackedByteArray("AAAGAAYAAQAQAAIAAAAGAAcAAQAQAAMAAAAGAAgAAQAQAAQAAAAHAAYAAQARAAIAAAAHAAcAAQARAAMAAAAHAAgAAQARAAQAAAAHAAoAAQAQAAEAAAALAAcAAQAWAAIAAAALAAgAAQAWAAMAAAAMAAcAAQAXAAIAAAAMAAgAAQAXAAMAAAAJAAQAAQAaAAEAAAAJAAUAAQAaAAIAAAAKAAQAAQAbAAEAAAAKAAUAAQAbAAIAAAAEAAYAAQAeAAEAAAACAAMAAQATAAQAAAAFAAEAAQAWAAcAAAAFAAIAAQAWAAgAAAANAAcAAQAAAAEAAAANAAgAAQAAAAIAAAANAAkAAQAAAAMAAAANAAoAAQAAAAQAAAAOAAYAAQABAAAAAAAOAAcAAQABAAEAAAAOAAgAAQABAAIAAAAOAAkAAQABAAMAAAAOAAoAAQABAAQAAAAPAAYAAQACAAAAAAAPAAcAAQACAAEAAAAPAAgAAQACAAIAAAAPAAkAAQACAAMAAAAPAAoAAQACAAQAAAAPAAsAAQACAAUAAAAPAAwAAQACAAYAAAAPAA0AAQACAAcAAAAQAAYAAQADAAAAAAAQAAcAAQADAAEAAAAQAAgAAQADAAIAAAAQAAkAAQADAAMAAAAQAAoAAQADAAQAAAAQAAsAAQADAAUAAAAQAAwAAQADAAYAAAAQAA0AAQADAAcAAAARAAYAAQAEAAAAAAARAAcAAQAEAAEAAAARAAgAAQAEAAIAAAARAAkAAQAEAAMAAAARAAoAAQAEAAQAAAARAAsAAQAEAAUAAAARAAwAAQAEAAYAAAARAA0AAQAEAAcAAAASAAcAAQAFAAEAAAASAAgAAQAFAAIAAAASAAkAAQAFAAMAAAASAAoAAQAFAAQAAAA=")
+tile_set = ExtResource("1_8ayb0")
diff --git a/mapgen/layout/misc/001.tscn b/mapgen/layout/misc/001.tscn
new file mode 100644
index 0000000..3fb8104
--- /dev/null
+++ b/mapgen/layout/misc/001.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=2 format=4 uid="uid://bftvnbc5cyniy"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_78ovd"]
+
+[node name="Misc-001" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAJAAEAAAADAAIAAAAJAAAAAAADAAIAAAAIAAEAAAADAAIAAAAIAAAAAAADAAIAAAAHAAEAAAADAAIAAAAHAAAAAAADAAIAAAAGAAEAAAADAAIAAAAGAAAAAAADAAIAAAAFAAEAAAADAAIAAAAFAAAAAAADAAIAAAAEAAEAAAADAAIAAAAEAAAAAAADAAIAAAADAAEAAAADAAIAAAADAAAAAAADAAIAAAACAAEAAAADAAIAAAACAAAAAAADAAIAAAABAAEAAAADAAIAAAABAAAAAAADAAIAAAAAAAEAAAADAAIAAAAAAAAAAAADAAIAAAATAAEAAAADAAIAAAATAAAAAAADAAIAAAASAAEAAAADAAIAAAASAAAAAAADAAIAAAARAAEAAAADAAIAAAARAAAAAAADAAIAAAAQAAEAAAADAAIAAAAQAAAAAAADAAIAAAAPAAEAAAADAAIAAAAPAAAAAAADAAIAAAAOAAEAAAADAAIAAAAOAAAAAAADAAIAAAANAAEAAAADAAIAAAANAAAAAAADAAIAAAAMAAEAAAADAAIAAAAMAAAAAAADAAIAAAALAAEAAAADAAIAAAALAAAAAAADAAIAAAAKAAEAAAADAAIAAAAKAAAAAAADAAIAAAATAAMAAAADAAIAAAATAAIAAAADAAIAAAASAAMAAAADAAIAAAASAAIAAAADAAIAAAARAAMAAAADAAIAAAARAAIAAAADAAIAAAAQAAMAAAADAAIAAAAQAAIAAAADAAIAAAAPAAMAAAADAAIAAAAPAAIAAAADAAIAAAAOAAMAAAADAAIAAAAOAAIAAAADAAIAAAANAAMAAAADAAIAAAANAAIAAAADAAIAAAAMAAMAAAADAAIAAAAMAAIAAAADAAIAAAALAAMAAAADAAIAAAALAAIAAAADAAIAAAAKAAMAAAADAAIAAAAKAAIAAAADAAIAAAAJAAMAAAADAAIAAAAJAAIAAAADAAIAAAAIAAMAAAADAAIAAAAIAAIAAAADAAIAAAAHAAMAAAADAAIAAAAHAAIAAAADAAIAAAAGAAMAAAADAAIAAAAGAAIAAAADAAIAAAAFAAMAAAADAAIAAAAFAAIAAAADAAIAAAAEAAMAAAADAAIAAAAEAAIAAAADAAIAAAADAAMAAAADAAIAAAADAAIAAAADAAIAAAACAAMAAAADAAIAAAACAAIAAAADAAIAAAABAAMAAAADAAIAAAABAAIAAAADAAIAAAAAAAMAAAADAAIAAAAAAAIAAAADAAIAAAATAAUAAAADAAIAAAATAAQAAAADAAIAAAASAAUAAAADAAIAAAASAAQAAAADAAIAAAARAAUAAAADAAIAAAARAAQAAAADAAIAAAAQAAUAAAADAAIAAAAQAAQAAAADAAIAAAAPAAUAAAADAAIAAAAPAAQAAAADAAIAAAAOAAUAAAADAAIAAAAOAAQAAAADAAIAAAANAAUAAAADAAIAAAANAAQAAAADAAIAAAAMAAUAAAADAAIAAAAMAAQAAAADAAIAAAALAAUAAAADAAIAAAALAAQAAAADAAIAAAAKAAUAAAADAAIAAAAKAAQAAAADAAIAAAAJAAUAAAADAAIAAAAJAAQAAAADAAIAAAAIAAUAAAADAAIAAAAIAAQAAAADAAIAAAAHAAUAAAADAAIAAAAHAAQAAAADAAIAAAAGAAUAAAADAAIAAAAGAAQAAAADAAIAAAAFAAUAAAADAAIAAAAFAAQAAAADAAIAAAAEAAUAAAADAAIAAAAEAAQAAAADAAIAAAADAAUAAAADAAIAAAADAAQAAAADAAIAAAACAAUAAAADAAIAAAACAAQAAAADAAIAAAABAAUAAAADAAIAAAABAAQAAAADAAIAAAAAAAUAAAADAAIAAAAAAAQAAAADAAIAAAATAAkAAAADAAIAAAATAAgAAAADAAIAAAATAAcAAAADAAIAAAATAAYAAAADAAIAAAASAAkAAAADAAIAAAASAAgAAAADAAIAAAASAAcAAAADAAIAAAASAAYAAAADAAIAAAARAAkAAAADAAIAAAARAAgAAAADAAIAAAARAAcAAAADAAIAAAARAAYAAAADAAIAAAAQAAkAAAADAAIAAAAQAAgAAAADAAIAAAAQAAcAAAADAAIAAAAQAAYAAAADAAIAAAAPAAkAAAADAAIAAAAPAAgAAAADAAIAAAAPAAcAAAADAAIAAAAPAAYAAAADAAIAAAAOAAkAAAADAAIAAAAOAAgAAAADAAIAAAAOAAcAAAADAAIAAAAOAAYAAAADAAIAAAANAAkAAAADAAIAAAANAAgAAAADAAIAAAANAAcAAAADAAIAAAANAAYAAAADAAIAAAAMAAkAAAADAAIAAAAMAAgAAAADAAIAAAAMAAcAAAADAAIAAAAMAAYAAAADAAIAAAALAAkAAAADAAIAAAALAAgAAAADAAIAAAALAAcAAAADAAIAAAALAAYAAAADAAIAAAAKAAkAAAADAAIAAAAKAAgAAAADAAIAAAAKAAcAAAADAAIAAAAKAAYAAAADAAIAAAAJAAkAAAADAAIAAAAJAAgAAAADAAIAAAAJAAcAAAADAAIAAAAJAAYAAAADAAIAAAAIAAkAAAADAAIAAAAIAAgAAAADAAIAAAAIAAcAAAADAAIAAAAIAAYAAAADAAIAAAAHAAkAAAADAAIAAAAHAAgAAAADAAIAAAAHAAcAAAADAAIAAAAHAAYAAAADAAIAAAAGAAkAAAADAAIAAAAGAAgAAAADAAIAAAAGAAcAAAADAAIAAAAGAAYAAAADAAIAAAAFAAkAAAADAAIAAAAFAAgAAAADAAIAAAAFAAcAAAADAAIAAAAFAAYAAAADAAIAAAAEAAkAAAADAAIAAAAEAAgAAAADAAIAAAAEAAcAAAADAAIAAAAEAAYAAAADAAIAAAADAAkAAAADAAIAAAADAAgAAAADAAIAAAADAAcAAAADAAIAAAADAAYAAAADAAIAAAACAAkAAAADAAIAAAACAAgAAAADAAIAAAACAAcAAAADAAIAAAACAAYAAAADAAIAAAABAAkAAAADAAIAAAABAAgAAAADAAIAAAABAAcAAAADAAIAAAABAAYAAAADAAIAAAAAAAkAAAADAAIAAAAAAAgAAAADAAIAAAAAAAcAAAADAAIAAAAAAAYAAAADAAIAAAATAA8AAAADAAIAAAATAA4AAAADAAIAAAATAA0AAAADAAIAAAATAAwAAAADAAIAAAATAAsAAAADAAIAAAATAAoAAAADAAIAAAASAA8AAAADAAIAAAASAA4AAAADAAIAAAASAA0AAAADAAIAAAASAAwAAAADAAIAAAASAAsAAAADAAIAAAASAAoAAAADAAIAAAARAA8AAAADAAIAAAARAA4AAAADAAIAAAARAA0AAAADAAIAAAARAAwAAAADAAIAAAARAAsAAAADAAIAAAARAAoAAAADAAIAAAAQAA8AAAADAAIAAAAQAA4AAAADAAIAAAAQAA0AAAADAAIAAAAQAAwAAAADAAIAAAAQAAsAAAADAAIAAAAQAAoAAAADAAIAAAAPAA8AAAADAAIAAAAPAA4AAAADAAIAAAAPAA0AAAADAAIAAAAPAAwAAAADAAIAAAAPAAsAAAADAAIAAAAPAAoAAAADAAIAAAAOAA8AAAADAAIAAAAOAA4AAAADAAIAAAAOAA0AAAADAAIAAAAOAAwAAAADAAIAAAAOAAsAAAADAAIAAAAOAAoAAAADAAIAAAANAA8AAAADAAIAAAANAA4AAAADAAIAAAANAA0AAAADAAIAAAANAAwAAAADAAIAAAANAAsAAAADAAIAAAANAAoAAAADAAIAAAAMAA8AAAADAAIAAAAMAA4AAAADAAIAAAAMAA0AAAADAAIAAAAMAAwAAAADAAIAAAAMAAsAAAADAAIAAAAMAAoAAAADAAIAAAALAA8AAAADAAIAAAALAA4AAAADAAIAAAALAA0AAAADAAIAAAALAAwAAAADAAIAAAALAAsAAAADAAIAAAALAAoAAAADAAIAAAAKAA8AAAADAAIAAAAKAA4AAAADAAIAAAAKAA0AAAADAAIAAAAKAAwAAAADAAIAAAAKAAsAAAADAAIAAAAKAAoAAAADAAIAAAAJAA8AAAADAAIAAAAJAA4AAAADAAIAAAAJAA0AAAADAAIAAAAJAAwAAAADAAIAAAAJAAsAAAADAAIAAAAJAAoAAAADAAIAAAAIAA8AAAADAAIAAAAIAA4AAAADAAIAAAAIAA0AAAADAAIAAAAIAAwAAAADAAIAAAAIAAsAAAADAAIAAAAIAAoAAAADAAIAAAAHAA8AAAADAAIAAAAHAA4AAAADAAIAAAAHAA0AAAADAAIAAAAHAAwAAAADAAIAAAAHAAsAAAADAAIAAAAHAAoAAAADAAIAAAAGAA8AAAADAAIAAAAGAA4AAAADAAIAAAAGAA0AAAADAAIAAAAGAAwAAAADAAIAAAAGAAsAAAADAAIAAAAGAAoAAAADAAIAAAAFAA8AAAADAAIAAAAFAA4AAAADAAIAAAAFAA0AAAADAAIAAAAFAAwAAAADAAIAAAAFAAsAAAADAAIAAAAFAAoAAAADAAIAAAAEAA8AAAADAAIAAAAEAA4AAAADAAIAAAAEAA0AAAADAAIAAAAEAAwAAAADAAIAAAAEAAsAAAADAAIAAAAEAAoAAAADAAIAAAADAA8AAAADAAIAAAADAA4AAAADAAIAAAADAA0AAAADAAIAAAADAAwAAAADAAIAAAADAAsAAAADAAIAAAADAAoAAAADAAIAAAACAA8AAAADAAIAAAACAA4AAAADAAIAAAACAA0AAAADAAIAAAACAAwAAAADAAIAAAACAAsAAAADAAIAAAACAAoAAAADAAIAAAABAA8AAAADAAIAAAABAA4AAAADAAIAAAABAA0AAAADAAIAAAABAAwAAAADAAIAAAABAAsAAAADAAIAAAABAAoAAAADAAIAAAAAAA8AAAADAAIAAAAAAA4AAAADAAIAAAAAAA0AAAADAAIAAAAAAAwAAAADAAIAAAAAAAsAAAADAAIAAAAAAAoAAAADAAIAAAA=")
+tile_set = ExtResource("1_78ovd")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAIAAQAAAAMAAIAAAAIAAMAAAAMAAEAAAAIAAIAAAAMAAAAAAAHAAQAAAAOAAEAAAAHAAMAAAALAAEAAAAHAAIAAAALAAAAAAAGAAQAAAAKAAEAAAAGAAMAAAAKAAEAAAAGAAIAAAAKAAAAAAAHAAcAAAAMAAIAAAAHAAYAAAAMAAEAAAAHAAUAAAAMAAEAAAAGAAcAAAALAAIAAAAGAAYAAAALAAEAAAAGAAUAAAANAAAAAAAFAAcAAAAKAAIAAAAFAAYAAAAKAAEAAAAFAAUAAAAKAAAAAAANAAcAAAALAAEAAAANAAYAAAAOAAAAAAANAAUAAAAMAAAAAAAMAAcAAAANAAEAAAAMAAYAAAALAAEAAAAMAAUAAAALAAAAAAALAAcAAAALAAIAAAALAAYAAAALAAEAAAALAAUAAAALAAAAAAAKAAcAAAAKAAIAAAAKAAYAAAAKAAEAAAAKAAUAAAAKAAAAAAAPAAkAAAAMAAIAAAAPAAgAAAAMAAEAAAAPAAcAAAAMAAEAAAAPAAYAAAAMAAAAAAAOAAkAAAALAAIAAAAOAAgAAAALAAEAAAAOAAcAAAALAAEAAAAOAAYAAAALAAAAAAANAAkAAAALAAIAAAANAAgAAAALAAEAAAAMAAkAAAAKAAIAAAAMAAgAAAAKAAEAAAADAA0AAAAMAAEAAAADAAwAAAAMAAAAAAACAA0AAAALAAEAAAACAAwAAAALAAAAAAABAA0AAAANAAAAAAABAAwAAAAKAAAAAAADAA4AAAAMAAEAAAACAA4AAAALAAEAAAABAA4AAAALAAEAAAADAA8AAAAMAAIAAAACAA8AAAALAAIAAAABAA8AAAALAAIAAAAAAA8AAAAKAAIAAAAAAA4AAAAKAAEAAAAAAA0AAAAKAAAAAAA=")
+tile_set = ExtResource("1_78ovd")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAJAAIABQABAAAAAAAJAAMABQABAAEAAAAJAAQABQABAAIAAAAKAAIABQACAAAAAAAKAAMABQACAAEAAAAKAAQABQACAAIAAAALAAIABQADAAAAAAALAAMABQADAAEAAAALAAQABQADAAIAAAA=")
+tile_set = ExtResource("1_78ovd")
diff --git a/mapgen/layout/misc/002.tscn b/mapgen/layout/misc/002.tscn
new file mode 100644
index 0000000..3427329
--- /dev/null
+++ b/mapgen/layout/misc/002.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=2 format=4 uid="uid://88fqx4nh5j7q"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_cxtl7"]
+
+[node name="Misc-002" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAJAAEAAAADAAIAAAAJAAAAAAADAAIAAAAIAAEAAAADAAIAAAAIAAAAAAADAAIAAAAHAAEAAAADAAIAAAAHAAAAAAADAAIAAAAGAAEAAAADAAIAAAAGAAAAAAADAAIAAAAFAAEAAAADAAIAAAAFAAAAAAADAAIAAAAEAAEAAAADAAIAAAAEAAAAAAADAAIAAAADAAEAAAADAAIAAAADAAAAAAADAAIAAAACAAEAAAADAAIAAAACAAAAAAADAAIAAAABAAEAAAADAAIAAAABAAAAAAADAAIAAAAAAAEAAAADAAIAAAAAAAAAAAADAAIAAAATAAEAAAADAAIAAAATAAAAAAADAAIAAAASAAEAAAADAAIAAAASAAAAAAADAAIAAAARAAEAAAADAAIAAAARAAAAAAADAAIAAAAQAAEAAAADAAIAAAAQAAAAAAADAAIAAAAPAAEAAAADAAIAAAAPAAAAAAADAAIAAAAOAAEAAAADAAIAAAAOAAAAAAADAAIAAAANAAEAAAADAAIAAAANAAAAAAADAAIAAAAMAAEAAAADAAIAAAAMAAAAAAADAAIAAAALAAEAAAADAAIAAAALAAAAAAADAAIAAAAKAAEAAAADAAIAAAAKAAAAAAADAAIAAAATAAMAAAADAAIAAAATAAIAAAADAAIAAAASAAMAAAADAAIAAAASAAIAAAADAAIAAAARAAMAAAADAAIAAAARAAIAAAADAAIAAAAQAAMAAAADAAIAAAAQAAIAAAADAAIAAAAPAAMAAAADAAIAAAAPAAIAAAADAAIAAAAOAAMAAAADAAIAAAAOAAIAAAADAAIAAAANAAMAAAADAAIAAAANAAIAAAADAAIAAAAMAAMAAAADAAIAAAAMAAIAAAADAAIAAAALAAMAAAADAAIAAAALAAIAAAADAAIAAAAKAAMAAAADAAIAAAAKAAIAAAADAAIAAAAJAAMAAAADAAIAAAAJAAIAAAADAAIAAAAIAAMAAAADAAIAAAAIAAIAAAADAAIAAAAHAAMAAAADAAIAAAAHAAIAAAADAAIAAAAGAAMAAAADAAIAAAAGAAIAAAADAAIAAAAFAAMAAAADAAIAAAAFAAIAAAADAAIAAAAEAAMAAAADAAIAAAAEAAIAAAADAAIAAAADAAMAAAADAAIAAAADAAIAAAADAAIAAAACAAMAAAADAAIAAAACAAIAAAADAAIAAAABAAMAAAADAAIAAAABAAIAAAADAAIAAAAAAAMAAAADAAIAAAAAAAIAAAADAAIAAAATAAUAAAADAAIAAAATAAQAAAADAAIAAAASAAUAAAADAAIAAAASAAQAAAADAAIAAAARAAUAAAADAAIAAAARAAQAAAADAAIAAAAQAAUAAAADAAIAAAAQAAQAAAADAAIAAAAPAAUAAAADAAIAAAAPAAQAAAADAAIAAAAOAAUAAAADAAIAAAAOAAQAAAADAAIAAAANAAUAAAADAAIAAAANAAQAAAADAAIAAAAMAAUAAAADAAIAAAAMAAQAAAADAAIAAAALAAUAAAADAAIAAAALAAQAAAADAAIAAAAKAAUAAAADAAIAAAAKAAQAAAADAAIAAAAJAAUAAAADAAIAAAAJAAQAAAADAAIAAAAIAAUAAAADAAIAAAAIAAQAAAADAAIAAAAHAAUAAAADAAIAAAAHAAQAAAADAAIAAAAGAAUAAAADAAIAAAAGAAQAAAADAAIAAAAFAAUAAAADAAIAAAAFAAQAAAADAAIAAAAEAAUAAAADAAIAAAAEAAQAAAADAAIAAAADAAUAAAADAAIAAAADAAQAAAADAAIAAAACAAUAAAADAAIAAAACAAQAAAADAAIAAAABAAUAAAADAAIAAAABAAQAAAADAAIAAAAAAAUAAAADAAIAAAAAAAQAAAADAAIAAAATAAkAAAADAAIAAAATAAgAAAADAAIAAAATAAcAAAADAAIAAAATAAYAAAADAAIAAAASAAkAAAADAAIAAAASAAgAAAADAAIAAAASAAcAAAADAAIAAAASAAYAAAADAAIAAAARAAkAAAADAAIAAAARAAgAAAADAAIAAAARAAcAAAADAAIAAAARAAYAAAADAAIAAAAQAAkAAAADAAIAAAAQAAgAAAADAAIAAAAQAAcAAAADAAIAAAAQAAYAAAADAAIAAAAPAAkAAAADAAIAAAAPAAgAAAADAAIAAAAPAAcAAAADAAIAAAAPAAYAAAADAAIAAAAOAAkAAAADAAIAAAAOAAgAAAADAAIAAAAOAAcAAAADAAIAAAAOAAYAAAADAAIAAAANAAkAAAADAAIAAAANAAgAAAADAAIAAAANAAcAAAADAAIAAAANAAYAAAADAAIAAAAMAAkAAAADAAIAAAAMAAgAAAADAAIAAAAMAAcAAAADAAIAAAAMAAYAAAADAAIAAAALAAkAAAADAAIAAAALAAgAAAADAAIAAAALAAcAAAADAAIAAAALAAYAAAADAAIAAAAKAAkAAAADAAIAAAAKAAgAAAADAAIAAAAKAAcAAAADAAIAAAAKAAYAAAADAAIAAAAJAAkAAAADAAIAAAAJAAgAAAADAAIAAAAJAAcAAAADAAIAAAAJAAYAAAADAAIAAAAIAAkAAAADAAIAAAAIAAgAAAADAAIAAAAIAAcAAAADAAIAAAAIAAYAAAADAAIAAAAHAAkAAAADAAIAAAAHAAgAAAADAAIAAAAHAAcAAAADAAIAAAAHAAYAAAADAAIAAAAGAAkAAAADAAIAAAAGAAgAAAADAAIAAAAGAAcAAAADAAIAAAAGAAYAAAADAAIAAAAFAAkAAAADAAIAAAAFAAgAAAADAAIAAAAFAAcAAAADAAIAAAAFAAYAAAADAAIAAAAEAAkAAAADAAIAAAAEAAgAAAADAAIAAAAEAAcAAAADAAIAAAAEAAYAAAADAAIAAAADAAkAAAADAAIAAAADAAgAAAADAAIAAAADAAcAAAADAAIAAAADAAYAAAADAAIAAAACAAkAAAADAAIAAAACAAgAAAADAAIAAAACAAcAAAADAAIAAAACAAYAAAADAAIAAAABAAkAAAADAAIAAAABAAgAAAADAAIAAAABAAcAAAADAAIAAAABAAYAAAADAAIAAAAAAAkAAAADAAIAAAAAAAgAAAADAAIAAAAAAAcAAAADAAIAAAAAAAYAAAADAAIAAAATAA8AAAADAAIAAAATAA4AAAADAAIAAAATAA0AAAADAAIAAAATAAwAAAADAAIAAAATAAsAAAADAAIAAAATAAoAAAADAAIAAAASAA8AAAADAAIAAAASAA4AAAADAAIAAAASAA0AAAADAAIAAAASAAwAAAADAAIAAAASAAsAAAADAAIAAAASAAoAAAADAAIAAAARAA8AAAADAAIAAAARAA4AAAADAAIAAAARAA0AAAADAAIAAAARAAwAAAADAAIAAAARAAsAAAADAAIAAAARAAoAAAADAAIAAAAQAA8AAAADAAIAAAAQAA4AAAADAAIAAAAQAA0AAAADAAIAAAAQAAwAAAADAAIAAAAQAAsAAAADAAIAAAAQAAoAAAADAAIAAAAPAA8AAAADAAIAAAAPAA4AAAADAAIAAAAPAA0AAAADAAIAAAAPAAwAAAADAAIAAAAPAAsAAAADAAIAAAAPAAoAAAADAAIAAAAOAA8AAAADAAIAAAAOAA4AAAADAAIAAAAOAA0AAAADAAIAAAAOAAwAAAADAAIAAAAOAAsAAAADAAIAAAAOAAoAAAADAAIAAAANAA8AAAADAAIAAAANAA4AAAADAAIAAAANAA0AAAADAAIAAAANAAwAAAADAAIAAAANAAsAAAADAAIAAAANAAoAAAADAAIAAAAMAA8AAAADAAIAAAAMAA4AAAADAAIAAAAMAA0AAAADAAIAAAAMAAwAAAADAAIAAAAMAAsAAAADAAIAAAAMAAoAAAADAAIAAAALAA8AAAADAAIAAAALAA4AAAADAAIAAAALAA0AAAADAAIAAAALAAwAAAADAAIAAAALAAsAAAADAAIAAAALAAoAAAADAAIAAAAKAA8AAAADAAIAAAAKAA4AAAADAAIAAAAKAA0AAAADAAIAAAAKAAwAAAADAAIAAAAKAAsAAAADAAIAAAAKAAoAAAADAAIAAAAJAA8AAAADAAIAAAAJAA4AAAADAAIAAAAJAA0AAAADAAIAAAAJAAwAAAADAAIAAAAJAAsAAAADAAIAAAAJAAoAAAADAAIAAAAIAA8AAAADAAIAAAAIAA4AAAADAAIAAAAIAA0AAAADAAIAAAAIAAwAAAADAAIAAAAIAAsAAAADAAIAAAAIAAoAAAADAAIAAAAHAA8AAAADAAIAAAAHAA4AAAADAAIAAAAHAA0AAAADAAIAAAAHAAwAAAADAAIAAAAHAAsAAAADAAIAAAAHAAoAAAADAAIAAAAGAA8AAAADAAIAAAAGAA4AAAADAAIAAAAGAA0AAAADAAIAAAAGAAwAAAADAAIAAAAGAAsAAAADAAIAAAAGAAoAAAADAAIAAAAFAA8AAAADAAIAAAAFAA4AAAADAAIAAAAFAA0AAAADAAIAAAAFAAwAAAADAAIAAAAFAAsAAAADAAIAAAAFAAoAAAADAAIAAAAEAA8AAAADAAIAAAAEAA4AAAADAAIAAAAEAA0AAAADAAIAAAAEAAwAAAADAAIAAAAEAAsAAAADAAIAAAAEAAoAAAADAAIAAAADAA8AAAADAAIAAAADAA4AAAADAAIAAAADAA0AAAADAAIAAAADAAwAAAADAAIAAAADAAsAAAADAAIAAAADAAoAAAADAAIAAAACAA8AAAADAAIAAAACAA4AAAADAAIAAAACAA0AAAADAAIAAAACAAwAAAADAAIAAAACAAsAAAADAAIAAAACAAoAAAADAAIAAAABAA8AAAADAAIAAAABAA4AAAADAAIAAAABAA0AAAADAAIAAAABAAwAAAADAAIAAAABAAsAAAADAAIAAAABAAoAAAADAAIAAAAAAA8AAAADAAIAAAAAAA4AAAADAAIAAAAAAA0AAAADAAIAAAAAAAwAAAADAAIAAAAAAAsAAAADAAIAAAAAAAoAAAADAAIAAAA=")
+tile_set = ExtResource("1_cxtl7")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAIAAQAAAAMAAIAAAAIAAMAAAAMAAEAAAAIAAIAAAAOAAEAAAAHAAQAAAAOAAEAAAAHAAMAAAALAAEAAAAHAAIAAAANAAAAAAAGAAQAAAAKAAEAAAAGAAMAAAAKAAEAAAAGAAIAAAAKAAAAAAAHAAcAAAAMAAIAAAAHAAYAAAAMAAEAAAAHAAUAAAAMAAEAAAAGAAcAAAALAAIAAAAGAAYAAAALAAEAAAAGAAUAAAANAAAAAAAFAAcAAAAKAAIAAAAFAAYAAAAKAAEAAAAFAAUAAAAKAAAAAAADAA0AAAAOAAEAAAADAAwAAAANAAAAAAACAA0AAAALAAEAAAACAAwAAAALAAAAAAABAA0AAAANAAAAAAABAAwAAAAKAAAAAAADAA4AAAAMAAEAAAACAA4AAAALAAEAAAABAA4AAAALAAEAAAADAA8AAAAMAAIAAAACAA8AAAALAAIAAAABAA8AAAALAAIAAAAAAA8AAAAKAAIAAAAAAA4AAAAKAAEAAAAAAA0AAAAKAAAAAAATAAMAAAAMAAEAAAATAAIAAAAMAAEAAAATAAEAAAAMAAAAAAASAAMAAAALAAEAAAASAAIAAAALAAEAAAASAAEAAAALAAAAAAARAAMAAAALAAEAAAARAAIAAAALAAEAAAARAAEAAAALAAAAAAAQAAMAAAALAAEAAAAQAAIAAAALAAEAAAAQAAEAAAALAAAAAAAPAAMAAAALAAEAAAAPAAIAAAALAAEAAAAPAAEAAAALAAAAAAAOAAMAAAALAAEAAAAOAAIAAAALAAEAAAAOAAEAAAALAAAAAAANAAMAAAANAAEAAAANAAIAAAALAAEAAAANAAEAAAALAAAAAAAMAAMAAAALAAIAAAAMAAIAAAALAAEAAAAMAAEAAAALAAAAAAALAAMAAAAKAAIAAAALAAIAAAANAAEAAAALAAEAAAALAAAAAAATAAYAAAAMAAEAAAATAAUAAAAMAAEAAAATAAQAAAAMAAEAAAASAAYAAAALAAEAAAASAAUAAAALAAEAAAASAAQAAAALAAEAAAARAAYAAAANAAEAAAARAAUAAAALAAEAAAARAAQAAAALAAEAAAAQAAYAAAALAAIAAAAQAAUAAAALAAEAAAAQAAQAAAALAAEAAAAPAAYAAAAKAAIAAAAPAAUAAAANAAEAAAAPAAQAAAALAAEAAAAOAAUAAAALAAIAAAAOAAQAAAALAAEAAAANAAUAAAAKAAIAAAANAAQAAAAKAAEAAAATAAgAAAAMAAIAAAATAAcAAAAMAAEAAAASAAgAAAALAAIAAAASAAcAAAALAAEAAAARAAgAAAAKAAIAAAARAAcAAAAKAAEAAAAKAAIAAAALAAIAAAAJAAIAAAALAAIAAAAKAAEAAAALAAAAAAAJAAEAAAALAAAAAAAIAAEAAAALAAAAAAAHAAEAAAAKAAAAAAAGAA0AAAAMAAIAAAAGAAwAAAAMAAEAAAAGAAsAAAAMAAAAAAAFAA0AAAALAAIAAAAFAAwAAAALAAEAAAAFAAsAAAALAAAAAAAEAA0AAAALAAIAAAAEAAwAAAALAAEAAAAEAAsAAAALAAAAAAADAAsAAAAKAAAAAAA=")
+tile_set = ExtResource("1_cxtl7")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAJAAIABQABAAAAAAAJAAMABQABAAEAAAAJAAQABQABAAIAAAAKAAIABQACAAAAAAAKAAMABQACAAEAAAAKAAQABQACAAIAAAALAAIABQADAAAAAAALAAMABQADAAEAAAALAAQABQADAAIAAAA=")
+tile_set = ExtResource("1_cxtl7")
diff --git a/mapgen/layout/path/001.tscn b/mapgen/layout/path/001.tscn
new file mode 100644
index 0000000..5061fef
--- /dev/null
+++ b/mapgen/layout/path/001.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=2 format=4 uid="uid://c18iub1rftxu2"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_ndgny"]
+
+[node name="Template" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAA8AAAADAAIAAAAAAA4AAAADAAIAAAAAAA0AAAADAAIAAAAAAAwAAAADAAIAAAAAAAsAAAADAAIAAAAAAAoAAAADAAIAAAAAAAkAAAADAAIAAAAAAAgAAAADAAIAAAAAAAcAAAADAAIAAAAAAAYAAAADAAIAAAAAAAUAAAADAAIAAAAAAAQAAAADAAIAAAAAAAMAAAADAAIAAAAAAAIAAAADAAIAAAAAAAEAAAADAAIAAAAAAAAAAAADAAIAAAAJAAAAAAADAAIAAAAIAAAAAAADAAIAAAAHAAAAAAADAAIAAAAGAAAAAAADAAIAAAAFAAAAAAADAAIAAAAEAAAAAAADAAIAAAADAAAAAAADAAIAAAACAAAAAAADAAIAAAABAAAAAAADAAIAAAATAAAAAAADAAIAAAASAAAAAAADAAIAAAARAAAAAAADAAIAAAAQAAAAAAADAAIAAAAPAAAAAAADAAIAAAAOAAAAAAADAAIAAAANAAAAAAADAAIAAAAMAAAAAAADAAIAAAALAAAAAAADAAIAAAAKAAAAAAADAAIAAAATAA8AAAADAAIAAAATAA4AAAADAAIAAAATAA0AAAADAAIAAAATAAwAAAADAAIAAAATAAsAAAADAAIAAAATAAoAAAADAAIAAAATAAkAAAADAAIAAAATAAgAAAADAAIAAAATAAcAAAADAAIAAAATAAYAAAADAAIAAAATAAUAAAADAAIAAAATAAQAAAADAAIAAAATAAMAAAADAAIAAAATAAIAAAADAAIAAAATAAEAAAADAAIAAAASAA8AAAADAAIAAAASAA4AAAADAAIAAAASAA0AAAADAAIAAAASAAwAAAADAAIAAAASAAsAAAADAAIAAAASAAoAAAADAAIAAAASAAkAAAADAAIAAAASAAgAAAADAAIAAAASAAcAAAADAAIAAAASAAYAAAADAAIAAAASAAUAAAADAAIAAAASAAQAAAADAAIAAAASAAMAAAADAAIAAAASAAIAAAADAAIAAAASAAEAAAADAAIAAAARAA8AAAADAAIAAAARAA4AAAADAAIAAAARAA0AAAADAAIAAAARAAwAAAADAAIAAAARAAsAAAADAAIAAAARAAoAAAADAAIAAAARAAkAAAADAAIAAAARAAgAAAADAAIAAAARAAcAAAADAAIAAAARAAYAAAADAAIAAAARAAUAAAADAAIAAAARAAQAAAADAAIAAAARAAMAAAADAAIAAAARAAIAAAADAAIAAAARAAEAAAADAAIAAAAQAA8AAAADAAIAAAAQAA4AAAADAAIAAAAQAA0AAAADAAIAAAAQAAwAAAADAAIAAAAQAAsAAAADAAIAAAAQAAoAAAADAAIAAAAQAAkAAAADAAIAAAAQAAgAAAADAAIAAAAQAAcAAAADAAIAAAAQAAYAAAADAAIAAAAQAAUAAAADAAIAAAAQAAQAAAADAAIAAAAQAAMAAAADAAIAAAAQAAIAAAADAAIAAAAQAAEAAAADAAIAAAAPAA8AAAADAAIAAAAPAA4AAAADAAIAAAAPAA0AAAADAAIAAAAPAAwAAAADAAIAAAAPAAsAAAADAAIAAAAPAAoAAAADAAIAAAAPAAkAAAADAAIAAAAPAAgAAAADAAIAAAAPAAcAAAADAAIAAAAPAAYAAAADAAIAAAAPAAUAAAADAAIAAAAPAAQAAAADAAIAAAAPAAMAAAADAAIAAAAPAAIAAAADAAIAAAAPAAEAAAADAAIAAAAOAA8AAAADAAIAAAAOAA4AAAADAAIAAAAOAA0AAAADAAIAAAAOAAwAAAADAAIAAAAOAAsAAAADAAIAAAAOAAoAAAADAAIAAAAOAAkAAAADAAIAAAAOAAgAAAADAAIAAAAOAAcAAAADAAIAAAAOAAYAAAADAAIAAAAOAAUAAAADAAIAAAAOAAQAAAADAAIAAAAOAAMAAAADAAIAAAAOAAIAAAADAAIAAAAOAAEAAAADAAIAAAANAA8AAAADAAIAAAANAA4AAAADAAIAAAANAA0AAAADAAIAAAANAAwAAAADAAIAAAANAAsAAAADAAIAAAANAAoAAAADAAIAAAANAAkAAAADAAIAAAANAAgAAAADAAIAAAANAAcAAAADAAIAAAANAAYAAAADAAIAAAANAAUAAAADAAIAAAANAAQAAAADAAIAAAANAAMAAAADAAIAAAANAAIAAAADAAIAAAANAAEAAAADAAIAAAAMAA8AAAADAAIAAAAMAA4AAAADAAIAAAAMAA0AAAADAAIAAAAMAAwAAAADAAIAAAAMAAsAAAADAAIAAAAMAAoAAAADAAIAAAAMAAkAAAADAAIAAAAMAAgAAAADAAIAAAAMAAcAAAADAAIAAAAMAAYAAAADAAIAAAAMAAUAAAADAAIAAAAMAAQAAAADAAIAAAAMAAMAAAADAAIAAAAMAAIAAAADAAIAAAAMAAEAAAADAAIAAAALAA8AAAADAAIAAAALAA4AAAADAAIAAAALAA0AAAADAAIAAAALAAwAAAADAAIAAAALAAsAAAADAAIAAAALAAoAAAADAAIAAAALAAkAAAADAAIAAAALAAgAAAADAAIAAAALAAcAAAADAAIAAAALAAYAAAADAAIAAAALAAUAAAADAAIAAAALAAQAAAADAAIAAAALAAMAAAADAAIAAAALAAIAAAADAAIAAAALAAEAAAADAAIAAAAKAA8AAAADAAIAAAAKAA4AAAADAAIAAAAKAA0AAAADAAIAAAAKAAwAAAADAAIAAAAKAAsAAAADAAIAAAAKAAoAAAADAAIAAAAKAAkAAAADAAIAAAAKAAgAAAADAAIAAAAKAAcAAAADAAIAAAAKAAYAAAADAAIAAAAKAAUAAAADAAIAAAAKAAQAAAADAAIAAAAKAAMAAAADAAIAAAAKAAIAAAADAAIAAAAKAAEAAAADAAIAAAAJAA8AAAADAAIAAAAJAA4AAAADAAIAAAAJAA0AAAADAAIAAAAJAAwAAAADAAIAAAAJAAsAAAADAAIAAAAJAAoAAAADAAIAAAAJAAkAAAADAAIAAAAJAAgAAAADAAIAAAAJAAcAAAADAAIAAAAJAAYAAAADAAIAAAAJAAUAAAADAAIAAAAJAAQAAAADAAIAAAAJAAMAAAADAAIAAAAJAAIAAAADAAIAAAAJAAEAAAADAAIAAAAIAA8AAAADAAIAAAAIAA4AAAADAAIAAAAIAA0AAAADAAIAAAAIAAwAAAADAAIAAAAIAAsAAAADAAIAAAAIAAoAAAADAAIAAAAIAAkAAAADAAIAAAAIAAgAAAADAAIAAAAIAAcAAAADAAIAAAAIAAYAAAADAAIAAAAIAAUAAAADAAIAAAAIAAQAAAADAAIAAAAIAAMAAAADAAIAAAAIAAIAAAADAAIAAAAIAAEAAAADAAIAAAAHAA8AAAADAAIAAAAHAA4AAAADAAIAAAAHAA0AAAADAAIAAAAHAAwAAAADAAIAAAAHAAsAAAADAAIAAAAHAAoAAAADAAIAAAAHAAkAAAADAAIAAAAHAAgAAAADAAIAAAAHAAcAAAADAAIAAAAHAAYAAAADAAIAAAAHAAUAAAADAAIAAAAHAAQAAAADAAIAAAAHAAMAAAADAAIAAAAHAAIAAAADAAIAAAAHAAEAAAADAAIAAAAGAA8AAAADAAIAAAAGAA4AAAADAAIAAAAGAA0AAAADAAIAAAAGAAwAAAADAAIAAAAGAAsAAAADAAIAAAAGAAoAAAADAAIAAAAGAAkAAAADAAIAAAAGAAgAAAADAAIAAAAGAAcAAAADAAIAAAAGAAYAAAADAAIAAAAGAAUAAAADAAIAAAAGAAQAAAADAAIAAAAGAAMAAAADAAIAAAAGAAIAAAADAAIAAAAGAAEAAAADAAIAAAAFAA8AAAADAAIAAAAFAA4AAAADAAIAAAAFAA0AAAADAAIAAAAFAAwAAAADAAIAAAAFAAsAAAADAAIAAAAFAAoAAAADAAIAAAAFAAkAAAADAAIAAAAFAAgAAAADAAIAAAAFAAcAAAADAAIAAAAFAAYAAAADAAIAAAAFAAUAAAADAAIAAAAFAAQAAAADAAIAAAAFAAMAAAADAAIAAAAFAAIAAAADAAIAAAAFAAEAAAADAAIAAAAEAA8AAAADAAIAAAAEAA4AAAADAAIAAAAEAA0AAAADAAIAAAAEAAwAAAADAAIAAAAEAAsAAAADAAIAAAAEAAoAAAADAAIAAAAEAAkAAAADAAIAAAAEAAgAAAADAAIAAAAEAAcAAAADAAIAAAAEAAYAAAADAAIAAAAEAAUAAAADAAIAAAAEAAQAAAADAAIAAAAEAAMAAAADAAIAAAAEAAIAAAADAAIAAAAEAAEAAAADAAIAAAADAA8AAAADAAIAAAADAA4AAAADAAIAAAADAA0AAAADAAIAAAADAAwAAAADAAIAAAADAAsAAAADAAIAAAADAAoAAAADAAIAAAADAAkAAAADAAIAAAADAAgAAAADAAIAAAADAAcAAAADAAIAAAADAAYAAAADAAIAAAADAAUAAAADAAIAAAADAAQAAAADAAIAAAADAAMAAAADAAIAAAADAAIAAAADAAIAAAADAAEAAAADAAIAAAACAA8AAAADAAIAAAACAA4AAAADAAIAAAACAA0AAAADAAIAAAACAAwAAAADAAIAAAACAAsAAAADAAIAAAACAAoAAAADAAIAAAACAAkAAAADAAIAAAACAAgAAAADAAIAAAACAAcAAAADAAIAAAACAAYAAAADAAIAAAACAAUAAAADAAIAAAACAAQAAAADAAIAAAACAAMAAAADAAIAAAACAAIAAAADAAIAAAACAAEAAAADAAIAAAABAA8AAAADAAIAAAABAA4AAAADAAIAAAABAA0AAAADAAIAAAABAAwAAAADAAIAAAABAAsAAAADAAIAAAABAAoAAAADAAIAAAABAAkAAAADAAIAAAABAAgAAAADAAIAAAABAAcAAAADAAIAAAABAAYAAAADAAIAAAABAAUAAAADAAIAAAABAAQAAAADAAIAAAABAAMAAAADAAIAAAABAAIAAAADAAIAAAABAAEAAAADAAIAAAA=")
+tile_set = ExtResource("1_ndgny")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAEAAAALAAIAAAAEAAAAAAALAAAAAAADAAEAAAALAAIAAAADAAAAAAALAAAAAAACAAEAAAALAAIAAAACAAAAAAALAAAAAAABAAEAAAAOAAEAAAABAAAAAAALAAAAAAAAAAEAAAAKAAEAAAAAAAAAAAAKAAAAAAABAAMAAAAMAAIAAAABAAIAAAAMAAEAAAAAAAMAAAAKAAIAAAAAAAIAAAAKAAEAAAATAAEAAAAMAAEAAAATAAAAAAAMAAAAAAASAAEAAAANAAEAAAASAAAAAAALAAAAAAARAAEAAAALAAIAAAARAAAAAAALAAAAAAAQAAEAAAAKAAIAAAAQAAAAAAAKAAAAAAATAAMAAAAMAAIAAAATAAIAAAAMAAEAAAASAAMAAAAKAAIAAAASAAIAAAAKAAEAAAATAA8AAAAMAAIAAAATAA4AAAAMAAEAAAASAA8AAAALAAIAAAASAA4AAAALAAEAAAARAA8AAAALAAIAAAARAA4AAAANAAAAAAAQAA8AAAALAAIAAAAQAA4AAAALAAAAAAATAA0AAAAMAAEAAAATAAwAAAAMAAAAAAASAA0AAAALAAEAAAASAAwAAAAOAAAAAAADAA8AAAALAAIAAAADAA4AAAALAAAAAAACAA8AAAALAAIAAAACAA4AAAALAAAAAAABAA8AAAALAAIAAAABAA4AAAAOAAAAAAAAAA8AAAAKAAIAAAAAAA4AAAAKAAEAAAABAA0AAAAMAAEAAAABAAwAAAAMAAAAAAAAAA0AAAAKAAEAAAAAAAwAAAAKAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAGAAEAAAALAAEAAAAHAAEAAAALAAEAAAAHAAIAAAALAAEAAAAIAAIAAAAMAAEAAAAIAAMAAAAMAAEAAAAIAAQAAAAMAAIAAAAFAAEAAAANAAEAAAAFAAIAAAAKAAEAAAAFAAMAAAAKAAEAAAAFAAQAAAAKAAEAAAAFAAUAAAAKAAIAAAAGAAUAAAANAAEAAAAGAAQAAAALAAEAAAAHAAQAAAAOAAEAAAAHAAMAAAALAAEAAAAGAAIAAAALAAEAAAAGAAMAAAALAAEAAAAHAAAAAAALAAAAAAAIAAEAAAAMAAEAAAAIAAAAAAAMAAAAAAAHAAUAAAAMAAEAAAAHAAYAAAAMAAEAAAAHAAcAAAAMAAIAAAAGAAcAAAAKAAIAAAAGAAYAAAAKAAEAAAARAA0AAAAKAAEAAAARAAwAAAAKAAEAAAARAAsAAAAKAAEAAAARAAoAAAAKAAEAAAARAAkAAAAKAAAAAAASAAkAAAAMAAAAAAASAAoAAAAMAAEAAAASAAsAAAAMAAEAAAAPAA8AAAALAAIAAAAOAA8AAAALAAIAAAANAA8AAAAKAAIAAAANAA4AAAAKAAAAAAAOAA4AAAALAAAAAAAPAA4AAAALAAAAAAAEAA4AAAANAAAAAAAFAA4AAAALAAEAAAAGAA4AAAAOAAAAAAAHAA4AAAAMAAAAAAAEAA8AAAALAAIAAAAFAA8AAAALAAIAAAAGAA8AAAALAAIAAAAHAA8AAAAMAAIAAAAEAA0AAAAKAAEAAAAEAAwAAAAKAAEAAAAEAAsAAAAKAAEAAAAEAAoAAAAKAAAAAAAFAAsAAAALAAEAAAAFAAwAAAALAAEAAAAFAA0AAAALAAEAAAAFAAoAAAALAAAAAAAOAAMAAAAMAAAAAAAOAAQAAAAMAAEAAAANAAQAAAAKAAEAAAANAAUAAAAKAAIAAAAOAAUAAAAMAAIAAAANAAMAAAAKAAAAAAAGAA0AAAAMAAEAAAAGAAwAAAAMAAEAAAAGAAsAAAAOAAEAAAAGAAoAAAALAAAAAAAHAAsAAAAMAAIAAAAHAAoAAAAMAAAAAAA=")
+tile_set = ExtResource("1_ndgny")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAANAAsAEwAGAAcAAAANAAwAEwAGAAgAAAAOAAsAEwAHAAcAAAAOAAwAEwAHAAgAAAAHAAkAEwAKAAUAAAAHAAoAEwAKAAYAAAAIAAkAEwALAAUAAAAIAAoAEwALAAYAAAARAAUAEwAGAAkAAAASAAUAEwAHAAkAAAATAAUAEwAIAAkAAAA=")
+tile_set = ExtResource("1_ndgny")
diff --git a/mapgen/layout/path/002.tscn b/mapgen/layout/path/002.tscn
new file mode 100644
index 0000000..d85b619
--- /dev/null
+++ b/mapgen/layout/path/002.tscn
@@ -0,0 +1,16 @@
+[gd_scene load_steps=2 format=4 uid="uid://cm5qjm3qtooki"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_joa6j"]
+
+[node name="Path-002" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAA8AAAADAAIAAAAAAA4AAAADAAIAAAAAAA0AAAADAAIAAAAAAAwAAAADAAIAAAAAAAsAAAADAAIAAAAAAAoAAAADAAIAAAAAAAkAAAADAAIAAAAAAAgAAAADAAIAAAAAAAcAAAADAAIAAAAAAAYAAAADAAIAAAAAAAUAAAADAAIAAAAAAAQAAAADAAIAAAAAAAMAAAADAAIAAAAAAAIAAAADAAIAAAAAAAEAAAADAAIAAAAAAAAAAAADAAIAAAAJAAAAAAADAAIAAAAIAAAAAAADAAIAAAAHAAAAAAADAAIAAAAGAAAAAAADAAIAAAAFAAAAAAADAAIAAAAEAAAAAAADAAIAAAADAAAAAAADAAIAAAACAAAAAAADAAIAAAABAAAAAAADAAIAAAATAAAAAAADAAIAAAASAAAAAAADAAIAAAARAAAAAAADAAIAAAAQAAAAAAADAAIAAAAPAAAAAAADAAIAAAAOAAAAAAADAAIAAAANAAAAAAADAAIAAAAMAAAAAAADAAIAAAALAAAAAAADAAIAAAAKAAAAAAADAAIAAAATAA8AAAADAAIAAAATAA4AAAADAAIAAAATAA0AAAADAAIAAAATAAwAAAADAAIAAAATAAsAAAADAAIAAAATAAoAAAADAAIAAAATAAkAAAADAAIAAAATAAgAAAADAAIAAAATAAcAAAADAAIAAAATAAYAAAADAAIAAAATAAUAAAADAAIAAAATAAQAAAADAAIAAAATAAMAAAADAAIAAAATAAIAAAADAAIAAAATAAEAAAADAAIAAAASAA8AAAADAAIAAAASAA4AAAADAAIAAAASAA0AAAADAAIAAAASAAwAAAADAAIAAAASAAsAAAADAAIAAAASAAoAAAADAAIAAAASAAkAAAADAAIAAAASAAgAAAADAAIAAAASAAcAAAADAAIAAAASAAYAAAADAAIAAAASAAUAAAADAAIAAAASAAQAAAADAAIAAAASAAMAAAADAAIAAAASAAIAAAADAAIAAAASAAEAAAADAAIAAAARAA8AAAADAAIAAAARAA4AAAADAAIAAAARAA0AAAADAAIAAAARAAwAAAADAAIAAAARAAsAAAADAAIAAAARAAoAAAADAAIAAAARAAkAAAADAAIAAAARAAgAAAADAAIAAAARAAcAAAADAAIAAAARAAYAAAADAAIAAAARAAUAAAADAAIAAAARAAQAAAADAAIAAAARAAMAAAADAAIAAAARAAIAAAADAAIAAAARAAEAAAADAAIAAAAQAA8AAAADAAIAAAAQAA4AAAADAAIAAAAQAA0AAAADAAIAAAAQAAwAAAADAAIAAAAQAAsAAAADAAIAAAAQAAoAAAADAAIAAAAQAAkAAAADAAIAAAAQAAgAAAADAAIAAAAQAAcAAAADAAIAAAAQAAYAAAADAAIAAAAQAAUAAAADAAIAAAAQAAQAAAADAAIAAAAQAAMAAAADAAIAAAAQAAIAAAADAAIAAAAQAAEAAAADAAIAAAAPAA8AAAADAAIAAAAPAA4AAAADAAIAAAAPAA0AAAADAAIAAAAPAAwAAAADAAIAAAAPAAsAAAADAAIAAAAPAAoAAAADAAIAAAAPAAkAAAADAAIAAAAPAAgAAAADAAIAAAAPAAcAAAADAAIAAAAPAAYAAAADAAIAAAAPAAUAAAADAAIAAAAPAAQAAAADAAIAAAAPAAMAAAADAAIAAAAPAAIAAAADAAIAAAAPAAEAAAADAAIAAAAOAA8AAAADAAIAAAAOAA4AAAADAAIAAAAOAA0AAAADAAIAAAAOAAwAAAADAAIAAAAOAAsAAAADAAIAAAAOAAoAAAADAAIAAAAOAAkAAAADAAIAAAAOAAgAAAADAAIAAAAOAAcAAAADAAIAAAAOAAYAAAADAAIAAAAOAAUAAAADAAIAAAAOAAQAAAADAAIAAAAOAAMAAAADAAIAAAAOAAIAAAADAAIAAAAOAAEAAAADAAIAAAANAA8AAAADAAIAAAANAA4AAAADAAIAAAANAA0AAAADAAIAAAANAAwAAAADAAIAAAANAAsAAAADAAIAAAANAAoAAAADAAIAAAANAAkAAAADAAIAAAANAAgAAAADAAIAAAANAAcAAAADAAIAAAANAAYAAAADAAIAAAANAAUAAAADAAIAAAANAAQAAAADAAIAAAANAAMAAAADAAIAAAANAAIAAAADAAIAAAANAAEAAAADAAIAAAAMAA8AAAADAAIAAAAMAA4AAAADAAIAAAAMAA0AAAADAAIAAAAMAAwAAAADAAIAAAAMAAsAAAADAAIAAAAMAAoAAAADAAIAAAAMAAkAAAADAAIAAAAMAAgAAAADAAIAAAAMAAcAAAADAAIAAAAMAAYAAAADAAIAAAAMAAUAAAADAAIAAAAMAAQAAAADAAIAAAAMAAMAAAADAAIAAAAMAAIAAAADAAIAAAAMAAEAAAADAAIAAAALAA8AAAADAAIAAAALAA4AAAADAAIAAAALAA0AAAADAAIAAAALAAwAAAADAAIAAAALAAsAAAADAAIAAAALAAoAAAADAAIAAAALAAkAAAADAAIAAAALAAgAAAADAAIAAAALAAcAAAADAAIAAAALAAYAAAADAAIAAAALAAUAAAADAAIAAAALAAQAAAADAAIAAAALAAMAAAADAAIAAAALAAIAAAADAAIAAAALAAEAAAADAAIAAAAKAA8AAAADAAIAAAAKAA4AAAADAAIAAAAKAA0AAAADAAIAAAAKAAwAAAADAAIAAAAKAAsAAAADAAIAAAAKAAoAAAADAAIAAAAKAAkAAAADAAIAAAAKAAgAAAADAAIAAAAKAAcAAAADAAIAAAAKAAYAAAADAAIAAAAKAAUAAAADAAIAAAAKAAQAAAADAAIAAAAKAAMAAAADAAIAAAAKAAIAAAADAAIAAAAKAAEAAAADAAIAAAAJAA8AAAADAAIAAAAJAA4AAAADAAIAAAAJAA0AAAADAAIAAAAJAAwAAAADAAIAAAAJAAsAAAADAAIAAAAJAAoAAAADAAIAAAAJAAkAAAADAAIAAAAJAAgAAAADAAIAAAAJAAcAAAADAAIAAAAJAAYAAAADAAIAAAAJAAUAAAADAAIAAAAJAAQAAAADAAIAAAAJAAMAAAADAAIAAAAJAAIAAAADAAIAAAAJAAEAAAADAAIAAAAIAA8AAAADAAIAAAAIAA4AAAADAAIAAAAIAA0AAAADAAIAAAAIAAwAAAADAAIAAAAIAAsAAAADAAIAAAAIAAoAAAADAAIAAAAIAAkAAAADAAIAAAAIAAgAAAADAAIAAAAIAAcAAAADAAIAAAAIAAYAAAADAAIAAAAIAAUAAAADAAIAAAAIAAQAAAADAAIAAAAIAAMAAAADAAIAAAAIAAIAAAADAAIAAAAIAAEAAAADAAIAAAAHAA8AAAADAAIAAAAHAA4AAAADAAIAAAAHAA0AAAADAAIAAAAHAAwAAAADAAIAAAAHAAsAAAADAAIAAAAHAAoAAAADAAIAAAAHAAkAAAADAAIAAAAHAAgAAAADAAIAAAAHAAcAAAADAAIAAAAHAAYAAAADAAIAAAAHAAUAAAADAAIAAAAHAAQAAAADAAIAAAAHAAMAAAADAAIAAAAHAAIAAAADAAIAAAAHAAEAAAADAAIAAAAGAA8AAAADAAIAAAAGAA4AAAADAAIAAAAGAA0AAAADAAIAAAAGAAwAAAADAAIAAAAGAAsAAAADAAIAAAAGAAoAAAADAAIAAAAGAAkAAAADAAIAAAAGAAgAAAADAAIAAAAGAAcAAAADAAIAAAAGAAYAAAADAAIAAAAGAAUAAAADAAIAAAAGAAQAAAADAAIAAAAGAAMAAAADAAIAAAAGAAIAAAADAAIAAAAGAAEAAAADAAIAAAAFAA8AAAADAAIAAAAFAA4AAAADAAIAAAAFAA0AAAADAAIAAAAFAAwAAAADAAIAAAAFAAsAAAADAAIAAAAFAAoAAAADAAIAAAAFAAkAAAADAAIAAAAFAAgAAAADAAIAAAAFAAcAAAADAAIAAAAFAAYAAAADAAIAAAAFAAUAAAADAAIAAAAFAAQAAAADAAIAAAAFAAMAAAADAAIAAAAFAAIAAAADAAIAAAAFAAEAAAADAAIAAAAEAA8AAAADAAIAAAAEAA4AAAADAAIAAAAEAA0AAAADAAIAAAAEAAwAAAADAAIAAAAEAAsAAAADAAIAAAAEAAoAAAADAAIAAAAEAAkAAAADAAIAAAAEAAgAAAADAAIAAAAEAAcAAAADAAIAAAAEAAYAAAADAAIAAAAEAAUAAAADAAIAAAAEAAQAAAADAAIAAAAEAAMAAAADAAIAAAAEAAIAAAADAAIAAAAEAAEAAAADAAIAAAADAA8AAAADAAIAAAADAA4AAAADAAIAAAADAA0AAAADAAIAAAADAAwAAAADAAIAAAADAAsAAAADAAIAAAADAAoAAAADAAIAAAADAAkAAAADAAIAAAADAAgAAAADAAIAAAADAAcAAAADAAIAAAADAAYAAAADAAIAAAADAAUAAAADAAIAAAADAAQAAAADAAIAAAADAAMAAAADAAIAAAADAAIAAAADAAIAAAADAAEAAAADAAIAAAACAA8AAAADAAIAAAACAA4AAAADAAIAAAACAA0AAAADAAIAAAACAAwAAAADAAIAAAACAAsAAAADAAIAAAACAAoAAAADAAIAAAACAAkAAAADAAIAAAACAAgAAAADAAIAAAACAAcAAAADAAIAAAACAAYAAAADAAIAAAACAAUAAAADAAIAAAACAAQAAAADAAIAAAACAAMAAAADAAIAAAACAAIAAAADAAIAAAACAAEAAAADAAIAAAABAA8AAAADAAIAAAABAA4AAAADAAIAAAABAA0AAAADAAIAAAABAAwAAAADAAIAAAABAAsAAAADAAIAAAABAAoAAAADAAIAAAABAAkAAAADAAIAAAABAAgAAAADAAIAAAABAAcAAAADAAIAAAABAAYAAAADAAIAAAABAAUAAAADAAIAAAABAAQAAAADAAIAAAABAAMAAAADAAIAAAABAAIAAAADAAIAAAABAAEAAAADAAIAAAA=")
+tile_set = ExtResource("1_joa6j")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAEAAAALAAIAAAAEAAAAAAALAAAAAAADAAEAAAALAAIAAAADAAAAAAALAAAAAAACAAEAAAALAAIAAAACAAAAAAALAAAAAAABAAEAAAAOAAEAAAABAAAAAAALAAAAAAAAAAEAAAAKAAEAAAAAAAAAAAAKAAAAAAABAAMAAAAMAAIAAAABAAIAAAAMAAEAAAAAAAMAAAAKAAIAAAAAAAIAAAAKAAEAAAATAAEAAAAMAAEAAAATAAAAAAAMAAAAAAASAAEAAAANAAEAAAASAAAAAAALAAAAAAARAAEAAAALAAIAAAARAAAAAAALAAAAAAAQAAEAAAALAAIAAAAQAAAAAAALAAAAAAATAAMAAAAMAAIAAAATAAIAAAAMAAEAAAASAAMAAAAKAAIAAAASAAIAAAAKAAEAAAATAA8AAAAMAAIAAAATAA4AAAAMAAEAAAASAA8AAAALAAIAAAASAA4AAAALAAEAAAARAA8AAAALAAIAAAARAA4AAAANAAAAAAAQAA8AAAALAAIAAAAQAA4AAAALAAAAAAATAA0AAAAMAAEAAAATAAwAAAAMAAAAAAASAA0AAAALAAEAAAASAAwAAAAOAAAAAAADAA8AAAALAAIAAAADAA4AAAALAAAAAAACAA8AAAALAAIAAAACAA4AAAALAAAAAAABAA8AAAALAAIAAAABAA4AAAAOAAAAAAAAAA8AAAAKAAIAAAAAAA4AAAAKAAEAAAABAA0AAAAMAAEAAAABAAwAAAAMAAAAAAAAAA0AAAAKAAEAAAAAAAwAAAAKAAAAAAAFAAAAAAALAAAAAAAGAAAAAAALAAAAAAAGAAEAAAALAAIAAAAHAAEAAAALAAIAAAAFAAEAAAALAAIAAAAHAAAAAAALAAAAAAAIAAEAAAAMAAIAAAAIAAAAAAAMAAAAAAARAA0AAAAKAAEAAAARAAwAAAAKAAEAAAARAAsAAAAKAAAAAAASAAsAAAAMAAAAAAAPAA8AAAALAAIAAAAOAA8AAAALAAIAAAANAA8AAAALAAIAAAANAA4AAAALAAAAAAAOAA4AAAALAAAAAAAPAA4AAAALAAAAAAAEAA4AAAALAAAAAAAFAA4AAAALAAAAAAAGAA4AAAANAAAAAAAHAA4AAAALAAEAAAAEAA8AAAALAAIAAAAFAA8AAAALAAIAAAAGAA8AAAALAAIAAAAHAA8AAAALAAIAAAAIAA4AAAALAAEAAAAJAA4AAAALAAEAAAAKAA4AAAALAAEAAAALAA4AAAAOAAAAAAAMAA4AAAALAAAAAAAMAA8AAAALAAIAAAALAA8AAAALAAIAAAAKAA8AAAALAAIAAAAJAA8AAAALAAIAAAAIAA8AAAALAAIAAAAPAAEAAAAOAAEAAAAOAAEAAAALAAAAAAANAAEAAAAKAAAAAAANAAIAAAANAAAAAAAMAAIAAAAKAAAAAAAMAAMAAAANAAAAAAALAAMAAAALAAAAAAALAAQAAAALAAEAAAAKAAQAAAAKAAEAAAAKAAUAAAANAAAAAAAPAAIAAAAMAAIAAAAOAAMAAAAMAAIAAAANAAQAAAAMAAIAAAAMAAQAAAAOAAEAAAAMAAUAAAAMAAIAAAALAAUAAAAOAAEAAAALAAYAAAAMAAIAAAANAAMAAAAOAAEAAAAOAAIAAAAOAAEAAAAPAAAAAAALAAAAAAAKAAMAAAAKAAAAAAAKAAYAAAAOAAEAAAAKAAcAAAAMAAEAAAAJAAcAAAANAAAAAAAIAAcAAAAKAAAAAAAJAAYAAAAKAAEAAAAJAAUAAAAKAAAAAAAIAAgAAAAKAAIAAAAKAAgAAAAMAAIAAAAJAAgAAAALAAIAAAAKAA0AAAAOAAAAAAAKAAwAAAAMAAAAAAAJAA0AAAALAAEAAAAJAAwAAAALAAAAAAAIAA0AAAALAAEAAAAIAAwAAAALAAAAAAAHAA0AAAALAAEAAAAHAAwAAAALAAAAAAAGAA0AAAAKAAEAAAAGAAwAAAAKAAAAAAALAA0AAAAMAAAAAAA=")
+tile_set = ExtResource("1_joa6j")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_set = ExtResource("1_joa6j")
diff --git a/mapgen/layout/path/003.tscn b/mapgen/layout/path/003.tscn
new file mode 100644
index 0000000..05ffe6a
--- /dev/null
+++ b/mapgen/layout/path/003.tscn
@@ -0,0 +1,11 @@
+[gd_scene format=4 uid="uid://myaqar677n7t"]
+
+[node name="Path-002" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAA8AGwAEAAAAAAAAAA4AGwACAAIAAAAAAA0AGwAEAAAAAAAAAAwAGwAEAAAAAAAAAAsAGwAEAAAAAAAAAAoAGwAEAAAAAAAAAAkAGwABAAIAAAAAAAgAGwACAAEAAAAAAAcAGwAEAAAAAAAAAAYAGwAEAAAAAAAAAAUAGwAEAAAAAAAAAAQAGwAEAAAAAAAAAAMAGwABAAMAAAAAAAIAGwABAAIAAAAAAAEAGwAEAAAAAAAAAAAAGwAEAAAAAAAJAAAAGwACAAEAAAAIAAAAGwAEAAAAAAAHAAAAGwAEAAAAAAAGAAAAGwABAAAAAAAFAAAAGwAEAAAAAAAEAAAAGwAEAAAAAAADAAAAGwAEAAAAAAACAAAAGwADAAIAAAABAAAAGwAEAAAAAAATAAAAGwADAAAAAAASAAAAGwAEAAAAAAARAAAAGwAEAAAAAAAQAAAAGwAEAAAAAAAPAAAAGwAEAAAAAAAOAAAAGwAEAAAAAAANAAAAGwAEAAAAAAAMAAAAGwAEAAAAAAALAAAAGwAAAAEAAAAKAAAAGwAEAAAAAAATAA8AGwAEAAAAAAATAA4AGwAEAAAAAAATAA0AGwAEAAAAAAATAAwAGwAEAAAAAAATAAsAGwAEAAAAAAATAAoAGwAEAAAAAAATAAkAGwAEAAAAAAATAAgAGwAEAAAAAAATAAcAGwAEAAAAAAATAAYAGwAEAAAAAAATAAUAGwAEAAAAAAATAAQAGwAEAAAAAAATAAMAGwAEAAAAAAATAAIAGwAEAAAAAAATAAEAGwAEAAAAAAASAA8AGwAEAAAAAAASAA4AGwAEAAAAAAASAA0AGwAEAAAAAAASAAwAGwACAAEAAAASAAsAGwAEAAAAAAASAAoAGwAEAAAAAAASAAkAGwAEAAAAAAASAAgAGwAEAAAAAAASAAcAGwAEAAAAAAASAAYAGwAAAAAAAAASAAUAGwACAAAAAAASAAQAGwAEAAAAAAASAAMAGwAEAAAAAAASAAIAGwAEAAAAAAASAAEAGwAEAAAAAAARAA8AGwAEAAAAAAARAA4AGwAEAAAAAAARAA0AGwABAAAAAAARAAwAGwAEAAAAAAARAAsAGwAEAAAAAAARAAoAGwAAAAIAAAARAAkAGwAEAAAAAAARAAgAGwAEAAAAAAARAAcAGwAEAAAAAAARAAYAGwAEAAAAAAARAAUAGwACAAIAAAARAAQAGwAEAAAAAAARAAMAGwAEAAAAAAARAAIAGwAEAAAAAAARAAEAGwAEAAAAAAAQAA8AGwAAAAAAAAAQAA4AGwAEAAAAAAAQAA0AGwAEAAAAAAAQAAwAGwAEAAAAAAAQAAsAGwACAAIAAAAQAAoAGwAEAAAAAAAQAAkAGwAEAAAAAAAQAAgAGwAEAAAAAAAQAAcAGwADAAEAAAAQAAYAGwAEAAAAAAAQAAUAGwAEAAAAAAAQAAQAGwABAAMAAAAQAAMAGwABAAAAAAAQAAIAGwAEAAAAAAAQAAEAGwAEAAAAAAAPAA8AGwABAAEAAAAPAA4AGwAEAAAAAAAPAA0AGwAEAAAAAAAPAAwAGwAEAAAAAAAPAAsAGwAEAAAAAAAPAAoAGwAEAAAAAAAPAAkAGwAEAAAAAAAPAAgAGwAEAAAAAAAPAAcAGwAEAAAAAAAPAAYAGwADAAAAAAAPAAUAGwAEAAAAAAAPAAQAGwAEAAAAAAAPAAMAGwAEAAAAAAAPAAIAGwAEAAAAAAAPAAEAGwAEAAAAAAAOAA8AGwAEAAAAAAAOAA4AGwAEAAAAAAAOAA0AGwAEAAAAAAAOAAwAGwAEAAAAAAAOAAsAGwAEAAAAAAAOAAoAGwAEAAAAAAAOAAkAGwAEAAAAAAAOAAgAGwABAAIAAAAOAAcAGwAEAAAAAAAOAAYAGwAEAAAAAAAOAAUAGwAAAAIAAAAOAAQAGwAEAAAAAAAOAAMAGwAEAAAAAAAOAAIAGwAEAAAAAAAOAAEAGwAEAAAAAAANAA8AGwAEAAAAAAANAA4AGwAEAAAAAAANAA0AGwABAAAAAAANAAwAGwAEAAAAAAANAAsAGwAEAAAAAAANAAoAGwABAAMAAAANAAkAGwAEAAAAAAANAAgAGwACAAIAAAANAAcAGwAEAAAAAAANAAYAGwABAAIAAAANAAUAGwAEAAAAAAANAAQAGwAEAAAAAAANAAMAGwAEAAAAAAANAAIAGwAEAAAAAAANAAEAGwABAAAAAAAMAA8AGwAEAAAAAAAMAA4AGwAEAAAAAAAMAA0AGwAAAAEAAAAMAAwAGwABAAIAAAAMAAsAGwAEAAAAAAAMAAoAGwAEAAAAAAAMAAkAGwAEAAAAAAAMAAgAGwAEAAAAAAAMAAcAGwAEAAAAAAAMAAYAGwAEAAAAAAAMAAUAGwAEAAAAAAAMAAQAGwAEAAAAAAAMAAMAGwAEAAAAAAAMAAIAGwAEAAAAAAAMAAEAGwAEAAAAAAALAA8AGwAEAAAAAAALAA4AGwAEAAAAAAALAA0AGwAEAAAAAAALAAwAGwAEAAAAAAALAAsAGwAEAAAAAAALAAoAGwAEAAAAAAALAAkAGwAEAAAAAAALAAgAGwAEAAAAAAALAAcAGwAEAAAAAAALAAYAGwAEAAAAAAALAAUAGwAAAAAAAAALAAQAGwAEAAAAAAALAAMAGwAEAAAAAAALAAIAGwAEAAAAAAALAAEAGwABAAAAAAAKAA8AGwAEAAAAAAAKAA4AGwAEAAAAAAAKAA0AGwAEAAAAAAAKAAwAGwAEAAAAAAAKAAsAGwAEAAAAAAAKAAoAGwAEAAAAAAAKAAkAGwAEAAAAAAAKAAgAGwAEAAAAAAAKAAcAGwAEAAAAAAAKAAYAGwAEAAAAAAAKAAUAGwAEAAAAAAAKAAQAGwAEAAAAAAAKAAMAGwAEAAAAAAAKAAIAGwADAAAAAAAKAAEAGwABAAIAAAAJAA8AGwAEAAAAAAAJAA4AGwAEAAAAAAAJAA0AGwAEAAAAAAAJAAwAGwAEAAAAAAAJAAsAGwAEAAAAAAAJAAoAGwAEAAAAAAAJAAkAGwAEAAAAAAAJAAgAGwADAAAAAAAJAAcAGwAEAAAAAAAJAAYAGwAEAAAAAAAJAAUAGwAAAAAAAAAJAAQAGwAEAAAAAAAJAAMAGwAEAAAAAAAJAAIAGwAEAAAAAAAJAAEAGwAEAAAAAAAIAA8AGwAEAAAAAAAIAA4AGwAEAAAAAAAIAA0AGwAEAAAAAAAIAAwAGwAAAAEAAAAIAAsAGwAEAAAAAAAIAAoAGwAEAAAAAAAIAAkAGwAEAAAAAAAIAAgAGwAEAAAAAAAIAAcAGwAEAAAAAAAIAAYAGwAEAAAAAAAIAAUAGwAEAAAAAAAIAAQAGwAEAAAAAAAIAAMAGwAEAAAAAAAIAAIAGwAEAAAAAAAIAAEAGwAEAAAAAAAHAA8AGwABAAIAAAAHAA4AGwAEAAAAAAAHAA0AGwAEAAAAAAAHAAwAGwABAAAAAAAHAAsAGwAEAAAAAAAHAAoAGwAEAAAAAAAHAAkAGwAEAAAAAAAHAAgAGwAEAAAAAAAHAAcAGwAEAAAAAAAHAAYAGwAEAAAAAAAHAAUAGwAEAAAAAAAHAAQAGwAEAAAAAAAHAAMAGwAEAAAAAAAHAAIAGwAEAAAAAAAHAAEAGwAEAAAAAAAGAA8AGwAEAAAAAAAGAA4AGwAEAAAAAAAGAA0AGwAEAAAAAAAGAAwAGwAEAAAAAAAGAAsAGwAEAAAAAAAGAAoAGwAEAAAAAAAGAAkAGwAEAAAAAAAGAAgAGwACAAEAAAAGAAcAGwAEAAAAAAAGAAYAGwAEAAAAAAAGAAUAGwAAAAAAAAAGAAQAGwAEAAAAAAAGAAMAGwAEAAAAAAAGAAIAGwAEAAAAAAAGAAEAGwAEAAAAAAAFAA8AGwACAAEAAAAFAA4AGwABAAIAAAAFAA0AGwAEAAAAAAAFAAwAGwAEAAAAAAAFAAsAGwAEAAAAAAAFAAoAGwAEAAAAAAAFAAkAGwAEAAAAAAAFAAgAGwAEAAAAAAAFAAcAGwAEAAAAAAAFAAYAGwAEAAAAAAAFAAUAGwAAAAAAAAAFAAQAGwAEAAAAAAAFAAMAGwAEAAAAAAAFAAIAGwAEAAAAAAAFAAEAGwACAAEAAAAEAA8AGwAEAAAAAAAEAA4AGwAEAAAAAAAEAA0AGwAAAAAAAAAEAAwAGwAEAAAAAAAEAAsAGwAEAAAAAAAEAAoAGwAEAAAAAAAEAAkAGwAEAAAAAAAEAAgAGwAEAAAAAAAEAAcAGwAEAAAAAAAEAAYAGwAEAAAAAAAEAAUAGwAEAAAAAAAEAAQAGwAEAAAAAAAEAAMAGwAEAAAAAAAEAAIAGwAEAAAAAAAEAAEAGwAEAAAAAAADAA8AGwAEAAAAAAADAA4AGwAEAAAAAAADAA0AGwAEAAAAAAADAAwAGwAEAAAAAAADAAsAGwAEAAAAAAADAAoAGwAEAAAAAAADAAkAGwAEAAAAAAADAAgAGwAEAAAAAAADAAcAGwAEAAAAAAADAAYAGwAEAAAAAAADAAUAGwAEAAAAAAADAAQAGwAEAAAAAAADAAMAGwAEAAAAAAADAAIAGwAEAAAAAAADAAEAGwABAAIAAAACAA8AGwAEAAAAAAACAA4AGwAAAAAAAAACAA0AGwAAAAEAAAACAAwAGwAEAAAAAAACAAsAGwAEAAAAAAACAAoAGwAEAAAAAAACAAkAGwADAAIAAAACAAgAGwAEAAAAAAACAAcAGwAEAAAAAAACAAYAGwABAAIAAAACAAUAGwAEAAAAAAACAAQAGwAEAAAAAAACAAMAGwAEAAAAAAACAAIAGwAEAAAAAAACAAEAGwAAAAMAAAABAA8AGwAEAAAAAAABAA4AGwAEAAAAAAABAA0AGwAEAAAAAAABAAwAGwAEAAAAAAABAAsAGwADAAMAAAABAAoAGwABAAAAAAABAAkAGwAEAAAAAAABAAgAGwAEAAAAAAABAAcAGwABAAAAAAABAAYAGwAEAAAAAAABAAUAGwAEAAAAAAABAAQAGwAEAAAAAAABAAMAGwAEAAAAAAABAAIAGwAEAAAAAAABAAEAGwAEAAAAAAA=")
+
+[node name="Walls" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAEABAAFAAQAAAAEAAAABAACAAQAAAADAAEABAACAAYAAAADAAAABAACAAQAAAACAAEABAACAAYAAAACAAAABAACAAQAAAABAAEABAAEAAQAAAABAAAABAACAAQAAAAAAAEABAABAAUAAAAAAAAABAABAAQAAAABAAMABAADAAYAAAABAAIABAADAAUAAAAAAAMABAABAAYAAAAAAAIABAABAAUAAAATAAEABAADAAUAAAATAAAABAADAAQAAAASAAEABAAFAAQAAAASAAAABAACAAQAAAARAAEABAACAAYAAAARAAAABAACAAQAAAAQAAEABAACAAYAAAAQAAAABAACAAQAAAATAAMABAADAAYAAAATAAIABAADAAUAAAASAAMABAABAAYAAAASAAIABAABAAUAAAATAA8ABAADAAYAAAATAA4ABAADAAUAAAASAA8ABAACAAYAAAASAA4ABAACAAUAAAARAA8ABAACAAYAAAARAA4ABAAFAAUAAAAQAA8ABAACAAYAAAAQAA4ABAACAAQAAAATAA0ABAADAAQAAAASAA0ABAACAAQAAAADAA8ABAACAAYAAAADAA4ABAACAAQAAAACAA8ABAACAAYAAAACAA4ABAACAAQAAAABAA8ABAACAAYAAAABAA4ABAACAAQAAAAAAA8ABAABAAYAAAAAAA4ABAABAAQAAAAFAAAABAACAAQAAAAGAAAABAACAAQAAAAGAAEABAACAAYAAAAHAAEABAACAAYAAAAFAAEABAAEAAQAAAAHAAAABAACAAQAAAAIAAEABAACAAYAAAAIAAAABAACAAQAAAARAA0ABAABAAQAAAAPAA8ABAACAAYAAAAOAA8ABAACAAYAAAANAA8ABAACAAYAAAANAA4ABAAFAAUAAAAOAA4ABAAEAAUAAAAPAA4ABAACAAQAAAAEAA4ABAACAAQAAAAFAA4ABAAFAAUAAAAGAA4ABAACAAUAAAAHAA4ABAACAAUAAAAEAA8ABAACAAYAAAAFAA8ABAACAAYAAAAGAA8ABAACAAYAAAAHAA8ABAACAAYAAAAIAA4ABAAEAAUAAAAJAA4ABAACAAQAAAAKAA4ABAACAAQAAAALAA4ABAACAAQAAAAMAA4ABAACAAQAAAAMAA8ABAACAAYAAAALAA8ABAACAAYAAAAKAA8ABAACAAYAAAAJAA8ABAACAAYAAAAIAA8ABAACAAYAAAAJAAAABAACAAQAAAAKAAAABAACAAQAAAALAAEABAACAAUAAAAMAAEABAACAAUAAAANAAEABAAEAAQAAAAOAAEABAACAAYAAAAPAAEABAACAAYAAAAPAAAABAACAAQAAAAOAAAABAACAAQAAAANAAAABAACAAQAAAAMAAAABAACAAQAAAAKAAEABAACAAUAAAAJAAEABAAFAAQAAAALAAAABAACAAQAAAANAAIABAADAAYAAAAMAAIABAACAAYAAAALAAIABAACAAYAAAAKAAMABAADAAUAAAAJAAMABAAFAAUAAAAIAAQABAACAAUAAAAHAAQABAABAAUAAAAHAAUABAAFAAUAAAAGAAUABAABAAQAAAAJAAQABAACAAUAAAAKAAIABAAEAAQAAAAJAAIABAABAAUAAAAIAAMABAACAAQAAAAIAAUABAAEAAQAAAAHAAYABAAEAAQAAAAIAAYABAADAAYAAAAJAAUABAACAAYAAAAKAAUABAACAAYAAAAKAAQABAAEAAUAAAAGAAYABAAFAAUAAAAGAAcABAACAAYAAAAFAAcABAABAAYAAAAHAAcABAADAAYAAAAHAAMABAABAAQAAAALAAUABAAFAAYAAAAMAAUABAADAAQAAAAMAAYABAADAAUAAAANAAcABAACAAQAAAAOAAcABAACAAQAAAAOAAgABAACAAYAAAAPAAgABAADAAYAAAANAAgABAACAAYAAAAMAAcABAAFAAYAAAALAAcABAABAAYAAAALAAYABAABAAUAAAALAAQABAADAAQAAAAMAAgABAABAAYAAAAFAAYABAABAAQAAAAPAAcABAADAAQAAAAIAA0ABAADAAQAAAAHAA0ABAAEAAUAAAAHAAwABAADAAQAAAAGAAwABAAEAAUAAAAGAAsABAADAAQAAAAFAAsABAABAAQAAAAFAAwABAABAAUAAAAFAA0ABAABAAUAAAAGAA0ABAACAAUAAAAOAA0ABAADAAUAAAANAAwABAABAAQAAAANAA0ABAABAAUAAAAOAAwABAADAAQAAAABAAcABAADAAQAAAAAAAcABAABAAQAAAAAAAgABAABAAYAAAABAAgABAADAAYAAAAEAAIABAABAAYAAAAFAAIABAADAAYAAAA=")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
diff --git a/mapgen/layout/start/001.tscn b/mapgen/layout/start/001.tscn
new file mode 100644
index 0000000..69739cb
--- /dev/null
+++ b/mapgen/layout/start/001.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=2 format=4 uid="uid://ddvhey8d6vfbl"]
+
+[ext_resource type="TileSet" uid="uid://c66l102pgntht" path="res://assets/sprites/tilesets/forest.tres" id="1_atytl"]
+
+[node name="Start-001" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAAAAAAADAAIAAAAAAAEAAAADAAIAAAAAAAIAAAADAAIAAAAAAAMAAAADAAIAAAAAAAQAAAADAAIAAAAAAAUAAAADAAIAAAAAAAYAAAADAAIAAAAAAAcAAAADAAIAAAAAAAgAAAADAAIAAAAAAAkAAAADAAIAAAAAAAoAAAADAAIAAAAAAAsAAAADAAIAAAAAAAwAAAADAAIAAAAAAA0AAAADAAIAAAAAAA4AAAADAAIAAAAAAA8AAAADAAIAAAABAAAAAAADAAIAAAABAAEAAAADAAIAAAABAAIAAAADAAIAAAABAAMAAAADAAIAAAABAAQAAAADAAIAAAABAAUAAAADAAIAAAABAAYAAAADAAIAAAABAAcAAAADAAIAAAABAAgAAAADAAIAAAABAAkAAAADAAIAAAABAAoAAAADAAIAAAABAAsAAAADAAIAAAABAAwAAAADAAIAAAABAA0AAAADAAIAAAABAA4AAAADAAIAAAABAA8AAAADAAIAAAACAAAAAAADAAIAAAACAAEAAAADAAIAAAACAAIAAAADAAIAAAACAAMAAAADAAIAAAACAAQAAAADAAIAAAACAAUAAAADAAIAAAACAAYAAAADAAIAAAACAAcAAAADAAIAAAACAAgAAAADAAIAAAACAAkAAAADAAIAAAACAAoAAAADAAIAAAACAAsAAAADAAIAAAACAAwAAAADAAIAAAACAA0AAAADAAIAAAACAA4AAAADAAIAAAACAA8AAAADAAIAAAADAAAAAAADAAIAAAADAAEAAAADAAIAAAADAAIAAAADAAIAAAADAAMAAAADAAIAAAADAAQAAAADAAIAAAADAAUAAAADAAIAAAADAAYAAAADAAIAAAADAAcAAAADAAIAAAADAAgAAAADAAIAAAADAAkAAAADAAIAAAADAAoAAAADAAIAAAADAAsAAAADAAIAAAADAAwAAAADAAIAAAADAA0AAAADAAIAAAADAA4AAAADAAIAAAADAA8AAAADAAIAAAAEAAAAAAADAAIAAAAEAAEAAAADAAIAAAAEAAIAAAADAAIAAAAEAAMAAAADAAIAAAAEAAQAAAADAAIAAAAEAAUAAAADAAIAAAAEAAYAAAADAAIAAAAEAAcAAAADAAIAAAAEAAgAAAADAAIAAAAEAAkAAAADAAIAAAAEAAoAAAADAAIAAAAEAAsAAAADAAIAAAAEAAwAAAADAAIAAAAEAA0AAAADAAIAAAAEAA4AAAADAAIAAAAEAA8AAAADAAIAAAAFAAAAAAADAAIAAAAFAAEAAAADAAIAAAAFAAIAAAADAAIAAAAFAAMAAAADAAIAAAAFAAQAAAADAAIAAAAFAAUAAAADAAIAAAAFAAYAAAADAAIAAAAFAAcAAAADAAIAAAAFAAgAAAADAAIAAAAFAAkAAAADAAIAAAAFAAoAAAADAAIAAAAFAAsAAAADAAIAAAAFAAwAAAADAAIAAAAFAA0AAAADAAIAAAAFAA4AAAADAAIAAAAFAA8AAAADAAIAAAAGAAAAAAADAAIAAAAGAAEAAAADAAIAAAAGAAIAAAADAAIAAAAGAAMAAAADAAIAAAAGAAQAAAADAAIAAAAGAAUAAAADAAIAAAAGAAYAAAADAAIAAAAGAAcAAAADAAIAAAAGAAgAAAADAAIAAAAGAAkAAAADAAIAAAAGAAoAAAADAAIAAAAGAAsAAAADAAIAAAAGAAwAAAADAAIAAAAGAA0AAAADAAIAAAAGAA4AAAADAAIAAAAGAA8AAAADAAIAAAAHAAAAAAADAAIAAAAHAAEAAAADAAIAAAAHAAIAAAADAAIAAAAHAAMAAAADAAIAAAAHAAQAAAADAAIAAAAHAAUAAAADAAIAAAAHAAYAAAADAAIAAAAHAAcAAAADAAIAAAAHAAgAAAADAAIAAAAHAAkAAAADAAIAAAAHAAoAAAADAAIAAAAHAAsAAAADAAIAAAAHAAwAAAADAAIAAAAHAA0AAAADAAIAAAAHAA4AAAADAAIAAAAHAA8AAAADAAIAAAAIAAAAAAADAAIAAAAIAAEAAAADAAIAAAAIAAIAAAADAAIAAAAIAAMAAAADAAIAAAAIAAQAAAADAAIAAAAIAAUAAAADAAIAAAAIAAYAAAADAAIAAAAIAAcAAAADAAIAAAAIAAgAAAADAAIAAAAIAAkAAAADAAIAAAAIAAoAAAADAAIAAAAIAAsAAAADAAIAAAAIAAwAAAADAAIAAAAIAA0AAAADAAIAAAAIAA4AAAADAAIAAAAIAA8AAAADAAIAAAAJAAAAAAADAAIAAAAJAAEAAAADAAIAAAAJAAIAAAADAAIAAAAJAAMAAAADAAIAAAAJAAQAAAADAAIAAAAJAAUAAAADAAIAAAAJAAYAAAADAAIAAAAJAAcAAAADAAIAAAAJAAgAAAADAAIAAAAJAAkAAAADAAIAAAAJAAoAAAADAAIAAAAJAAsAAAADAAIAAAAJAAwAAAADAAIAAAAJAA0AAAADAAIAAAAJAA4AAAADAAIAAAAJAA8AAAADAAIAAAAKAAAAAAADAAIAAAAKAAEAAAADAAIAAAAKAAIAAAADAAIAAAAKAAMAAAADAAIAAAAKAAQAAAADAAIAAAAKAAUAAAADAAIAAAAKAAYAAAADAAIAAAAKAAcAAAADAAIAAAAKAAgAAAADAAIAAAAKAAkAAAADAAIAAAAKAAoAAAADAAIAAAAKAAsAAAADAAIAAAAKAAwAAAADAAIAAAAKAA0AAAADAAIAAAAKAA4AAAADAAIAAAAKAA8AAAADAAIAAAALAAAAAAADAAIAAAALAAEAAAADAAIAAAALAAIAAAADAAIAAAALAAMAAAADAAIAAAALAAQAAAADAAIAAAALAAUAAAADAAIAAAALAAYAAAADAAIAAAALAAcAAAADAAIAAAALAAgAAAADAAIAAAALAAkAAAADAAIAAAALAAoAAAADAAIAAAALAAsAAAADAAIAAAALAAwAAAADAAIAAAALAA0AAAADAAIAAAALAA4AAAADAAIAAAALAA8AAAADAAIAAAAMAAAAAAADAAIAAAAMAAEAAAADAAIAAAAMAAIAAAADAAIAAAAMAAMAAAADAAIAAAAMAAQAAAADAAIAAAAMAAUAAAADAAIAAAAMAAYAAAADAAIAAAAMAAcAAAADAAIAAAAMAAgAAAADAAIAAAAMAAkAAAADAAIAAAAMAAoAAAADAAIAAAAMAAsAAAADAAIAAAAMAAwAAAADAAIAAAAMAA0AAAADAAIAAAAMAA4AAAADAAIAAAAMAA8AAAADAAIAAAANAAAAAAADAAIAAAANAAEAAAADAAIAAAANAAIAAAADAAIAAAANAAMAAAADAAIAAAANAAQAAAADAAIAAAANAAUAAAADAAIAAAANAAYAAAADAAIAAAANAAcAAAADAAIAAAANAAgAAAADAAIAAAANAAkAAAADAAIAAAANAAoAAAADAAIAAAANAAsAAAADAAIAAAANAAwAAAADAAIAAAANAA0AAAADAAIAAAANAA4AAAADAAIAAAANAA8AAAADAAIAAAAOAAAAAAADAAIAAAAOAAEAAAADAAIAAAAOAAIAAAADAAIAAAAOAAMAAAADAAIAAAAOAAQAAAADAAIAAAAOAAUAAAADAAIAAAAOAAYAAAADAAIAAAAOAAcAAAADAAIAAAAOAAgAAAADAAIAAAAOAAkAAAADAAIAAAAOAAoAAAADAAIAAAAOAAsAAAADAAIAAAAOAAwAAAADAAIAAAAOAA0AAAADAAIAAAAOAA4AAAADAAIAAAAOAA8AAAADAAIAAAAPAAAAAAADAAIAAAAPAAEAAAADAAIAAAAPAAIAAAADAAIAAAAPAAMAAAADAAIAAAAPAAQAAAADAAIAAAAPAAUAAAADAAIAAAAPAAYAAAADAAIAAAAPAAcAAAADAAIAAAAPAAgAAAADAAIAAAAPAAkAAAADAAIAAAAPAAoAAAADAAIAAAAPAAsAAAADAAIAAAAPAAwAAAADAAIAAAAPAA0AAAADAAIAAAAPAA4AAAADAAIAAAAPAA8AAAADAAIAAAAQAAAAAAADAAIAAAAQAAEAAAADAAIAAAAQAAIAAAADAAIAAAAQAAMAAAADAAIAAAAQAAQAAAADAAIAAAAQAAUAAAADAAIAAAAQAAYAAAADAAIAAAAQAAcAAAADAAIAAAAQAAgAAAADAAIAAAAQAAkAAAADAAIAAAAQAAoAAAADAAIAAAAQAAsAAAADAAIAAAAQAAwAAAADAAIAAAAQAA0AAAADAAIAAAAQAA4AAAADAAIAAAAQAA8AAAADAAIAAAARAAAAAAADAAIAAAARAAEAAAADAAIAAAARAAIAAAADAAIAAAARAAMAAAADAAIAAAARAAQAAAADAAIAAAARAAUAAAADAAIAAAARAAYAAAADAAIAAAARAAcAAAADAAIAAAARAAgAAAADAAIAAAARAAkAAAADAAIAAAARAAoAAAADAAIAAAARAAsAAAADAAIAAAARAAwAAAADAAIAAAARAA0AAAADAAIAAAARAA4AAAADAAIAAAARAA8AAAADAAIAAAASAAAAAAADAAIAAAASAAEAAAADAAIAAAASAAIAAAADAAIAAAASAAMAAAADAAIAAAASAAQAAAADAAIAAAASAAUAAAADAAIAAAASAAYAAAADAAIAAAASAAcAAAADAAIAAAASAAgAAAADAAIAAAASAAkAAAADAAIAAAASAAoAAAADAAIAAAASAAsAAAADAAIAAAASAAwAAAADAAIAAAASAA0AAAADAAIAAAASAA4AAAADAAIAAAASAA8AAAADAAIAAAATAAAAAAADAAIAAAATAAEAAAADAAIAAAATAAIAAAADAAIAAAATAAMAAAADAAIAAAATAAQAAAADAAIAAAATAAUAAAADAAIAAAATAAYAAAADAAIAAAATAAcAAAADAAIAAAATAAgAAAADAAIAAAATAAkAAAADAAIAAAATAAoAAAADAAIAAAATAAsAAAADAAIAAAATAAwAAAADAAIAAAATAA0AAAADAAIAAAATAA4AAAADAAIAAAATAA8AAAADAAIAAAA=")
+tile_set = ExtResource("1_atytl")
+
+[node name="Water" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAANAAgAAAAMAAIAAAANAAcAAAAMAAEAAAANAAYAAAAMAAAAAAAMAAgAAAALAAIAAAAMAAcAAAALAAEAAAAMAAYAAAALAAAAAAALAAgAAAAOAAEAAAALAAcAAAALAAEAAAALAAYAAAALAAAAAAAKAAgAAAANAAAAAAAKAAcAAAAKAAEAAAAKAAYAAAAKAAAAAAALAAoAAAAMAAIAAAALAAkAAAAMAAEAAAAKAAoAAAALAAIAAAAKAAkAAAALAAEAAAAJAAoAAAAOAAEAAAAJAAkAAAANAAAAAAAJAAgAAAAKAAAAAAAJAAsAAAAMAAIAAAAIAAsAAAALAAIAAAAIAAoAAAALAAEAAAAIAAkAAAALAAAAAAAHAAsAAAAOAAEAAAAHAAoAAAANAAAAAAAHAAkAAAAKAAAAAAAHAAwAAAAMAAIAAAAGAAwAAAALAAIAAAAGAAsAAAALAAEAAAAGAAoAAAALAAAAAAAFAAwAAAAKAAIAAAAFAAsAAAAKAAEAAAAFAAoAAAAKAAAAAAA=")
+tile_set = ExtResource("1_atytl")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAMAEwADAAEAAAAFAAMAEwAEAAEAAAALAAcACwAAAAAAAAALAAgACwAAAAEAAAALAAkACwAAAAIAAAAMAAcACwABAAAAAAAMAAgACwABAAEAAAAMAAkACwABAAIAAAANAAcACwACAAAAAAANAAgACwACAAEAAAANAAkACwACAAIAAAA=")
+tile_set = ExtResource("1_atytl")
diff --git a/mapgen/layout/template.tscn b/mapgen/layout/template.tscn
new file mode 100644
index 0000000..5e63f13
--- /dev/null
+++ b/mapgen/layout/template.tscn
@@ -0,0 +1,17 @@
+[gd_scene load_steps=2 format=4 uid="uid://ktvm2hjo3ywv"]
+
+[ext_resource type="TileSet" uid="uid://eucffgw5x26k" path="res://assets/sprites/tilesets/basic.tres" id="1_13h24"]
+
+[node name="Template" type="Node2D"]
+
+[node name="Ground" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAAAA8AGwAEAAAAAAAAAA4AGwACAAIAAAAAAA0AGwAEAAAAAAAAAAwAGwAEAAAAAAAAAAsAGwAEAAAAAAAAAAoAGwAEAAAAAAAAAAkAGwABAAIAAAAAAAgAGwACAAEAAAAAAAcAGwAEAAAAAAAAAAYAGwAEAAAAAAAAAAUAGwAEAAAAAAAAAAQAGwAEAAAAAAAAAAMAGwABAAMAAAAAAAIAGwABAAIAAAAAAAEAGwAEAAAAAAAAAAAAGwAEAAAAAAAJAAAAGwACAAEAAAAIAAAAGwAEAAAAAAAHAAAAGwAEAAAAAAAGAAAAGwABAAAAAAAFAAAAGwAEAAAAAAAEAAAAGwAEAAAAAAADAAAAGwAEAAAAAAACAAAAGwADAAIAAAABAAAAGwAEAAAAAAATAAAAGwADAAAAAAASAAAAGwAEAAAAAAARAAAAGwAEAAAAAAAQAAAAGwAEAAAAAAAPAAAAGwAEAAAAAAAOAAAAGwAEAAAAAAANAAAAGwAEAAAAAAAMAAAAGwAEAAAAAAALAAAAGwAAAAEAAAAKAAAAGwAEAAAAAAATAA8AGwAEAAAAAAATAA4AGwAEAAAAAAATAA0AGwAEAAAAAAATAAwAGwAEAAAAAAATAAsAGwAEAAAAAAATAAoAGwAEAAAAAAATAAkAGwAEAAAAAAATAAgAGwAEAAAAAAATAAcAGwAEAAAAAAATAAYAGwAEAAAAAAATAAUAGwAEAAAAAAATAAQAGwAEAAAAAAATAAMAGwAEAAAAAAATAAIAGwAEAAAAAAATAAEAGwAEAAAAAAASAA8AGwAEAAAAAAASAA4AGwAEAAAAAAASAA0AGwAEAAAAAAASAAwAGwACAAEAAAASAAsAGwAEAAAAAAASAAoAGwAEAAAAAAASAAkAGwAEAAAAAAASAAgAGwAEAAAAAAASAAcAGwAEAAAAAAASAAYAGwAAAAAAAAASAAUAGwACAAAAAAASAAQAGwAEAAAAAAASAAMAGwAEAAAAAAASAAIAGwAEAAAAAAASAAEAGwAEAAAAAAARAA8AGwAEAAAAAAARAA4AGwAEAAAAAAARAA0AGwABAAAAAAARAAwAGwAEAAAAAAARAAsAGwAEAAAAAAARAAoAGwAAAAIAAAARAAkAGwAEAAAAAAARAAgAGwAEAAAAAAARAAcAGwAEAAAAAAARAAYAGwAEAAAAAAARAAUAGwACAAIAAAARAAQAGwAEAAAAAAARAAMAGwAEAAAAAAARAAIAGwAEAAAAAAARAAEAGwAEAAAAAAAQAA8AGwAAAAAAAAAQAA4AGwAEAAAAAAAQAA0AGwAEAAAAAAAQAAwAGwAEAAAAAAAQAAsAGwACAAIAAAAQAAoAGwAEAAAAAAAQAAkAGwAEAAAAAAAQAAgAGwAEAAAAAAAQAAcAGwADAAEAAAAQAAYAGwAEAAAAAAAQAAUAGwAEAAAAAAAQAAQAGwABAAMAAAAQAAMAGwABAAAAAAAQAAIAGwAEAAAAAAAQAAEAGwAEAAAAAAAPAA8AGwABAAEAAAAPAA4AGwAEAAAAAAAPAA0AGwAEAAAAAAAPAAwAGwAEAAAAAAAPAAsAGwAEAAAAAAAPAAoAGwAEAAAAAAAPAAkAGwAEAAAAAAAPAAgAGwAEAAAAAAAPAAcAGwAEAAAAAAAPAAYAGwADAAAAAAAPAAUAGwAEAAAAAAAPAAQAGwAEAAAAAAAPAAMAGwAEAAAAAAAPAAIAGwAEAAAAAAAPAAEAGwAEAAAAAAAOAA8AGwAEAAAAAAAOAA4AGwAEAAAAAAAOAA0AGwAEAAAAAAAOAAwAGwAEAAAAAAAOAAsAGwAEAAAAAAAOAAoAGwAEAAAAAAAOAAkAGwAEAAAAAAAOAAgAGwABAAIAAAAOAAcAGwAEAAAAAAAOAAYAGwAEAAAAAAAOAAUAGwAAAAIAAAAOAAQAGwAEAAAAAAAOAAMAGwAEAAAAAAAOAAIAGwAEAAAAAAAOAAEAGwAEAAAAAAANAA8AGwAEAAAAAAANAA4AGwAEAAAAAAANAA0AGwABAAAAAAANAAwAGwAEAAAAAAANAAsAGwAEAAAAAAANAAoAGwABAAMAAAANAAkAGwAEAAAAAAANAAgAGwACAAIAAAANAAcAGwAEAAAAAAANAAYAGwABAAIAAAANAAUAGwAEAAAAAAANAAQAGwAEAAAAAAANAAMAGwAEAAAAAAANAAIAGwAEAAAAAAANAAEAGwABAAAAAAAMAA8AGwAEAAAAAAAMAA4AGwAEAAAAAAAMAA0AGwAAAAEAAAAMAAwAGwABAAIAAAAMAAsAGwAEAAAAAAAMAAoAGwAEAAAAAAAMAAkAGwAEAAAAAAAMAAgAGwAEAAAAAAAMAAcAGwAEAAAAAAAMAAYAGwAEAAAAAAAMAAUAGwAEAAAAAAAMAAQAGwAEAAAAAAAMAAMAGwAEAAAAAAAMAAIAGwAEAAAAAAAMAAEAGwAEAAAAAAALAA8AGwAEAAAAAAALAA4AGwAEAAAAAAALAA0AGwAEAAAAAAALAAwAGwAEAAAAAAALAAsAGwAEAAAAAAALAAoAGwAEAAAAAAALAAkAGwAEAAAAAAALAAgAGwAEAAAAAAALAAcAGwAEAAAAAAALAAYAGwAEAAAAAAALAAUAGwAAAAAAAAALAAQAGwAEAAAAAAALAAMAGwAEAAAAAAALAAIAGwAEAAAAAAALAAEAGwABAAAAAAAKAA8AGwAEAAAAAAAKAA4AGwAEAAAAAAAKAA0AGwAEAAAAAAAKAAwAGwAEAAAAAAAKAAsAGwAEAAAAAAAKAAoAGwAEAAAAAAAKAAkAGwAEAAAAAAAKAAgAGwAEAAAAAAAKAAcAGwAEAAAAAAAKAAYAGwAEAAAAAAAKAAUAGwAEAAAAAAAKAAQAGwAEAAAAAAAKAAMAGwAEAAAAAAAKAAIAGwADAAAAAAAKAAEAGwABAAIAAAAJAA8AGwAEAAAAAAAJAA4AGwAEAAAAAAAJAA0AGwAEAAAAAAAJAAwAGwAEAAAAAAAJAAsAGwAEAAAAAAAJAAoAGwAEAAAAAAAJAAkAGwAEAAAAAAAJAAgAGwADAAAAAAAJAAcAGwAEAAAAAAAJAAYAGwAEAAAAAAAJAAUAGwAAAAAAAAAJAAQAGwAEAAAAAAAJAAMAGwAEAAAAAAAJAAIAGwAEAAAAAAAJAAEAGwAEAAAAAAAIAA8AGwAEAAAAAAAIAA4AGwAEAAAAAAAIAA0AGwAEAAAAAAAIAAwAGwAAAAEAAAAIAAsAGwAEAAAAAAAIAAoAGwAEAAAAAAAIAAkAGwAEAAAAAAAIAAgAGwAEAAAAAAAIAAcAGwAEAAAAAAAIAAYAGwAEAAAAAAAIAAUAGwAEAAAAAAAIAAQAGwAEAAAAAAAIAAMAGwAEAAAAAAAIAAIAGwAEAAAAAAAIAAEAGwAEAAAAAAAHAA8AGwABAAIAAAAHAA4AGwAEAAAAAAAHAA0AGwAEAAAAAAAHAAwAGwABAAAAAAAHAAsAGwAEAAAAAAAHAAoAGwAEAAAAAAAHAAkAGwAEAAAAAAAHAAgAGwAEAAAAAAAHAAcAGwAEAAAAAAAHAAYAGwAEAAAAAAAHAAUAGwAEAAAAAAAHAAQAGwAEAAAAAAAHAAMAGwAEAAAAAAAHAAIAGwAEAAAAAAAHAAEAGwAEAAAAAAAGAA8AGwAEAAAAAAAGAA4AGwAEAAAAAAAGAA0AGwAEAAAAAAAGAAwAGwAEAAAAAAAGAAsAGwAEAAAAAAAGAAoAGwAEAAAAAAAGAAkAGwAEAAAAAAAGAAgAGwACAAEAAAAGAAcAGwAEAAAAAAAGAAYAGwAEAAAAAAAGAAUAGwAAAAAAAAAGAAQAGwAEAAAAAAAGAAMAGwAEAAAAAAAGAAIAGwAEAAAAAAAGAAEAGwAEAAAAAAAFAA8AGwACAAEAAAAFAA4AGwABAAIAAAAFAA0AGwAEAAAAAAAFAAwAGwAEAAAAAAAFAAsAGwAEAAAAAAAFAAoAGwAEAAAAAAAFAAkAGwAEAAAAAAAFAAgAGwAEAAAAAAAFAAcAGwAEAAAAAAAFAAYAGwAEAAAAAAAFAAUAGwAAAAAAAAAFAAQAGwAEAAAAAAAFAAMAGwAEAAAAAAAFAAIAGwAEAAAAAAAFAAEAGwACAAEAAAAEAA8AGwAEAAAAAAAEAA4AGwAEAAAAAAAEAA0AGwAAAAAAAAAEAAwAGwAEAAAAAAAEAAsAGwAEAAAAAAAEAAoAGwAEAAAAAAAEAAkAGwAEAAAAAAAEAAgAGwAEAAAAAAAEAAcAGwAEAAAAAAAEAAYAGwAEAAAAAAAEAAUAGwAEAAAAAAAEAAQAGwAEAAAAAAAEAAMAGwAEAAAAAAAEAAIAGwAEAAAAAAAEAAEAGwAEAAAAAAADAA8AGwAEAAAAAAADAA4AGwAEAAAAAAADAA0AGwAEAAAAAAADAAwAGwAEAAAAAAADAAsAGwAEAAAAAAADAAoAGwAEAAAAAAADAAkAGwAEAAAAAAADAAgAGwAEAAAAAAADAAcAGwAEAAAAAAADAAYAGwAEAAAAAAADAAUAGwAEAAAAAAADAAQAGwAEAAAAAAADAAMAGwAEAAAAAAADAAIAGwAEAAAAAAADAAEAGwABAAIAAAACAA8AGwAEAAAAAAACAA4AGwAAAAAAAAACAA0AGwAAAAEAAAACAAwAGwAEAAAAAAACAAsAGwAEAAAAAAACAAoAGwAEAAAAAAACAAkAGwADAAIAAAACAAgAGwAEAAAAAAACAAcAGwAEAAAAAAACAAYAGwABAAIAAAACAAUAGwAEAAAAAAACAAQAGwAEAAAAAAACAAMAGwAEAAAAAAACAAIAGwAEAAAAAAACAAEAGwAAAAMAAAABAA8AGwAEAAAAAAABAA4AGwAEAAAAAAABAA0AGwAEAAAAAAABAAwAGwAEAAAAAAABAAsAGwADAAMAAAABAAoAGwABAAAAAAABAAkAGwAEAAAAAAABAAgAGwAEAAAAAAABAAcAGwABAAAAAAABAAYAGwAEAAAAAAABAAUAGwAEAAAAAAABAAQAGwAEAAAAAAABAAMAGwAEAAAAAAABAAIAGwAEAAAAAAABAAEAGwAEAAAAAAA=")
+tile_set = ExtResource("1_13h24")
+
+[node name="Walls" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAEABAADAAYAAAAEAAAABAADAAQAAAADAAEABAACAAYAAAADAAAABAACAAQAAAACAAEABAACAAYAAAACAAAABAACAAQAAAABAAEABAAEAAQAAAABAAAABAACAAQAAAAAAAEABAABAAUAAAAAAAAABAABAAQAAAABAAMABAADAAYAAAABAAIABAADAAUAAAAAAAMABAABAAYAAAAAAAIABAABAAUAAAATAAEABAADAAUAAAATAAAABAADAAQAAAASAAEABAAFAAQAAAASAAAABAACAAQAAAARAAEABAACAAYAAAARAAAABAACAAQAAAAQAAEABAABAAYAAAAQAAAABAABAAQAAAATAAMABAADAAYAAAATAAIABAADAAUAAAASAAMABAABAAYAAAASAAIABAABAAUAAAATAA8ABAADAAYAAAATAA4ABAADAAUAAAASAA8ABAACAAYAAAASAA4ABAAFAAUAAAARAA8ABAACAAYAAAARAA4ABAACAAQAAAAQAA8ABAABAAYAAAAQAA4ABAABAAQAAAATAA0ABAADAAUAAAATAAwABAADAAQAAAASAA0ABAABAAUAAAASAAwABAABAAQAAAADAA8ABAADAAYAAAADAA4ABAADAAQAAAACAA8ABAACAAYAAAACAA4ABAACAAQAAAABAA8ABAACAAYAAAABAA4ABAAEAAUAAAAAAA8ABAABAAYAAAAAAA4ABAABAAUAAAABAA0ABAADAAUAAAABAAwABAADAAQAAAAAAA0ABAABAAUAAAAAAAwABAABAAQAAAA=")
+tile_set = ExtResource("1_13h24")
+
+[node name="Scatter" type="TileMapLayer" parent="."]
+tile_map_data = PackedByteArray("AAAEAAMAEwADAAEAAAAFAAMAEwAEAAEAAAA=")
+tile_set = ExtResource("1_13h24")
diff --git a/player/abilities/ability.gd b/player/abilities/ability.gd
new file mode 100644
index 0000000..5888cf9
--- /dev/null
+++ b/player/abilities/ability.gd
@@ -0,0 +1,63 @@
+class_name Ability extends Node2D
+
+@export var cooldown: float = 5.0
+@export var effect_duration: float = 3.0
+
+var modifiers: Array = []
+var can_activate: bool = true
+var cooldown_timer: Timer
+var effect_timer: Timer
+var is_active: bool = false
+
+func _ready():
+ cooldown_timer = Timer.new()
+ add_child(cooldown_timer)
+ cooldown_timer.one_shot = true
+ cooldown_timer.connect("timeout", _on_cooldown_timeout)
+
+ effect_timer = Timer.new()
+ add_child(effect_timer)
+ effect_timer.one_shot = true
+ effect_timer.connect("timeout", _on_effect_timeout)
+
+func activate():
+ if !can_activate:
+ return
+
+ # Apply ability effects
+ _start_ability()
+
+ # Apply modifiers to the ability
+ for modifier in modifiers:
+ modifier.modify_ability(self)
+
+ is_active = true
+ can_activate = false
+ effect_timer.start(effect_duration)
+
+func add_modifier(modifier: Modifier):
+ modifiers.append(modifier)
+ modifier.on_equip(self)
+
+func remove_modifier(modifier_id: String):
+ for i in range(modifiers.size()):
+ if modifiers[i].id == modifier_id:
+ modifiers[i].on_unequip(self)
+ modifiers.remove_at(i)
+ break
+
+# Override in child classes
+func _start_ability():
+ pass
+
+# Override in child classes
+func _end_ability():
+ pass
+
+func _on_effect_timeout():
+ is_active = false
+ _end_ability()
+ cooldown_timer.start(cooldown)
+
+func _on_cooldown_timeout():
+ can_activate = true
\ No newline at end of file
diff --git a/player/abilities/ability.gd.uid b/player/abilities/ability.gd.uid
new file mode 100644
index 0000000..629ba9f
--- /dev/null
+++ b/player/abilities/ability.gd.uid
@@ -0,0 +1 @@
+uid://hhd27rd8ii8j
diff --git a/player/abilities/ice_field.gd b/player/abilities/ice_field.gd
new file mode 100644
index 0000000..1df0a30
--- /dev/null
+++ b/player/abilities/ice_field.gd
@@ -0,0 +1,23 @@
+class_name IceFieldAbility extends Ability
+
+@export var field_radius: float = 150.0
+@export var damage_per_second: float = 5.0
+@export var slow_amount: float = 0.5
+
+var field_instance: Node2D
+
+func _init():
+ cooldown = 10.0
+ effect_duration = 6.0
+
+func _start_ability():
+ field_instance = preload("scenes/ice_field.tscn").instantiate()
+ field_instance.global_position = global_position
+ field_instance.radius = field_radius
+ field_instance.damage = damage_per_second
+ field_instance.slow = slow_amount
+ get_tree().root.add_child(field_instance)
+
+func _end_ability():
+ if is_instance_valid(field_instance):
+ field_instance.queue_free()
\ No newline at end of file
diff --git a/player/abilities/ice_field.gd.uid b/player/abilities/ice_field.gd.uid
new file mode 100644
index 0000000..bc38dc1
--- /dev/null
+++ b/player/abilities/ice_field.gd.uid
@@ -0,0 +1 @@
+uid://cs28213tcth26
diff --git a/player/abilities/scenes/ice_field.tscn b/player/abilities/scenes/ice_field.tscn
new file mode 100644
index 0000000..b5c0706
--- /dev/null
+++ b/player/abilities/scenes/ice_field.tscn
@@ -0,0 +1,8 @@
+[gd_scene load_steps=2 format=3 uid="uid://btymtl71y0hx7"]
+
+[sub_resource type="ImageTexture" id="ImageTexture_6kos1"]
+
+[node name="IceField" type="Node2D"]
+
+[node name="Sprite2D" type="Sprite2D" parent="."]
+texture = SubResource("ImageTexture_6kos1")
diff --git a/player/modifiers/fire_rate_additive.gd b/player/modifiers/fire_rate_additive.gd
new file mode 100644
index 0000000..9ffdbd8
--- /dev/null
+++ b/player/modifiers/fire_rate_additive.gd
@@ -0,0 +1,13 @@
+class_name FireRateAdditive extends Modifier
+
+@export var fire_rate_bonus: float = 1.0 # +1 shot per second
+
+func _init():
+ id = "fire_rate_additive"
+ display_name = "Rapid Fire"
+ description = "Increases fire rate by %0.1f shots per second" % fire_rate_bonus
+ modifier_type = ModifierType.ADDITIVE
+
+func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void:
+ if final_stats.has("fire_rate"):
+ final_stats.fire_rate += fire_rate_bonus
\ No newline at end of file
diff --git a/player/modifiers/fire_rate_additive.gd.uid b/player/modifiers/fire_rate_additive.gd.uid
new file mode 100644
index 0000000..24a2b14
--- /dev/null
+++ b/player/modifiers/fire_rate_additive.gd.uid
@@ -0,0 +1 @@
+uid://cqamoc42g8sam
diff --git a/player/modifiers/fire_rate_multiplicative.gd b/player/modifiers/fire_rate_multiplicative.gd
new file mode 100644
index 0000000..9a7867f
--- /dev/null
+++ b/player/modifiers/fire_rate_multiplicative.gd
@@ -0,0 +1,13 @@
+class_name FireRateMultiplicative extends Modifier
+
+@export var fire_rate_multiplier: float = 1.2 # 20% faster firing
+
+func _init():
+ id = "fire_rate_multiplicative"
+ display_name = "Frenzy"
+ description = "Increases fire rate by %d%%" % ((fire_rate_multiplier - 1.0) * 100)
+ modifier_type = ModifierType.MULTIPLICATIVE
+
+func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void:
+ if final_stats.has("fire_rate"):
+ final_stats.fire_rate *= fire_rate_multiplier
\ No newline at end of file
diff --git a/player/modifiers/fire_rate_multiplicative.gd.uid b/player/modifiers/fire_rate_multiplicative.gd.uid
new file mode 100644
index 0000000..0178172
--- /dev/null
+++ b/player/modifiers/fire_rate_multiplicative.gd.uid
@@ -0,0 +1 @@
+uid://bbqp2rhogkicu
diff --git a/player/modifiers/modifier.gd b/player/modifiers/modifier.gd
new file mode 100644
index 0000000..ae7f82a
--- /dev/null
+++ b/player/modifiers/modifier.gd
@@ -0,0 +1,31 @@
+class_name Modifier extends Resource
+
+enum ModifierType {
+ ADDITIVE, # Simply adds values (e.g., +5 damage)
+ MULTIPLICATIVE, # Multiplies by a percentage (e.g., 20% more damage)
+ OVERRIDE, # Completely replaces the value
+ CONDITIONAL # Applies under certain conditions
+}
+
+@export var id: String
+@export var display_name: String
+@export var description: String
+@export var icon: Texture
+@export var rarity: int
+@export var modifier_type: ModifierType = ModifierType.ADDITIVE
+@export var priority: int = 0 # Higher priority modifiers apply first
+
+# Called when the modifier is added to a weapon or ability
+func on_equip(_owner) -> void:
+ pass
+
+# Called when the modifier is removed
+func on_unequip(_owner) -> void:
+ pass
+
+# Override in child classes for specific modification logic
+func modify_projectile(_projectile) -> void:
+ pass
+
+func modify_ability(_ability) -> void:
+ pass
\ No newline at end of file
diff --git a/player/modifiers/modifier.gd.uid b/player/modifiers/modifier.gd.uid
new file mode 100644
index 0000000..3108eea
--- /dev/null
+++ b/player/modifiers/modifier.gd.uid
@@ -0,0 +1 @@
+uid://c2vpdeqk0vvrg
diff --git a/player/modifiers/piercing.gd b/player/modifiers/piercing.gd
new file mode 100644
index 0000000..5259cda
--- /dev/null
+++ b/player/modifiers/piercing.gd
@@ -0,0 +1,18 @@
+class_name PiercingModifier extends Modifier
+
+@export var pierce_count: int = 2
+
+func _init():
+ id = "piercing"
+ display_name = "Piercing Shot"
+ description = "Projectiles pass through %d enemies" % pierce_count
+
+func modify_projectile(projectile):
+ projectile.pierce_count = pierce_count
+ projectile.connect("on_hit", _on_projectile_hit)
+
+func _on_projectile_hit(projectile, _target):
+ projectile.pierce_count -= 1
+ if projectile.pierce_count <= 0:
+ projectile.pierce_count = 0
+ projectile.set_piercing(false)
\ No newline at end of file
diff --git a/player/modifiers/piercing.gd.uid b/player/modifiers/piercing.gd.uid
new file mode 100644
index 0000000..910337a
--- /dev/null
+++ b/player/modifiers/piercing.gd.uid
@@ -0,0 +1 @@
+uid://b60nonvh7ml2o
diff --git a/player/modifiers/projectile_size_additive.gd b/player/modifiers/projectile_size_additive.gd
new file mode 100644
index 0000000..cac295b
--- /dev/null
+++ b/player/modifiers/projectile_size_additive.gd
@@ -0,0 +1,25 @@
+class_name ProjectileSizeAdditive extends Modifier
+
+@export var size_increase: float = 0.5 # +50% bigger
+
+func _init():
+ id = "size_additive"
+ display_name = "Enlarged Projectiles"
+ description = "Increases projectile size by %d%%" % (size_increase * 100)
+ modifier_type = ModifierType.ADDITIVE
+
+func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void:
+ if final_stats.has("projectile_size"):
+ final_stats.projectile_size += size_increase
+
+func modify_projectile(projectile) -> void:
+ # This will be called when the projectile is created
+ # Scale is often handled in the recalculate_stats method, but we can also add visual effects here
+ projectile.connect("on_spawned", _on_projectile_spawned)
+
+func _on_projectile_spawned(projectile):
+ # Add a trail effect for larger projectiles
+ if projectile.scale.x > 1.2:
+ pass
+ #var trail = preload("res://scenes/projectile_trail.tscn").instantiate()
+ #projectile.add_child(trail)
\ No newline at end of file
diff --git a/player/modifiers/projectile_size_additive.gd.uid b/player/modifiers/projectile_size_additive.gd.uid
new file mode 100644
index 0000000..b4f7466
--- /dev/null
+++ b/player/modifiers/projectile_size_additive.gd.uid
@@ -0,0 +1 @@
+uid://hsl3es4bcvqf
diff --git a/player/modifiers/projectile_size_multiplicative.gd b/player/modifiers/projectile_size_multiplicative.gd
new file mode 100644
index 0000000..f520d7a
--- /dev/null
+++ b/player/modifiers/projectile_size_multiplicative.gd
@@ -0,0 +1,14 @@
+class_name ProjectileSizeMultiplicative extends Modifier
+
+@export var size_multiplier: float = 1.5 # 50% bigger
+
+func _init():
+ id = "size_multiplicative"
+ display_name = "Giant Projectiles"
+ description = "Multiplies projectile size by %0.1fx" % size_multiplier
+ modifier_type = ModifierType.MULTIPLICATIVE
+ priority = 10 # Higher priority than the additive version
+
+func apply_stats_modification(final_stats: Dictionary, base_stats: Dictionary) -> void:
+ if final_stats.has("projectile_size"):
+ final_stats.projectile_size *= size_multiplier
\ No newline at end of file
diff --git a/player/modifiers/projectile_size_multiplicative.gd.uid b/player/modifiers/projectile_size_multiplicative.gd.uid
new file mode 100644
index 0000000..6161101
--- /dev/null
+++ b/player/modifiers/projectile_size_multiplicative.gd.uid
@@ -0,0 +1 @@
+uid://bvfir8srnaraa
diff --git a/player/player.tscn b/player/player.tscn
new file mode 100644
index 0000000..65945b8
--- /dev/null
+++ b/player/player.tscn
@@ -0,0 +1,558 @@
+[gd_scene load_steps=82 format=3 uid="uid://bo5aw2cad3akl"]
+
+[ext_resource type="Script" uid="uid://bq038uo4cm6nv" path="res://player/scripts/player.gd" id="1_oul6g"]
+[ext_resource type="Texture2D" uid="uid://dqgq2c1h6yk3k" path="res://assets/sprites/characters/pink/Pink_Monster_Attack1_4.png" id="2_yllr7"]
+[ext_resource type="Texture2D" uid="uid://dinwwco5v2km5" path="res://assets/sprites/characters/pink/Pink_Monster_Attack2_6.png" id="3_kb6p2"]
+[ext_resource type="Texture2D" uid="uid://bx6nst3hqsww0" path="res://assets/sprites/characters/pink/Pink_Monster_Climb_4.png" id="4_wodsf"]
+[ext_resource type="Texture2D" uid="uid://0muqa26ngic7" path="res://assets/sprites/characters/pink/Pink_Monster_Death_8.png" id="5_32hag"]
+[ext_resource type="Texture2D" uid="uid://nyfqe1b4k8sx" path="res://assets/sprites/characters/pink/Pink_Monster_Hurt_4.png" id="6_tqiix"]
+[ext_resource type="Texture2D" uid="uid://b6ihjsu8qhm2x" path="res://assets/sprites/characters/pink/Pink_Monster_Idle_4.png" id="7_e7oew"]
+[ext_resource type="Texture2D" uid="uid://hnw46ye6vusn" path="res://assets/sprites/characters/pink/Pink_Monster_Jump_8.png" id="8_c35mf"]
+[ext_resource type="Texture2D" uid="uid://dun0f0rkhaew5" path="res://assets/sprites/characters/pink/Pink_Monster_Push_6.png" id="9_65viv"]
+[ext_resource type="Texture2D" uid="uid://cx2x6p0dnnm6y" path="res://assets/sprites/characters/pink/Pink_Monster_Run_6.png" id="10_x7c3f"]
+[ext_resource type="Texture2D" uid="uid://538sc3bsdell" path="res://assets/sprites/characters/pink/Pink_Monster_Throw_4.png" id="11_bjvpn"]
+[ext_resource type="Texture2D" uid="uid://efnfh4mf0ia2" path="res://assets/sprites/characters/pink/Pink_Monster_Walk_6.png" id="12_s7qer"]
+[ext_resource type="Texture2D" uid="uid://cyfq0x0h2qeof" path="res://assets/sprites/characters/pink/Pink_Monster_Walk+Attack_6.png" id="13_g4c7l"]
+
+[sub_resource type="CircleShape2D" id="CircleShape2D_rkbax"]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_knn7v"]
+atlas = ExtResource("2_yllr7")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7pqkx"]
+atlas = ExtResource("2_yllr7")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_uw77l"]
+atlas = ExtResource("2_yllr7")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_rptt4"]
+atlas = ExtResource("2_yllr7")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_r7pn3"]
+atlas = ExtResource("3_kb6p2")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_b8hi5"]
+atlas = ExtResource("3_kb6p2")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_2uvqg"]
+atlas = ExtResource("3_kb6p2")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_1s1yo"]
+atlas = ExtResource("3_kb6p2")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_5ejfk"]
+atlas = ExtResource("3_kb6p2")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jd2rm"]
+atlas = ExtResource("3_kb6p2")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_q28gw"]
+atlas = ExtResource("4_wodsf")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ge24q"]
+atlas = ExtResource("4_wodsf")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_pnw6m"]
+atlas = ExtResource("4_wodsf")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_1so5t"]
+atlas = ExtResource("4_wodsf")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_55jnj"]
+atlas = ExtResource("5_32hag")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_dx5ib"]
+atlas = ExtResource("5_32hag")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_a2xo5"]
+atlas = ExtResource("5_32hag")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_art4l"]
+atlas = ExtResource("5_32hag")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fjxoa"]
+atlas = ExtResource("5_32hag")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_gswnw"]
+atlas = ExtResource("5_32hag")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_xcnuv"]
+atlas = ExtResource("5_32hag")
+region = Rect2(192, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_j55il"]
+atlas = ExtResource("5_32hag")
+region = Rect2(224, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_hjies"]
+atlas = ExtResource("6_tqiix")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_mmwog"]
+atlas = ExtResource("6_tqiix")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_8pxes"]
+atlas = ExtResource("6_tqiix")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fmu53"]
+atlas = ExtResource("6_tqiix")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_w7j2h"]
+atlas = ExtResource("7_e7oew")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_n0b8q"]
+atlas = ExtResource("7_e7oew")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jfgyi"]
+atlas = ExtResource("7_e7oew")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_237xx"]
+atlas = ExtResource("7_e7oew")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_wpyo2"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7crtr"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_vgvch"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_a1u5o"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_3dxkp"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_6wior"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_p5tca"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(192, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jbx34"]
+atlas = ExtResource("8_c35mf")
+region = Rect2(224, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_s3g0c"]
+atlas = ExtResource("9_65viv")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_35wys"]
+atlas = ExtResource("9_65viv")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_pjwc4"]
+atlas = ExtResource("9_65viv")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_or4qq"]
+atlas = ExtResource("9_65viv")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_hggc2"]
+atlas = ExtResource("9_65viv")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_b2j0d"]
+atlas = ExtResource("9_65viv")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_7l6ig"]
+atlas = ExtResource("10_x7c3f")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_j3r50"]
+atlas = ExtResource("10_x7c3f")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_utr5e"]
+atlas = ExtResource("10_x7c3f")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_kewli"]
+atlas = ExtResource("10_x7c3f")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_do7qw"]
+atlas = ExtResource("10_x7c3f")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_2h6tc"]
+atlas = ExtResource("10_x7c3f")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_8rogd"]
+atlas = ExtResource("11_bjvpn")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_48p26"]
+atlas = ExtResource("11_bjvpn")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_anp34"]
+atlas = ExtResource("11_bjvpn")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jdjat"]
+atlas = ExtResource("11_bjvpn")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_j420s"]
+atlas = ExtResource("12_s7qer")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_kcq4v"]
+atlas = ExtResource("12_s7qer")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_qavcc"]
+atlas = ExtResource("12_s7qer")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_bd26m"]
+atlas = ExtResource("12_s7qer")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_lufic"]
+atlas = ExtResource("12_s7qer")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_nxclr"]
+atlas = ExtResource("12_s7qer")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_onxu2"]
+atlas = ExtResource("13_g4c7l")
+region = Rect2(0, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_gci7n"]
+atlas = ExtResource("13_g4c7l")
+region = Rect2(32, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_tcwf7"]
+atlas = ExtResource("13_g4c7l")
+region = Rect2(64, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_20bc7"]
+atlas = ExtResource("13_g4c7l")
+region = Rect2(96, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_jfgxe"]
+atlas = ExtResource("13_g4c7l")
+region = Rect2(128, 0, 32, 32)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_yg0n1"]
+atlas = ExtResource("13_g4c7l")
+region = Rect2(160, 0, 32, 32)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_qjt2w"]
+animations = [{
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_knn7v")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_7pqkx")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_uw77l")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_rptt4")
+}],
+"loop": true,
+"name": &"attack_1",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_r7pn3")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_b8hi5")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_2uvqg")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_1s1yo")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_5ejfk")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_jd2rm")
+}],
+"loop": true,
+"name": &"attack_2",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_q28gw")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ge24q")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_pnw6m")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_1so5t")
+}],
+"loop": true,
+"name": &"climb",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_55jnj")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_dx5ib")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_a2xo5")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_art4l")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_fjxoa")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_gswnw")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_xcnuv")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_j55il")
+}],
+"loop": true,
+"name": &"death",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_hjies")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_mmwog")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_8pxes")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_fmu53")
+}],
+"loop": true,
+"name": &"hurt",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_w7j2h")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_n0b8q")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_jfgyi")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_237xx")
+}],
+"loop": true,
+"name": &"idle",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_wpyo2")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_7crtr")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_vgvch")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_a1u5o")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_3dxkp")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_6wior")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_p5tca")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_jbx34")
+}],
+"loop": true,
+"name": &"jump",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_s3g0c")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_35wys")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_pjwc4")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_or4qq")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_hggc2")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_b2j0d")
+}],
+"loop": true,
+"name": &"push",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_7l6ig")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_j3r50")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_utr5e")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_kewli")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_do7qw")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_2h6tc")
+}],
+"loop": true,
+"name": &"run",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_8rogd")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_48p26")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_anp34")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_jdjat")
+}],
+"loop": true,
+"name": &"throw",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_j420s")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_kcq4v")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_qavcc")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_bd26m")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_lufic")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_nxclr")
+}],
+"loop": true,
+"name": &"walk",
+"speed": 5.0
+}, {
+"frames": [{
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_onxu2")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_gci7n")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_tcwf7")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_20bc7")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_jfgxe")
+}, {
+"duration": 0.5,
+"texture": SubResource("AtlasTexture_yg0n1")
+}],
+"loop": true,
+"name": &"walk_attack",
+"speed": 5.0
+}]
+
+[node name="Player" type="CharacterBody2D"]
+collision_mask = 14
+script = ExtResource("1_oul6g")
+
+[node name="PlayerCollision" type="CollisionShape2D" parent="."]
+position = Vector2(0, 7)
+shape = SubResource("CircleShape2D_rkbax")
+
+[node name="PlayerSprite" type="AnimatedSprite2D" parent="."]
+sprite_frames = SubResource("SpriteFrames_qjt2w")
+animation = &"attack_2"
+frame_progress = 0.752485
+
+[node name="Camera2D" type="Camera2D" parent="."]
+zoom = Vector2(2, 2)
diff --git a/player/scripts/modifier_management.gd b/player/scripts/modifier_management.gd
new file mode 100644
index 0000000..173547d
--- /dev/null
+++ b/player/scripts/modifier_management.gd
@@ -0,0 +1,84 @@
+class_name ModifierManager extends Node
+
+signal modifier_added(modifier)
+signal modifier_removed(modifier)
+signal stats_updated()
+
+# Stores all active modifiers
+var modifiers: Array[Modifier] = []
+
+# Base stats (before modifiers)
+var base_stats: Dictionary = {}
+
+# Final calculated stats
+var final_stats: Dictionary = {}
+
+func _init(initial_base_stats: Dictionary = {}):
+ base_stats = initial_base_stats.duplicate()
+ final_stats = initial_base_stats.duplicate()
+
+func add_modifier(modifier: Modifier) -> void:
+ modifiers.append(modifier)
+ modifier.on_equip(get_parent())
+ emit_signal("modifier_added", modifier)
+ recalculate_stats()
+
+func remove_modifier(modifier_id: String) -> void:
+ for i in range(modifiers.size()):
+ if modifiers[i].id == modifier_id:
+ var modifier = modifiers[i]
+ modifier.on_unequip(get_parent())
+ modifiers.remove_at(i)
+ emit_signal("modifier_removed", modifier)
+ recalculate_stats()
+ break
+
+func recalculate_stats() -> void:
+ # Reset stats to base values
+ final_stats = base_stats.duplicate()
+
+ # Sort modifiers by priority
+ modifiers.sort_custom(func(a, b): return a.priority > b.priority)
+
+ # First pass: Apply OVERRIDE modifiers (highest priority first)
+ for modifier in modifiers:
+ if modifier.modifier_type == Modifier.ModifierType.OVERRIDE:
+ _apply_modifier_stats(modifier)
+
+ # Second pass: Apply ADDITIVE modifiers
+ for modifier in modifiers:
+ if modifier.modifier_type == Modifier.ModifierType.ADDITIVE:
+ _apply_modifier_stats(modifier)
+
+ # Third pass: Apply MULTIPLICATIVE modifiers
+ for modifier in modifiers:
+ if modifier.modifier_type == Modifier.ModifierType.MULTIPLICATIVE:
+ _apply_modifier_stats(modifier)
+
+ # Last pass: Apply CONDITIONAL modifiers
+ for modifier in modifiers:
+ if modifier.modifier_type == Modifier.ModifierType.CONDITIONAL:
+ _apply_modifier_stats(modifier)
+
+ # Apply caps and floors to stats
+ _apply_stat_limits()
+
+ emit_signal("stats_updated")
+
+func _apply_modifier_stats(modifier: Modifier) -> void:
+ if modifier.has_method("apply_stats_modification"):
+ modifier.apply_stats_modification(final_stats, base_stats)
+
+func _apply_stat_limits() -> void:
+ # Example: Cap fire rate
+ if final_stats.has("fire_rate"):
+ final_stats.fire_rate = min(final_stats.fire_rate, 20.0) # Max 20 shots per second
+ final_stats.fire_rate = max(final_stats.fire_rate, 0.5) # Min 0.5 shots per second
+
+ # Example: Cap projectile size
+ if final_stats.has("projectile_size"):
+ final_stats.projectile_size = min(final_stats.projectile_size, 5.0) # Max 5x normal size
+ final_stats.projectile_size = max(final_stats.projectile_size, 0.2) # Min 0.2x normal size
+
+func get_stat(stat_name: String, default_value = 0):
+ return final_stats.get(stat_name, default_value)
\ No newline at end of file
diff --git a/player/scripts/modifier_management.gd.uid b/player/scripts/modifier_management.gd.uid
new file mode 100644
index 0000000..6f277c5
--- /dev/null
+++ b/player/scripts/modifier_management.gd.uid
@@ -0,0 +1 @@
+uid://cvklefr50qhfb
diff --git a/player/scripts/movement.gd.uid b/player/scripts/movement.gd.uid
new file mode 100644
index 0000000..eaa4cf4
--- /dev/null
+++ b/player/scripts/movement.gd.uid
@@ -0,0 +1 @@
+uid://bw6kcdfiq3ugv
diff --git a/player/scripts/player.gd b/player/scripts/player.gd
new file mode 100644
index 0000000..ab1dff0
--- /dev/null
+++ b/player/scripts/player.gd
@@ -0,0 +1,47 @@
+extends CharacterBody2D
+
+@export var speed = 200
+@export var weapon: RangedWeapon
+@export var special_ability: Ability
+@export var movement: PlayerMovement
+
+# Last direction for idle state
+var last_direction = Vector2.DOWN
+
+@onready var animated_sprite = $PlayerSprite
+
+func _ready():
+ weapon = RangedWeapon.new()
+ Log.pr("Weapon", weapon)
+
+ # Initialize the movement resource with references
+ if movement:
+ movement.player = self
+ movement.animated_sprite = animated_sprite
+ movement.last_direction = Vector2.DOWN # Default direction
+ else:
+ # Create a new resource instance if none was assigned in the editor
+ movement = PlayerMovement.new()
+ movement.player = self
+ movement.animated_sprite = animated_sprite
+
+ Log.pr("Adding projectile size additive modifier")
+ weapon.add_modifier(ProjectileSizeAdditive.new())
+ Log.pr(weapon.stats.get_stat("projectile_size")) # Size is now 1.0 + 0.5 = 1.5
+# Size is now 1.0 + 0.5 = 1.5
+
+# Add the multiplicative size modifier (1.5x multiplier)
+ Log.pr("Adding projectile size multiplicative modifier")
+ weapon.add_modifier(ProjectileSizeMultiplicative.new())
+ Log.pr(weapon.stats.get_stat("projectile_size"))
+# Size is now 1.5 * 1.5 = 2.25
+
+# Add another additive size modifier (+0.7 or 70% increase)
+ Log.pr("Adding another projectile size additive modifier", 0.7)
+ var another_size_mod = ProjectileSizeAdditive.new()
+ another_size_mod.size_increase = 0.7
+ weapon.add_modifier(another_size_mod)
+ Log.pr(weapon.stats.get_stat("projectile_size"))
+
+func _physics_process(delta):
+ movement.process(delta)
diff --git a/player/scripts/player.gd.uid b/player/scripts/player.gd.uid
new file mode 100644
index 0000000..d6fafd1
--- /dev/null
+++ b/player/scripts/player.gd.uid
@@ -0,0 +1 @@
+uid://bq038uo4cm6nv
diff --git a/player/scripts/player_movement.gd b/player/scripts/player_movement.gd
new file mode 100644
index 0000000..dea2c9e
--- /dev/null
+++ b/player/scripts/player_movement.gd
@@ -0,0 +1,48 @@
+# PlayerMovement.gd
+extends Resource
+class_name PlayerMovement
+
+var player: CharacterBody2D
+var animated_sprite: AnimatedSprite2D
+var speed: float = 300.0
+var last_direction: Vector2 = Vector2.ZERO
+
+func process(_delta):
+ # Get input direction
+ var direction = Vector2.ZERO
+
+ if Input.is_action_pressed("move_right"):
+ direction.x += 1
+ if Input.is_action_pressed("move_left"):
+ direction.x -= 1
+ if Input.is_action_pressed("move_down"):
+ direction.y += 1
+ if Input.is_action_pressed("move_up"):
+ direction.y -= 1
+
+ # Normalize the direction
+ if direction.length() > 0:
+ direction = direction.normalized()
+ last_direction = direction
+
+ # Set velocity
+ player.velocity = direction * speed
+
+ # Move the character
+ player.move_and_slide()
+
+ # Update animation
+ update_animation(direction)
+
+func update_animation(direction):
+ var anim_name = "idle" # Default animation
+
+ if direction == Vector2.ZERO:
+ # Character is idle
+ anim_name = "idle"
+ else:
+ # Character is moving
+ anim_name = "walk"
+
+ if animated_sprite.animation != anim_name:
+ animated_sprite.play(anim_name)
diff --git a/player/scripts/player_movement.gd.uid b/player/scripts/player_movement.gd.uid
new file mode 100644
index 0000000..1154f3b
--- /dev/null
+++ b/player/scripts/player_movement.gd.uid
@@ -0,0 +1 @@
+uid://b3wtxr3udscoa
diff --git a/player/weapons/ranged_weapon.gd b/player/weapons/ranged_weapon.gd
new file mode 100644
index 0000000..167bf68
--- /dev/null
+++ b/player/weapons/ranged_weapon.gd
@@ -0,0 +1,85 @@
+class_name RangedWeapon extends Node2D
+
+signal projectile_spawned(projectile)
+
+@export var projectile_scene: PackedScene
+
+# Base stats - will be modified by modifiers
+var base_stats = {
+ "damage": 10.0,
+ "fire_rate": 2.0, # Shots per second
+ "projectile_speed": 500.0,
+ "projectile_size": 1.0,
+ "projectile_lifetime": 5.0,
+ "max_pierce": 0
+}
+
+# Components
+var stats: ModifierManager
+var can_fire: bool = true
+var fire_timer: Timer
+
+func _init() -> void:
+ stats = ModifierManager.new(base_stats)
+ Log.pr(stats)
+ add_child(stats)
+
+func _ready():
+ # Connect to stats updated signal
+ stats.connect("stats_updated", _on_stats_updated)
+
+ # Setup fire timer
+ fire_timer = Timer.new()
+ add_child(fire_timer)
+ fire_timer.one_shot = true
+ fire_timer.connect("timeout", _on_fire_timer_timeout)
+
+ # Initial update
+ _on_stats_updated()
+
+func fire(direction: Vector2):
+ if !can_fire:
+ return
+
+ _spawn_projectile(global_position, direction)
+
+ can_fire = false
+ fire_timer.start(1.0 / stats.get_stat("fire_rate"))
+
+func _spawn_projectile(spawn_position: Vector2, spawn_direction: Vector2):
+ var projectile = projectile_scene.instantiate()
+ projectile.global_position = spawn_position
+ projectile.direction = spawn_direction
+
+ # Apply stats to projectile
+ projectile.speed = stats.get_stat("projectile_speed")
+ projectile.damage = stats.get_stat("damage")
+ projectile.lifetime = stats.get_stat("projectile_lifetime")
+ projectile.pierce_count = stats.get_stat("max_pierce")
+ projectile.source_weapon = self
+
+ # Apply size (scale)
+ var size = stats.get_stat("projectile_size")
+ projectile.scale = Vector2(size, size)
+
+ # Allow modifiers to directly modify the projectile
+ for modifier in stats.modifiers:
+ modifier.modify_projectile(projectile)
+
+ get_tree().root.add_child(projectile)
+ projectile.emit_signal("on_spawned", projectile)
+ emit_signal("projectile_spawned", projectile)
+
+func add_modifier(modifier: Modifier):
+ stats.add_modifier(modifier)
+
+func remove_modifier(modifier_id: String):
+ stats.remove_modifier(modifier_id)
+
+func _on_stats_updated():
+ # Update any visual components based on new stats
+ # For example, if weapon appearance changes based on damage/fire rate
+ pass
+
+func _on_fire_timer_timeout():
+ can_fire = true
diff --git a/player/weapons/ranged_weapon.gd.uid b/player/weapons/ranged_weapon.gd.uid
new file mode 100644
index 0000000..91bd784
--- /dev/null
+++ b/player/weapons/ranged_weapon.gd.uid
@@ -0,0 +1 @@
+uid://dcenqdci4hjes
diff --git a/playground/level.gd b/playground/level.gd
deleted file mode 100644
index c29f29e..0000000
--- a/playground/level.gd
+++ /dev/null
@@ -1,38 +0,0 @@
-extends Node2D
-
-var ground: TileMapLayer
-var walls : TileMapLayer
-var cells : Array
-
-var map_width : int = 40
-var map_height : int = 32
-
-func _ready() -> void:
- print("Level ready")
-
- ground = $Ground
- walls = $Walls
-
- Log.pr(ground)
-
- cells = CoordUtil.all_cells(Vector2i(-1, -1), Vector2i(map_width + 1, map_height + 1))
-
- Log.pr(cells)
-
- ground.clear()
- ground.set_cells_terrain_connect(cells, 0, 1, false)
-
- var perimeter_cells : Array = CoordUtil.perimeter_cells(Vector2i(0, 0), Vector2i(map_width, map_height), false, 2)
- walls.set_cells_terrain_connect(perimeter_cells, 0, 0, false)
- # walls.set_cells_terrain_connect(cells, 0, 0, true)
-
- #var map_generator = load("res://utility/MapGenerator.gd")
- var map_generator = MapGenerator.new()
- # Generate a new map
- map_generator.generate_map()
-
- # Print the map to console
- map_generator.print_map()
-
- # Get the generated map data for use in your game
- # var map_data = map_generator.get_map()
diff --git a/playground/level.gd.uid b/playground/level.gd.uid
deleted file mode 100644
index 9e5c9f9..0000000
--- a/playground/level.gd.uid
+++ /dev/null
@@ -1 +0,0 @@
-uid://danlcoyd8tlji
diff --git a/playground/level.tscn b/playground/level.tscn
deleted file mode 100644
index bf53de0..0000000
--- a/playground/level.tscn
+++ /dev/null
@@ -1,18 +0,0 @@
-[gd_scene load_steps=3 format=4 uid="uid://fqdbtka7hqn"]
-
-[ext_resource type="TileSet" uid="uid://eucffgw5x26k" path="res://assets/tileset/basic.tres" id="1_bdmjt"]
-[ext_resource type="Script" uid="uid://danlcoyd8tlji" path="res://playground/level.gd" id="1_ny3pf"]
-
-[node name="Level" type="Node2D"]
-script = ExtResource("1_ny3pf")
-
-[node name="Ground" type="TileMapLayer" parent="."]
-tile_map_data = PackedByteArray("AAATABAAGwAEAAAAAAATAA8AGwAEAAAAAAATAA4AGwAEAAAAAAATAA0AGwADAAAAAAATAAwAGwAEAAAAAAATAAsAGwACAAAAAAATAAoAGwAEAAAAAAATAAkAGwAEAAAAAAATAAgAGwAAAAAAAAATAAcAGwAEAAAAAAATAAYAGwAEAAAAAAATAAUAGwAEAAAAAAATAAQAGwAEAAAAAAATAAMAGwAEAAAAAAATAAIAGwAEAAAAAAATAAEAGwAEAAAAAAATAAAAGwAEAAAAAAASABIAGwAEAAAAAAASAA8AGwAEAAAAAAASAA4AGwAEAAAAAAASAA0AGwAEAAAAAAASAAwAGwAEAAAAAAASAAsAGwAEAAAAAAASAAoAGwADAAAAAAASAAkAGwAEAAAAAAASAAgAGwAEAAAAAAASAAcAGwAEAAAAAAASAAYAGwAEAAAAAAASAAUAGwAEAAAAAAASAAQAGwAEAAAAAAASAAMAGwAEAAAAAAASAAIAGwAEAAAAAAASAAEAGwAEAAAAAAASAAAAGwAAAAIAAAARABIAGwAEAAAAAAARABEAGwAEAAAAAAARABAAGwACAAIAAAARAA4AGwAEAAAAAAARAA0AGwAEAAAAAAARAAwAGwAEAAAAAAARAAsAGwAEAAAAAAARAAoAGwAEAAAAAAARAAkAGwAEAAAAAAARAAgAGwABAAIAAAARAAcAGwAEAAAAAAARAAYAGwACAAAAAAARAAUAGwAEAAAAAAARAAQAGwAEAAAAAAARAAMAGwAEAAAAAAARAAIAGwAAAAEAAAARAAEAGwAEAAAAAAARAAAAGwAEAAAAAAAQABIAGwABAAIAAAAQABEAGwAEAAAAAAAQABAAGwAEAAAAAAAQAA4AGwAEAAAAAAAQAA0AGwAEAAAAAAAQAAwAGwAAAAAAAAAQAAsAGwAEAAAAAAAQAAoAGwAEAAAAAAAQAAkAGwAEAAAAAAAQAAgAGwAEAAAAAAAQAAcAGwAEAAAAAAAQAAYAGwAEAAAAAAAQAAUAGwAEAAAAAAAQAAQAGwAEAAAAAAAQAAMAGwAEAAAAAAAQAAIAGwABAAMAAAAQAAEAGwAEAAAAAAAQAAAAGwADAAAAAAAPABIAGwADAAIAAAAPABEAGwAEAAAAAAAPABAAGwAEAAAAAAAPAA8AGwAEAAAAAAAPAA0AGwAEAAAAAAAPAAwAGwAAAAIAAAAPAAsAGwACAAAAAAAPAAoAGwAEAAAAAAAPAAkAGwAEAAAAAAAPAAgAGwADAAEAAAAPAAcAGwAEAAAAAAAPAAYAGwAEAAAAAAAPAAUAGwAEAAAAAAAPAAQAGwAEAAAAAAAPAAMAGwAEAAAAAAAPAAIAGwAEAAAAAAAPAAEAGwAEAAAAAAAPAAAAGwAEAAAAAAAOABIAGwAEAAAAAAAOABEAGwAEAAAAAAAOABAAGwAEAAAAAAAOAA8AGwAEAAAAAAAOAA4AGwAEAAAAAAAOAAwAGwAEAAAAAAAOAAsAGwAEAAAAAAAOAAoAGwAEAAAAAAAOAAkAGwAEAAAAAAAOAAgAGwAEAAAAAAAOAAcAGwAEAAAAAAAOAAYAGwAEAAAAAAAOAAUAGwAEAAAAAAAOAAQAGwACAAIAAAAOAAMAGwAEAAAAAAAOAAIAGwAEAAAAAAAOAAEAGwAEAAAAAAAOAAAAGwAEAAAAAAANABIAGwAEAAAAAAANABEAGwAEAAAAAAANABAAGwAEAAAAAAANAA8AGwAEAAAAAAANAA4AGwACAAIAAAANAA0AGwAEAAAAAAANAAsAGwAEAAAAAAANAAoAGwAEAAAAAAANAAkAGwACAAIAAAANAAgAGwAEAAAAAAANAAcAGwAAAAIAAAANAAYAGwAEAAAAAAANAAUAGwAEAAAAAAANAAQAGwAEAAAAAAANAAMAGwAEAAAAAAANAAIAGwAEAAAAAAANAAEAGwAEAAAAAAANAAAAGwAEAAAAAAAMABIAGwAEAAAAAAAMABEAGwACAAEAAAAMABAAGwAEAAAAAAAMAA8AGwAEAAAAAAAMAA4AGwAEAAAAAAAMAA0AGwAEAAAAAAAMAAwAGwAEAAAAAAAMAAoAGwAEAAAAAAAMAAkAGwAEAAAAAAAMAAgAGwAEAAAAAAAMAAcAGwAEAAAAAAAMAAYAGwAEAAAAAAAMAAUAGwAEAAAAAAAMAAQAGwAEAAAAAAAMAAMAGwAEAAAAAAAMAAIAGwAEAAAAAAAMAAEAGwACAAIAAAAMAAAAGwAEAAAAAAALABIAGwAEAAAAAAALABEAGwAEAAAAAAALABAAGwAEAAAAAAALAA8AGwAEAAAAAAALAA4AGwAEAAAAAAALAA0AGwACAAAAAAALAAwAGwAEAAAAAAALAAsAGwADAAAAAAALAAgAGwAEAAAAAAALAAcAGwAEAAAAAAALAAYAGwAEAAAAAAALAAUAGwAEAAAAAAALAAQAGwAEAAAAAAALAAMAGwAEAAAAAAALAAIAGwAEAAAAAAALAAEAGwAEAAAAAAALAAAAGwAEAAAAAAAKABIAGwABAAMAAAAKABEAGwAEAAAAAAAKABAAGwAEAAAAAAAKAA8AGwAEAAAAAAAKAA4AGwAEAAAAAAAKAA0AGwAEAAAAAAAKAAwAGwAEAAAAAAAKAAsAGwABAAIAAAAKAAoAGwAEAAAAAAAKAAkAGwAEAAAAAAAKAAcAGwAEAAAAAAAKAAYAGwAEAAAAAAAKAAUAGwADAAAAAAAKAAQAGwAEAAAAAAAKAAMAGwAEAAAAAAAKAAIAGwAEAAAAAAAKAAEAGwAEAAAAAAAKAAAAGwACAAEAAAAJABIAGwAEAAAAAAAJABEAGwAEAAAAAAAJABAAGwAEAAAAAAAJAA8AGwABAAIAAAAJAA4AGwAEAAAAAAAJAA0AGwAEAAAAAAAJAAwAGwAEAAAAAAAJAAsAGwAEAAAAAAAJAAoAGwAEAAAAAAAJAAkAGwAEAAAAAAAJAAgAGwAEAAAAAAAJAAYAGwAEAAAAAAAJAAUAGwAEAAAAAAAJAAQAGwAEAAAAAAAJAAMAGwAEAAAAAAAJAAIAGwAEAAAAAAAJAAEAGwAEAAAAAAAJAAAAGwAEAAAAAAAIABIAGwAEAAAAAAAIABEAGwADAAMAAAAIABAAGwAEAAAAAAAIAA8AGwAEAAAAAAAIAA4AGwAEAAAAAAAIAA0AGwAEAAAAAAAIAAwAGwAEAAAAAAAIAAsAGwAAAAIAAAAIAAoAGwAEAAAAAAAIAAkAGwAEAAAAAAAIAAgAGwAEAAAAAAAIAAYAGwAEAAAAAAAIAAUAGwAEAAAAAAAIAAQAGwAAAAIAAAAIAAMAGwAEAAAAAAAIAAIAGwAEAAAAAAAIAAEAGwAEAAAAAAAIAAAAGwACAAIAAAAHABIAGwADAAEAAAAHABEAGwAEAAAAAAAHABAAGwACAAMAAAAHAA8AGwAEAAAAAAAHAA4AGwAAAAIAAAAHAA0AGwAEAAAAAAAHAAwAGwAEAAAAAAAHAAsAGwADAAEAAAAHAAoAGwAEAAAAAAAHAAkAGwAEAAAAAAAHAAgAGwAEAAAAAAAHAAcAGwAEAAAAAAAHAAUAGwAEAAAAAAAHAAQAGwAAAAIAAAAHAAMAGwAEAAAAAAAHAAIAGwACAAIAAAAHAAEAGwABAAIAAAAHAAAAGwAEAAAAAAAGABIAGwAEAAAAAAAGABEAGwAEAAAAAAAGABAAGwAEAAAAAAAGAA8AGwAEAAAAAAAGAA4AGwAEAAAAAAAGAA0AGwAEAAAAAAAGAAwAGwAEAAAAAAAGAAsAGwAEAAAAAAAGAAoAGwAEAAAAAAAGAAkAGwAEAAAAAAAGAAgAGwAEAAAAAAAGAAcAGwAEAAAAAAAGAAYAGwAEAAAAAAAGAAQAGwACAAAAAAAGAAMAGwAEAAAAAAAGAAIAGwABAAIAAAAGAAEAGwAEAAAAAAAGAAAAGwAEAAAAAAAFABIAGwAEAAAAAAAFABEAGwAEAAAAAAAFABAAGwAEAAAAAAAFAA8AGwAEAAAAAAAFAA4AGwAEAAAAAAAFAA0AGwAEAAAAAAAFAAwAGwAEAAAAAAAFAAsAGwAAAAMAAAAFAAoAGwAEAAAAAAAFAAkAGwABAAAAAAAFAAgAGwAEAAAAAAAFAAcAGwAAAAAAAAAFAAYAGwABAAAAAAAFAAUAGwAEAAAAAAAFAAMAGwADAAMAAAAFAAIAGwAEAAAAAAAFAAEAGwAEAAAAAAAFAAAAGwAEAAAAAAAEABIAGwAEAAAAAAAEABEAGwAAAAIAAAAEABAAGwADAAAAAAAEAA8AGwAAAAEAAAAEAA4AGwAEAAAAAAAEAA0AGwAEAAAAAAAEAAwAGwABAAIAAAAEAAsAGwAEAAAAAAAEAAoAGwAEAAAAAAAEAAkAGwABAAIAAAAEAAgAGwAEAAAAAAAEAAcAGwAAAAIAAAAEAAYAGwAEAAAAAAAEAAUAGwAEAAAAAAAEAAQAGwAEAAAAAAAEAAIAGwAEAAAAAAAEAAEAGwADAAMAAAAEAAAAGwAEAAAAAAADABIAGwAEAAAAAAADABEAGwAEAAAAAAADABAAGwAEAAAAAAADAA8AGwAEAAAAAAADAA4AGwAEAAAAAAADAA0AGwAEAAAAAAADAAwAGwAEAAAAAAADAAsAGwAEAAAAAAADAAoAGwAEAAAAAAADAAkAGwAEAAAAAAADAAgAGwAEAAAAAAADAAcAGwAEAAAAAAADAAYAGwAEAAAAAAADAAUAGwAEAAAAAAADAAQAGwABAAAAAAADAAMAGwAEAAAAAAADAAEAGwAEAAAAAAADAAAAGwAEAAAAAAACABIAGwAEAAAAAAACABEAGwAEAAAAAAACABAAGwAEAAAAAAACAA8AGwAEAAAAAAACAA4AGwAEAAAAAAACAA0AGwAEAAAAAAACAAwAGwAEAAAAAAACAAsAGwAEAAAAAAACAAoAGwABAAIAAAACAAkAGwAEAAAAAAACAAgAGwAEAAAAAAACAAcAGwAEAAAAAAACAAYAGwAEAAAAAAACAAUAGwAEAAAAAAACAAQAGwAEAAAAAAACAAMAGwADAAIAAAACAAEAGwAEAAAAAAACAAAAGwAEAAAAAAABABIAGwAAAAAAAAABABEAGwAEAAAAAAABABAAGwAEAAAAAAABAA8AGwAEAAAAAAABAA4AGwAEAAAAAAABAA0AGwAEAAAAAAABAAwAGwAEAAAAAAABAAsAGwAEAAAAAAABAAoAGwADAAIAAAABAAkAGwAEAAAAAAABAAgAGwAEAAAAAAABAAcAGwACAAIAAAABAAYAGwAEAAAAAAABAAUAGwADAAMAAAABAAQAGwAEAAAAAAABAAMAGwACAAAAAAABAAIAGwAEAAAAAAABAAAAGwAEAAAAAAAAABIAGwAEAAAAAAAAABEAGwAEAAAAAAAAABAAGwAEAAAAAAAAAA8AGwAEAAAAAAAAAA4AGwAEAAAAAAAAAA0AGwAEAAAAAAAAAAwAGwAEAAAAAAAAAAsAGwAEAAAAAAAAAAoAGwAEAAAAAAAAAAkAGwAEAAAAAAAAAAgAGwAEAAAAAAAAAAcAGwADAAEAAAAAAAYAGwAEAAAAAAAAAAUAGwAEAAAAAAAAAAQAGwAEAAAAAAAAAAMAGwAEAAAAAAAAAAIAGwAEAAAAAAAAAAEAGwAEAAAAAAAAAAAAGwAEAAAAAAABAAEAGwAEAAAAAAACAAIAGwAEAAAAAAAIAAcAGwABAAAAAAAHAAYAGwAEAAAAAAALAAoAGwAEAAAAAAAMAAsAGwABAAAAAAANAAwAGwADAAMAAAAOAA0AGwAEAAAAAAAPAA4AGwAEAAAAAAAQAA8AGwAEAAAAAAARAA8AGwAEAAAAAAASABAAGwAEAAAAAAASABEAGwAEAAAAAAATABEAGwAEAAAAAAATABIAGwACAAAAAAAUABIAGwAEAAAAAAAUABEAGwAEAAAAAAAVABEAGwAEAAAAAAAWABEAGwAEAAAAAAAWABAAGwACAAAAAAAWAA8AGwAEAAAAAAAWAA4AGwAEAAAAAAAWAA0AGwAEAAAAAAAWAAwAGwAEAAAAAAAWAAsAGwAEAAAAAAAWAAoAGwAEAAAAAAAWAAkAGwAEAAAAAAAWAAgAGwAEAAAAAAAWAAcAGwAEAAAAAAAWAAYAGwAEAAAAAAAWAAUAGwAEAAAAAAAWAAQAGwAEAAAAAAAWAAMAGwAEAAAAAAAWAAIAGwAEAAAAAAAVAAIAGwAEAAAAAAAVAAEAGwAEAAAAAAAVAAAAGwAEAAAAAAAUAAAAGwAEAAAAAAAUAAEAGwAEAAAAAAAUAAIAGwAEAAAAAAAUAAMAGwACAAAAAAAVAAMAGwAAAAEAAAAVAAQAGwAEAAAAAAAVAAUAGwAEAAAAAAAVAAYAGwAEAAAAAAAVAAcAGwAEAAAAAAAVAAgAGwAEAAAAAAAVAAkAGwAEAAAAAAAVAAoAGwAEAAAAAAAVAAsAGwACAAAAAAAVAAwAGwABAAAAAAAWABIAGwAEAAAAAAAVABIAGwAEAAAAAAAVABAAGwAEAAAAAAAVAA8AGwAEAAAAAAAVAA4AGwAEAAAAAAAVAA0AGwAEAAAAAAAUABAAGwAEAAAAAAAUAA8AGwAEAAAAAAAUAA4AGwAEAAAAAAAUAA0AGwAEAAAAAAAUAAwAGwAEAAAAAAAUAAsAGwAEAAAAAAAUAAoAGwAEAAAAAAAUAAkAGwAEAAAAAAAUAAgAGwAEAAAAAAAUAAcAGwAEAAAAAAAUAAYAGwABAAIAAAAUAAUAGwAEAAAAAAAUAAQAGwAEAAAAAAAWAAEAGwAAAAMAAAAWAAAAGwAEAAAAAAAJAAcAGwAEAAAAAAAKAAgAGwAEAAAAAAADAAIAGwAEAAAAAAAEAAMAGwAEAAAAAAAFAAQAGwAEAAAAAAAGAAUAGwAAAAIAAAALAAkAGwAEAAAAAAA=")
-tile_set = ExtResource("1_bdmjt")
-
-[node name="Walls" type="TileMapLayer" parent="."]
-tile_map_data = PackedByteArray("AAAGAAUABAABAAQAAAAGAAYABAABAAUAAAAGAAcABAAFAAUAAAAHAAcABAACAAUAAAAHAAgABAACAAUAAAAIAAgABAACAAUAAAAJAAgABAADAAUAAAAJAAkABAADAAYAAAAIAAkABAACAAYAAAAHAAkABAACAAYAAAAGAAkABAABAAYAAAAGAAgABAAFAAQAAAAHAAUABAACAAQAAAAHAAYABAACAAUAAAAIAAYABAACAAUAAAAIAAcABAACAAUAAAAIAAUABAAFAAUAAAANAAYABAABAAQAAAAOAAYABAACAAQAAAAPAAYABAACAAQAAAAQAAYABAADAAQAAAANAAcABAABAAUAAAAOAAcABAACAAUAAAAPAAcABAACAAUAAAAQAAcABAADAAUAAAANAAgABAABAAUAAAAOAAgABAACAAUAAAAPAAgABAACAAUAAAAQAAgABAADAAUAAAAQAAkABAADAAYAAAAPAAkABAACAAYAAAAOAAkABAACAAYAAAANAAkABAABAAYAAAAMAAsABAACAAQAAAANAAsABAACAAQAAAAOAAsABAADAAQAAAAOAAwABAADAAYAAAANAAwABAACAAYAAAAMAAwABAACAAYAAAALAAwABAABAAYAAAALAAsABAABAAQAAAAJAAYABAAEAAQAAAAJAAcABAADAAUAAAAJAAUABAAEAAUAAAAKAAUABAACAAQAAAALAAUABAADAAQAAAALAAYABAADAAYAAAAKAAYABAACAAYAAAAEAAcABAAEAAUAAAAEAAgABAACAAYAAAAFAAgABAACAAYAAAAFAAcABAACAAQAAAAEAAYABAADAAUAAAADAAYABAAFAAQAAAADAAcABAABAAUAAAADAAgABAABAAYAAAADAAUABAACAAQAAAAEAAUABAADAAQAAAACAAUABAACAAQAAAABAAUABAACAAQAAAABAAYABAAEAAQAAAACAAYABAACAAYAAAABAAcABAADAAUAAAABAAgABAADAAYAAAAAAAgABAABAAYAAAAAAAcABAABAAUAAAAAAAYABAABAAUAAAAAAAUABAABAAQAAAAJAAQABAADAAUAAAAJAAMABAADAAQAAAAIAAMABAABAAQAAAAIAAQABAABAAUAAAACAAMABAABAAcAAAADAAMABAACAAcAAAAEAAMABAACAAcAAAAFAAMABAACAAcAAAAGAAMABAADAAcAAAAOAAIABAAAAAQAAAAOAAMABAAAAAUAAAAOAAQABAAAAAYAAAARAAMABAAAAAcAAAADAAsABAABAAQAAAAEAAsABAADAAQAAAADAAwABAABAAYAAAAEAAwABAAFAAYAAAAFAA0ABAADAAYAAAAFAAwABAADAAQAAAAEAA0ABAABAAYAAAA=")
-tile_set = ExtResource("1_bdmjt")
-
-[node name="Camera2D" type="Camera2D" parent="."]
-position = Vector2(98, 86)
diff --git a/project.godot b/project.godot
index 452c276..7a567a2 100644
--- a/project.godot
+++ b/project.godot
@@ -11,22 +11,57 @@ config_version=5
[application]
config/name="Randomgeon"
-run/main_scene="uid://fqdbtka7hqn"
+run/main_scene="uid://cfkusqucsap26"
config/features=PackedStringArray("4.4", "Forward Plus")
[autoload]
CoordUtil="*res://utility/CoordUtility.gd"
+RNG="*res://utility/RngUtility.gd"
+Global="*res://utility/Globals.gd"
+SceneSelector="*res://utility/SceneSelector.gd"
+MapBuilder="*res://utility/MapBuilder.gd"
[display]
-window/size/viewport_width=800
-window/size/viewport_height=600
window/size/resizable=false
window/stretch/mode="viewport"
-window/stretch/scale=2.0
window/stretch/scale_mode="integer"
[editor_plugins]
enabled=PackedStringArray("res://addons/log/plugin.cfg")
+
+[input]
+
+move_up={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
+]
+}
+move_down={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
+]
+}
+move_left={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
+]
+}
+move_right={
+"deadzone": 0.2,
+"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
+]
+}
+
+[layer_names]
+
+2d_physics/layer_1="Player"
+2d_physics/layer_2="Water"
+2d_physics/layer_3="Objects"
+2d_physics/layer_4="Enemies"
+
+[rendering]
+
+environment/defaults/default_clear_color=Color(0, 0, 0, 1)
diff --git a/utility/CoordUtility.gd b/utility/CoordUtility.gd
index 85bf36e..a51252e 100644
--- a/utility/CoordUtility.gd
+++ b/utility/CoordUtility.gd
@@ -4,7 +4,7 @@ func _ready() -> void:
Log.pr("CoordUtility ready")
-func all_cells(start : Vector2i, end : Vector2i) -> Array:
+func all_cells(start: Vector2i, end: Vector2i) -> Array:
# Returns all cells between start and end
var cells = []
@@ -21,7 +21,6 @@ func perimeter_cells(start: Vector2i, end: Vector2i, inclusive: bool = true, wid
# Returns cells on the perimeter between start and end
# inclusive: if true, includes the area cells, if false, excludes them
# width: the thickness of the perimeter (default: 1)
-
var cells: Array[Vector2i] = []
var min_x: int = min(start.x, end.x)
var max_x: int = max(start.x, end.x)
@@ -39,6 +38,7 @@ func perimeter_cells(start: Vector2i, end: Vector2i, inclusive: bool = true, wid
var outer_max_y: int
if inclusive:
+ Log.pr("Drawing within bounds of the original rectangle")
# For inclusive, inner bounds are the original rectangle
inner_min_x = min_x
inner_min_y = min_y
@@ -51,6 +51,7 @@ func perimeter_cells(start: Vector2i, end: Vector2i, inclusive: bool = true, wid
outer_max_x = max_x + (width - 1)
outer_max_y = max_y + (width - 1)
else:
+ Log.pr("Drawing outside bounds of the original rectangle")
# For exclusive, inner bounds are the original rectangle
inner_min_x = min_x
inner_min_y = min_y
@@ -77,4 +78,21 @@ func perimeter_cells(start: Vector2i, end: Vector2i, inclusive: bool = true, wid
if x < inner_min_x or x > inner_max_x or y < inner_min_y or y > inner_max_y:
cells.append(pos)
- return cells
\ No newline at end of file
+ return cells
+
+func get_surrounding_tiles(given_tile: Vector2i, spread: int = 1) -> Array[Vector2i]:
+ Log.pr("Getting surrounding tiles for tile: ", given_tile, " with spread: ", spread)
+ var surrounding_tiles: Array[Vector2i] = []
+
+ # Loop from -spread to +spread in both directions
+ for y in range(-spread, spread + 1):
+ for x in range(-spread, spread + 1):
+ # Skip the center tile if you don't want it included
+ if x == 0 and y == 0:
+ continue
+
+ var target_tile = given_tile + Vector2i(x, y)
+ surrounding_tiles.append(target_tile)
+
+ Log.pr("Surrounding tiles: ", surrounding_tiles)
+ return surrounding_tiles
\ No newline at end of file
diff --git a/utility/Globals.gd b/utility/Globals.gd
new file mode 100644
index 0000000..534d90f
--- /dev/null
+++ b/utility/Globals.gd
@@ -0,0 +1,14 @@
+extends Node
+
+class_name GlobalVariables
+
+const TILE_SIZE = 32
+const ROOM_WIDTH = 20
+const ROOM_HEIGHT = 16
+const GRID_WIDTH = 4
+const GRID_HEIGHT = 4
+const MAP_EMPTY = 0
+const MAP_PATH = 1
+const MAP_START = 2
+const MAP_FINISH = 4
+const MAP_UP_CELL = 3
\ No newline at end of file
diff --git a/utility/Globals.gd.uid b/utility/Globals.gd.uid
new file mode 100644
index 0000000..6a09020
--- /dev/null
+++ b/utility/Globals.gd.uid
@@ -0,0 +1 @@
+uid://gf47g1mgttf5
diff --git a/utility/MapBuilder.gd b/utility/MapBuilder.gd
new file mode 100644
index 0000000..1a0e126
--- /dev/null
+++ b/utility/MapBuilder.gd
@@ -0,0 +1,86 @@
+extends Node
+
+class_name MapBuilderClass
+
+# Function to copy a TileMap layer from one scene to a target TileMap at a specific grid position
+func copy_tilemap_to_target(source_scene, target_tilemap: TileMapLayer, target_layer: String, grid_position: Vector2i):
+ # First, load and instantiate the source scene if it's a resource path
+ Log.pr("Copying tilemap from source scene to target tilemap at grid position: ", grid_position)
+ Log.pr("Source scene: ", source_scene)
+ var source_instance
+ if source_scene is String:
+ source_instance = load(source_scene).instantiate()
+ elif source_scene is PackedScene:
+ Log.pr("Source scene is a PackedScene, instantiating it")
+ source_instance = source_scene.instantiate()
+ else:
+ source_instance = source_scene
+
+ # Find the source TileMap in the loaded scene
+ var source_tilemap = find_tilemap_by_name(source_instance, target_layer)
+ if not source_tilemap:
+ push_error("Could not find TileMapLayer in source scene", source_tilemap)
+ if source_instance is Node and source_instance.is_inside_tree():
+ source_instance.queue_free()
+ return
+
+ # Get the size of the source room in tiles
+ var source_used_cells = source_tilemap.get_used_cells() # 0 is the layer index
+ if source_used_cells.size() == 0:
+ push_warning("Source TileMapLayer has no cells")
+ if source_instance is Node and source_instance.is_inside_tree():
+ source_instance.queue_free()
+ return
+
+ # Calculate the offset for placement in the target grid
+ var offset_x = grid_position.x * Global.ROOM_WIDTH
+ var offset_y = grid_position.y * Global.ROOM_HEIGHT
+
+ # Copy cells from source to target with proper offset
+ for cell in source_used_cells:
+ var target_cell = Vector2i(cell.x + offset_x, cell.y + offset_y)
+
+ # Get the tile data from source
+ #var source_tile_data = source_tilemap.get_cell_tile_data(cell)
+ var source_atlas_coords = source_tilemap.get_cell_atlas_coords(cell)
+ var source_alternative_tile = source_tilemap.get_cell_alternative_tile(cell)
+
+ # Set the same tile in the target TileMap
+ target_tilemap.set_cell(target_cell, source_tilemap.get_cell_source_id(cell), source_atlas_coords, source_alternative_tile)
+
+
+ # Clean up the source instance if we instantiated it
+ if source_instance is Node and source_instance.is_inside_tree():
+ source_instance.queue_free()
+
+
+# Helper function to find a TileMap by name in a scene
+func find_tilemap_by_name(root_node: Node, tilemap_name: String) -> TileMapLayer:
+ if root_node is TileMapLayer and root_node.name == tilemap_name:
+ return root_node
+
+ for child in root_node.get_children():
+ var result = find_tilemap_by_name(child, tilemap_name)
+ if result:
+ return result
+
+ return null
+
+
+func redraw_terrain(positions: Array, layer: TileMapLayer, terrain_set: int, terrain: int) -> void:
+ Log.pr("Filtering and redrawing surrounding tiles", positions)
+
+ # Filter positions to only include cells with terrainset 0 and terrain 0
+ var filtered_positions: Array = []
+ for cell in positions:
+ var tile_data = layer.get_cell_tile_data(cell)
+
+ if tile_data and tile_data.get_terrain_set() == terrain_set and tile_data.get_terrain() == terrain:
+ filtered_positions.append(cell)
+
+ # Only redraw if we have filtered cells
+ if filtered_positions.size() > 0:
+ Log.pr("Redrawing filtered tiles:", filtered_positions)
+ layer.set_cells_terrain_connect(filtered_positions, terrain_set, terrain)
+ else:
+ Log.pr("No tiles to redraw after filtering")
diff --git a/utility/MapBuilder.gd.uid b/utility/MapBuilder.gd.uid
new file mode 100644
index 0000000..daff072
--- /dev/null
+++ b/utility/MapBuilder.gd.uid
@@ -0,0 +1 @@
+uid://cuyp1adhi86bs
diff --git a/utility/MapGenerator.gd b/utility/MapGenerator.gd
index 11f5e56..bcb3606 100644
--- a/utility/MapGenerator.gd
+++ b/utility/MapGenerator.gd
@@ -2,157 +2,157 @@ extends Node
class_name MapGenerator
# Map constants
-const GRID_WIDTH = 4
-const GRID_HEIGHT = 4
-const EMPTY = 0
-const PATH = 1
-const START = 2
-const FINISH = 4
-const UP_CELL = 3
+var GRID_WIDTH = Global.GRID_WIDTH
+var GRID_HEIGHT = Global.GRID_HEIGHT
+var EMPTY = Global.MAP_EMPTY
+var PATH = Global.MAP_PATH
+var START = Global.MAP_START
+var FINISH = Global.MAP_FINISH
+var UP_CELL = Global.MAP_UP_CELL
# Map generation parameters
-var up_probability_base = 0.1 # Base probability to move up
-var up_probability_increase = 0.2 # How much the probability increases each time we stay on the same row
+var up_probability_base = 0.1 # Base probability to move up
+var up_probability_increase = 0.2 # How much the probability increases each time we stay on the same row
# The map grid
var map = []
func generate_map():
- # Initialize map with empty cells
- map = []
- for y in range(GRID_HEIGHT):
- var row = []
- for x in range(GRID_WIDTH):
- row.append(EMPTY)
- map.append(row)
-
- # Pick a starting location in the bottom row
- var current_x = randi() % GRID_WIDTH
- var current_y = GRID_HEIGHT - 1
-
- # Mark as start
- map[current_y][current_x] = START
-
- # Track visited cells to avoid loops
- var visited = {}
- visited[Vector2(current_x, current_y)] = true
-
- # Path generation
- var final_position = generate_path(current_x, current_y, visited)
-
- # Set finish cell in top row and connect path to it
- connect_to_finish(final_position.x, final_position.y, visited)
+ # Initialize map with empty cells
+ map = []
+ for y in range(GRID_HEIGHT):
+ var row = []
+ for x in range(GRID_WIDTH):
+ row.append(EMPTY)
+ map.append(row)
+
+ # Pick a starting location in the bottom row
+ var current_x = RNG.randi() % GRID_WIDTH
+ var current_y = GRID_HEIGHT - 1
+
+ # Mark as start
+ map[current_y][current_x] = START
+
+ # Track visited cells to avoid loops
+ var visited = {}
+ visited[Vector2(current_x, current_y)] = true
+
+ # Path generation
+ var final_position = generate_path(current_x, current_y, visited)
+
+ # Set finish cell in top row and connect path to it
+ connect_to_finish(final_position.x, final_position.y, visited)
# Generate path from start to top row, return final position
func generate_path(current_x, current_y, visited):
- var up_probability = up_probability_base
-
- while current_y > 0: # Continue until we reach the top row
- # Try to move left or right until we can't proceed further
- var can_move_horizontal = true
-
- while can_move_horizontal:
- # Decide whether to try moving up
- if randf() < up_probability:
- # We'll try moving up
- break
-
- # Increase probability for next time
- up_probability += up_probability_increase
-
- # Choose direction randomly (left or right)
- var direction = 1 if randf() > 0.5 else -1
- var new_x = current_x + direction
-
- # Check if we can move in that direction
- if new_x >= 0 and new_x < GRID_WIDTH and not Vector2(new_x, current_y) in visited:
- current_x = new_x
- map[current_y][current_x] = PATH
- visited[Vector2(current_x, current_y)] = true
- else:
- # Try the other direction
- direction = -direction
- new_x = current_x + direction
-
- if new_x >= 0 and new_x < GRID_WIDTH and not Vector2(new_x, current_y) in visited:
- current_x = new_x
- map[current_y][current_x] = PATH
- visited[Vector2(current_x, current_y)] = true
- else:
- # Can't move horizontally anymore
- can_move_horizontal = false
-
- # Mark current cell as an UP_CELL before moving up
- map[current_y][current_x] = UP_CELL
-
- # Move up
- current_y -= 1
- map[current_y][current_x] = PATH
- visited[Vector2(current_x, current_y)] = true
-
- # Reset up probability since we've moved up
- up_probability = up_probability_base
-
- # Return the final position (on the top row)
- return Vector2(current_x, current_y)
+ var up_probability = up_probability_base
+
+ while current_y > 0: # Continue until we reach the top row
+ # Try to move left or right until we can't proceed further
+ var can_move_horizontal = true
+
+ while can_move_horizontal:
+ # Decide whether to try moving up
+ if RNG.randf() < up_probability:
+ # We'll try moving up
+ break
+
+ # Increase probability for next time
+ up_probability += up_probability_increase
+
+ # Choose direction randomly (left or right)
+ var direction = 1 if RNG.randf() > 0.5 else -1
+ var new_x = current_x + direction
+
+ # Check if we can move in that direction
+ if new_x >= 0 and new_x < GRID_WIDTH and not Vector2(new_x, current_y) in visited:
+ current_x = new_x
+ map[current_y][current_x] = PATH
+ visited[Vector2(current_x, current_y)] = true
+ else:
+ # Try the other direction
+ direction = - direction
+ new_x = current_x + direction
+
+ if new_x >= 0 and new_x < GRID_WIDTH and not Vector2(new_x, current_y) in visited:
+ current_x = new_x
+ map[current_y][current_x] = PATH
+ visited[Vector2(current_x, current_y)] = true
+ else:
+ # Can't move horizontally anymore
+ can_move_horizontal = false
+
+ # Mark current cell as an UP_CELL before moving up
+ map[current_y][current_x] = UP_CELL
+
+ # Move up
+ current_y -= 1
+ map[current_y][current_x] = PATH
+ visited[Vector2(current_x, current_y)] = true
+
+ # Reset up probability since we've moved up
+ up_probability = up_probability_base
+
+ # Return the final position (on the top row)
+ return Vector2(current_x, current_y)
# Connect the path to a finish point in the top row
func connect_to_finish(current_x, current_y, visited):
- # Choose a finish position
- var finish_x = randi() % GRID_WIDTH
-
- # If we randomly picked the current position, we're done
- if finish_x == current_x:
- map[current_y][current_x] = FINISH
- return
-
- # Otherwise, create a path to the finish
- var direction = 1 if finish_x > current_x else -1
-
- # Move horizontally toward the finish position
- while current_x != finish_x:
- current_x += direction
-
- # If we've already visited this cell, we have a loop - break and place finish here
- if Vector2(current_x, current_y) in visited:
- break
-
- map[current_y][current_x] = PATH
- visited[Vector2(current_x, current_y)] = true
-
- # Place finish at the final position
- map[current_y][current_x] = FINISH
+ # Choose a finish position
+ var finish_x = RNG.randi() % GRID_WIDTH
+
+ # If we randomly picked the current position, we're done
+ if finish_x == current_x:
+ map[current_y][current_x] = FINISH
+ return
+
+ # Otherwise, create a path to the finish
+ var direction = 1 if finish_x > current_x else -1
+
+ # Move horizontally toward the finish position
+ while current_x != finish_x:
+ current_x += direction
+
+ # If we've already visited this cell, we have a loop - break and place finish here
+ if Vector2(current_x, current_y) in visited:
+ break
+
+ map[current_y][current_x] = PATH
+ visited[Vector2(current_x, current_y)] = true
+
+ # Place finish at the final position
+ map[current_y][current_x] = FINISH
func print_map():
- print("Generated Map:")
- print("- Legend: 0=Empty, 1=Path, 2=Start, 4=Finish, 3=MoveUp")
- print("")
-
- var visual_map = ""
-
- for y in range(GRID_HEIGHT):
- var row_str = ""
- for x in range(GRID_WIDTH):
- var cell_value = map[y][x]
- match cell_value:
- EMPTY: row_str += "[ ] "
- PATH: row_str += "[•] "
- START: row_str += "[S] "
- FINISH: row_str += "[F] "
- UP_CELL: row_str += "[↑] "
- visual_map += row_str + "\n"
-
- print(visual_map)
-
- # Also print as numerical grid
- visual_map = ""
- for y in range(GRID_HEIGHT):
- var row_str = ""
- for x in range(GRID_WIDTH):
- row_str += str(map[y][x]) + " "
- visual_map += row_str + "\n"
-
- print(visual_map)
+ print("Generated Map:")
+ print("- Legend: 0=Empty, 1=Path, 2=Start, 4=Finish, 3=MoveUp")
+ print("")
+
+ var visual_map = ""
+
+ for y in range(GRID_HEIGHT):
+ var row_str = ""
+ for x in range(GRID_WIDTH):
+ var cell_value = map[y][x]
+ match cell_value:
+ EMPTY: row_str += "[ ] "
+ PATH: row_str += "[•] "
+ START: row_str += "[S] "
+ FINISH: row_str += "[F] "
+ UP_CELL: row_str += "[↑] "
+ visual_map += row_str + "\n"
+
+ print(visual_map)
+
+ # Also print as numerical grid
+ visual_map = ""
+ for y in range(GRID_HEIGHT):
+ var row_str = ""
+ for x in range(GRID_WIDTH):
+ row_str += str(map[y][x]) + " "
+ visual_map += row_str + "\n"
+
+ print(visual_map)
func get_map():
- return map
\ No newline at end of file
+ return map
diff --git a/utility/RngUtility.gd b/utility/RngUtility.gd
new file mode 100644
index 0000000..2dd302d
--- /dev/null
+++ b/utility/RngUtility.gd
@@ -0,0 +1,59 @@
+# file: global_rng.gd
+extends Node
+
+class_name RngUtility
+
+# Underlying RandomNumberGenerator
+var _rng: RandomNumberGenerator
+
+func _ready():
+ _rng = RandomNumberGenerator.new()
+ randomize()
+ print("Global RNG initialized with seed: ", _rng.seed)
+
+# Set a specific seed for reproducible results
+func set_seed(seed_value: int) -> void:
+ _rng.seed = seed_value
+ print("RNG seed set to: ", seed_value)
+
+# Randomize the seed (for non-reproducible results)
+func randomize() -> void:
+ _rng.randomize()
+ print("RNG seed randomized to: ", _rng.seed)
+
+# Get the current seed value
+func get_seed() -> int:
+ return _rng.seed
+
+# Get a random integer between min and max (inclusive)
+func randi_range(min_value: int, max_value: int) -> int:
+ return _rng.randi_range(min_value, max_value)
+
+# Get a random float between 0.0 and 1.0
+func randf() -> float:
+ return _rng.randf()
+
+func randi() -> int:
+ return _rng.randi()
+
+# Get a random float between min and max
+func randf_range(min_value: float, max_value: float) -> float:
+ return _rng.randf_range(min_value, max_value)
+
+# Get a random normalized vector
+func random_unit_vector() -> Vector2:
+ return Vector2(randf_range(-1.0, 1.0), randf_range(-1.0, 1.0)).normalized()
+
+# Get a random point inside a circle with radius 1
+func random_point_in_circle() -> Vector2:
+ var r = sqrt(randf())
+ var theta = randf() * 2.0 * PI
+ return Vector2(r * cos(theta), r * sin(theta))
+
+# Get a random point inside a circle with specified radius
+func random_point_in_circle_with_radius(radius: float) -> Vector2:
+ return random_point_in_circle() * radius
+
+# Get a random boolean value with specified probability
+func random_bool(probability: float = 0.5) -> bool:
+ return randf() < probability
\ No newline at end of file
diff --git a/utility/RngUtility.gd.uid b/utility/RngUtility.gd.uid
new file mode 100644
index 0000000..f608d26
--- /dev/null
+++ b/utility/RngUtility.gd.uid
@@ -0,0 +1 @@
+uid://b8ita2lyak2o3
diff --git a/utility/SceneSelector.gd b/utility/SceneSelector.gd
new file mode 100644
index 0000000..7862481
--- /dev/null
+++ b/utility/SceneSelector.gd
@@ -0,0 +1,69 @@
+extends Node
+
+class_name SceneSelectorUtility
+
+# Cache of scene paths
+var _scene_paths_cache = {}
+
+# Cache of loaded scenes (only populated if preload_scenes is true)
+var _loaded_scenes_cache = {}
+
+# Initialize the cache for a specific folder
+func initialize_cache(folder_path: String, preload_scenes: bool = false):
+ if _scene_paths_cache.has(folder_path):
+ return # Already cached
+
+ var dir = DirAccess.open(folder_path)
+ if not dir:
+ push_error("Error: Could not open directory: " + folder_path)
+ return
+
+ var scene_files = []
+
+ # List all scene files
+ dir.list_dir_begin()
+ var file_name = dir.get_next()
+
+ while file_name != "":
+ if file_name.ends_with(".tscn"):
+ scene_files.append(folder_path + "/" + file_name)
+ file_name = dir.get_next()
+
+ Log.pr("Found %d scene files in %s" % [scene_files.size(), folder_path])
+ # Store in cache
+ _scene_paths_cache[folder_path] = scene_files
+
+ # Optionally preload all scenes
+ if preload_scenes and scene_files.size() > 0:
+ _loaded_scenes_cache[folder_path] = []
+ for scene_path in scene_files:
+ Log.pr("Preloading scene: " + scene_path)
+ # Load the scene and store it in the cache
+ #var loaded_scene = preload(scene_path)
+ _loaded_scenes_cache[folder_path].append(load(scene_path))
+
+
+# Get a random scene from a folder (using caches)
+func get_random_scene(folder_path: String):
+ # Make sure the cache is initialized
+ if not _scene_paths_cache.has(folder_path):
+ Log.pr("Initializing cache for folder: " + folder_path)
+ initialize_cache(folder_path, true)
+
+ var scene_paths = _scene_paths_cache[folder_path]
+ if scene_paths.size() == 0:
+ push_error("No scene files found in directory: " + folder_path)
+ return null
+
+ # Get random index
+ var random_index = RNG.randi_range(0, scene_paths.size() - 1)
+
+ # Return from loaded scenes cache if available
+ if _loaded_scenes_cache.has(folder_path):
+ Log.pr("Returning preloaded scene from cache")
+ return _loaded_scenes_cache[folder_path][random_index]
+
+ # Otherwise load the scene
+ var scene = load(scene_paths[random_index])
+ Log.pr(scene)
+ return scene
diff --git a/utility/SceneSelector.gd.uid b/utility/SceneSelector.gd.uid
new file mode 100644
index 0000000..eeab268
--- /dev/null
+++ b/utility/SceneSelector.gd.uid
@@ -0,0 +1 @@
+uid://cs18x6clkepoa