Files
agame/tests/test_face.gd
T

133 lines
4.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.
@tool
extends McpTestSuite
## 回归测试:面部那套「UV 孤岛 + 预设 + 画笔」的契约。
##
## 脸是 body_mesh 的 surface 1,UV 是一个孤岛;shader 靠 face_uv_min / face_uv_size 把孤岛
## 重映射到 0~1,再拿这个 UV 去采样预设和笔画层。**模型重导后 UV 一变,脸就会画歪或者被裁掉,
## 而且不报任何错** —— 所以这里把数值钉死,改模型时先让测试红一次。
const PLAYER_SCENE_PATH := "res://character/player/player.tscn"
const SHADER_PATH := "res://character/player/face/face_paint.gdshader"
const PRESET_DIR := "res://character/player/face/presets"
const BODY_MESH_PATH := "Body/mouse/MouseRig/Skeleton3D/body_mesh"
## FaceRig 里 face_uv_min / face_uv_size 的默认值,必须和 shader 的 uniform 默认值一致
const FACE_UV_MIN := Vector2(0.142, 0.031)
const FACE_UV_SIZE := 0.214
## UV 是浮点,两边各写一份,给点容差
const EPS := 0.003
func suite_name() -> String:
return "face"
func _player() -> Node:
var packed := load(PLAYER_SCENE_PATH) as PackedScene
assert_true(packed != null, "%s 未能加载" % PLAYER_SCENE_PATH)
if packed == null:
return null
var player := packed.instantiate()
track(player)
return player
func _body_mesh() -> MeshInstance3D:
var player := _player()
if player == null:
return null
return player.get_node_or_null(BODY_MESH_PATH) as MeshInstance3D
func test_player_has_face_rig_wired() -> void:
var player := _player()
if player == null:
return
var rig := player.get_node_or_null("FaceRig") as FaceRig
assert_true(rig != null, "Player 下缺少 FaceRig 节点")
if rig == null:
return
assert_eq(rig.surface_index, 1, "面部是 surface 1(名字叫 face),索引写错就画到身体上了")
assert_true(rig.get_node_or_null(rig.mesh_path) is MeshInstance3D,
"FaceRig.mesh_path(%s)解析不到 MeshInstance3D —— 换模型后路径要跟着改" % rig.mesh_path)
func test_face_surface_exists() -> void:
var mi := _body_mesh()
assert_true(mi != null, "找不到 %s" % BODY_MESH_PATH)
if mi == null:
return
var m := mi.mesh as ArrayMesh
assert_true(m != null, "body_mesh 的 mesh 不是 ArrayMesh")
if m == null:
return
assert_gt(m.get_surface_count(), 1, "body_mesh 至少要有两个 surface(0 身体 / 1 脸)")
if m.get_surface_count() < 2:
return
assert_eq(m.surface_get_name(1), "face", "surface 1 不叫 face 了 —— 确认过再改 surface_index")
func test_face_uv_island_matches_shader() -> void:
var mi := _body_mesh()
if mi == null:
return
var m := mi.mesh as ArrayMesh
if m == null or m.get_surface_count() < 2:
return
var uv: PackedVector2Array = m.surface_get_arrays(1)[Mesh.ARRAY_TEX_UV]
var mn := Vector2(INF, INF)
var mx := Vector2(-INF, -INF)
for v in uv:
mn = mn.min(v)
mx = mx.max(v)
var far := FACE_UV_MIN + Vector2(FACE_UV_SIZE, FACE_UV_SIZE)
# 孤岛必须整个落在 shader 重映射用的那个方框里,否则脸会错位 / 被裁掉
assert_true(mn.x >= FACE_UV_MIN.x - EPS and mn.y >= FACE_UV_MIN.y - EPS,
"面部 UV 起点 %s 跑出了 face_uv_min %s —— 模型重导后 UV 变了?" % [mn, FACE_UV_MIN])
assert_true(mx.x <= far.x + EPS and mx.y <= far.y + EPS,
"面部 UV 终点 %s 超出了 face_uv_min + face_uv_size = %s" % [mx, far])
func test_face_shader_has_expected_uniforms() -> void:
var sh: Shader = load(SHADER_PATH)
assert_true(sh != null, "%s 加载失败" % SHADER_PATH)
if sh == null:
return
var names: Array[String] = []
for p in sh.get_shader_uniform_list():
names.append(str(p.get("name")))
for required in ["face_uv_min", "face_uv_size", "base_tex", "paint_tex"]:
assert_true(names.has(required), "shader 少了 uniform %s,实际为 %s" % [required, names])
## 联机同步靠这三个方法名,改名了要一起改 —— 改了就不同步,而且不报错
func test_face_sync_methods_exist() -> void:
var script := load("res://character/player/face/face_rig.gd") as GDScript
assert_true(script != null, "face_rig.gd 加载失败")
if script == null:
return
var names: Array[String] = []
for m in script.get_script_method_list():
names.append(str(m.get("name")))
for required in ["_pull_remote_face", "_rpc_request_face", "_rpc_send_face"]:
assert_true(names.has(required), "face_rig.gd 少了 %s,联机时脸不会同步" % required)
func test_presets_are_square() -> void:
var dir := DirAccess.open(PRESET_DIR)
assert_true(dir != null, "打不开预设目录 %s" % PRESET_DIR)
if dir == null:
return
var found := 0
for f in dir.get_files():
if not f.ends_with(".png"):
continue
found += 1
var tex: Texture2D = load(PRESET_DIR + "/" + f)
assert_true(tex != null, "预设 %s 加载失败" % f)
if tex == null:
continue
var s := tex.get_size()
assert_eq(int(s.x), int(s.y), "预设 %s 不是正方形(%s),贴到脸上会拉伸" % [f, s])
assert_gt(found, 0, "%s 里一张预设都没有" % PRESET_DIR)