Files
agame/tests/test_carry.gd
T
2026-10-03 18:04:07 +08:00

153 lines
6.6 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.
@tool
extends McpTestSuite
## 回归测试:抱起物品系统的契约。
##
## 谁能被抱由**物品自己**决定(挂 carryable.gd + 进 carryable 组),玩家只管找/抱/扔;
## 抱持偏移和投掷速度也写在物品上。这里盯住这几条契约,改物品或改玩家时先让测试红一次。
const PLAYER_SCENE_PATH := "res://character/player/player.tscn"
const ITEM_SCENE_PATH := "res://world/items/carryable_box.tscn"
const CARRY_ACTION := "carry"
const THROW_ACTION := "throw_carry"
func suite_name() -> String:
return "carry"
## 编辑器进程里的 InputMap 只有内建动作,项目动作要手动 load 一次才在
func _ensure_input_map() -> void:
InputMap.load_from_project_settings()
## 把节点塞进场景树,让 _ready() 真的跑起来;track() 负责回收
func _into_tree(node: Node) -> Node:
var editor := Engine.get_singleton("EditorInterface")
var root: Node = null
if editor != null:
root = editor.get_edited_scene_root()
if root == null:
root = Engine.get_main_loop().root
root.add_child(node)
track(node)
return node
func test_carry_actions_exist() -> void:
_ensure_input_map()
# 动作没了按键就完全没反应,而且不报错 —— 这种静默失效必须由测试兜住
assert_true(InputMap.has_action(CARRY_ACTION), "缺少 %s 动作,左键抱不起来" % CARRY_ACTION)
assert_true(InputMap.has_action(THROW_ACTION), "缺少 %s 动作,长按 Q 扔不出去" % THROW_ACTION)
func test_player_scene_has_carry_nodes() -> void:
var packed := load(PLAYER_SCENE_PATH) as PackedScene
assert_true(packed != null, "%s 未能加载" % PLAYER_SCENE_PATH)
if packed == null:
return
var player := packed.instantiate()
track(player)
var socket := player.get_node_or_null("CameraRig/CarrySocket")
assert_true(socket is Marker3D, "CameraRig 下缺少 CarrySocket(Marker3D)—— 物品没有抱持点")
var arms := player.get_node_or_null("Body/CarryArms")
assert_true(arms != null, "Body 下缺少 CarryArms 节点")
if arms != null:
assert_true(arms.get_script() != null and str(arms.get_script().resource_path).ends_with("carry_arms.gd"),
"Body/CarryArms 没挂 carry_arms.gd,胳膊不会抱东西")
func test_example_item_is_carryable() -> void:
var packed := load(ITEM_SCENE_PATH) as PackedScene
assert_true(packed != null, "%s 未能加载 —— 可抱物品的示例没了" % ITEM_SCENE_PATH)
if packed == null:
return
var item := packed.instantiate()
assert_true(item is Carryable, "示例物品根节点不是 Carryable(%s)" % item.get_class())
if not item is Carryable:
track(item)
return
var carryable := item as Carryable
_into_tree(carryable)
assert_true(carryable.is_in_group(Carryable.GROUP),
"可抱物品没进 %s 组,玩家就找不到它" % Carryable.GROUP)
assert_true(carryable.get_node_or_null("Collision") is CollisionShape3D,
"示例物品没有碰撞体,扔出去会穿地")
func test_carry_freezes_body_and_restores_on_release() -> void:
var item := _into_tree((load(ITEM_SCENE_PATH) as PackedScene).instantiate()) as Carryable
assert_true(item != null, "示例物品实例化失败")
if item == null:
return
var layer := item.collision_layer
assert_true(item.can_be_carried(), "新物品应该是可以抱的")
var holder := Node3D.new()
track(holder)
item.begin_carry(holder)
assert_true(not item.can_be_carried(), "抱起来之后不该还能被别人抱走")
assert_true(item.freeze, "抱起来要冻结物理,否则会和抱着它的人打架")
assert_eq(item.collision_layer, 0, "抱起来要清掉碰撞层,否则物品会把玩家顶飞")
item.end_carry(Vector3(1.0, 2.0, 3.0))
assert_true(item.can_be_carried(), "放下之后应该又能被抱")
assert_true(not item.freeze, "放下要恢复物理")
assert_eq(item.collision_layer, layer, "放下要恢复原来的碰撞层")
assert_true(item.linear_velocity.is_equal_approx(Vector3(1.0, 2.0, 3.0)),
"放下时要继承扔出去的速度,实际 %s" % item.linear_velocity)
func test_hold_transform_snaps_to_socket() -> void:
var item := _into_tree((load(ITEM_SCENE_PATH) as PackedScene).instantiate()) as Carryable
if item == null:
return
var socket := Marker3D.new()
track(socket)
socket.position = Vector3(1.0, 2.0, -3.0)
socket.rotation = Vector3(0.0, PI * 0.5, 0.0)
var root: Node = Engine.get_main_loop().root
root.add_child(socket)
item.apply_hold_transform(socket)
var expected := socket.global_position + socket.global_basis * item.hold_offset
assert_true(item.global_position.distance_to(expected) < 0.001,
"物品没摆到抱持点上:实际 %s,期望 %s" % [item.global_position, expected])
func test_throw_speed_scales_with_charge() -> void:
var item := _into_tree((load(ITEM_SCENE_PATH) as PackedScene).instantiate()) as Carryable
if item == null:
return
assert_true(absf(item.throw_speed(0.0) - item.throw_speed_min) < 0.001,
"蓄力为 0 时应该是最低出手速度")
assert_true(absf(item.throw_speed(1.0) - item.throw_speed_max) < 0.001,
"蓄满时应该是最高出手速度")
assert_true(item.throw_speed(0.5) > item.throw_speed_min and item.throw_speed(0.5) < item.throw_speed_max,
"出手速度应该随蓄力单调变化")
assert_true(item.throw_speed(9.0) <= item.throw_speed_max, "蓄力超出范围要被夹住")
## 抱起姿势必须真的把胳膊伸到身体**前方**(模型正面是本地 +Z,尾巴在 -Z 那一侧)。
## 参数写反或者骨骼名写错时,这里会红 —— 光看代码看不出胳膊朝哪边伸。
func test_carry_pose_reaches_forward() -> void:
var packed := load(PLAYER_SCENE_PATH) as PackedScene
if packed == null:
return
var player := _into_tree(packed.instantiate())
var arms := player.get_node_or_null("Body/CarryArms") as CarryArms
var skeleton := player.get_node_or_null("Body/mouse/MouseRig/Skeleton3D") as Skeleton3D
assert_true(arms != null and skeleton != null, "玩家场景里缺 CarryArms 或骨架")
if arms == null or skeleton == null:
return
var hand := skeleton.find_bone("arm_l_03")
assert_true(hand >= 0, "骨架里没有 arm_l_03,胳膊姿势会不完整")
if hand < 0:
return
var before := skeleton.get_bone_global_pose(hand).origin
arms.set_carrying(true, 0.0)
# 直接推进两次让它混合到位(测试里没有引擎帧)
arms._process(1.0)
arms._process(1.0)
var after := skeleton.get_bone_global_pose(hand).origin
assert_true(after.z > before.z + 0.5,
"抱起来时手要伸到身体前方:抱之前 z=%.2f,抱之后 z=%.2f" % [before.z, after.z])
assert_true(after.y > before.y,
"抱起来时手要抬到身前的高度:抱之前 y=%.2f,抱之后 y=%.2f" % [before.y, after.y])