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 := 3.0 var _tweeked_position := Vector2.ZERO var zoom_tween: Tween var focus_offset := Vector2.ZERO var shaked_offset := Vector2.ZERO func _ready() -> void: if not focusing_node: push_error("Focusing node not found") reset_position_immediately() func _enter_tree() -> void: if is_node_ready(): reset_position_immediately() func shake_camera(strength := 7.0): shake_strength = strength func reset_position_immediately(): if focusing_node: global_position = focusing_node.global_position + _tweeked_position + force_offset 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 func _physics_process(delta: float) -> void: if not focusing_node: return # set camera's position var target_position = ( focusing_node.global_position + _tweeked_position + force_offset + focus_offset + shaked_offset ) if focusing_node is MainPlayer: # player 的焦点在脚底,所以需要偏移 player 的高度。注意 y 轴是向下的,所以是减去 player 的高度 target_position.y -= focusing_node.current_animation_config.os_height * 0.7 # easing with speed var new_position = lerp(global_position, target_position, speed * delta) # var new_position = global_position + (target_position - global_position) * speed * delta # clamp the position var margin = half_screen_size / zoom_ratio # var margin = half_screen_size margin.y += shaded_height new_position.x = clamp(new_position.x, limit_left + margin.x, limit_right - margin.x) new_position.y = clamp(new_position.y, limit_top + margin.y, limit_bottom - margin.y) global_position = new_position # var taget_zoom = lerpf(zoom.x, zoom_ratio, speed * delta) # zoom = Vector2(taget_zoom, taget_zoom) zoom = Vector2(zoom_ratio, zoom_ratio) # handle shake, via _shaked_position if shake_strength > 0.0: shake_strength = lerpf(shake_strength, 0.0, shake_recovery_speed * delta) shaked_offset = Vector2(randf_range(-shake_strength, shake_strength), randf_range(-shake_strength, shake_strength)) global_position += shaked_offset 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 # )