145 lines
5.6 KiB
GDScript
145 lines
5.6 KiB
GDScript
# player_move_test —— §3.1 Src/Dst 移动模型(player_controller.gd)headless 自检。
|
||
# godot --headless --path project --script player_move_test.gd
|
||
# 覆盖:NEW_Goto 设 Src/Dst/is_going、__IsMovableGroundDistance 太近不动、
|
||
# 三门(frozen/lock/moving_skill)、__ReserveClickGround 的 0.1s delay 重试。
|
||
extends SceneTree
|
||
|
||
const PlayerCtl = preload("res://player_controller.gd")
|
||
|
||
var _fail := 0
|
||
func _ck(c: bool, m: String) -> void:
|
||
if not c:
|
||
_fail += 1
|
||
printerr("FAIL: " + m)
|
||
|
||
func _init() -> void:
|
||
await _run()
|
||
if _fail == 0:
|
||
print("PASS: player_move_test (Src/Dst move model / gates / reserved ground)")
|
||
quit(0)
|
||
else:
|
||
printerr("%d check(s) failed" % _fail)
|
||
quit(1)
|
||
|
||
func _run() -> void:
|
||
# SceneTree._init() 早于 root 就绪;等一帧再建 Node3D,避免 !is_inside_tree()。
|
||
await process_frame
|
||
var pc: Node = PlayerCtl.new()
|
||
get_root().add_child(pc)
|
||
pc.player = Node3D.new()
|
||
get_root().add_child(pc.player)
|
||
pc.player.position = Vector3.ZERO
|
||
|
||
# 1) NEW_Goto:远点 -> 设 Dst + is_going + 面向
|
||
var ok: bool = pc._goto(Vector3(10, 0, 0))
|
||
_ck(ok, "_goto(far) returns true")
|
||
_ck(pc.is_going(), "_goto -> is_going()")
|
||
_ck(pc._dst_pos.is_equal_approx(Vector3(10, 0, 0)), "Dst set to click point")
|
||
_ck(pc._src_pos.is_equal_approx(Vector3.ZERO), "Src set to current pos")
|
||
_ck(is_equal_approx(pc._dst_rot, atan2(10.0, 0.0)), "_dst_rot faces the target")
|
||
|
||
# 2) __IsMovableGroundDistance:太近(< 1 m)不动
|
||
pc._is_going = false
|
||
var near_ok: bool = pc._goto(Vector3(0.5, 0, 0))
|
||
_ck(near_ok, "_goto(near) returns true (treated as done)")
|
||
_ck(not pc.is_going(), "within MOVABLE_GROUND_DISTANCE -> no movement")
|
||
|
||
# 3) 每帧推进:朝 Dst 走,抵达后 is_going 归零
|
||
pc._goto(Vector3(3, 0, 0))
|
||
for i in 240:
|
||
pc._process(0.05)
|
||
if not pc.is_going():
|
||
break
|
||
_ck(pc.player.position.distance_to(Vector3(3, 0, 0)) < 0.5, "advances to Dst, got %s" % pc.player.position)
|
||
_ck(not pc.is_going(), "arrival clears is_going")
|
||
|
||
# 4) 三门:frozen / lock -> _goto 被挡 -> 预约 + 0.1s 后重试
|
||
pc.player.position = Vector3.ZERO
|
||
pc._is_going = false
|
||
pc.frozen = true
|
||
var blocked: bool = pc._goto(Vector3(8, 0, 0))
|
||
_ck(not blocked, "_goto while frozen returns false (gated)")
|
||
_ck(not pc.is_going(), "frozen -> not going yet")
|
||
_ck(pc._reserved_ground != null, "frozen -> click ground reserved")
|
||
# 冻结中推进:不平移、不消预约
|
||
pc._process(0.05)
|
||
_ck(pc.player.position.is_equal_approx(Vector3.ZERO), "frozen -> no translation")
|
||
# 解冻:delay 递减到 0 后自动重试预约点
|
||
pc.frozen = false
|
||
for i in 10:
|
||
pc._process(0.02)
|
||
_ck(pc.is_going() or pc.player.position.x > 0.1,
|
||
"reserved ground fires after delay once unfrozen (going=%s x=%.2f)" % [pc.is_going(), pc.player.position.x])
|
||
|
||
# 5) moving_skill:只转向不平移
|
||
pc.player.position = Vector3.ZERO
|
||
pc._is_going = false
|
||
pc.moving_skill = true
|
||
pc._goto(Vector3(0, 0, 6))
|
||
_ck(not pc.is_going(), "_goto while moving_skill is gated (no translate)")
|
||
pc.moving_skill = false
|
||
# 清掉第 5 组留下的预约点,免得下面各组被它改向。
|
||
pc._reserved_ground = null
|
||
pc._reserved_delay_time = 0.0
|
||
|
||
# 6) Actor-Actor 碰撞(TestActorCollision / BlockMovement 近似)——正前方有远端 Actor
|
||
# 时走不过去,停在两 body 球相接前;is_going 归零(BlockMovement = 不滑移硬停)。
|
||
pc.player.position = Vector3.ZERO
|
||
pc.player.rotation.y = 0.0
|
||
pc._is_going = false
|
||
pc.skip_actor_collision = false
|
||
var blocker := Node3D.new()
|
||
get_root().add_child(blocker)
|
||
blocker.global_position = Vector3(2, 0, 0) # 正前方 2 m
|
||
pc.pickables.append(blocker)
|
||
pc._goto(Vector3(10, 0, 0)) # 想穿过 blocker
|
||
for i in 240:
|
||
pc._process(0.05)
|
||
if not pc.is_going():
|
||
break
|
||
_ck(pc.player.position.x > 0.3 and pc.player.position.x < 1.5,
|
||
"actor collision halts before overlap (x=%.2f)" % pc.player.position.x)
|
||
_ck(not pc.is_going(), "blocked advance clears is_going (BlockMovement)")
|
||
|
||
# 6b) 死亡的 Actor 不挡(rVictim.IsDead())
|
||
pc.player.position = Vector3.ZERO
|
||
pc._is_going = false
|
||
blocker.set_meta("dead", true)
|
||
pc._goto(Vector3(10, 0, 0))
|
||
for i in 400:
|
||
pc._process(0.05)
|
||
if not pc.is_going():
|
||
break
|
||
_ck(pc.player.position.distance_to(Vector3(10, 0, 0)) < 0.6,
|
||
"dead actor does not block (x=%.2f)" % pc.player.position.x)
|
||
blocker.set_meta("dead", false)
|
||
|
||
# 6c) CanSkipCollision(skip_actor_collision)为真 → 整段跳过,可穿过
|
||
pc.player.position = Vector3.ZERO
|
||
pc._is_going = false
|
||
pc.skip_actor_collision = true
|
||
pc._goto(Vector3(10, 0, 0))
|
||
for i in 400:
|
||
pc._process(0.05)
|
||
if not pc.is_going():
|
||
break
|
||
_ck(pc.player.position.distance_to(Vector3(10, 0, 0)) < 0.6,
|
||
"skip_actor_collision passes straight through (x=%.2f)" % pc.player.position.x)
|
||
pc.skip_actor_collision = false
|
||
pc.pickables.erase(blocker)
|
||
blocker.free()
|
||
|
||
# 7) 坐骑转向速度:MountHorse → c_fDefaultHorseRotationSpeed(300),逐帧收敛不瞬转
|
||
_ck(is_equal_approx(pc.rotation_speed_deg, pc.ROT_SPEED_DEFAULT_DEG),
|
||
"default rotation speed is c_fDefaultRotationSpeed 1200")
|
||
pc.rotation_speed_deg = pc.ROT_SPEED_HORSE_DEG
|
||
pc.player.position = Vector3.ZERO
|
||
pc.player.rotation.y = 0.0
|
||
pc._is_going = false
|
||
pc._goto(Vector3(0, 0, -6)) # 需要转到 yaw ≈ ±PI
|
||
pc._process(0.05)
|
||
_ck(absf(pc.player.rotation.y) <= deg_to_rad(pc.ROT_SPEED_HORSE_DEG) * 0.05 + 1e-3,
|
||
"mounted: turn clamped to 300 deg/s in one 50ms step (%.3f rad)" % pc.player.rotation.y)
|
||
_ck(pc.player.position.z < -0.05, "mounted: still translates toward Dst while turning")
|
||
pc.rotation_speed_deg = pc.ROT_SPEED_DEFAULT_DEG
|