Files
mtgodot-poc/project/player_move_test.gd
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

196 lines
7.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# player_move_test —— §3.1 Src/Dst 移动模型(player_controller.gdheadless 自检。
# 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")
const GameCam = preload("res://game_camera.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) CanSkipCollisionskip_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
# 8) 相机相对移动:W / 摇杆上 = 远离相机(沿视线),D / 摇杆右 = 屏幕右。
# GameCamera 摆在 head + (sin yaw, ·, cos yaw)*dist 并看向 head,前方取真实相机几何。
var cam: Camera3D = GameCam.new()
get_root().add_child(cam)
cam.target = pc.player
cam.yaw = deg_to_rad(30.0)
pc.camera = cam
pc.player.rotation.y = 0.0
var off: Vector3 = cam._desired_pos(true) - (pc.player.global_position + cam.head_offset)
var away := -Vector3(off.x, 0, off.z).normalized()
var screen_right := away.cross(Vector3.UP)
for c in [["mobile axis up", Vector2(0, -1), away], ["mobile axis right", Vector2(1, 0), screen_right]]:
pc.player.position = Vector3.ZERO
pc._is_going = false
pc.set_mobile_axis(c[1])
pc._process(0.1)
pc.clear_mobile_input()
var moved := Vector3(pc.player.position.x, 0, pc.player.position.z)
_ck(moved.length() > 0.05 and moved.normalized().dot(c[2]) > 0.95,
"%s moves camera-relative (want %s, moved %s)" % [c[0], c[2], moved])
for c in [[KEY_W, "W", away], [KEY_S, "S", -away], [KEY_D, "D", screen_right]]:
pc.player.position = Vector3.ZERO
pc._is_going = false
var down := InputEventKey.new()
down.keycode = c[0]
down.physical_keycode = c[0]
down.pressed = true
Input.parse_input_event(down)
Input.flush_buffered_events()
_ck(Input.is_key_pressed(c[0]), "synthetic %s press reaches Input" % c[1])
pc._process(0.1)
var up := down.duplicate()
up.pressed = false
Input.parse_input_event(up)
Input.flush_buffered_events()
var moved := Vector3(pc.player.position.x, 0, pc.player.position.z)
_ck(moved.length() > 0.05 and moved.normalized().dot(c[2]) > 0.95,
"key %s moves camera-relative (want %s, moved %s)" % [c[1], c[2], moved])
# 9) __OnClickSmart fallback:点击到既不是实体、掉落物也不是地面的空白处
# 必须 NEW_Stop,不能让上一次点地目标继续驱动角色。
pc._goto(Vector3(8, 0, 0))
_ck(pc.is_going(), "click-move is active before empty-space click")
pc.world = null # no ground hit -> __OnClickSmart's NEW_Stop branch
pc._on_click(Vector2.ZERO)
_ck(not pc.is_going(), "empty-space click stops the previous click-move")
pc.camera = null
cam.free()