From 8e463d924760ae923f6792c8a9b6d6ef15a7f40d Mon Sep 17 00:00:00 2001 From: yvmou_ Date: Thu, 8 Oct 2026 17:21:44 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E7=BB=B3=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- world/zipline/zipline.gd | 180 ----------------------------------- world/zipline/zipline.gd.uid | 1 - world/zipline/zipline.tscn | 12 --- 3 files changed, 193 deletions(-) delete mode 100644 world/zipline/zipline.gd delete mode 100644 world/zipline/zipline.gd.uid delete mode 100644 world/zipline/zipline.tscn diff --git a/world/zipline/zipline.gd b/world/zipline/zipline.gd deleted file mode 100644 index 5f6a0bb..0000000 --- a/world/zipline/zipline.gd +++ /dev/null @@ -1,180 +0,0 @@ -@tool -extends Node3D -class_name Zipline -## 滑索(Zipline):两点之间一条会下垂的钢缆。玩家走到缆下按 E 挂上, -## 沿缆滑向较低的那一端,中途可按 S 刹车、按空格主动跳离。 -## -## 场景结构(res://world/zipline/zipline.tscn): -## Zipline (Node3D, 本脚本) -## ├─ AnchorA (Marker3D) ← 高处起点,在视口里直接拖 -## └─ AnchorB (Marker3D) ← 低处终点 -## 缆绳网格是本脚本在 _ready() 里生成的一个 MeshInstance3D 子节点, -## 故意不给 owner,所以不会被写进 .tscn —— 它完全由两个锚点推导出来, -## 不存在「文件里的网格和锚点对不上」这种脏数据。 -## -## 手感参数(速度/刹车/吸附半径)放在「缆」这一侧而不是玩家身上: -## 同一张关卡里可以有快缆和慢缆,玩家只负责读参数,不负责定义手感。 -## -## 曲线模型:两点连线再减去一个抛物线形下垂量 -## P(t) = lerp(A, B, t) - UP * sag * 4t(1-t) -## 权重 4t(1-t) 在 t=0/1 处为 0、t=0.5 处为 1,所以锚点高度就是真实挂点高度、 -## sag 就是中点下垂深度 —— 调参所见即所得,不用去解悬链线方程。 -## -## 约束:本节点不要旋转/缩放,靠拖动两个锚点摆位。 - -## 所有滑索都进这个组,玩家靠它找缆 -const GROUP: StringName = &"zipline" -## 缆绳截面的边数。缆很细,6 边足够,还省顶点 -const RING_SEGMENTS: int = 6 - -@export_group("缆绳") -## 中点下垂深度(米)。0 = 绷直。真实滑索是绷紧的,所以别调太大 -@export_range(0.0, 12.0, 0.05) var sag: float = 1.2 -## 沿缆采样段数。越高越圆滑,也越费顶点 -@export_range(8, 128, 1) var segments: int = 48 -## 缆绳半径(米) -@export_range(0.01, 0.3, 0.005) var radius: float = 0.05 -## 缆绳材质。留空则用内置的深色金属 -@export var cable_material: Material - -@export_group("乘坐") -## 人挂在缆下方多深(米)。约等于「双手举过头顶抓住缆」的高度 -@export var hang_offset: float = 1.15 -## 玩家离缆多近能按 E 挂上(米)。给得宽一点:缆悬在头顶时也算「够得着」 -@export var attach_radius: float = 4.0 -## 沿缆最高速度(米/秒) -@export var max_speed: float = 18.0 -## 重力沿缆方向的缩放。1 = 完全按坡度加速,调小则「黏」一些 -@export var gravity_scale: float = 1.0 -## 按住后退键时的刹车减速度(米/秒²) -@export var brake_accel: float = 14.0 -## 剩余行程短于此值就不让挂(米)。否则刚挂上就到头,像被弹开 -@export var min_ride: float = 3.0 - -@onready var anchor_a: Marker3D = $AnchorA -@onready var anchor_b: Marker3D = $AnchorB - -## 缆绳网格节点,_ready() 里现造 -var _cable: MeshInstance3D = null -## 缆绳总长(米)。生成网格时顺带算出来,玩家用它把速度换算成 t 的推进量 -var length: float = 0.0 -## 编辑器里用来比对「锚点/参数变了没有」,避免每帧重建网格 -var _editor_sig: String = "" - - -func _ready() -> void: - add_to_group(GROUP) - _cable = MeshInstance3D.new() - _cable.name = "Cable" - add_child(_cable) - _rebuild_cable() - # 编辑器下开着 _process,拖动锚点能立刻看到缆绳跟着变;运行时不需要 - set_process(Engine.is_editor_hint()) - - -func _process(_delta: float) -> void: - if anchor_a == null or anchor_b == null: - return - # 只在签名变化时重建 —— SurfaceTool 生成网格不便宜,不能每帧做 - var sig := "%s|%s|%.3f|%d|%.4f" % [anchor_a.position, anchor_b.position, sag, segments, radius] - if sig == _editor_sig: - return - _editor_sig = sig - _rebuild_cable() - - -# ---------------- 对外接口(玩家控制器只读这几个) ---------------- - -## 缆上参数 t∈[0,1] 处的世界坐标。 -## 曲线先在**本地空间**算完再转世界:sag 是本地单位,父节点一有缩放整条缆要跟着一起缩, -## 形状才不走样(混用 global 锚点 + 未缩放的 sag,下垂比例会随父节点缩放而变)。 -func sample(t: float) -> Vector3: - return to_global(_curve_local(t)) - - -## 缆上参数 t 处的世界方向(单位向量,指向 B)。 -## 用解析导数而不是前后差分:差分在 t=0/1 处要越界取样,端点会偏。 -func tangent(t: float) -> Vector3: - var d := (anchor_b.position - anchor_a.position) - Vector3.UP * (sag * 4.0 * (1.0 - 2.0 * t)) - return (global_transform.basis * d).normalized() - - -## 乘坐时人应该待的世界坐标:缆点再往下挂 hang_offset。 -func ride_point(t: float) -> Vector3: - return to_global(_curve_local(t) - Vector3.UP * hang_offset) - - -## 找出世界坐标 pos 在缆上最近的一点,供玩家「就近吸附」。 -## 返回 { t, point, distance, dir, remaining }: -## dir +1 表示往 B 滑、-1 表示往 A 滑(永远是往低的那端) -## remaining 沿 dir 方向还剩多少米 -func closest_point(pos: Vector3) -> Dictionary: - var best_t := 0.0 - var best_d := INF - for i in range(segments + 1): - var t := float(i) / float(segments) - var d := sample(t).distance_squared_to(pos) - if d < best_d: - best_d = d - best_t = t - var dir := _downhill_dir() - return { - "t": best_t, - "point": sample(best_t), - "distance": sqrt(best_d), - "dir": dir, - "remaining": absf((1.0 if dir > 0 else 0.0) - best_t) * length, - } - - -# ---------------- 内部 ---------------- - -## 低的那一端才是出口 —— 滑索只会往下滑。两端等高时退回 A→B。 -func _downhill_dir() -> int: - return 1 if anchor_b.global_position.y <= anchor_a.global_position.y else -1 - - -## 下垂曲线在**本节点局部空间**的点。sag 是本地单位,所以父节点缩放时整条缆一起缩、形状不变。 -func _curve_local(t: float) -> Vector3: - return anchor_a.position.lerp(anchor_b.position, t) - Vector3.UP * (sag * 4.0 * t * (1.0 - t)) - - -## 把下垂曲线扫成一根管子。用三角形带:每个截面 RING_SEGMENTS+1 个顶点, -## 多出来的那个和首顶点重合,接缝处法线才连续、不会裂开一条缝。 -func _rebuild_cable() -> void: - if _cable == null or anchor_a == null or anchor_b == null: - return - var st := SurfaceTool.new() - st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP) - var prev := _curve_local(0.0) - length = 0.0 - for i in range(segments + 1): - var t := float(i) / float(segments) - var p := _curve_local(t) - if i > 0: - length += p.distance_to(prev) - prev = p - # 截面参考轴取「切线 × 世界上方向」。切线接近竖直时叉乘会退化成零向量, - # 这时换 RIGHT 兜底,否则 normalize() 会吐 NaN 把整根缆搅烂。 - var tan_dir := (_curve_local(minf(t + 0.01, 1.0)) - _curve_local(maxf(t - 0.01, 0.0))).normalized() - var n := tan_dir.cross(Vector3.UP) - if n.length_squared() < 0.0001: - n = tan_dir.cross(Vector3.RIGHT) - n = n.normalized() - var binormal := n.cross(tan_dir).normalized() - for j in range(RING_SEGMENTS + 1): - var ang := TAU * float(j) / float(RING_SEGMENTS) - var off := n * cos(ang) + binormal * sin(ang) - st.set_normal(off) - st.add_vertex(p + off * radius) - _cable.mesh = st.commit() - var mat: Material = cable_material - if mat == null: - var std := StandardMaterial3D.new() - std.albedo_color = Color(0.14, 0.15, 0.18) - std.metallic = 0.85 - std.roughness = 0.3 - # 三角形带的绕序不保证,直接双面,省得算错朝向 - std.cull_mode = BaseMaterial3D.CULL_DISABLED - mat = std - _cable.material_override = mat diff --git a/world/zipline/zipline.gd.uid b/world/zipline/zipline.gd.uid deleted file mode 100644 index 8967632..0000000 --- a/world/zipline/zipline.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ddh4pqdknx8yf diff --git a/world/zipline/zipline.tscn b/world/zipline/zipline.tscn deleted file mode 100644 index 4b176fa..0000000 --- a/world/zipline/zipline.tscn +++ /dev/null @@ -1,12 +0,0 @@ -[gd_scene format=3 uid="uid://12qonfnowpas"] - -[ext_resource type="Script" uid="uid://ddh4pqdknx8yf" path="res://world/zipline/zipline.gd" id="1_pj3rq"] - -[node name="Zipline" type="Node3D" unique_id=982324784] -script = ExtResource("1_pj3rq") - -[node name="AnchorA" type="Marker3D" parent="." unique_id=267373356] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 8, -24) - -[node name="AnchorB" type="Marker3D" parent="." unique_id=1805183028] -transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 18)