# 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