207 lines
5.3 KiB
GDScript
207 lines
5.3 KiB
GDScript
extends CameraToolBasic
|
||
class_name ReedCameraFollowController
|
||
|
||
const _CONSTANTS := preload("res://addons/reedcamera/_data/CameraSystemConst.gd")
|
||
const _DEBUG_TOOL := preload("res://addons/reedcamera/scripts/camera_tools/DeadZoneDebug.tscn")
|
||
|
||
@export_group("Dead Zone")
|
||
@export var dead_zone_ratio : Vector2 = Vector2(.6,.6)
|
||
|
||
@export_group("Follow")
|
||
@export var enabled_follow: bool = true
|
||
@export var follow_speed: float = 10000.0 # 世界单位 / 秒
|
||
@export var follow_lerp := 0.12 # 0~1,越大越“跟手”,越小越“蔚蓝感”的滞后
|
||
@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 时为最大速率
|
||
|
||
##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
|
||
var _follow_node2d : Node2D = null
|
||
##最终的位置
|
||
var _final_position: Vector2
|
||
|
||
##缓存全局系统
|
||
var _sys: Object = null
|
||
##缓存DebugTool
|
||
var _screen_ratio_rect_draw_tool: Node = null
|
||
##缓存camera
|
||
var _camera: Camera2D = null
|
||
|
||
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:
|
||
if not enabled_follow:
|
||
return
|
||
if not _follow_node2d:
|
||
return
|
||
|
||
var cam := _get_camera_from_pointer()
|
||
if not cam:
|
||
return
|
||
|
||
# 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
|
||
)
|
||
|
||
if offset == Vector2.ZERO:
|
||
_current_state = State.STATIC
|
||
return
|
||
_current_state = State.CHASING
|
||
|
||
|
||
#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)
|
||
|
||
var move_dir := offset.normalized()
|
||
var world_velocity : Vector2 = (move_dir * follow_speed * speed_scale) / cam.zoom
|
||
|
||
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
|
||
|
||
## 从相机指针获取相机组件
|
||
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
|
||
|
||
## 获取全局相机管理器
|
||
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
|
||
|
||
func has_base_position() -> bool:
|
||
return _current_state == State.CHASING
|