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

184 lines
4.4 KiB
GDScript3
Raw Normal View History

2025-12-31 16:24:11 +08:00
class_name Hook
extends Node2D
2025-12-31 13:07:31 +08:00
## ================
## Export Field
## ================
2025-12-31 16:24:11 +08:00
@export var min_length := 140.0
@export var max_length := 200.0
@export var stretching_speed: float = 1400.0
2025-12-31 13:07:31 +08:00
@onready var line_2d: Line2D = %Line2D
2025-12-31 16:24:11 +08:00
@onready var ray: RayCast2D = %RayCast2D
2025-12-31 13:07:31 +08:00
const GRAPABLE_GROUP = &"GRAPABLE"
2025-12-31 16:24:11 +08:00
signal stretching_finished(reach_limit: bool, anchor_node: Node2D)
2025-12-31 13:07:31 +08:00
## ================
## Private Field
## ================
var _binded_hook_comp
2025-12-31 16:24:11 +08:00
var _is_stretching := false
var _stretching_dir := Vector2.ZERO
var _cached_cancel := false
var _current_length := 0.0
2025-12-31 13:07:31 +08:00
var _anchor: Node2D
2026-01-06 23:18:36 +08:00
var _dir_id: int = -1
2025-12-31 16:24:11 +08:00
# =================
# Life Cycle
# =================
2025-12-31 13:07:31 +08:00
func _ready() -> void:
2025-12-31 16:24:11 +08:00
ray.enabled = true
ray.target_position = Vector2.ZERO
## 初始化
func init(hook_comp: SpawnHookComponet, reset_to_target: bool) -> void:
2025-12-31 13:07:31 +08:00
_binded_hook_comp = hook_comp
if reset_to_target:
2025-12-31 16:24:11 +08:00
global_position = hook_comp.owner.global_position
# =================
# Stretch Control
# =================
2025-12-31 13:07:31 +08:00
func start_stretching(direction: Vector2) -> void:
_is_stretching = true
_cached_cancel = false
2025-12-31 16:24:11 +08:00
_stretching_dir = direction.normalized()
_current_length = 0.0
2026-01-06 23:18:36 +08:00
_dir_id = _get_direction_id(direction,8)
2025-12-31 13:07:31 +08:00
func end_stretching(force_end: bool = false) -> bool:
if not force_end and _is_stretching:
2025-12-31 16:24:11 +08:00
if _current_length < min_length:
2025-12-31 13:07:31 +08:00
_cached_cancel = true
return false
2025-12-31 16:24:11 +08:00
2025-12-31 13:07:31 +08:00
_is_stretching = false
_stretching_dir = Vector2.ZERO
return true
func is_stretching() -> bool:
return _is_stretching
2025-12-31 16:24:11 +08:00
# =================
# Update
# =================
2025-12-31 13:07:31 +08:00
func _physics_process(delta: float) -> void:
if _is_stretching:
_update_stretching(delta)
2025-12-31 16:24:11 +08:00
func _process(_delta: float) -> void:
_update_line()
# =================
# Core Logic
# =================
2025-12-31 13:07:31 +08:00
func _update_stretching(delta: float) -> void:
2025-12-31 16:24:11 +08:00
# 先嘗試推進
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)
2025-12-31 13:07:31 +08:00
return
2025-12-31 16:24:11 +08:00
# ===== 沒命中,才正式推進 =====
_current_length = next_length
# 取消邏輯
if _cached_cancel and _current_length >= min_length:
stretching_finished.emit(true, null)
2025-12-31 13:07:31 +08:00
end_stretching(true)
return
2025-12-31 16:24:11 +08:00
# 超過最大距離
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
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
ray.target_position = to_local(hit_pos)
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
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
2025-12-31 13:07:31 +08:00
if _anchor and is_instance_valid(_anchor):
2025-12-31 16:24:11 +08:00
# 先脫離父節點,避免殘留引用問題
_anchor.get_parent().remove_child(_anchor)
2025-12-31 13:07:31 +08:00
_anchor.queue_free()
2025-12-31 16:24:11 +08:00
_anchor = null
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
# 3. 重置 Ray 與 Line視覺清乾淨
ray.target_position = Vector2.ZERO
_update_line()
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
func _update_line() -> void:
line_2d.set_point_position(0, Vector2.ZERO)
line_2d.set_point_position(1, ray.target_position)
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
func _create_anchor_on_node(target: Node2D, hit_global_pos: Vector2) -> Node2D:
if _anchor and is_instance_valid(_anchor):
_anchor.queue_free()
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
_anchor = Node2D.new()
_anchor.name = "Anchor"
target.add_child(_anchor)
2025-12-31 13:07:31 +08:00
2025-12-31 16:24:11 +08:00
# 關鍵:固定命中點偏移
_anchor.position = target.to_local(hit_global_pos)
2025-12-31 13:07:31 +08:00
return _anchor
2026-01-06 23:18:36 +08:00
## 计算方向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