171 lines
5.2 KiB
GDScript
171 lines
5.2 KiB
GDScript
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
|
||
# )
|