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
|