10 KiB
Tóm tắt refactor Chat / Emotion (session)
Tài liệu này ghi lại các thay đổi code liên quan Emotion → TMP, ChatInput / Emoji picker, và EmotionLibrarySpriteMap trong kho perfect-world-unity.
1. EmotionTMPTagBuilder — nhập liệu chat
Mục đích: Chèn tag TMP (<sprite name=…>, <sprite index=…>, <sprite anim=…>) vào TMP_InputField khi người chơi chọn emoji.
Đã làm:
-
Assets/Scripts/ChatInputHandler.cs- Thêm
using BrewMonster.Scripts.ChatvàBrewMonster.Scripts.Chat.EmotionData. - Thêm
[SerializeField] EmotionLibrarySpriteMap _spriteMap. InsertEmoji→ gọiAppendEmotionWire(buffer wire + refresh TMP) — xem mục 7.
- Thêm
-
Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs- Bỏ Reflection gọi
InsertEmojiquaMethodInfo. - Gọi
ChatInputHandler.InsertEmoji(nội bộ dùng wire + TMP display).
- Bỏ Reflection gọi
Ghi chú Inspector: Gán EmotionLibrarySpriteMap vào ChatInputHandler nếu dùng InsertEmoji; picker vẫn dùng _emotionSpriteMap riêng trên EmojiPickerUI.
2. ChatEmotionDisplayPipeline — cảnh báo spriteMap null
Vấn đề: Constructor luôn log warning dù spriteMap có giá trị (vì Debug.LogWarning nằm ngoài nhánh else).
Đã làm:
- Chỉ log khi
spriteMap == null(dùngStubEmotionSpriteMap). SetSpriteMap(null)ghi log rõ: cần gánEmotionLibrarySpriteMaptrênCECUIManager.
Luồng đúng: CECUIManager.Awake → gameUI.SetEmotionSpriteMap(_emotionLibrarySpriteMap) — nếu field không gán trên Inspector thì map vẫn null và fallback stub.
3. EmotionLibrarySpriteMap — nhiều bộ atlas / một TMP mỗi bộ
Vấn đề ban đầu: Chỉ có một TMP_SpriteAsset → tra index sai khi có nhiều bộ (ví dụ 9 atlas).
Tiến hóa thiết kế:
- Tạm thời: Thêm list
PerSetTmpSpriteAssets(SetIndex → TMP_SpriteAsset) + fallback. - Chốt: Di chuyển
TMP_SpriteAssetvàoEmotionSetSnapshottrongEmotionLibrarySO— mỗi snapshot một atlas + mộtTmpSpriteAssetcùng chỗ dữ liệu. - Xóa list trung gian trên
EmotionLibrarySpriteMap; chỉ cònLibrary,PreferSpriteNameTag,DefaultAnimFps.
File: Assets/PerfectWorld/Scripts/Chat/EmotionData/EmotionLibrarySO.cs — field TmpSpriteAsset trên EmotionSetSnapshot.
4. Index sprite: không còn tra spriteCharacterTable theo tên
Yêu cầu: Atlas cắt trái → phải, trên → dưới; sub-sprite đặt tên cell_XXXX.
Đã làm:
- Thay
FindSpriteIndex(TMP_SpriteAsset, name)(duyệtspriteCharacterTable) bằngParseCellIndex(string)— parse số sau prefixcell_→ index tuyến tính khớp thứ tự cắt. - Áp dụng cho:
PreferSpriteNameTag == false(một frame):<sprite index=N>.- Emoji động (nhiều frame):
start/endtừ frame đầu và frame cuối.
Hệ quả: Không phụ thuộc thứ tự entry trong TMP_SpriteAsset.spriteCharacterTable để khớp index; vẫn cần gán đúng TMP Sprite Asset trên TextMeshProUGUI chat để hiển thị.
5. Đường dẫn file chính
| File | Vai trò |
|---|---|
Assets/PerfectWorld/Scripts/Chat/EmotionTMPTagBuilder.cs |
Build tag (dùng để khớp tag khi TMP → wire) |
Assets/Scripts/ChatInputHandler.cs |
_chatWireBody, AppendEmotionWire / InsertEmoji, đồng bộ input |
Assets/PerfectWorld/Scripts/Chat/UI/EmojiPickerUI.cs |
Gọi InsertEmoji khi chọn emoji |
Assets/PerfectWorld/Scripts/Chat/ChatEmotionDisplayPipeline.cs |
Cảnh báo / SetSpriteMap / ConvertWireBodyToTmpDisplay |
Assets/PerfectWorld/Scripts/Chat/ChatWireTmpCodec.cs |
Wire ↔ TMP body: marshal emotion, TmpBodyToWire |
Assets/PerfectWorld/Scripts/Chat/EmotionData/EmotionLibrarySO.cs |
EmotionSetSnapshot.TmpSpriteAsset |
Assets/PerfectWorld/Scripts/Chat/EmotionData/EmotionLibrarySpriteMap.cs |
TryGetSprite, ParseCellIndex |
Assets/PerfectWorld/Scripts/Network/CSNetwork/AUICommon.cs |
EditBoxItemBase.Serialize (port C++), EditBoxItemsSet + PUA |
Assets/PerfectWorld/Scripts/UI/GamePlay/EC_GameUIMan.cs |
ConvertWireChatBodyForDisplay |
Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs |
ConvertWireBodyForChatPanel (EventBus system) |
7. Chat wire (raw) vs TMP — gửi server & hiển thị (cập nhật)
Mục đích: Giữ chuỗi wire đúng protocol PC (MarshalEditBoxText / PUA + payload <type><…>) khi gửi; ô nhập chỉ hiển thị bản TMP (<sprite>). Khi nhận, tin từ server vẫn là wire; chuyển sang TMP ở pipeline hiển thị (và bổ sung chỗ chỉ đi EventBus).
Đã làm:
EditBoxItemBase.Serialize()(AUICommon.cs) — port từAUICommon.cpp(coord / image / default gồm emotion). Trước đó trả rỗng →MarshalEditBoxTextkhông nối payload sau ký tự PUA.EditBoxItemsSet.IsEditboxItemCode— dùngAUICommon.IsEditboxItemCode(PUA\uE000…) thay khoảng\u0001…\u0010lệch vớiAppendItem.ChatWireTmpCodec.cs(mới)BuildMarshaledEmotionWire(set, index)— một đoạn emotion đã marshal.TmpBodyToWire— parse<sprite…>, khớp tag với output củaEmotionTMPTagBuilder(brute-force set/index trong giới hạn).WireBodyToTmpForDisplay— ủy quyềnChatEmotionDisplayPipeline.ConvertWireBodyToTmpDisplay.
ChatEmotionDisplayPipeline.ConvertWireBodyToTmpDisplay(static) —FilterEmotionSet+ConvertInlineItemsToTmp(dùng cho input và helper).ChatInputHandler- Field
_chatWireBody: nội dung thân tin nhắn ở dạng wire gửi server (sauFilterBadWordsvẫn cập nhật lại). onValueChanged→TmpBodyToWiretrên phần thân (sau prefix kênh / whisper).ParseAndSendMessage/ whisper →SendChat/SendPrivateChatdùng_chatWireBody, không lấy từ chuỗi TMP.- Emoji:
AppendEmotionWire/InsertEmoji— nối wire +RefreshInputDisplayFromWire()(prefix +WireBodyToTmpForDisplay, SuperFarCry dùngGameSession.SUPER_FAR_CRY_EMOTION_SET).
- Field
CECGameUIMan.ConvertWireChatBodyForDisplay— wire → TMP tái sử dụng cho UI.GameSessionConvertWireBodyForChatPanel: wire → TMP cho các nhánh chỉEventBus.Publish(ChatMessageEvent)(system/broadcast), trước đây có thể đẩy raw wire lên panel.OnPrtcWorldChat/OnPrtcPrivateChat/ playerOnPrtcChatMessage: tin người chơi vẫn quaAddChatMessage— wire→TMP đã có trong pipeline; không gọi thêm convert trên body (tránh double conversion). Comment trong code ghi rõ.
Ghi chú: Cần gán EmotionLibrarySpriteMap trên ChatInputHandler để TMP ↔ wire khớp emoji; không gán thì phần thân gần như text thường (emoji marshal không đầy đủ).
6. Việc cần làm trên Editor (một lần)
- Gán
EmotionLibrarySpriteMap(hoặcEmotionLibrarySO) đúng vàoCECUIManagervà các chỗ SerializeField liên quan. - Trong
EmotionLibraryasset: mỗiEmotionSetSnapshottrongSets→ gánTmp Sprite Assettương ứng từng atlas (nếu dùng cho tooling / tài liệu; logic index hiện tại dựa trên têncell_XXXX). - Trên
TextMeshProUGUIchat: gán Sprite Asset khớp atlas đang dùng.
8. Hiển thị wire <0><set:index> & crash khi nhận tin (cập nhật phiên tháng 4/2026)
8.1 Vấn đề hiển thị literal <0><0:23> thay vì TMP
- Nguyên nhân: Chuỗi wire đúng chuẩn PC dùng ký tự PUA (
\uE000–\uE3FF) + payloadSerialize()dạng<0><set:index>. Nếu thiếu PUA (server/encoding, hoặc chỉ còn phần serialize),UnmarshalEditBoxTextkhông tạo item →ConvertInlineItemsToTmptrả về nguyên chuỗi → TMP in literal. - Đã làm: Trong
ChatEmotionDisplayPipeline.ConvertInlineItemsToTmp, sau bước unmarshal/TMP thông thường, thêm fallback regex<0><(\d+):(\d+)>: thay bằng tag TMP quaIEmotionSpriteMap.TryGetSprite+EmotionTMPTagBuilder.BuildSpriteTag(cùng logic với emotion đã marshal đầy đủ). - Vị trí file:
Assets/PerfectWorld/Scripts/Chat/ChatEmotionDisplayPipeline.cs(LooseMarshaledEmotionRegex,ReplaceLooseMarshaledEmotionTags). - Phương án kiến trúc: Ưu tiên sửa một chỗ trong pipeline (
ChatEmotionDisplayPipeline/CECGameUIMan.ConvertWireChatBodyForDisplay), không chỉ trongChatMessageView.Bind, để mini chat và panel chung một luồng.
8.2 Trùng ChatMessageEvent nhánh NPC
- Vấn đề:
OnPrtcChatMessage(NPC) gọiAddChatMessage(đã publish bản wire→TMP), sau đó cònEventBus.Publish(new ChatMessageEvent(message))vớimessagevẫn chứa thân wire trong format string → trùng tin / tin thứ hai chưa convert. - Đã làm: Xóa publish thứ hai; giữ comment giải thích.
- Vị trí file:
Assets/PerfectWorld/Scripts/Network/CSNetwork/GameSession.cs(nhánhISNPCID).
8.3 Crash khi nhận tin có emoji (NullReferenceException)
- Nguyên nhân: Trong
AUICommon.FilterEmotionSet, vòng lặp dùngDictionaryenumerator truy cậpit.Current.Valuetrướcit.MoveNext()— trước lầnMoveNext()đầu tiên,Current.Valuelànull→pItem.GetType()crash. Tin không có emoji (GetItemCount() == 0) không vào vòng lặp nên bug ẩn; tin nhận từ server có PUA emotion thì kích hoạt ngay. - Đã làm: Đổi sang
while (it.MoveNext()) { ... }vàif (pItem == null) continue;(cùng pattern vớiAUI_FilterEditboxItem). - Vị trí file:
Assets/PerfectWorld/Scripts/Network/CSNetwork/AUICommon.cs— hàmFilterEmotionSet.
Cập nhật: tháng 4/2026 — mục 7 (chat wire vs TMP); mục 8 (fallback loose tag, NPC event, fix FilterEmotionSet).
Last update: April 2026 — added section 8 (loose tag fallback, NPC duplicate event, FilterEmotionSet iterator fix).