8.3 KiB
8.3 KiB
Tóm tắt nameplate world-space — các file liên quan
Tài liệu tóm tắt toàn bộ công việc cho neo tên / HUD trên đầu (world-space Canvas), tập trung vào NameplateWorldAnchor (MonoBehaviour) làm một chỗ chỉnh độ cao / neo 3D.
Danh sách file chính
| # | Đường dẫn | Vai trò |
|---|---|---|
| 1 | Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs |
MonoBehaviour: toàn bộ logic neo 3D (player + NPC/Monster); TryGetWorldPosition, CacheRefs, RefreshNpcTopBone. |
| 2 | Assets/PerfectWorld/Scripts/UI/UIPlayer.cs |
Player UI: LateUpdate gán canvasRoot.position mỗi frame; gọi NameplateWorldAnchor.TryGetWorldPosition. [RequireComponent(typeof(NameplateWorldAnchor))]. |
| 3 | Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs |
NPC/Monster UI: coroutine retry ở Start + LateUpdate mỗi frame sau khi coroutine hoàn tất; gọi NameplateWorldAnchor.TryGetWorldPosition. [RequireComponent(typeof(NameplateWorldAnchor))]. |
| 4 | Assets/Scripts/PlayerVisual.cs |
Player: TryGetNamePlateAnchorWorld — gộp bounds SkinnedMeshRenderer (dùng sharedMesh.bounds). |
| 5 | Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs |
NPC/Monster: TryGetNamePlateAnchorWorld (SMR gộp) + CacheTopBone / TryGetTopBoneWorld (bone cao nhất). |
| 6 | Assets/PerfectWorld/Scripts/Objet/CECMatter.cs |
Matter (item/mine): neo tên bằng TryGetCombinedRendererBounds (gộp mọi Renderer). Không dùng NameplateWorldAnchor. |
Prefab: GameObject "UIPlayer" bên trong PlayerPrefab, MonsterPrefab, NPCServer.prefab đã có cả UIPlayer.cs / UINPC.cs lẫn NameplateWorldAnchor được serialize sẵn.
1. NameplateWorldAnchor.cs (MonoBehaviour)
Gắn trên object chứa UIPlayer hoặc UINPC (cùng transform hoặc cha).
CacheRefs: tự tìmCECPlayer/CECNPC/PlayerVisual/NPCVisualtừ hierarchy nếu chưa gán.RefreshNpcTopBone: gọinpcVisual.CacheTopBone()— trigger scan xương sau khi model sẵn sàng.TryGetWorldPosition(out Vector3 worldPos):- Có
CECPlayer→ nhánh player:- Hook xương
HH_Head(useHeadSkeletonHook,headHookName,headHookWorldOffset). PlayerVisual.TryGetNamePlateAnchorWorld(SMR bounds).- Fallback đỉnh
m_aabb+ male offset +extraWorldYOffset.
- Hook xương
- Có
CECNPC→ nhánh NPC/Monster (ưu tiên theo thứ tự):- Hook xương (
useNpcHeadHook):hostNpc.GetHook("HH_Head")— theo animation mỗi frame. - Bone cao nhất (
NPCVisual.TryGetTopBoneWorld): bone đã cache từCacheTopBone. Dùng khi không cóHH_Head, giải quyết NPC/Monster bay/nổi (vd: 小星星). - SMR merged bounds (
NPCVisual.TryGetNamePlateAnchorWorld): rest-pose bounds. - Root fallback:
hostNpc.transform.position + up * fallbackHeightAboveRoot.
- Luôn cộng
extraWorldYOffset.
- Hook xương (
- Có
→ Một class duy nhất chỉnh độ cao / neo; UI chỉ đọc điểm world và đặt Canvas.
2. UIPlayer.cs
- Giữ reference
NameplateWorldAnchor(auto-find nếu null). LateUpdatemỗi frame:nameplateAnchor.TryGetWorldPosition→canvasRoot.position.- Các field hook / SMR / AABB / offset đã chuyển sang
NameplateWorldAnchortrên cùng prefab.
3. UINPC.cs
- Giữ
_canvasRootcho world HUD; mọi offset / fallback NPC nằm trênNameplateWorldAnchor. LateUpdatemỗi frame (thêm mới): chỉ chạy sau khi_initialNameplateRoutine == null(coroutine kết thúc); gọiTryGetWorldPosition→ApplyCanvasRootLocalPosition.
→ Cho phép nameplate theo animation (vd: xương đầu di chuyển khi nhân vật bay/nổi).Start()dùng coroutine defer/retry để chờ model/SMR load xong rồi set vị trí lần đầu.- Khi model load xong từ
CECNPC.QueueLoadNPCModel, gọiRefreshWorldNameplatePosition()để restart coroutine. - Khi coroutine xác nhận model sẵn sàng (SMR/hook/top-bone ready), gọi thêm
_nameplateAnchor.RefreshNpcTopBone()để cache xương cao nhất. - Set theo local position (convert từ world bằng
parent.InverseTransformPoint).
4. PlayerVisual.cs
TryGetNamePlateAnchorWorld: gộp bounds tất cảSkinnedMeshRenderercon.- Đọc
sharedMesh.bounds(mesh local space) +TransformPoint(center)+lossyScale→ world bounds thủ công.
Không dùngrenderer.bounds(có thể stale ngay sauSetActive(true)trong cùng frame). - Kết quả:
worldPos = (combinedBounds.center.x, combinedBounds.max.y, combinedBounds.center.z). debugNamePlateBoundslog tiền tố[Cuong].
5. NPCVisual.cs
TryGetNamePlateAnchorWorld: cùng thuật toán SMR gộp nhưPlayerVisual(dùngsharedMesh.bounds).CacheTopBone()(thêm mới):- Lấy tất cả
SkinnedMeshRenderercon → duyệtsmr.bones(dedup bằngHashSet<Transform>). - Tìm bone có
world.ycao nhất → lưu vào_cachedTopBone. - Log
[Cuong] [NPCVisual] CacheTopBonekhidebugNamePlateBounds = true.
- Lấy tất cả
TryGetTopBoneWorld(out Vector3)(thêm mới): trả_cachedTopBone.position;falsenếu chưa cache.debugNamePlateBoundslog tiền tố[Cuong].
6. CECMatter.cs
Nameplate cho item/mine drop. Không dùng NameplateWorldAnchor (Matter là mesh tĩnh, không có skeleton).
TryGetCombinedRendererBounds(matterRoot, excludeSubtree):- Duyệt tất cả
Renderercon (gồm cảMeshRenderervàSkinnedMeshRenderer). - Với
MeshRenderer: lấy mesh từMeshFilter.sharedMesh. - Với
SkinnedMeshRenderer: lấysmr.sharedMesh. - Cùng cách tính world bounds như
PlayerVisual/NPCVisual.
- Duyệt tất cả
TryGetItemNameAnchorLocal: gọiTryGetCombinedRendererBounds→ anchor tại(center.x, max.y + 0.05, center.z)→InverseTransformPoint→localPosition.- Fallback nếu không có renderer:
y = 0.6f+LogWarning [Cuong]. - BoxCollider khi spawn cũng dùng
combinedBounds.size+combinedBounds.center(thay vì chỉ lấy renderer đầu tiên).
Cập nhật phiên hiện tại (2026-05)
CECMatter — fix multi-mesh nameplate height
- Vấn đề: offset cố định
0.6fsai với model nhiều mesh (vd: 噬人花 có mesh_0cao +_1thấp → tên đè lên cây thấp). - Nguyên nhân sâu hơn:
renderer.boundsstale sauSetActive(true)cùng frame. - Fix:
TryGetCombinedRendererBoundsđọcsharedMesh.bounds+ transform thủ công, gộp mọiRenderer(cảMeshRenderer). Anchor tạicombinedBounds.max.y.
NameplateWorldAnchor — thêm NPC hook + cached top bone
- Thêm Priority 1 NPC:
hostNpc.GetHook("HH_Head")— dùng chungheadHookNamevới player. - Thêm Priority 2 NPC:
NPCVisual.TryGetTopBoneWorld()(bone cao nhất đã cache). - Thêm
RefreshNpcTopBone()để trigger scan từUINPC. - Field mới:
useNpcHeadHook(defaulttrue).
NPCVisual — thêm CacheTopBone
CacheTopBone(): scan tất cả SMR bones, dedupHashSet, lưu boneworld.ycao nhất.TryGetTopBoneWorld(): trả vị trí bone đã cache.
UINPC — thêm LateUpdate
- Vấn đề: 小星星 và các monster có animation nổi/bay bị đè tên vì canvas set vị trí một lần từ rest-pose.
- Fix:
LateUpdatere-readTryGetWorldPositionmỗi frame sau khi coroutine kết thúc → nameplate theo bone animation. - Coroutine gọi
RefreshNpcTopBone()ở cả early-exit lẫn timeout để đảm bảo top bone được cache.
Đối chiếu nhanh với PC (tham khảo)
- PC:
FillPateContentquyết định điểm 3D;RenderNamechỉ layout pixel. - Unity world Canvas: bắt chước bước neo 3D; player có hook + SMR + AABB; NPC/Monster có hook + top bone + SMR + root fallback.
Chi tiết player & matter PC vs Unity: EC_Player_RenderName_vs_UIPlayer.md.
PC gốc (tham khảo)
CElementClient/EC_Player.cpp—FillPateContent,RenderNameCElementClient/EC_NPC.cpp—FillPateContent,RenderNameCElementClient/EC_Matter.cpp—RenderName(~889):vPos = modelAABB.Center + Y * (Extents.y * 1.3f)