godot-plateformer/addons/reedcomponent/grap_hook/garpping_hook_v_2.gd

184 lines
4.4 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 Hook
extends Node2D
## ================
## Export Field
## ================
@export var min_length := 140.0
@export var max_length := 200.0
@export var stretching_speed: float = 1400.0
@onready var line_2d: Line2D = %Line2D
@onready var ray: RayCast2D = %RayCast2D
const GRAPABLE_GROUP = &"GRAPABLE"
signal stretching_finished(reach_limit: bool, anchor_node: Node2D)
## ================
## Private Field
## ================
var _binded_hook_comp
var _is_stretching := false
var _stretching_dir := Vector2.ZERO
var _cached_cancel := false
var _current_length := 0.0
var _anchor: Node2D
var _dir_id: int = -1
# =================
# Life Cycle
# =================
func _ready() -> void:
ray.enabled = true
ray.target_position = Vector2.ZERO
## 初始化
func init(hook_comp: SpawnHookComponet, reset_to_target: bool) -> void:
_binded_hook_comp = hook_comp
if reset_to_target:
global_position = hook_comp.owner.global_position
# =================
# Stretch Control
# =================
func start_stretching(direction: Vector2) -> void:
_is_stretching = true
_cached_cancel = false
_stretching_dir = direction.normalized()
_current_length = 0.0
_dir_id = _get_direction_id(direction,8)
func end_stretching(force_end: bool = false) -> bool:
if not force_end and _is_stretching:
if _current_length < min_length:
_cached_cancel = true
return false
_is_stretching = false
_stretching_dir = Vector2.ZERO
return true
func is_stretching() -> bool:
return _is_stretching
# =================
# Update
# =================
func _physics_process(delta: float) -> void:
if _is_stretching:
_update_stretching(delta)
func _process(_delta: float) -> void:
_update_line()
# =================
# Core Logic
# =================
func _update_stretching(delta: float) -> void:
# 先嘗試推進
var next_length := _current_length + stretching_speed * delta
next_length = min(next_length, max_length)
# 先用「下一幀長度」做 Ray
ray.target_position = _stretching_dir * next_length
ray.force_raycast_update()
# ===== 命中檢測(最高優先)=====
if ray.is_colliding():
var collider := ray.get_collider()
if collider is Node2D and collider.is_in_group(GRAPABLE_GROUP):
var hit_pos := ray.get_collision_point()
_current_length = global_position.distance_to(hit_pos)
ray.target_position = _stretching_dir * _current_length
_handle_hit(collider as Node2D, hit_pos)
return
# ===== 沒命中,才正式推進 =====
_current_length = next_length
# 取消邏輯
if _cached_cancel and _current_length >= min_length:
stretching_finished.emit(true, null)
end_stretching(true)
return
# 超過最大距離
if _current_length >= max_length:
stretching_finished.emit(true, null)
end_stretching(true)
func _handle_hit(target: Node2D, hit_pos: Vector2) -> void:
_is_stretching = false
_stretching_dir = Vector2.ZERO
ray.target_position = to_local(hit_pos)
var reach_max := is_equal_approx(_current_length, max_length)
var anchor := _create_anchor_on_node(target, hit_pos)
stretching_finished.emit(reach_max, anchor)
## 釋放鉤爪(清理 Anchor 與狀態)
func release_hook() -> void:
# 1. 停止拉伸(保險)
_is_stretching = false
_stretching_dir = Vector2.ZERO
_cached_cancel = false
_current_length = 0.0
# 2. 清掉 Anchor
if _anchor and is_instance_valid(_anchor):
# 先脫離父節點,避免殘留引用問題
_anchor.get_parent().remove_child(_anchor)
_anchor.queue_free()
_anchor = null
# 3. 重置 Ray 與 Line視覺清乾淨
ray.target_position = Vector2.ZERO
_update_line()
func _update_line() -> void:
line_2d.set_point_position(0, Vector2.ZERO)
line_2d.set_point_position(1, ray.target_position)
func _create_anchor_on_node(target: Node2D, hit_global_pos: Vector2) -> Node2D:
if _anchor and is_instance_valid(_anchor):
_anchor.queue_free()
_anchor = Node2D.new()
_anchor.name = "Anchor"
target.add_child(_anchor)
# 關鍵:固定命中點偏移
_anchor.position = target.to_local(hit_global_pos)
return _anchor
## 计算方向id私有
func _get_direction_id(direction: Vector2, sector_count: int) -> int:
if direction == Vector2.ZERO:
return -1
var angle = rad_to_deg(direction.angle())
angle += 90.0
var sector_size = 360.0 / sector_count
angle += sector_size / 2.0
angle = fposmod(angle, 360.0)
return int(angle / sector_size)
## 获取方向id
func get_direction_id() -> int:
return _dir_id