godot-plateformer/addons/reedcomponent/locomotion/wall_detector.gd

123 lines
4.2 KiB
GDScript3
Raw Normal View History

2025-12-29 11:54:31 +08:00
class_name WallDetector extends Node2D
@onready var up_r: RayCast2D = $UpRayCast2D
@onready var mid_r: RayCast2D = $MidRayCast2D
@onready var down_r: RayCast2D = $DownRayCast2D
var flip_h: bool = false:
set(value):
if flip_h == value:
return
else:
flip_h = value
flip(flip_h)
return
signal wall_state_changed(enter_wall: bool)
signal up_ray_state_changed(enter_wall: bool)
signal mid_ray_state_changed(enter_wall: bool)
signal down_ray_state_changed(enter_wall: bool)
var is_on_wall: bool = false : set = _change_wall_state
var up_ray_on_wall: bool = false:
set(value):
if up_ray_on_wall == value:
return
else:
up_ray_on_wall = value
up_ray_state_changed.emit(value)
var mid_ray_on_wall: bool = false:
set(value):
if mid_ray_on_wall == value:
return
else:
mid_ray_on_wall = value
mid_ray_state_changed.emit(value)
var down_ray_on_wall: bool = false:
set(value):
if down_ray_on_wall == value:
return
else:
down_ray_on_wall = value
down_ray_state_changed.emit(value)
func _physics_process(delta: float) -> void:
var n1 = up_r.get_collision_normal() as Vector2
var n2 = mid_r.get_collision_normal() as Vector2
var n3 = down_r.get_collision_normal() as Vector2
##更新其他的射线的OnWall状态
up_ray_on_wall = up_r.is_colliding() and is_nearly_vertical_plane(n1)
mid_ray_on_wall = mid_r.is_colliding() and is_nearly_vertical_plane(n2)
down_ray_on_wall = down_r.is_colliding() and is_nearly_vertical_plane(n3)
var all_collid = up_r.is_colliding() and mid_r.is_colliding() and down_r.is_colliding() as bool
var vertical_plane = is_nearly_vertical_plane(n1) and is_nearly_vertical_plane(n2) and is_nearly_vertical_plane(n3) as bool
is_on_wall = all_collid and vertical_plane
##修改wall state的函数如果需要改就在这里改
func _change_wall_state(inis_on_wall: bool) -> void:
if inis_on_wall: #如果在wall上的状态为真则检测现在的状态是不是在wall上如果不是则发送状态改变
if is_on_wall:
return
else:
wall_state_changed.emit(true)
is_on_wall = true
else: #如果在wall上的状态为false则检测现在的状态是不是不在wall上如果不是则发送状态改变
if not is_on_wall:
return
else:
wall_state_changed.emit(false)
is_on_wall = false
##修改ray state的函数如果需要改就在这里改
func _change_ray_state(inis_on_wall: bool,field_name: StringName,signal_name:StringName) -> void:
var current_state = get(field_name)
if inis_on_wall:
if current_state: return
else:
emit_signal(signal_name,true)
set(field_name,true)
else:
if not current_state: return
else:
emit_signal(signal_name,false)
set(field_name,false)
func flip(flip: bool) -> void:
var r = Vector2(-1,1) if flip else Vector2(1,1)
up_r.scale = r
mid_r.scale = r
down_r.scale = r
##Helper函数判断是否是几乎垂直的面
func is_nearly_vertical_plane(normal: Vector2) -> bool:
# 定义一个容忍度/阈值,例如 0.1 (表示角度偏离 90 度最多约 5.7 度)
# 90 度的法线夹角意味着点积为 0。
# 我们要找的是法线接近水平的情况,即法线与 Y 轴(垂直)的点积接近 0。
# 垂直向量 (Y 轴)
var world_up = Vector2.UP # (0, -1) 或 Vector2.DOWN (0, 1),取决于你的坐标系
# 为了代码健壮性,我们检查法线与垂直方向的点积的绝对值是否小于一个很小的阈值。
# 点积接近 0 => 向量(法线)和垂直向量接近正交(即法线接近水平)。
var dot_product_with_vertical = abs(normal.dot(world_up))
# 设定一个阈值 (epsilon),例如 0.1
# 如果点积的绝对值很小(接近 0说明法线与世界Y轴接近垂直即平面接近水平
# **更正:如果点积接近 0说明法线与Y轴垂直即平面是水平的。**
# 我们要找的是“几乎垂直的平面”,这意味着法线接近水平方向 (X 轴)
var world_right = Vector2.RIGHT # (1, 0)
var dot_product_with_horizontal = abs(normal.dot(world_right))
const VERTICAL_PLANE_THRESHOLD = 0.9 # 阈值设为接近 1
# 如果法线与水平向量的点积绝对值很大(接近 1则平面接近垂直
return dot_product_with_horizontal >= VERTICAL_PLANE_THRESHOLD