xiandie/scene/ground/camera/camera_focus_marker.gd
2025-06-30 18:33:40 +08:00

171 lines
5.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

class_name CameraFocusMarker extends Camera2D
@export var focusing_node: Node2D
@export var force_offset := Vector2.ZERO
@export_group("Status")
@export var lock_horizontal = true
@export_group("Config")
@export var half_screen_size := Vector2(564, 240) / 2.0
@export var shaded_height := 38
@export_group("Shake", "shake_")
@export var shake_strength := 0.0
@export var shake_recovery_speed := 4.0
# @export_group("Limit", "limit_")
# @export var limit_left := 0.0
# @export var limit_right := 564.0
# @export var limit_top := 0
# @export var limit_bottom := 316.0
@export var zoom_ratio := 1.0
var speed := 2.0
var _tweeked_position := Vector2.ZERO
var zoom_tween: Tween
var focus_offset := Vector2.ZERO
var shake_ignore_boundary := false
func _ready() -> void:
if not focusing_node:
push_error("Focusing node not found")
func shake_camera(strength := 6.0, recovery_speed := 2.0, ignore_boundary := true):
shake_strength = strength
shake_recovery_speed = recovery_speed
shake_ignore_boundary = ignore_boundary
func reset_position_immediately():
if focusing_node:
global_position = focusing_node.global_position + _tweeked_position + force_offset
print("CameraFocusMarker reset_position_immediately to:", global_position)
func tweak_position(velocity, facing_direction):
var ideal_x = facing_direction.x * min(50.0, 0.5 * abs(velocity.x))
var current_x = _tweeked_position.x
var delta = ideal_x - current_x
if abs(delta) > 10.0:
_tweeked_position.x = move_toward(current_x, ideal_x, speed * 2.0)
if lock_horizontal:
global_position.y = 0
_tweeked_position.y = 0
else:
_tweeked_position.y = facing_direction.y * abs(velocity.y) * 0.2
# 处理过程的当下理想位置
var progressing_position: Vector2
func _physics_process(delta: float) -> void:
if not focusing_node:
return
# 最终目标位置
var target_position = focusing_node.global_position + _tweeked_position + force_offset
if focusing_node is MainPlayer:
# player 的焦点在脚底,所以需要偏移 player 的高度。注意 y 轴是向下的,所以是减去 player 的高度
target_position.y -= focusing_node.current_animation_config.os_height * 0.7
# clamp the position
target_position = _clamp_boundary(target_position)
# easing with speed
progressing_position = lerp(progressing_position, target_position, speed * delta)
global_position = progressing_position
# handle shake
if shake_strength > 0.0:
# 让 shake_strength 逐帧衰减
shake_strength = lerpf(shake_strength, 0.0, shake_recovery_speed * delta)
# [-shake_strength, +shake_strength] 范围内的同时,尽可能偏离中心
# 在 0 2π 之间随机一个方向,在 _last_shake_angle 的对角范围
# _last_shake_angle = wrapf(_last_shake_angle + randf_range(-2.0, 2.0), 0, TAU) # TAU = 2π
var _last_shake_angle := randf_range(-TAU, TAU) # TAU = 2π
var shaked_offset := (
Vector2(cos(_last_shake_angle), sin(_last_shake_angle)) * shake_strength
)
# var shaked_offset := Vector2(
# randf_range(-shake_strength, shake_strength),
# randf_range(-shake_strength, shake_strength)
# )
global_position += shaked_offset
if not shake_ignore_boundary:
global_position = _clamp_boundary(global_position)
# var taget_zoom = lerpf(zoom.x, zoom_ratio, speed * delta)
# zoom = Vector2(taget_zoom, taget_zoom)
zoom = Vector2(zoom_ratio, zoom_ratio)
func _clamp_boundary(target: Vector2) -> Vector2:
var margin = half_screen_size / zoom_ratio
margin.y += shaded_height
target.x = clamp(target.x, limit_left + margin.x, limit_right - margin.x)
target.y = clamp(target.y, limit_top + margin.y, limit_bottom - margin.y)
return target
func tween_zoom(ratio: float, duration := 1.5):
if zoom_tween and zoom_tween.is_running():
zoom_tween.kill()
zoom_tween = create_tween()
(
zoom_tween
. tween_property(self, "zoom_ratio", ratio, duration)
. set_trans(Tween.TRANS_SINE)
. set_ease(Tween.EASE_IN_OUT)
. from(zoom_ratio)
)
# if refocus:
# zooming_and_focus_start_position = global_position
# (
# zoom_tween
# . parallel()
# . tween_property(self, "zooming_and_focus_progress", 1.0, duration)
# . from(0.0)
# . set_trans(Tween.TRANS_SINE)
# . set_ease(Tween.EASE_IN_OUT)
# )
var tween_focus: Tween
func focus_node(node: Node2D, duration := 0.0) -> void:
if focusing_node == node:
return
if not node:
focusing_node = null
return
if duration > 0.0:
focus_offset = global_position - node.global_position
if tween_focus and tween_focus.is_running():
tween_focus.kill()
tween_focus = create_tween()
(
tween_focus
. tween_property(self, "focus_offset", Vector2.ZERO, duration)
. set_trans(Tween.TRANS_SINE)
. set_ease(Tween.EASE_IN_OUT)
)
focusing_node = node
# var exited := false
# var exit_position: Vector2
# var enter_tree_tween: Tween
# func _exit_tree() -> void:
# exit_position = global_position
# exited = true
# func _enter_tree() -> void:
# if exited and is_node_ready():
# exited = false
# if enter_tree_tween and enter_tree_tween.is_running():
# enter_tree_tween.kill()
# enter_tree_tween = create_tween()
# global_position = exit_position
# enter_tree_tween.tween_property(self, "position", Vector2.ZERO, 0.2).set_trans(
# Tween.TRANS_CUBIC
# )