# name_tail_layout —— §8.8 seam ⑨ 收口:头顶名字尾标「有聊天气泡时上移贴气泡」1:1。 # CPythonTextTail::UpdateShowingTextTail 里,每条角色名字尾标投影完屏幕坐标后, # 若该 VID 同时有一条 chat 尾标且 `pChatTail->bNameFlag`(= RegisterChatTail 注册的 # 「聊天」气泡,非 RegisterInfoTail 的「信息」气泡),就把名字尾标的屏幕 y 顶到 # `chatTail.y - 17.0f`(屏幕 y 向下,减 17px = 上移一行,正好贴在气泡上沿)。 # (与 name_color.gd / text_tail.gd / chat_tail.gd / name_show.gd / damage_effect.gd / # target_board.gd / pick_show.gd 同一套纯静态镜像套路,本模块是第 28 个 canonical。) # # REF(40250 ClientVS22 source/ 为唯一基线): # UserInterface/PythonTextTail.cpp:19 const float c_TextTail_Name_Position = -10.0f # UserInterface/PythonTextTail.cpp:509 RegisterCharacterTextTail(..., float fAddHeight=10.0f) # :519 fHeight = GetHeight() + fAddHeight —— 名字尾标世界高=角色高 + 10 # UserInterface/PythonTextTail.cpp:581 RegisterChatTail:新建时 fHeight = GetHeight() + 10.0f、 # :603 / :618 pTextTail->bNameFlag = TRUE —— 「聊天」气泡(白) # UserInterface/PythonTextTail.cpp:623 RegisterInfoTail:同高, # :646 / :661 pTextTail->bNameFlag = FALSE —— 「信息」气泡(淡红),不顶名字 # UserInterface/PythonTextTail.cpp:88 UpdateShowingTextTail() # :101 for (角色名字尾标 : m_CharacterTextTailList) # :104 UpdateTextTail(pTextTail) —— 先投影出屏幕 x/y/z # :106 itor = m_ChatTailMap.find(pTextTail->dwVirtualID) # :110 if (m_ChatTailMap.end() != itor) # :112 if (pChatTail->bNameFlag) # :114 pTextTail->y = pChatTail->y - 17.0f —— 名字屏幕 y 顶到气泡上方 17px # UserInterface/PythonTextTail.cpp:74 UpdateAllTextTail:同一个 bNameFlag 还驱动 # ShowCharacterTextTail(dwVID) 强显(增量 112/113 已接 chat_tail.gd / name_show.gd)。 # UserInterface/PythonTextTail.cpp:151 ArrangeTextTail() # :211 for (pTextTail : m_CharacterTextTailList) —— 逐条角色名字尾标 # :228 pGuildNameInstance->SetPosition(x + iImageHalfSize, pTextTail->y - c_fyGuildNamePosition, z) # :21 const float c_fyGuildNamePosition = 15.0f —— 公会名锚在名字 y 上方 15px # :258 pTitle->SetPosition(x - iNameWidth/2 - fxAdd, pTextTail->y, z) —— 称号与名字同一行 # :283 pLevel->SetPosition(x - iNameWidth/2 - fxAdd - iTitleWidth, pTextTail->y, z) —— 等级同一行 # —— 公会名 / 称号 / 等级三个子标签的屏幕 y 全部取自 `pTextTail->y`,而这个 y 已在 # UpdateShowingTextTail :114 被聊天气泡顶过 -17。所以名字尾标一旦上移,子标签整体跟随。 # # seam ⑨(已收口):参考端名字与气泡默认投影到同一世界点(GetHeight()+10),靠这条屏幕 # -17px 把名字从「和气泡重叠」顶成「贴气泡上沿」。poc 是每实体一个 billboard Label3D、 # 气泡默认在名字上方(局部 y 2.35 vs 2.1),本模块把屏幕 y 向下的 -17px 换成世界 y # 向上的 +17px*pixel_size:有「聊天」气泡时名字世界 y 顶到气泡 y 之上 17px,气泡消失 # (或只是「信息」气泡)时回默认 y。像素 vs 世界的换算按名字 Label3D 的 pixel_size, # 与参考的字体无关常量 17 对齐。 # seam ⑦(部分收口 · 增量 119):子标签(GuildTag/TitleTag/LevelTag)随名字尾标纵向位移 # 已 1:1(name_tail_lift_delta / subtag_world_y:名字上移多少子标签整体上移多少)。 # seam ⑦(水平排版 · 增量 121):ArrangeTextTail 角色块(PythonTextTail.cpp:211-320)非 # EUROPE 分支的逐像素水平排布也已 1:1(下方 name_x_shift / title_x_offset / # level_x_offset / guild_name_x_offset / mark_x_offset / subtag_world_x): # :236 fxAdd = 0 —— 无称号无等级 # :242 if (pTitle) fxAdd = 8.0f # :258 pTitle->SetPosition(x - iNameWidth/2 - fxAdd, y, z) (称号,居中对齐) # :263 if (pLevel) —— 有称号时等级在称号左侧 # :282 pLevel->SetPosition(x - iNameWidth/2 - fxAdd - iTitleWidth, y, z)(等级,右对齐) # :288 else fxAdd = 4.0f —— 只有等级 # :312 pLevel->SetPosition(x - iNameWidth/2 - fxAdd, y, z) # :320 pTextInstance->SetPosition(x + fxAdd, y, z) —— 名字本身右移 fxAdd # :224 iImageHalfSize = pMarkInstance->GetWidth()/2 + c_fxMarkPosition(1.5) # :227 pMarkInstance->SetPosition(x - guildW/2 - iImageHalfSize, y - c_fyMarkPosition(26)) # :228 pGuildNameInstance->SetPosition(x + iImageHalfSize, y - c_fyGuildNamePosition(15), z) # —— 公会名的水平锚点只在「有公会徽记 pMarkInstance」时才被 ArrangeTextTail 改写。 # 仍近似(seam ⑦ 遗留):① poc 无公会徽记 mark 实体 —— 参考端 :220 的 # `if (pMarkInstance && pGuildNameInstance)` 不成立时公会名水平锚点不动,poc 亦让 # GuildTag 留在名字中线正上方(x 偏移 0),不做 +markW/2+1.5;② 文本宽度 poc 用 # 「字符数 * 约 7px」估,参考端是 GetTextSize 精确像素;③ 三子标签竖排 vs 参考同一 # 水平行(poc 3D billboard 里三条挤在一行会互相糊,保留竖排换 x 偏移的折衷)。 extends RefCounted # PythonTextTail.cpp:114 的 `17.0f`。屏幕坐标下是「名字 y 减 17」(上移一行贴气泡)。 const NAME_OFFSET_ABOVE_BUBBLE_PX := 17.0 # PythonTextTail.cpp:21 的 `c_fyGuildNamePosition = 15.0f`:参考端公会名锚在名字尾标 # 屏幕 y 上方 15px(称号 / 等级则与名字同一行)。poc 竖排近似不用它做像素排版,仅作 # 1:1 记录——子标签真正跟随的是「名字尾标位移量」,见 subtag_world_y。 const GUILD_NAME_ABOVE_NAME_PX := 15.0 # UpdateShowingTextTail :110 `m_ChatTailMap.end() != itor` && :112 `pChatTail->bNameFlag`。 # has_chat_tail —— 该 VID 在 m_ChatTailMap 里有一条(聊天或信息); # chat_name_flag —— 那条的 bNameFlag(RegisterChatTail=TRUE / RegisterInfoTail=FALSE)。 static func name_follows_bubble(has_chat_tail: bool, chat_name_flag: bool) -> bool: return has_chat_tail and chat_name_flag # 参考端字面公式(屏幕坐标,y 向下):follows 时 `pChatTail->y - 17.0f`,否则名字投影原值。 static func name_tail_screen_y(default_y: float, bubble_y: float, follows: bool) -> float: if follows: return bubble_y - NAME_OFFSET_ABOVE_BUBBLE_PX return default_y # poc 3D 适配(世界坐标,y 向上):屏幕的 -17px 变成 +17px*pixel_size —— follows 时名字 # 世界 y 顶到气泡世界 y 之上 17 个名字像素,否则回默认世界 y。 static func name_tail_world_y(default_world_y: float, bubble_world_y: float, follows: bool, px_size: float) -> float: if follows: return bubble_world_y + NAME_OFFSET_ABOVE_BUBBLE_PX * px_size return default_world_y # 名字尾标相对默认世界 y 的位移量(follows 时 = 顶到气泡上方后的净上移,否则 0)。 # ArrangeTextTail 里公会名 / 称号 / 等级都锚在 pTextTail->y 上,位移量就是子标签要跟随的量。 static func name_tail_lift_delta(default_world_y: float, bubble_world_y: float, follows: bool, px_size: float) -> float: return name_tail_world_y(default_world_y, bubble_world_y, follows, px_size) - default_world_y # ArrangeTextTail :228 / :258 / :283:子标签屏幕 y 取自 pTextTail->y(已被聊天气泡顶过)。 # poc 里子标签各有自己的默认局部 y(竖排近似),名字尾标上移 lift_delta 时三者整体加同一段。 static func subtag_world_y(default_subtag_y: float, name_tail_lift_delta: float) -> float: return default_subtag_y + name_tail_lift_delta # --- seam ⑦ 水平排版(ArrangeTextTail 角色块,PythonTextTail.cpp:211-320 非 EUROPE 分支)--- # :242 `fxAdd = 8.0f`(if pTitle)。屏幕像素,名字锚点 x 与三子标签公用。 const NAME_X_ADD_TITLE := 8.0 # :288 `fxAdd = 4.0f`(else,只有等级)。 const NAME_X_ADD_LEVEL_ONLY := 4.0 # :20 `const float c_fxMarkPosition = 1.5f`。 const MARK_X_PAD := 1.5 # :22 `const float c_fyMarkPosition = 15.0f + 11.0f`。公会徽记锚在名字 y 上方 26px。 const MARK_ABOVE_NAME_PX := 26.0 # :320 `pTextInstance->SetPosition(pTextTail->x + fxAdd, ...)`:名字本身相对锚点右移 fxAdd。 # 有称号 -> 8;无称号只有等级 -> 4;都没有 -> 0。 static func name_x_shift(has_title: bool, has_level: bool) -> float: if has_title: return NAME_X_ADD_TITLE if has_level: return NAME_X_ADD_LEVEL_ONLY return 0.0 # :258 `pTitle->SetPosition(pTextTail->x - (iNameWidth/2) - fxAdd, ...)`(fxAdd = 8)。 # 返回称号中心相对名字锚点 x 的像素偏移(负 = 在名字左侧)。 static func title_x_offset(name_w: float) -> float: return -(name_w / 2.0) - NAME_X_ADD_TITLE # 等级相对名字锚点 x 的像素偏移(右对齐实例,故这是其右边缘位置): # 有称号(:282,fxAdd = 8):x - iNameWidth/2 - 8 - iTitleWidth —— 再往称号左边挪一个称号宽; # 只有等级(:312,fxAdd = 4):x - iNameWidth/2 - 4。 static func level_x_offset(name_w: float, title_w: float, has_title: bool) -> float: if has_title: return -(name_w / 2.0) - NAME_X_ADD_TITLE - title_w return -(name_w / 2.0) - NAME_X_ADD_LEVEL_ONLY # :224/:228 公会名中心 = x + iImageHalfSize,iImageHalfSize = markW/2 + c_fxMarkPosition(1.5)。 # 仅在有公会徽记时 ArrangeTextTail 才改写公会名 x;无徽记(poc 现状)应传 mark_w = 0 # 并按「参考端此路不改写」处理(调用方直接留 0 偏移),此函数仅供有徽记时用。 static func guild_name_x_offset(mark_w: float) -> float: return mark_w / 2.0 + MARK_X_PAD # :227 徽记位置 = x - guildW/2 - iImageHalfSize(相对名字锚点 x 的像素偏移,负 = 左侧)。 static func mark_x_offset(guild_w: float, mark_w: float) -> float: return -(guild_w / 2.0) - (mark_w / 2.0 + MARK_X_PAD) # poc 3D 适配:参考端在屏幕像素空间摆位(+x 向右),poc 子标签是 billboard Label3D —— # billboard 本地 +x 恒等于屏幕右,按该 Label3D 的 pixel_size 把像素偏移折成世界本地 x。 static func subtag_world_x(px_offset: float, px_size: float) -> float: return px_offset * px_size