diff --git a/_camera/CameraSystem.gd b/_camera/CameraSystem.gd index e958f8f..19cbb78 100644 --- a/_camera/CameraSystem.gd +++ b/_camera/CameraSystem.gd @@ -4,7 +4,7 @@ # #''' #@tool -#extends Node +extends Node # #@onready var camera_2d: Camera2D = %Camera2D #@onready var camera_shake_player: CameraShakePlayer = %CameraShakePlayer diff --git a/_camera/PlateformerCamera.tscn b/_camera/PlateformerCamera.tscn new file mode 100644 index 0000000..1977e14 --- /dev/null +++ b/_camera/PlateformerCamera.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=5 format=3 uid="uid://cw6buluknvjj"] + +[ext_resource type="Script" uid="uid://djk7tg2puphgv" path="res://_camera/camera_test.gd" id="1_05blt"] +[ext_resource type="Script" uid="uid://py4h5jxlncro" path="res://addons/reedcamera/scripts/CameraPointer.gd" id="1_e7rkk"] +[ext_resource type="Script" uid="uid://dwr1s51svvank" path="res://addons/reedcamera/scripts/camera_tools/CameraShakeController.gd" id="2_rbequ"] +[ext_resource type="Script" uid="uid://bhl5it46hv4n2" path="res://addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd" id="4_877nu"] + +[node name="PlateformerCamera" type="Camera2D"] +script = ExtResource("1_05blt") + +[node name="CameraPointer" type="Node" parent="."] +script = ExtResource("1_e7rkk") +metadata/_custom_type_script = "uid://py4h5jxlncro" + +[node name="ReedCameraShakeController" type="Node" parent="CameraPointer"] +script = ExtResource("2_rbequ") +metadata/_custom_type_script = "uid://dwr1s51svvank" + +[node name="ReedCameraAnchorController" type="Node" parent="CameraPointer"] +script = ExtResource("4_877nu") +metadata/_custom_type_script = "uid://bhl5it46hv4n2" diff --git a/_camera/camera_follower.gd b/_camera/camera_follower.gd index 8a96188..7592dc8 100644 --- a/_camera/camera_follower.gd +++ b/_camera/camera_follower.gd @@ -1,8 +1,9 @@ extends RemoteTransform2D func _ready() -> void: - var global_camera: Camera2D = CameraSystem.get_cached_camera() - if not global_camera: - push_error("[CameraFollower]:No Global Camera Founded") - return - remote_path = global_camera.get_path() + pass + #var global_camera: Camera2D = CameraSystem.get_cached_camera() + #if not global_camera: + #push_error("[CameraFollower]:No Global Camera Founded") + #return + #remote_path = global_camera.get_path() diff --git a/_camera/camera_test.gd b/_camera/camera_test.gd new file mode 100644 index 0000000..798525e --- /dev/null +++ b/_camera/camera_test.gd @@ -0,0 +1,8 @@ +extends Camera2D + + +func _unhandled_input(event: InputEvent) -> void: + if event.is_action_pressed("ui_accept"): + $CameraPointer/ReedCameraShakeController.play_shake(preload("res://addons/reedcamera/_example/test_shake.tres")) + #elif event.is_action_pressed("ui_right"): + #self.global_position += Vector2(100,0) diff --git a/_camera/camera_test.gd.uid b/_camera/camera_test.gd.uid new file mode 100644 index 0000000..0d55b26 --- /dev/null +++ b/_camera/camera_test.gd.uid @@ -0,0 +1 @@ +uid://djk7tg2puphgv diff --git a/_player/Avatar.tscn b/_player/Avatar.tscn index 46c64c7..16210e9 100644 --- a/_player/Avatar.tscn +++ b/_player/Avatar.tscn @@ -78,6 +78,7 @@ unique_name_in_owner = true shape = SubResource("RectangleShape2D_qnulu") [node name="Sprite2D" type="Sprite2D" parent="."] +visible = false texture_filter = 1 position = Vector2(0, -2) texture = SubResource("AtlasTexture_basl5") diff --git a/_player/spider_avatar.tscn b/_player/spider_avatar.tscn new file mode 100644 index 0000000..fe7ff37 --- /dev/null +++ b/_player/spider_avatar.tscn @@ -0,0 +1,33 @@ +[gd_scene load_steps=5 format=3 uid="uid://ck8m7revy150r"] + +[ext_resource type="PackedScene" uid="uid://gwhff4qaouxy" path="res://_player/Avatar.tscn" id="1_irquc"] +[ext_resource type="Texture2D" uid="uid://dted7geb331y2" path="res://_asset/ksw/character.png" id="2_350jv"] + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_350jv"] +radius = 41.0 +height = 134.0 + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_e1o4i"] +radius = 45.0 +height = 145.0 + +[node name="Avatar" instance=ExtResource("1_irquc")] + +[node name="CollisionShape2D" parent="." index="1"] +visible = false +shape = SubResource("CapsuleShape2D_350jv") + +[node name="Sprite2D" parent="." index="2"] +visible = true +texture_filter = 0 +texture = ExtResource("2_350jv") + +[node name="LocomotionComponent" parent="." index="5"] +_can_move = false + +[node name="CollisionShape2D" parent="HitBox" index="0"] +visible = false +position = Vector2(0, 0) +shape = SubResource("CapsuleShape2D_e1o4i") + +[editable path="LocomotionComponent/WallDetector"] diff --git a/_player/spider_avatar.tscn827187919.tmp b/_player/spider_avatar.tscn827187919.tmp new file mode 100644 index 0000000..d020fe6 --- /dev/null +++ b/_player/spider_avatar.tscn827187919.tmp @@ -0,0 +1,30 @@ +[gd_scene load_steps=5 format=3 uid="uid://ck8m7revy150r"] + +[ext_resource type="PackedScene" uid="uid://gwhff4qaouxy" path="res://_player/Avatar.tscn" id="1_irquc"] +[ext_resource type="Texture2D" uid="uid://dted7geb331y2" path="res://_asset/ksw/character.png" id="2_350jv"] + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_350jv"] +radius = 41.0 +height = 134.0 + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_e1o4i"] +radius = 45.0 +height = 145.0 + +[node name="Avatar" instance=ExtResource("1_irquc")] + +[node name="CollisionShape2D" parent="." index="1"] +visible = false +shape = SubResource("CapsuleShape2D_350jv") + +[node name="Sprite2D" parent="." index="2"] +visible = true +texture_filter = 0 +texture = ExtResource("2_350jv") + +[node name="CollisionShape2D" parent="HitBox" index="0"] +visible = false +position = Vector2(0, 0) +shape = SubResource("CapsuleShape2D_e1o4i") + +[editable path="LocomotionComponent/WallDetector"] diff --git a/_player/states/grapping.gd b/_player/states/grapping.gd index a02035d..11ac4f7 100644 --- a/_player/states/grapping.gd +++ b/_player/states/grapping.gd @@ -44,8 +44,8 @@ func _enter() -> void: elif i == 3 or i == 7: csp = agent.camera_shake_preset.get("xy_light") - if csp: - CameraSystem.camera_shake_player.play(csp) + #if csp: + #CameraSystem.camera_shake_player.play(csp) if root.grap_hook_state._jump_grace_timer > 0: _hook_to_jump() diff --git a/addons/reedcamera/_example/test.tscn b/addons/reedcamera/_example/test.tscn new file mode 100644 index 0000000..0042307 --- /dev/null +++ b/addons/reedcamera/_example/test.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=3 uid="uid://b2rtcqvak066v"] + +[ext_resource type="Texture2D" uid="uid://c673bap4b12fx" path="res://icon.svg" id="1_6ducv"] +[ext_resource type="PackedScene" uid="uid://cw6buluknvjj" path="res://_camera/PlateformerCamera.tscn" id="2_owtx0"] + +[node name="Test" type="Node2D"] + +[node name="Icon" type="Sprite2D" parent="."] +position = Vector2(-1, -2) +texture = ExtResource("1_6ducv") + +[node name="PlateformerCamera" parent="." instance=ExtResource("2_owtx0")] +ignore_rotation = false +position_smoothing_enabled = true diff --git a/addons/reedcamera/_example/test_shake.tres b/addons/reedcamera/_example/test_shake.tres new file mode 100644 index 0000000..0464e5d --- /dev/null +++ b/addons/reedcamera/_example/test_shake.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" script_class="ReedCameraShakePreset" load_steps=2 format=3 uid="uid://wm3cmccp1ydl"] + +[ext_resource type="Script" uid="uid://wlqopoksgvjc" path="res://addons/reedcamera/resource/ReedCameraShakePreset.gd" id="1_8o8fw"] + +[resource] +script = ExtResource("1_8o8fw") +amplitude = Vector2(10, 10) +frequency = 100.0 +metadata/_custom_type_script = "uid://wlqopoksgvjc" diff --git a/_asset/icon/camera_anchor_icon.svg b/addons/reedcamera/icon/camera_anchor_icon.svg similarity index 100% rename from _asset/icon/camera_anchor_icon.svg rename to addons/reedcamera/icon/camera_anchor_icon.svg diff --git a/_asset/icon/camera_anchor_icon.svg.import b/addons/reedcamera/icon/camera_anchor_icon.svg.import similarity index 76% rename from _asset/icon/camera_anchor_icon.svg.import rename to addons/reedcamera/icon/camera_anchor_icon.svg.import index 94e3430..983253b 100644 --- a/_asset/icon/camera_anchor_icon.svg.import +++ b/addons/reedcamera/icon/camera_anchor_icon.svg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://bsdmq0essfmpk" -path="res://.godot/imported/camera_anchor_icon.svg-bc0c9f7b183031f0db701d2e858a9063.ctex" +path="res://.godot/imported/camera_anchor_icon.svg-d54e4ec18108e371c1abc23152af31de.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://_asset/icon/camera_anchor_icon.svg" -dest_files=["res://.godot/imported/camera_anchor_icon.svg-bc0c9f7b183031f0db701d2e858a9063.ctex"] +source_file="res://addons/reedcamera/icon/camera_anchor_icon.svg" +dest_files=["res://.godot/imported/camera_anchor_icon.svg-d54e4ec18108e371c1abc23152af31de.ctex"] [params] diff --git a/addons/reedcamera/ref_counted/ReedCameraShakePlayer.gd b/addons/reedcamera/ref_counted/ReedCameraShakePlayer.gd new file mode 100644 index 0000000..accda02 --- /dev/null +++ b/addons/reedcamera/ref_counted/ReedCameraShakePlayer.gd @@ -0,0 +1,55 @@ +extends RefCounted +class_name ReedCameraShakePlayer + +var _preset: ReedCameraShakePreset +var _time := 0.0 +var _strength := 0.0 +var _active := false + +var _noise := FastNoiseLite.new() +var _noise_seed := randi() + +func play(preset: ReedCameraShakePreset) -> void: + _preset = preset + _time = 0.0 + _strength = 0.0 + _active = true + _noise.seed = _noise_seed + +func stop() -> void: + _active = false + +func is_active() -> bool: + return _active + +func evaluate(delta: float) -> Vector2: + if not _active or not _preset: + return Vector2.ZERO + + _time += delta + + var total := _preset.fade_in + _preset.hold + _preset.fade_out + if _time >= total: + _active = false + return Vector2.ZERO + + # ===== 强度曲线 ===== + if _time < _preset.fade_in: + _strength = _time / _preset.fade_in + elif _time < _preset.fade_in + _preset.hold: + _strength = 1.0 + else: + var t := (_time - _preset.fade_in - _preset.hold) / _preset.fade_out + _strength = 1.0 - t + + # ===== Noise 偏移 ===== + var shake_t := _time * _preset.frequency + var offset := Vector2( + _noise.get_noise_1d(shake_t), + _noise.get_noise_1d(shake_t + 1000) + ) + + return Vector2( + offset.x * _preset.amplitude.x, + offset.y * _preset.amplitude.y + ) * _strength diff --git a/addons/reedcamera/ref_counted/ReedCameraShakePlayer.gd.uid b/addons/reedcamera/ref_counted/ReedCameraShakePlayer.gd.uid new file mode 100644 index 0000000..20c8992 --- /dev/null +++ b/addons/reedcamera/ref_counted/ReedCameraShakePlayer.gd.uid @@ -0,0 +1 @@ +uid://5xuwd0cde3y8 diff --git a/addons/reedcamera/resource/ReedCameraShakePreset.gd b/addons/reedcamera/resource/ReedCameraShakePreset.gd new file mode 100644 index 0000000..6aa10d9 --- /dev/null +++ b/addons/reedcamera/resource/ReedCameraShakePreset.gd @@ -0,0 +1,9 @@ +# CameraShakePreset.gd +extends Resource +class_name ReedCameraShakePreset + +@export var amplitude := Vector2(6, 6) # 最大位移 +@export var frequency := 25.0 # 抖动频率 +@export var fade_in := 0.05 +@export var hold := 0.1 +@export var fade_out := 0.15 diff --git a/addons/reedcamera/resource/ReedCameraShakePreset.gd.uid b/addons/reedcamera/resource/ReedCameraShakePreset.gd.uid new file mode 100644 index 0000000..29637c0 --- /dev/null +++ b/addons/reedcamera/resource/ReedCameraShakePreset.gd.uid @@ -0,0 +1 @@ +uid://wlqopoksgvjc diff --git a/addons/reedcamera/scripts/CameraAnchor.gd b/addons/reedcamera/scripts/CameraAnchor.gd index 96f4dee..75f5acc 100644 --- a/addons/reedcamera/scripts/CameraAnchor.gd +++ b/addons/reedcamera/scripts/CameraAnchor.gd @@ -1,6 +1,6 @@ ##TODO:清楚掉這裏和PhantomCamera相關的部分 @tool -@icon("uid://bsdmq0essfmpk") +@icon("res://addons/reedcamera/icon/camera_anchor_icon.svg") class_name CameraAnchor extends Node2D const _CONSTANTS = preload("res://addons/reedcamera/_data/CameraSystemConst.gd") @@ -10,6 +10,10 @@ const _CONSTANTS = preload("res://addons/reedcamera/_data/CameraSystemConst.gd") @export var priority: int = 0 ##此Anchor是否有效 @export var enabled: bool = true +## ========================= +## Blend Config +## ========================= +@export_subgroup("Blending") ##是否要存在相機過渡時間 @export var use_blend: bool = true ##過度時間 @@ -49,12 +53,6 @@ const _CONSTANTS = preload("res://addons/reedcamera/_data/CameraSystemConst.gd") limit_right = value if Engine.is_editor_hint(): queue_redraw() -@export_subgroup("Follow") -@export var follow_player: bool = false - - - -var _camera_global : Node = null ## 编辑器预览面板设置 @export_group("Editor Preview") @@ -66,6 +64,15 @@ var _camera_global : Node = null @export var limit_preview_line_width: float = 2.0 +#@export_subgroup("Follow") +#@export var follow_player: bool = false + + + +var _camera_global : Node = null + + + var _priority: int : set(value): if _priority != value: @@ -147,7 +154,6 @@ func _get_camera_global() -> Object: if Engine.has_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME): _camera_global = Engine.get_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME) - print("") return _camera_global diff --git a/addons/reedcamera/scripts/CameraPointer.gd b/addons/reedcamera/scripts/CameraPointer.gd new file mode 100644 index 0000000..e898c88 --- /dev/null +++ b/addons/reedcamera/scripts/CameraPointer.gd @@ -0,0 +1,125 @@ +@tool +extends Node +class_name CameraPointer + +const _CONSTANTS := preload("res://addons/reedcamera/_data/CameraSystemConst.gd") + +var _camera: Camera2D +var _editor_valid := false +var _runtime_registered := false +var _warned := false + +var _final_offset := Vector2.ZERO +var _base_position: Vector2 + +func _enter_tree() -> void: + if Engine.is_editor_hint(): + call_deferred("_editor_verify") + +func _exit_tree() -> void: + if Engine.is_editor_hint(): + return + + _request_unregister() + +func _process(delta: float) -> void: + if Engine.is_editor_hint(): + return + + if not _camera: + return + + _update_base_position() + _update_camera_offset() + _apply_camera_transform() + +func _update_base_position(): + for child in get_children(): + if child.has_method("get_base_position"): + _base_position = child.get_base_position() + return + + # fallback + _base_position = _camera.global_position + +func _update_camera_offset(): + var offset := Vector2.ZERO + + for child in get_children(): + if child.has_method("get_camera_offset"): + offset += child.get_camera_offset() + + _final_offset = offset + +func _apply_camera_transform(): + _camera.global_position = _base_position + _camera.offset = _final_offset + + +func _notification(what: int) -> void: + if not Engine.is_editor_hint(): + return + + match what: + NOTIFICATION_PARENTED, NOTIFICATION_UNPARENTED: + #print("重設parent") + call_deferred("_editor_verify") + +## 編輯器層面的通過性驗證 +func _editor_verify() -> void: + _camera = _find_camera() + _editor_valid = _camera != null + #print(_editor_valid) + + if not _editor_valid: + _emit_config_warning() + else: + _warned = false + +func _ready() -> void: + if Engine.is_editor_hint(): + return + + _camera = _find_camera() + if not _camera: + return + + _request_register() + +func _find_camera() -> Camera2D: + var p := get_parent() + return p as Camera2D if p is Camera2D else null + +func _request_register() -> void: + var sys := Engine.get_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME) + if not sys: + return + + _runtime_registered = sys.request_register_pointer(self) + +func _request_unregister() -> void: + if not _runtime_registered: + return + + var sys := Engine.get_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME) + if sys: + sys.request_unregister_pointer(self) + + _runtime_registered = false + +func _emit_config_warning() -> void: + if not Engine.is_editor_hint(): + return + + if _warned: + return + + push_warning( + "[CameraPointer] Invalid configuration: parent node must be Camera2D. " + + "Current parent: %s" % (get_parent() if get_parent() else "null") + ) + + _warned = true + +func get_camera() -> Camera2D: + return _camera diff --git a/addons/reedcamera/scripts/CameraPointer.gd.uid b/addons/reedcamera/scripts/CameraPointer.gd.uid new file mode 100644 index 0000000..65e3781 --- /dev/null +++ b/addons/reedcamera/scripts/CameraPointer.gd.uid @@ -0,0 +1 @@ +uid://py4h5jxlncro diff --git a/addons/reedcamera/scripts/ReedCameraGlobal.gd b/addons/reedcamera/scripts/ReedCameraGlobal.gd index 5b217d1..7433c67 100644 --- a/addons/reedcamera/scripts/ReedCameraGlobal.gd +++ b/addons/reedcamera/scripts/ReedCameraGlobal.gd @@ -5,6 +5,63 @@ const _CONSTANTS = preload("res://addons/reedcamera/_data/CameraSystemConst.gd") var _screen_size : Vector2i +var _camera_points : Array[CameraPointer] +var _current_camera_point : CameraPointer + + +signal anchor_registered(anchor: CameraAnchor) +signal anchor_unregistered(anchor: CameraAnchor) + +var _anchors : Array[CameraAnchor] + +#region 相機指針 +func request_register_pointer(ptr: CameraPointer) -> bool: + if not is_instance_valid(ptr): + return false + + var cam := ptr.get_camera() + if not is_instance_valid(cam): + return false # System 决定:无效 camera 不接受注册 + + # 去重 + if ptr in _camera_points: + _current_camera_point = ptr + return true + + _camera_points.append(ptr) + + # 你可以在这里做选择策略:比如最新优先/priority + _current_camera_point = ptr + return true + +func request_unregister_pointer(ptr: CameraPointer) -> void: + _camera_points.erase(ptr) + if _current_camera_point == ptr: + _current_camera_point = _camera_points.back() if _camera_points.size() > 0 else null + +func get_current_camera_pointer() -> CameraPointer: + return _current_camera_point + +func get_camera() -> Camera2D: + return _current_camera_point.get_camera() if _current_camera_point else null +#endregion + +#region 相機錨點 +func register_anchor(anchor: CameraAnchor) -> void: + if anchor in _anchors: + return + _anchors.append(anchor) + anchor_registered.emit(anchor) + +func unregister_anchor(anchor: CameraAnchor) -> void: + if _anchors.has(anchor): + _anchors.erase(anchor) + anchor_unregistered.emit(anchor) + +func get_all_anchors() -> Array[CameraAnchor]: + return _anchors.duplicate() +#endregion + func _enter_tree() -> void: if not Engine.has_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME): Engine.register_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME, self) diff --git a/addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd b/addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd new file mode 100644 index 0000000..69c1050 --- /dev/null +++ b/addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd @@ -0,0 +1,99 @@ +extends Node +class_name ReedCameraAnchorController + +const _CONSTANTS = preload("res://addons/reedcamera/_data/CameraSystemConst.gd") +## ========================= +## Private +## ========================= +var _anchors: Array[CameraAnchor] = [] +var _current_anchor: CameraAnchor +var _current_position := Vector2.ZERO +var _tween: Tween + +## ========================= +## Config +## ========================= +@export var enabled := true +@export var move_duration := 0.4 +@export var ease_type := Tween.EASE_OUT +@export var trans_type := Tween.TRANS_CUBIC + +## ========================= +## Runtime State +## ========================= + +#var _current_position: Vector2 +var _target_position: Vector2 +#var _tween: Tween +var _has_target := false + +## ========================= +## Lifecycle +## ========================= + +func _ready() -> void: + var sys := Engine.get_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME) + if not sys: + return + + _anchors = sys.get_all_anchors() + + sys.anchor_registered.connect(_on_anchor_added) + sys.anchor_unregistered.connect(_on_anchor_removed) + + _evaluate() + +func _on_anchor_added(anchor: CameraAnchor) -> void: + _anchors.append(anchor) + _evaluate() + +func _on_anchor_removed(anchor: CameraAnchor) -> void: + _anchors.erase(anchor) + if _current_anchor == anchor: + _current_anchor = null + _evaluate() + +func _evaluate() -> void: + var winner := _pick_best_anchor() + if winner == _current_anchor: + return + + _switch_to(winner) + +func _pick_best_anchor() -> CameraAnchor: + var best: CameraAnchor + var best_p := -INF + + for a in _anchors: + if not a.enabled: + continue + if a.priority > best_p: + best = a + best_p = a.priority + + return best + +func _switch_to(anchor: CameraAnchor) -> void: + if not anchor: + return + + _current_anchor = anchor + var target := anchor.global_position + + if _tween and _tween.is_running(): + _tween.kill() + + if not anchor.use_blend or anchor.blend_time <= 0.0: + _current_position = target + return + + _tween = get_tree().create_tween() + _tween.set_trans(Tween.TRANS_CUBIC) + _tween.set_ease(Tween.EASE_OUT) + _tween.tween_property(self, "_current_position", target, anchor.blend_time) + +## ========================= +## CameraPointer Interface +## ========================= +func get_base_position() -> Vector2: + return _current_position diff --git a/addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd.uid b/addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd.uid new file mode 100644 index 0000000..a7acd0c --- /dev/null +++ b/addons/reedcamera/scripts/camera_tools/CameraAnchorController.gd.uid @@ -0,0 +1 @@ +uid://bhl5it46hv4n2 diff --git a/addons/reedcamera/scripts/camera_tools/CameraShakeController.gd b/addons/reedcamera/scripts/camera_tools/CameraShakeController.gd new file mode 100644 index 0000000..03fe761 --- /dev/null +++ b/addons/reedcamera/scripts/camera_tools/CameraShakeController.gd @@ -0,0 +1,66 @@ +@tool +extends Node +class_name ReedCameraShakeController + +## ========================= +## Config +## ========================= +@export var enabled := true + +## ========================= +## Runtime +## ========================= + +var _players: Array[ReedCameraShakePlayer] = [] +var _current_offset := Vector2.ZERO + +func _ready() -> void: + if Engine.is_editor_hint(): + return + set_process(true) + +func _exit_tree() -> void: + _players.clear() + _current_offset = Vector2.ZERO + +func _process(delta: float) -> void: + if Engine.is_editor_hint(): + return + + if not enabled: + _current_offset = Vector2.ZERO + return + + var offset := Vector2.ZERO + + for p in _players: + offset += p.evaluate(delta) + + # 移除已经结束的 player + _players = _players.filter(func(p): return p.is_active()) + + _current_offset = offset + +## ========================= +## Public API +## ========================= +func play_shake(preset: ReedCameraShakePreset) -> void: + if not preset: + return + + var player := ReedCameraShakePlayer.new() + player.play(preset) + _players.append(player) + +func stop_all() -> void: + for p in _players: + p.stop() + _players.clear() + _current_offset = Vector2.ZERO + +## ========================= +## Pipeline Output +## ========================= + +func get_camera_offset() -> Vector2: + return _current_offset diff --git a/addons/reedcamera/scripts/camera_tools/CameraShakeController.gd.uid b/addons/reedcamera/scripts/camera_tools/CameraShakeController.gd.uid new file mode 100644 index 0000000..6cf88f3 --- /dev/null +++ b/addons/reedcamera/scripts/camera_tools/CameraShakeController.gd.uid @@ -0,0 +1 @@ +uid://dwr1s51svvank diff --git a/project.godot b/project.godot index 79fd9e2..00b590f 100644 --- a/project.godot +++ b/project.godot @@ -17,16 +17,13 @@ config/icon="res://icon.svg" [autoload] -ReedCameraSystem="*res://addons/reedcamera/scripts/ReedCameraGlobal.gd" -CameraSystem="*res://_camera/CameraSystem.tscn" GlobalEvent="*res://_shared/GlobalEvent.gd" ReedVFX="*res://addons/reedfx/vfx/ReedVFXSystem.tscn" ReedSceneRegistry="*res://addons/reedscene/scene/SceneRegistry.gd" +ReedCameraSystem="*res://addons/reedcamera/scripts/ReedCameraGlobal.gd" [display] -window/size/viewport_width=640 -window/size/viewport_height=360 window/size/window_width_override=1920 window/size/window_height_override=1080 window/stretch/mode="canvas_items"