godot-plateformer/_shared/camera/CameraSystem.gd

209 lines
5.0 KiB
GDScript3
Raw Normal View History

2026-01-06 16:19:08 +08:00
'''全局的相机管理器
2026-01-06 11:58:41 +08:00
======= =======
'''
extends Node
2026-01-08 22:39:39 +08:00
@onready var camera_2d: Camera2D = %Camera2D
2026-01-11 23:31:06 +08:00
@onready var camera_shake_player: CameraShakePlayer = %CameraShakePlayer
2026-01-08 22:39:39 +08:00
2026-01-06 11:58:41 +08:00
var _cached_anchors: Array[CameraAnchor] = []
var _current_anchor: CameraAnchor
var _switch_tween: Tween
2026-01-08 22:39:39 +08:00
var _player_remote: RemoteTransform2D
2026-01-11 23:31:06 +08:00
var _base_camera_pos := Vector2.ZERO
2026-01-06 11:58:41 +08:00
2026-01-08 15:53:30 +08:00
##标记位,用来检测当前帧是否存在相机切换
var _switch_scheduled := false
var _dirty := false
2026-01-06 11:58:41 +08:00
## 玩家关卡内静态相机
2026-01-06 16:19:08 +08:00
const PLAYER_CAMERA_SCENE:= preload("res://_shared/camera/PlayerStaticCamera.tscn")
2026-01-08 22:39:39 +08:00
const CAMERA_FOLLOWER:= preload("res://_shared/camera/camera_follower.tscn")
2026-01-06 11:58:41 +08:00
2026-01-11 23:31:06 +08:00
func _ready() -> void:
_base_camera_pos = camera_2d.global_position
func _process(delta):
if not camera_2d:
return
var shake_offset := camera_shake_player.update(delta)
camera_2d.global_position = _base_camera_pos + shake_offset
2026-01-06 11:58:41 +08:00
## 外部获取玩家全局相机
2026-01-08 22:39:39 +08:00
func get_cached_camera() -> Camera2D:
return camera_2d
2026-01-06 11:58:41 +08:00
## 注册一个相机锚点
func register_anchor(anchor: CameraAnchor) -> void:
if anchor in _cached_anchors:
return
_cached_anchors.append(anchor)
2026-01-06 16:19:08 +08:00
anchor.on_priority_change.connect(on_anchor_priority_changed)
2026-01-08 15:53:30 +08:00
_request_evaluate()
2026-01-06 16:19:08 +08:00
## 当相机锚点的权重改变时,向管理器触发事件
func on_anchor_priority_changed(priority:int, anchor: CameraAnchor) -> void:
2026-01-08 15:53:30 +08:00
_request_evaluate()
2026-01-06 16:19:08 +08:00
2026-01-06 11:58:41 +08:00
## 注销一个相机锚点
func unregister_anchor(anchor: CameraAnchor) -> void:
_cached_anchors.erase(anchor)
if _current_anchor == anchor:
_current_anchor = null
2026-01-08 15:53:30 +08:00
_request_evaluate()
func _request_evaluate() -> void:
_dirty = true
if _switch_scheduled:
return
_switch_scheduled = true
call_deferred("_commit_camera_anchor")
func _commit_camera_anchor() -> void:
_switch_scheduled = false
if not _dirty:
return
_dirty = false
# 清理无效
_cached_anchors = _cached_anchors.filter(func(a): return is_instance_valid(a))
var winner: CameraAnchor = _pick_best_anchor()
if winner == null:
return
if winner == _current_anchor:
return
switch_anchor(winner)
func _pick_best_anchor() -> CameraAnchor:
var best: CameraAnchor = null
var best_p := -INF
for a in _cached_anchors:
if not a.enabled:
continue
if a._priority > best_p:
best_p = a._priority
best = a
return best
2026-01-06 11:58:41 +08:00
## 排序已有的锚点
func _sort_anchors() -> void:
_cached_anchors.sort_custom(func(a, b):
2026-01-06 16:19:08 +08:00
return a._priority > b._priority
2026-01-06 11:58:41 +08:00
)
## 尝试自切换
func _try_auto_switch() -> void:
for a in _cached_anchors:
if a.enabled:
switch_anchor(a)
2026-01-06 16:19:08 +08:00
_current_anchor = a
2026-01-06 11:58:41 +08:00
return
2026-01-06 16:19:08 +08:00
## 重置所有的Camera的_priority
2026-01-06 11:58:41 +08:00
func reset_all_camera_priority() -> void:
for a in _cached_anchors:
2026-01-06 16:19:08 +08:00
a._priority = 0
2026-01-06 11:58:41 +08:00
2026-01-08 22:39:39 +08:00
##应用Anchor的Limit
func _apply_anchor_limits(anchor: CameraAnchor) -> void:
if not camera_2d:
return
camera_2d.limit_left = anchor.global_position.x + anchor.limit_left
camera_2d.limit_right = anchor.global_position.x + anchor.limit_right
camera_2d.limit_top = anchor.global_position.y + anchor.limit_top
camera_2d.limit_bottom = anchor.global_position.y + anchor.limit_bottom
func _clear_camera_limits() -> void:
if not camera_2d:
return
camera_2d.limit_left = -10000000
camera_2d.limit_right = 10000000
camera_2d.limit_top = -10000000
camera_2d.limit_bottom = 10000000
2026-01-06 11:58:41 +08:00
## 切换相机
func switch_anchor(target_anchor: CameraAnchor) -> void:
if target_anchor == null:
return
if target_anchor == _current_anchor:
return
2026-01-08 22:39:39 +08:00
if not is_instance_valid(camera_2d):
2026-01-06 11:58:41 +08:00
return
2026-01-08 22:39:39 +08:00
#优先清除已有的remote
if _player_remote:
_player_remote.queue_free()
_player_remote = null
2026-01-06 11:58:41 +08:00
# 中断旧 Tween
if _switch_tween and _switch_tween.is_running():
_switch_tween.kill()
_switch_tween = null
2026-01-08 22:39:39 +08:00
var camera := camera_2d
2026-01-06 11:58:41 +08:00
var blend_time : float = max(target_anchor.blend_time, 0.001)
2026-01-08 22:39:39 +08:00
_clear_camera_limits()
2026-01-06 11:58:41 +08:00
_switch_tween = get_tree().create_tween()
_switch_tween.set_ignore_time_scale(true)
2026-01-08 15:53:30 +08:00
_switch_tween.set_trans(Tween.TRANS_CUBIC)
2026-01-06 11:58:41 +08:00
_switch_tween.set_ease(Tween.EASE_OUT)
# ===== 位置 =====
_switch_tween.tween_property(
2026-01-11 23:31:06 +08:00
self,
"_base_camera_pos",
2026-01-06 11:58:41 +08:00
target_anchor.global_position,
blend_time
)
# ===== Zoom =====
2026-01-08 15:53:30 +08:00
_switch_tween.parallel().tween_property(
2026-01-08 22:39:39 +08:00
camera,
2026-01-06 16:19:08 +08:00
"zoom",
2026-01-06 11:58:41 +08:00
target_anchor.zoom,
2026-01-08 15:53:30 +08:00
blend_time * 1.5
2026-01-06 11:58:41 +08:00
)
# 完成回调
_switch_tween.finished.connect(func():
_current_anchor = target_anchor
2026-01-08 22:39:39 +08:00
_update_anchor_follow_state(target_anchor)
_apply_anchor_limits(target_anchor)
2026-01-06 11:58:41 +08:00
)
_current_anchor = target_anchor
2026-01-08 22:39:39 +08:00
func _update_anchor_follow_state(anchor: CameraAnchor) -> void:
# 先清理旧的 RemoteTransform
if _player_remote and is_instance_valid(_player_remote):
_player_remote.queue_free()
_player_remote = null
if not anchor.follow_player:
return
var p := GlobalEvent.get_player()
if not p or not is_instance_valid(p):
push_warning("CameraAnchor wants to follow player, but player is not registered.")
return
# 创建 RemoteTransform2D
var rt := RemoteTransform2D.new()
rt.name = "__CameraAnchorFollow"
rt.remote_path = camera_2d.get_path()
p.add_child(rt)
_player_remote = rt