godot-plateformer/addons/reedcamera/scripts/camera_tools/CameraFollowController.gd

207 lines
5.3 KiB
GDScript3
Raw Permalink Normal View History

2026-01-13 10:32:36 +08:00
extends CameraToolBasic
class_name ReedCameraFollowController
const _CONSTANTS := preload("res://addons/reedcamera/_data/CameraSystemConst.gd")
2026-01-13 16:52:45 +08:00
const _DEBUG_TOOL := preload("res://addons/reedcamera/scripts/camera_tools/DeadZoneDebug.tscn")
2026-01-13 10:32:36 +08:00
@export_group("Dead Zone")
2026-01-13 16:52:45 +08:00
@export var dead_zone_ratio : Vector2 = Vector2(.6,.6)
2026-01-13 10:32:36 +08:00
@export_group("Follow")
@export var enabled_follow: bool = true
2026-01-13 16:52:45 +08:00
@export var follow_speed: float = 600.0 # 世界单位 / 秒
2026-01-13 10:32:36 +08:00
@export var follow_lerp := 0.12 # 0~1越大越“跟手”越小越“蔚蓝感”的滞后
2026-01-13 22:42:41 +08:00
@export_subgroup("Follow Dynamic Speed")
@export var min_speed_scale := 0.4 # 贴近死区时
@export var max_speed_scale := 2.0 # 远离死区时
@export var max_offset_ratio := 1.0 # offset 达到 1 个 dead_zone 时为最大速率
2026-01-13 10:32:36 +08:00
2026-01-13 16:52:45 +08:00
##TODO:后续添加一下Runtime的修改逻辑
@export_group("Debug")
@export var show_preview: bool = true
var _show_preview: bool = false:
set(value):
_update_debug(value)
_show_preview = value
@export var preview_color: Color = Color.AQUAMARINE
@export var preview_line_width : float = 2.0
enum State{
IDLE,
CHASING,
STATIC
}
## 用于描述和切换follower工具的状态
var _current_state: State = State.IDLE
##Follower工具绑定的followNode
2026-01-13 10:32:36 +08:00
var _follow_node2d : Node2D = null
2026-01-13 16:52:45 +08:00
##最终的位置
var _final_position: Vector2
##缓存全局系统
var _sys: Object = null
##缓存DebugTool
var _screen_ratio_rect_draw_tool: Node = null
##缓存camera
2026-01-13 10:32:36 +08:00
var _camera: Camera2D = null
2026-01-13 16:52:45 +08:00
func _ready() -> void:
_show_preview = show_preview
func _process(delta: float) -> void:
#print(_current_state)
if _current_state == State.IDLE:
return
if _current_state == State.CHASING or State.STATIC:
update_follow(delta)
func _update_debug(debug:bool) -> void:
if debug:
if _screen_ratio_rect_draw_tool:
var t := _screen_ratio_rect_draw_tool.get_child(0)
t.set_ratio(dead_zone_ratio)
t.set_target_node(_follow_node2d)
t.set_style(preview_color,preview_line_width)
return
else:
var t := _DEBUG_TOOL.instantiate()
self.add_child(t)
_screen_ratio_rect_draw_tool = t
t.get_child(0).set_ratio(dead_zone_ratio)
t.get_child(0).set_target_node(_follow_node2d)
t.get_child(0).set_style(preview_color,preview_line_width)
else:
if not _screen_ratio_rect_draw_tool:
return
else:
_screen_ratio_rect_draw_tool.get_child(0).clear()
func update_follow(delta: float) -> void:
2026-01-13 10:32:36 +08:00
if not enabled_follow:
return
if not _follow_node2d:
return
var cam := _get_camera_from_pointer()
if not cam:
return
2026-01-13 16:52:45 +08:00
# 0. 读取相机的全局坐标
_final_position = cam.global_position
# 1. target 的屏幕坐标Canvas Space
var t_in_screen :Vector2 = \
_follow_node2d.get_global_transform_with_canvas().get_origin()
var screen_size : Vector2 = _get_camera_system().get_screen_size()
var screen_center : Vector2 = screen_size * 0.5
var dead_half : Vector2 = screen_size * dead_zone_ratio * 0.5
# 2. 获得移动方向
var offset := \
_compute_deadzone_offset(
t_in_screen,
screen_center,
dead_half
2026-01-13 10:32:36 +08:00
)
2026-01-13 16:52:45 +08:00
if offset == Vector2.ZERO:
_current_state = State.STATIC
return
_current_state = State.CHASING
2026-01-13 22:42:41 +08:00
#var move_dir := offset.normalized()
#var world_velocity := (move_dir * follow_speed) / cam.zoom
var offset_len := offset.length()
var dead_len := dead_half.length()
# 0~1 的距离比例
var t := clamp(offset_len / (dead_len * max_offset_ratio), 0.0, 1.0)
# 距离越远,速度越快
var speed_scale := lerp(min_speed_scale, max_speed_scale, t)
2026-01-13 16:52:45 +08:00
var move_dir := offset.normalized()
2026-01-13 22:42:41 +08:00
var world_velocity : Vector2 = (move_dir * follow_speed * speed_scale) / cam.zoom
2026-01-13 16:52:45 +08:00
var desired_pos := _final_position + world_velocity * delta
if follow_lerp > 0.0:
_final_position = cam.global_position.lerp(desired_pos, follow_lerp)
else:
_final_position = desired_pos
func _compute_deadzone_offset(
screen_pos: Vector2,
screen_center: Vector2,
dead_half: Vector2
) -> Vector2:
var o := Vector2.ZERO
var dx := screen_pos.x - screen_center.x
var dy := screen_pos.y - screen_center.y
if dx < -dead_half.x:
o.x = dx + dead_half.x
elif dx > dead_half.x:
o.x = dx - dead_half.x
if dy < -dead_half.y:
o.y = dy + dead_half.y
elif dy > dead_half.y:
o.y = dy - dead_half.y
return o
## 从相机指针获取相机组件
2026-01-13 10:32:36 +08:00
func _get_camera_from_pointer() -> Camera2D:
if _camera:
return _camera
var p := get_parent()
if p and p.has_method("get_camera"):
_camera = p.get_camera() as Camera2D
return _camera
return _camera
2026-01-13 16:52:45 +08:00
## 获取全局相机管理器
func _get_camera_system() -> Object:
if _sys:
return _sys
if Engine.has_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME):
_sys = Engine.get_singleton(_CONSTANTS.CAMERA_SYSTEM_NAME)
return _sys
##外部调用,设置一个相机系统的跟随者
func register_follower(follower: Node2D) -> void:
_follow_node2d = follower
if self._current_state == State.IDLE:
start_follow()
##外部调用,移除一共相机系统的跟随者
func unregister_follower() -> void:
_follow_node2d = null
stop_follow()
##外部调用,开始跟随
func start_follow() -> void:
_current_state = State.STATIC
##外部调用,结束跟随
func stop_follow() -> void:
_current_state = State.IDLE
func get_base_position() -> Vector2:
return _final_position
2026-01-13 10:32:36 +08:00
2026-01-13 16:52:45 +08:00
func has_base_position() -> bool:
return _current_state == State.CHASING