From 8c72c3349ce96f1a090e7d2e79961fd7a9fff71e Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 15 Apr 2026 18:38:20 +0700 Subject: [PATCH 1/4] add code height name --- Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs | 69 +++++++++- .../UI/GamePlay/NameplateWorldAnchor.cs | 125 ++++++++++++++++++ .../UI/GamePlay/NameplateWorldAnchor.cs.meta | 2 + .../PerfectWorld/Scripts/UI/GamePlay/UINPC.cs | 31 ++++- Assets/PerfectWorld/Scripts/UI/UIPlayer.cs | 36 ++++- 5 files changed, 254 insertions(+), 9 deletions(-) create mode 100644 Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs create mode 100644 Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs.meta diff --git a/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs b/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs index 973daa245c..d435950d63 100644 --- a/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs +++ b/Assets/PerfectWorld/Scripts/NPC/NPCVisual.cs @@ -11,6 +11,7 @@ public class NPCVisual : MonoBehaviour protected CECNPC.INFO m_NPCInfo; [SerializeField] private Queue _animationQueue = new Queue(); [SerializeField] private AnimancerState _currentState; + private bool debugNamePlateBounds = true; private const float fadeTime = .2f; private const FadeMode FadeMode = Animancer.FadeMode.FixedDuration; @@ -134,7 +135,7 @@ public class NPCVisual : MonoBehaviour // 回退到从此组件的层次结构搜索 namedAnimancer = GetComponentInChildren(); } - + if (namedAnimancer == null) { BMLogger.LogWarning($"NPCVisual: RefreshNamedAnimancer - namedAnimancer == null after refresh (modelRoot: {modelRoot?.name ?? "null"})"); @@ -144,4 +145,70 @@ public class NPCVisual : MonoBehaviour BMLogger.LogMono(this, $"NPCVisual: RefreshNamedAnimancer - Successfully refreshed namedAnimancer from model: {modelRoot?.name ?? "default"}"); } } + + /// + /// Resolve world anchor from merged bounds of all SkinnedMeshRenderers. + /// 从所有SkinnedMeshRenderer合并后的包围盒解析世界锚点。 + /// + public bool TryGetNamePlateAnchorWorld(out Vector3 worldPos) + { + worldPos = default; + + var skinnedMeshRenderers = GetComponentsInChildren(true); + if (skinnedMeshRenderers == null || skinnedMeshRenderers.Length == 0) + { + return false; + } + + Bounds combinedBounds = default; + bool hasAnySkinnedMesh = false; + for (int i = 0; i < skinnedMeshRenderers.Length; i++) + { + var renderer = skinnedMeshRenderers[i]; + if (renderer == null || renderer.sharedMesh == null) + { + continue; + } + + var meshBounds = renderer.sharedMesh.bounds; + var scale = renderer.transform.lossyScale; + var worldCenter = renderer.transform.TransformPoint(meshBounds.center); + var worldSize = new Vector3( + Mathf.Abs(meshBounds.size.x * scale.x), + Mathf.Abs(meshBounds.size.y * scale.y), + Mathf.Abs(meshBounds.size.z * scale.z) + ); + var currentBounds = new Bounds(worldCenter, worldSize); + if (debugNamePlateBounds) + { + Debug.Log($"[Cuong] [NPCVisual] smr={renderer.name} localCenter={meshBounds.center} localSize={meshBounds.size} worldCenter={worldCenter} worldSize={worldSize} worldMaxY={currentBounds.max.y}"); + } + + if (!hasAnySkinnedMesh) + { + combinedBounds = currentBounds; + hasAnySkinnedMesh = true; + } + else + { + combinedBounds.Encapsulate(currentBounds); + } + } + + if (!hasAnySkinnedMesh) + { + if (debugNamePlateBounds) + { + Debug.LogWarning("[Cuong] [NPCVisual] TryGetNamePlateAnchorWorld: no valid SkinnedMeshRenderer/sharedMesh."); + } + return false; + } + + worldPos = new Vector3(combinedBounds.center.x, combinedBounds.max.y, combinedBounds.center.z); + if (debugNamePlateBounds) + { + Debug.Log($"[Cuong] [NPCVisual] combinedCenter={combinedBounds.center} combinedSize={combinedBounds.size} anchorWorld={worldPos}"); + } + return true; + } } diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs new file mode 100644 index 0000000000..16bb2ffc68 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs @@ -0,0 +1,125 @@ +using CSNetwork.GPDataType; +using UnityEngine; + +namespace BrewMonster +{ + /// + /// World-space nameplate height / anchor: player (hook → SMR → AABB) and NPC (SMR → root offset). No CHAABB on NPC. + /// 世界坐标名牌高度锚点:玩家(挂点→蒙皮包围盒→AABB)与NPC(蒙皮包围盒→根节点抬高);NPC不走CHAABB。 + /// + [DisallowMultipleComponent] + public class NameplateWorldAnchor : MonoBehaviour + { + [Header("Hosts (optional — filled from parents if empty)")] + [SerializeField] private CECPlayer hostPlayer; + [SerializeField] private CECNPC hostNpc; + [SerializeField] private PlayerVisual playerVisual; + [SerializeField] private NPCVisual npcVisual; + + [Header("Player — skeleton hook")] + [SerializeField] private bool useHeadSkeletonHook = true; + [SerializeField] private string headHookName = "HH_Head"; + [SerializeField] private float headHookWorldOffset; + + [Header("Player — SMR / AABB fallback")] + [SerializeField] private bool preferRendererBounds = true; + [SerializeField] private bool applyMaleHeadWorldOffsetForAabbOnly = true; + [SerializeField] private float maleHeadWorldOffset = 0.1f; + + [Header("NPC — SMR / root fallback")] + [SerializeField] private bool preferVisualBoundsFallback = true; + [SerializeField] private float fallbackHeightAboveRoot = 2f; + + [Header("Shared")] + [SerializeField] private float extraWorldYOffset; + + private void Awake() => CacheRefs(); + + /// + /// Refresh host and visual references (call after hierarchy changes if needed). + /// 刷新宿主与视觉引用(层级变动后可再调)。 + /// + public void CacheRefs() + { + if (hostPlayer == null && hostNpc == null) + { + hostPlayer = GetComponentInParent(); + if (hostPlayer == null) + hostNpc = GetComponentInParent(); + } + + if (playerVisual == null && hostPlayer != null) + playerVisual = hostPlayer.GetComponent(); + + if (npcVisual == null && hostNpc != null) + npcVisual = hostNpc.GetComponent(); + } + + /// + /// Resolve world position for the nameplate canvas root. + /// 解析名牌Canvas根的世界坐标。 + /// + public bool TryGetWorldPosition(out Vector3 worldPos) + { + CacheRefs(); + if (hostPlayer != null) + return TryGetForPlayer(out worldPos); + if (hostNpc != null) + return TryGetForNpc(out worldPos); + worldPos = default; + return false; + } + + private bool TryGetForPlayer(out Vector3 worldPos) + { + worldPos = default; + if (hostPlayer == null) + return false; + + if (useHeadSkeletonHook) + { + var headHook = hostPlayer.GetHook(headHookName); + if (headHook != null) + { + worldPos = headHook.position + Vector3.up * (headHookWorldOffset + extraWorldYOffset); + return true; + } + } + + if (preferRendererBounds && playerVisual != null && playerVisual.TryGetNamePlateAnchorWorld(out worldPos)) + { + worldPos.y += extraWorldYOffset; + return true; + } + + worldPos = new Vector3( + hostPlayer.m_aabb.Center.x, + hostPlayer.m_aabb.Center.y + hostPlayer.m_aabb.Extents.y, + hostPlayer.m_aabb.Center.z); + + if (applyMaleHeadWorldOffsetForAabbOnly && hostPlayer.GetGender() == (int)GENDER.GENDER_MALE) + worldPos.y += maleHeadWorldOffset; + + worldPos.y += extraWorldYOffset; + return true; + } + + private bool TryGetForNpc(out Vector3 worldPos) + { + worldPos = default; + if (hostNpc == null) + return false; + + if (preferVisualBoundsFallback + && npcVisual != null + && npcVisual.TryGetNamePlateAnchorWorld(out worldPos)) + { + worldPos.y += extraWorldYOffset; + return true; + } + + worldPos = hostNpc.transform.position + Vector3.up * (fallbackHeightAboveRoot + extraWorldYOffset); + return true; + } + } +} diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs.meta b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs.meta new file mode 100644 index 0000000000..494791c2b8 --- /dev/null +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: de86c6e56ad8e224fbd7d825ef43197b \ No newline at end of file diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs index 5b68423b5a..42a9785531 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs @@ -5,7 +5,6 @@ using UnityEngine.UI; namespace BrewMonster { - public enum IconTaskType { QI_NONE = -1, @@ -22,10 +21,11 @@ namespace BrewMonster QI_IN_TYPE2 = 9, // chua biet QI_OUT_TYPE3 = 10, // task daily (nhan) QI_IN_TYPE3 = 11, // task daily (hoan thanh) - QI_OUT_TYPE4 = 12, // task chinh (nhan) + QI_OUT_TYPE4 = 12, // task chinh (nhan) QI_IN_TYPE4 = 13, // task chinh (hoan thanh) } + [RequireComponent(typeof(NameplateWorldAnchor))] public class UINPC : MonoBehaviour { [SerializeField] private TextMeshProUGUI _nameText; @@ -36,10 +36,16 @@ namespace BrewMonster [SerializeField] private GameObject _iconTaskMain; [SerializeField] private List _listIconTask; + [Header("World Nameplate")] + [SerializeField] private Transform _canvasRoot; + + private NameplateWorldAnchor _nameplateAnchor; private Image _cachedIconImage; private void Awake() { + CacheRefs(); + _nameplateAnchor = GetComponent(); if (_iconTaskMain != null) { _cachedIconImage = _iconTaskMain.GetComponent(); @@ -50,6 +56,18 @@ namespace BrewMonster } } + private void LateUpdate() + { + if (_canvasRoot == null) + { + return; + } + if (_nameplateAnchor != null && _nameplateAnchor.TryGetWorldPosition(out var worldPos)) + { + _canvasRoot.position = worldPos; + } + } + // Start is called once before the first execution of Update after the MonoBehaviour is created public void SetName(string name) { @@ -90,6 +108,15 @@ namespace BrewMonster if (_iconTaskMain != null) _iconTaskMain.SetActive(isShow); } + + private void CacheRefs() + { + if (_canvasRoot == null) + { + _canvasRoot = transform; + } + + } } } diff --git a/Assets/PerfectWorld/Scripts/UI/UIPlayer.cs b/Assets/PerfectWorld/Scripts/UI/UIPlayer.cs index 155352d451..1679f4b094 100644 --- a/Assets/PerfectWorld/Scripts/UI/UIPlayer.cs +++ b/Assets/PerfectWorld/Scripts/UI/UIPlayer.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using BrewMonster; using BrewMonster.Scripts.ChatUI; using CSNetwork.GPDataType; using Cysharp.Threading.Tasks; @@ -9,6 +10,7 @@ using UnityEngine.UI; namespace BrewMonster.PerfectWorld.Scripts.UI { + [RequireComponent(typeof(NameplateWorldAnchor))] public class UIPlayer : MonoBehaviour { [Header("World HUD (optional)")] @@ -19,7 +21,8 @@ namespace BrewMonster.PerfectWorld.Scripts.UI [Header("References")] [SerializeField] private CECPlayer hostplayer; - + [SerializeField] private NameplateWorldAnchor nameplateAnchor; + [SerializeField] private float chatDisplayDuration = 5f; private CancellationTokenSource _chatCts; @@ -33,7 +36,7 @@ namespace BrewMonster.PerfectWorld.Scripts.UI } private void Start() - { + { CacheRefs(); if (hostplayer == null) @@ -49,6 +52,19 @@ namespace BrewMonster.PerfectWorld.Scripts.UI EventBus.SubscribeChannel(hostplayer.m_PlayerInfo.cid, UpdateHostPlayerInfoUI); } + private void LateUpdate() + { + if (!_isVisible || canvasRoot == null) + { + return; + } + + if (nameplateAnchor != null && nameplateAnchor.TryGetWorldPosition(out var worldPos)) + { + canvasRoot.position = worldPos; + } + } + private void OnDestroy() { _chatCts?.Cancel(); @@ -105,6 +121,14 @@ namespace BrewMonster.PerfectWorld.Scripts.UI if (hostplayer == null) hostplayer = GetComponentInParent(); + if (nameplateAnchor == null) + nameplateAnchor = GetComponent(); + + if (nameplateAnchor != null) + { + nameplateAnchor.CacheRefs(); + } + if (canvasRoot == null) { var t = transform.Find("Canvas"); @@ -133,7 +157,7 @@ namespace BrewMonster.PerfectWorld.Scripts.UI for (int i = 0; i < t.childCount; i++) SetLayerRecursively(t.GetChild(i).gameObject, layer); } - + private void SetChatMessage(EventChatMessageOnTopPlayer cxt) { if (chatText == null) @@ -145,7 +169,7 @@ namespace BrewMonster.PerfectWorld.Scripts.UI SetLastSaidWords(cxt.context); }); } - + public void SetLastSaidWords(string message, float duration = -1f) { if (chatText == null || string.IsNullOrEmpty(message)) @@ -162,7 +186,7 @@ namespace BrewMonster.PerfectWorld.Scripts.UI HideChatAsync(time, _chatCts.Token).Forget(); } - + private async UniTaskVoid HideChatAsync(float time, CancellationToken token) { try @@ -192,4 +216,4 @@ namespace BrewMonster.PerfectWorld.Scripts.UI this.context = context; } } -} \ No newline at end of file +} From 78ceafb347a4c53526bd84ba48bd1e560d413e6a Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 15 Apr 2026 18:38:31 +0700 Subject: [PATCH 2/4] add + --- Assets/Scripts/PlayerVisual.cs | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Assets/Scripts/PlayerVisual.cs b/Assets/Scripts/PlayerVisual.cs index e6123b63a1..f0e21fdb11 100644 --- a/Assets/Scripts/PlayerVisual.cs +++ b/Assets/Scripts/PlayerVisual.cs @@ -24,6 +24,7 @@ namespace BrewMonster [SerializeField] private bool isHit; [SerializeField] private int id; [SerializeField] private bool isDebug; + [SerializeField] private bool debugNamePlateBounds; private bool _eventBusSubscribed; private const float FadeTime = 100; @@ -311,5 +312,73 @@ namespace BrewMonster BMLogger.Log($"PlayerVisual: RefreshNamedAnimancer - Successfully refreshed namedAnimancer from model: {modelRoot?.name ?? "default"}"); } } + + /// + /// Resolve nameplate anchor from merged bounds of all SkinnedMeshRenderers. + /// 从所有SkinnedMeshRenderer合并后的包围盒解析名牌锚点。 + /// + public bool TryGetNamePlateAnchorWorld(out Vector3 worldPos) + { + worldPos = default; + + var skinnedMeshRenderers = GetComponentsInChildren(true); + if (skinnedMeshRenderers == null || skinnedMeshRenderers.Length == 0) + { + return false; + } + + Bounds combinedBounds = default; + bool hasAnySkinnedMesh = false; + for (int i = 0; i < skinnedMeshRenderers.Length; i++) + { + var renderer = skinnedMeshRenderers[i]; + if (renderer == null || renderer.sharedMesh == null) + { + continue; + } + + var meshBounds = renderer.sharedMesh.bounds; + var scale = renderer.transform.lossyScale; + var worldCenter = renderer.transform.TransformPoint(meshBounds.center); + var worldSize = new Vector3( + Mathf.Abs(meshBounds.size.x * scale.x), + Mathf.Abs(meshBounds.size.y * scale.y), + Mathf.Abs(meshBounds.size.z * scale.z) + ); + var currentBounds = new Bounds(worldCenter, worldSize); + if (debugNamePlateBounds) + { + Debug.Log($"[Cuong] [PlayerVisual] smr={renderer.name} localCenter={meshBounds.center} localSize={meshBounds.size} worldCenter={worldCenter} worldSize={worldSize} worldMaxY={currentBounds.max.y}"); + } + + if (!hasAnySkinnedMesh) + { + combinedBounds = currentBounds; + hasAnySkinnedMesh = true; + } + else + { + combinedBounds.Encapsulate(currentBounds); + } + } + + if (!hasAnySkinnedMesh) + { + if (debugNamePlateBounds) + { + Debug.LogWarning("[Cuong] [PlayerVisual] TryGetNamePlateAnchorWorld: no valid SkinnedMeshRenderer/sharedMesh."); + } + return false; + } + + // Use merged bounds center for X/Z and merged top for Y. + // 使用合并包围盒中心作为X/Z,使用合并包围盒顶部作为Y。 + worldPos = new Vector3(combinedBounds.center.x, combinedBounds.max.y, combinedBounds.center.z); + if (debugNamePlateBounds) + { + Debug.Log($"[Cuong] [PlayerVisual] combinedCenter={combinedBounds.center} combinedSize={combinedBounds.size} anchorWorld={worldPos}"); + } + return true; + } } } \ No newline at end of file From d5f6d7d6ea0d0ed71fc48c35944d336ab642a954 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Wed, 15 Apr 2026 18:38:57 +0700 Subject: [PATCH 3/4] update prefabs --- Assets/Resources/Monster/MonsterPrefab.prefab | 35 +++++++++++-- Assets/Resources/NPC/NPCServer.prefab | 45 +++++++++++++---- .../Pet/PetMount/PetMountPrefab.prefab | 26 ++++++++++ Assets/Resources/Pet/PetPrefab.prefab | 29 +++++++++++ Assets/Resources/Player/PlayerPrefab.prefab | 49 ++++++++++++++----- 5 files changed, 160 insertions(+), 24 deletions(-) diff --git a/Assets/Resources/Monster/MonsterPrefab.prefab b/Assets/Resources/Monster/MonsterPrefab.prefab index 99a0374cb5..9c3aa1b9b2 100644 --- a/Assets/Resources/Monster/MonsterPrefab.prefab +++ b/Assets/Resources/Monster/MonsterPrefab.prefab @@ -273,7 +273,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -0, y: 1.76} + m_AnchoredPosition: {x: -0, y: 0} m_SizeDelta: {x: 1, y: 1} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &2934704790758419263 @@ -460,6 +460,7 @@ GameObject: m_Component: - component: {fileID: 7528353842011023148} - component: {fileID: 15783571296627952} + - component: {fileID: 9001000111222333445} - component: {fileID: 775994425462537197} m_Layer: 0 m_Name: UIPlayer @@ -476,8 +477,8 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6780952369966265306} serializedVersion: 2 - m_LocalRotation: {x: 0.026024496, y: 0.5726356, z: -0.0181917, w: 0.8191949} - m_LocalPosition: {x: 0, y: 1, z: 0} + m_LocalRotation: {x: 0.02654451, y: 0.57262397, z: -0.018555203, w: 0.81917816} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: @@ -501,6 +502,32 @@ MonoBehaviour: _healthImage: {fileID: 3715353156977051930} _iconTaskMain: {fileID: 0} _listIconTask: [] + _canvasRoot: {fileID: 0} +--- !u!114 &9001000111222333445 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6780952369966265306} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de86c6e56ad8e224fbd7d825ef43197b, type: 3} + m_Name: + m_EditorClassIdentifier: + hostPlayer: {fileID: 0} + hostNpc: {fileID: 2542060226037108388} + playerVisual: {fileID: 0} + npcVisual: {fileID: -3520322077839857420} + useHeadSkeletonHook: 1 + headHookName: HH_Head + headHookWorldOffset: 0 + preferRendererBounds: 1 + applyMaleHeadWorldOffsetForAabbOnly: 1 + maleHeadWorldOffset: 0.1 + preferVisualBoundsFallback: 1 + fallbackHeightAboveRoot: 2 + extraWorldYOffset: 0.3 --- !u!114 &775994425462537197 MonoBehaviour: m_ObjectHideFlags: 0 @@ -671,7 +698,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -0, y: 1.51} + m_AnchoredPosition: {x: -0, y: 0} m_SizeDelta: {x: 1, y: 0.2} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &7187889842481329847 diff --git a/Assets/Resources/NPC/NPCServer.prefab b/Assets/Resources/NPC/NPCServer.prefab index b3ff78aa8e..2033443c8c 100644 --- a/Assets/Resources/NPC/NPCServer.prefab +++ b/Assets/Resources/NPC/NPCServer.prefab @@ -237,7 +237,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targetCamera: {fileID: 0} - mode: 1 + mode: 0 --- !u!1 &5100466107490290627 GameObject: m_ObjectHideFlags: 0 @@ -263,16 +263,16 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5100466107490290627} - m_LocalRotation: {x: 3.7702125e-16, y: -0.97228837, z: -0.23378475, w: 1.5679955e-15} - m_LocalPosition: {x: 0, y: 0, z: 1.2} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 8006159455096186264} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 2.36} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 1} m_SizeDelta: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &397635185778217656 @@ -323,6 +323,7 @@ GameObject: m_Component: - component: {fileID: 8745273338113588215} - component: {fileID: 7336483376496286557} + - component: {fileID: 9001000111222333446} m_Layer: 0 m_Name: UIPlayer m_TagString: Untagged @@ -377,6 +378,32 @@ MonoBehaviour: - {fileID: 536497386, guid: 75fb3bbb7c9421e4d8bf0728cf2b59b1, type: 3} - {fileID: -440769636, guid: 75fb3bbb7c9421e4d8bf0728cf2b59b1, type: 3} - {fileID: 844877792, guid: 75fb3bbb7c9421e4d8bf0728cf2b59b1, type: 3} + _canvasRoot: {fileID: 8006159455096186264} +--- !u!114 &9001000111222333446 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5709911674741944065} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de86c6e56ad8e224fbd7d825ef43197b, type: 3} + m_Name: + m_EditorClassIdentifier: + hostPlayer: {fileID: 0} + hostNpc: {fileID: -5899287755522118344} + playerVisual: {fileID: 0} + npcVisual: {fileID: 1882963580244400679} + useHeadSkeletonHook: 1 + headHookName: HH_Head + headHookWorldOffset: 0 + preferRendererBounds: 1 + applyMaleHeadWorldOffsetForAabbOnly: 1 + maleHeadWorldOffset: 0.1 + preferVisualBoundsFallback: 1 + fallbackHeightAboveRoot: 2 + extraWorldYOffset: 0.3 --- !u!1 &6510845919681767284 GameObject: m_ObjectHideFlags: 0 @@ -403,7 +430,7 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6510845919681767284} - m_LocalRotation: {x: 0.15080568, y: -0.8027092, z: -0.16719593, w: 0.5522329} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 @@ -412,7 +439,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0, y: 2.11} + m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 1, y: 1} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &4151558405913130980 @@ -521,7 +548,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6510845919681767284} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 1a05efb08a5fbef42b3e8414040b6c33, type: 3} m_Name: diff --git a/Assets/Resources/Pet/PetMount/PetMountPrefab.prefab b/Assets/Resources/Pet/PetMount/PetMountPrefab.prefab index 0076d33792..d378bb4487 100644 --- a/Assets/Resources/Pet/PetMount/PetMountPrefab.prefab +++ b/Assets/Resources/Pet/PetMount/PetMountPrefab.prefab @@ -531,6 +531,7 @@ GameObject: m_Component: - component: {fileID: 4817336479570982655} - component: {fileID: 623488520321375200} + - component: {fileID: 9001000111222333448} - component: {fileID: 7635475614995666769} m_Layer: 0 m_Name: UIPlayer @@ -570,6 +571,31 @@ MonoBehaviour: _nameText: {fileID: 2285106042110813528} _healthText: {fileID: 7174288820249738816} _healthImage: {fileID: 5673963502904499965} +--- !u!114 &9001000111222333448 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7411326708969747318} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de86c6e56ad8e224fbd7d825ef43197b, type: 3} + m_Name: + m_EditorClassIdentifier: + hostPlayer: {fileID: 0} + hostNpc: {fileID: 0} + playerVisual: {fileID: 0} + npcVisual: {fileID: 0} + useHeadSkeletonHook: 1 + headHookName: HH_Head + headHookWorldOffset: 0 + preferRendererBounds: 1 + applyMaleHeadWorldOffsetForAabbOnly: 1 + maleHeadWorldOffset: 0.1 + preferVisualBoundsFallback: 1 + fallbackHeightAboveRoot: 2 + extraWorldYOffset: 0.3 --- !u!114 &7635475614995666769 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Resources/Pet/PetPrefab.prefab b/Assets/Resources/Pet/PetPrefab.prefab index 271b08eb71..37d3668c4c 100644 --- a/Assets/Resources/Pet/PetPrefab.prefab +++ b/Assets/Resources/Pet/PetPrefab.prefab @@ -582,6 +582,7 @@ GameObject: m_Component: - component: {fileID: 7933090353384311527} - component: {fileID: 2684574450150702093} + - component: {fileID: 9001000111222333447} - component: {fileID: 8928742731626340957} m_Layer: 0 m_Name: UIPlayer @@ -621,6 +622,34 @@ MonoBehaviour: _nameText: {fileID: 6491069708694599127} _healthText: {fileID: 8939523771274757837} _healthImage: {fileID: 3651088183696393374} + _iconTaskMain: {fileID: 0} + _listIconTask: [] + _canvasRoot: {fileID: 0} +--- !u!114 &9001000111222333447 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209053004261175930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de86c6e56ad8e224fbd7d825ef43197b, type: 3} + m_Name: + m_EditorClassIdentifier: + hostPlayer: {fileID: 0} + hostNpc: {fileID: 4017591853686837057} + playerVisual: {fileID: 0} + npcVisual: {fileID: 1776217013714687322} + useHeadSkeletonHook: 1 + headHookName: HH_Head + headHookWorldOffset: 0 + preferRendererBounds: 1 + applyMaleHeadWorldOffsetForAabbOnly: 1 + maleHeadWorldOffset: 0.1 + preferVisualBoundsFallback: 1 + fallbackHeightAboveRoot: 2 + extraWorldYOffset: 0.3 --- !u!114 &8928742731626340957 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/Resources/Player/PlayerPrefab.prefab b/Assets/Resources/Player/PlayerPrefab.prefab index 8b7f7ad818..7157a6a614 100644 --- a/Assets/Resources/Player/PlayerPrefab.prefab +++ b/Assets/Resources/Player/PlayerPrefab.prefab @@ -103,7 +103,7 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 3356314488807061436} - m_LocalRotation: {x: -3.7702125e-16, y: 0.97228837, z: 0.23378475, w: 1.5679955e-15} + m_LocalRotation: {x: 0.02634692, y: 0.5726284, z: -0.018417083, w: 0.81918466} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 @@ -194,7 +194,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: targetCamera: {fileID: 0} - mode: 1 + mode: 0 --- !u!1 &3780435110686298972 GameObject: m_ObjectHideFlags: 0 @@ -236,6 +236,7 @@ GameObject: m_Component: - component: {fileID: 8793437051475293945} - component: {fileID: 2274946704047842688} + - component: {fileID: 9001000111222333444} m_Layer: 0 m_Name: UIPlayer m_TagString: Untagged @@ -273,10 +274,36 @@ MonoBehaviour: m_EditorClassIdentifier: healthImage: {fileID: 6923483925198672938} nameText: {fileID: 0} - canvasRoot: {fileID: 0} + canvasRoot: {fileID: 7804302986034915478} chatText: {fileID: 5163939573699579047} hostplayer: {fileID: 0} + nameplateAnchor: {fileID: 9001000111222333444} chatDisplayDuration: 5 +--- !u!114 &9001000111222333444 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5505736060713067023} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: de86c6e56ad8e224fbd7d825ef43197b, type: 3} + m_Name: + m_EditorClassIdentifier: + hostPlayer: {fileID: 0} + hostNpc: {fileID: 0} + playerVisual: {fileID: 6892195500068497589} + npcVisual: {fileID: 0} + useHeadSkeletonHook: 1 + headHookName: HH_Head + headHookWorldOffset: 0 + preferRendererBounds: 1 + applyMaleHeadWorldOffsetForAabbOnly: 1 + maleHeadWorldOffset: 0.1 + preferVisualBoundsFallback: 1 + fallbackHeightAboveRoot: 2 + extraWorldYOffset: 0 --- !u!1 &5826062684364525110 GameObject: m_ObjectHideFlags: 0 @@ -334,8 +361,8 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6417286913550183034} - m_LocalRotation: {x: 0.15073968, y: -0.8028954, z: -0.1672268, w: 0.5519708} - m_LocalPosition: {x: 0, y: 0, z: -0.263} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -343,7 +370,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: 0.01, y: 2.799} + m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 1, y: 1} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &4235828909678541546 @@ -452,7 +479,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6417286913550183034} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 1a05efb08a5fbef42b3e8414040b6c33, type: 3} m_Name: @@ -567,8 +594,8 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 7348274655157863130} - m_LocalRotation: {x: 0.1502115, y: -0.80294836, z: -0.16792805, w: 0.55182487} - m_LocalPosition: {x: 0, y: 0, z: -0.19} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] @@ -576,7 +603,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -0, y: 4.205} + m_AnchoredPosition: {x: -0, y: 2} m_SizeDelta: {x: 1.5, y: 1.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6415805438999997156 @@ -685,7 +712,7 @@ MonoBehaviour: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 7348274655157863130} - m_Enabled: 1 + m_Enabled: 0 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 1a05efb08a5fbef42b3e8414040b6c33, type: 3} m_Name: From 0c84e82d0363e447308650b1563f4e5b2d92a8b1 Mon Sep 17 00:00:00 2001 From: CuongNV <> Date: Thu, 16 Apr 2026 10:11:07 +0700 Subject: [PATCH 4/4] update prefab --- .../Scripts/UI/GamePlay/NameplateWorldAnchor.cs | 4 ++-- Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs | 12 ++++++++++-- Assets/Resources/Player/PlayerPrefab.prefab | 5 +++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs index 16bb2ffc68..e4238a8e24 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/NameplateWorldAnchor.cs @@ -56,8 +56,8 @@ namespace BrewMonster } /// - /// Resolve world position for the nameplate canvas root. - /// 解析名牌Canvas根的世界坐标。 + /// Resolve world position for the nameplate canvas root. Call once at init or every frame if the plate should follow the host. + /// 解析名牌Canvas根的世界坐标。仅在初始化时调用一次,或若名牌需跟随宿主则每帧调用。 /// public bool TryGetWorldPosition(out Vector3 worldPos) { diff --git a/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs b/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs index 42a9785531..cec231fbc0 100644 --- a/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs +++ b/Assets/PerfectWorld/Scripts/UI/GamePlay/UINPC.cs @@ -56,7 +56,16 @@ namespace BrewMonster } } - private void LateUpdate() + private void Start() + { + ApplyInitialCanvasRootPosition(); + } + + /// + /// World position for the nameplate root is applied once at startup (no per-frame follow). + /// 名牌根节点世界坐标仅在启动时设置一次(不做每帧跟随)。 + /// + private void ApplyInitialCanvasRootPosition() { if (_canvasRoot == null) { @@ -68,7 +77,6 @@ namespace BrewMonster } } - // Start is called once before the first execution of Update after the MonoBehaviour is created public void SetName(string name) { _nameText.text = name; diff --git a/Assets/Resources/Player/PlayerPrefab.prefab b/Assets/Resources/Player/PlayerPrefab.prefab index 7157a6a614..5b90c9b832 100644 --- a/Assets/Resources/Player/PlayerPrefab.prefab +++ b/Assets/Resources/Player/PlayerPrefab.prefab @@ -303,7 +303,7 @@ MonoBehaviour: maleHeadWorldOffset: 0.1 preferVisualBoundsFallback: 1 fallbackHeightAboveRoot: 2 - extraWorldYOffset: 0 + extraWorldYOffset: 0.3 --- !u!1 &5826062684364525110 GameObject: m_ObjectHideFlags: 0 @@ -568,6 +568,7 @@ MonoBehaviour: isHit: 0 id: 0 isDebug: 0 + debugNamePlateBounds: 0 --- !u!1 &7348274655157863130 GameObject: m_ObjectHideFlags: 0 @@ -603,7 +604,7 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} - m_AnchoredPosition: {x: -0, y: 2} + m_AnchoredPosition: {x: -0, y: 0} m_SizeDelta: {x: 1.5, y: 1.5} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6415805438999997156