8.8 KiB
Luồng Hoạt Động Của Hệ Thống Chat (C#)
Tài liệu này tổng hợp luồng xử lý của hệ thống Chat S2C và C2S trong client, dựa trên hai hàm cốt lõi SendChatData và OnPrtcChatMessage thuộc lớp GameSession.cs.
1. Luồng Gửi Tin Nhắn (C2S) - Hàm SendChatData
Hàm này được gọi khi người chơi nhập tin nhắn ở Local và muốn gửi lên Server (hoặc sử dụng vật phẩm liên quan đến Chat). Đặc biệt, Server sẽ KHÔNG tự động gửi lại (echo) tin nhắn cho người gửi, do đó client phải chủ động tự hiển thị tin nhắn của chính mình lên UI.
Các bước xử lý:
-
Khởi tạo và đóng gói dữ liệu:
- Tạo gói tin C2S
publicchatvà thiết lập các thông số cơ bản (Channel,Roleidcủa người gửi).
- Tạo gói tin C2S
-
Xử lý đính kèm vật phẩm (nếu có):
- Vật phẩm thẻ (
GENERALCARD_PACK): Nếu tin nhắn được gửi cùng một thẻ (card), hệ thống sẽ tìm trong túi đồ client (Client Pack) theo slot (iSlot), lấy racardIdvà chèn byte data lệnhCHAT_GENERALCARD_COLLECTIONvào giao thức. - Vật phẩm trang bị thông thường: Nếu
iPackvàiSlothợp lệ, đóng gói định dạngCHAT_EQUIP_ITEMgồm vị trí túiwherevà vị trí slotindexchèn vào gói tin.
- Vật phẩm thẻ (
-
Gửi gói tin lên Server:
- Tin nhắn chuỗi (
szMsg) được mã hóa sangUTF-16 LE(Unicode trong C#) và lưu vàoMsg(kiểu Octets). - Gọi
SendProtocol(p)để đẩy dữ liệu xuống tầng Network và gửi đi.
- Tin nhắn chuỗi (
-
Hiển thị tin nhắn Local ngay lập tức:
- Xác định
nEmotionSet(nếu có), ví dụ kênh Super Far Cry sẽ có hiệu ứng bong bóng đầu đặc biệt (trim 8 character cuối của text). - Bong bóng đầu (Head Bubble): Áp dụng nếu kênh thuộc dạng lân cận (
GP_CHAT_LOCAL,GP_CHAT_FARCRY,GP_CHAT_SUPERFARCRY,GP_CHAT_BATTLE,GP_CHAT_COUNTRY). - Lấy tên Host Player, ghép chuỗi với định dạng text của hệ thống (sử dụng cố định
FIXMSG_CHAT). - Gọi
EC_Game.GetGameRun().AddChatMessage(...): Hàm này sẽ đẩy dữ liệu lên UI Chat Box và tự động xử lý bong bóng trên đầu nhân vật.
- Xác định
2. Luồng Nhận Tin Nhắn (S2C) - Hàm OnPrtcChatMessage
Hàm này nhận giao thức chatmessage từ Server và xử lý việc hiển thị lên kênh chat, bong bóng đầu player/NPC.
Vì hàm OnPrtcChatMessage đang quá lớn nên tôi chia ra thêm 1 class Chat_GameSession để xử lý các logic nhỏ.
Các bước xử lý:
-
Tiền xử lý và Lọc (Filter):
- Kiểm tra cấp độ người gửi qua
Chat_GameSession.ShouldBlockByLevel(). Nếu bị chặn, hàm ngắt ngang và bỏ qua tin nhắn. - Tạo
EC_IvtrItem(Chat Item) từ dải byte đính kèm (nếu có vật phẩm). - Lọc các tag mã hóa không hợp lệ thông qua
AUICommon.FilterInvalidTags. - Chạy qua các policy chặn lọc hệ thống (
Chat_GameSession.PolicyResolver()) để ra chuỗi tinh chỉnh cuối cùng (szMsg).
- Kiểm tra cấp độ người gửi qua
-
Phân loại Channel và RoleID để xử lý:
Hệ thống phân chia theo 3 nhóm luồng chính:
Nhóm A: Broadcast, System, hoặc System Role (Srcroleid == 0)
- Nếu
ChannellàGP_CHAT_SYSTEMvà cóSrcroleid > 0, tin nhắn thuộc các thông báo riêng biệt của cục bộ tính năng, được chia nhánh theoSrcroleid(Hardcode ID):- ID = 1,2,3,4,6,7: Bản tin Chiến trường (Battle Message).
- ID = 18..22: Bản tin Đấu giá (Auction Message). Gọi
ChatMessageEventđưa lên kênh system. - ID = 24: Bản tin Nhiệm vụ (Task Message).
- ID = 29..45: Bản tin Pháo đài, Lãnh thổ (Fortress Message).
- ID = 46..49: Bản tin Quốc chiến (Country Battle).
- ID = 50..59: Bản tin Vua (King Chat).
- ID = 60..64: Bản tin PVP Bang hội (Faction PVP).
(Lưu ý: Một số hàm của luồng C++ đang bị ẩn/ẩn implementation, nếu cần data pending chưa có sẽ hoãn và chờ lần gọi lại (
bCalledagain).)
- Nếu không thuộc các ID đặc biệt trên, hệ thống đơn giản là phát ra
ChatMessageEventđẩy thẳng tin nhắn lên luồng EventBus.
Nhóm B: Instance Channel (
GP_CHAT_INSTANCE&Srcroleid == 1)- Tin nhắn riêng của Phó bản, hiện tại được thiết kế để ném vào luồng
AddHeartBeatHintở UIMan.
Nhóm C: Dành cho người chơi khác (Player) hoặc NPC
Sử dụng chuỗi định dạng lấy từ
FixedMsgkết hợp tên người / NPC.-
Nếu là người chơi (
ISPLAYERID):- Request tên người chơi
GetPlayerName. - Thiếu Data: Nếu không tìm thấy tên player trong cache, tin nhắn yêu cầu Server lấy tên (
AddChatPlayerID) và treo giao thức chat này vào Pending Protoco (AddElemForPendingProtocols). Giao thức này sẽ tự kích hoạt lại ở Tick sau khi Server trả về tên. - Có Data: Xây dựng câu (kiểu
[Kênh] Player: Message), gọiAddChatMessage(...)đẩy vào hộp thoại Chat và bắn bong bóng đầu tự động.
- Request tên người chơi
-
Nếu là NPC (
ISNPCID):- Tìm kiếm
CECNPCtừ danh sáchNPCMan. - Dựng chuỗi bằng format
FIXMSG_CHAT2. - Gọi
AddChatMessage()cho chat panel vàEventBus.Publish(ChatMessageEvent)cho UI liên quan. - Lọc riêng Name Flag trên đầu NPC, rồi gán nội dung bằng hàm
pNPC.SetLastSaidWords(szMsg)để hiện Bubble Text trên mô hình 3D NPC.
- Tìm kiếm
- Nếu
3. Phân Tích Khác Biệt Giữa C++ và C# (Triển khai OnPrtcChatMessage)
Trong quá trình chuyển đổi (port) hệ thống Chat từ C++ sang C# (Unity), kiến trúc đã được thay đổi từ mô hình liên kết chặt chẽ (Tight Coupling) sang mô hình hướng sự kiện (Event-Driven) nhằm tách biệt hoàn toàn tầng Network và tầng UI/Logic Game.
Dưới đây là các điểm khác biệt chính:
| Tiêu chí | C++ (EC_GameSession.cpp) |
C# Unity (GameSession.cs & UIPlayer.cs) |
|---|---|---|
| Kiến trúc liên kết (Giao tiếp với mô hình 3D) | Tầng Network gọi trực tiếp vào hệ thống quản lý Game World. Code chủ động tìm kiếm player: CECPlayer *pPlayer = GetWorld()->GetPlayerMan()->GetPlayer(...) rồi gọi trực tiếp hàm pPlayer->SetLastSaidWords(strTemp, p->emotion, pItem);. |
Gọi thông qua kiến trúc kênh Pub/Sub: EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg));. Tầng Network không cần biết đối tượng 3D có tồn tại hay không. |
| Bong bóng Chat trên đầu (Head Bubble) | Đối tượng CECPlayer tự tính toán, lưu trữ Text và dựa vào Tick update trung tâm C++ để hiển thị/tắt text sau một khoảng thời gian. |
Giao diện hiển thị UIPlayer.cs (gắn trên Prefab nhân vật) tự đăng ký sự kiện (EventBus.SubscribeChannel). Khi nhận event, tự đổi chuỗi TextMeshProUGUI, hiện Text và gọi một UniTask (HideChatAsync) độc lập để tự động tắt sau 5 giây. |
| Xử lý Thread/Đa luồng | Toàn bộ Network và Logic xử lý trên chung một luồng chính đồng bộ. | Tầng Network (nhận Packet) và tầng UI hiển thị Unity có thể bất đồng bộ. Vì vậy C# dùng ChatThreadDispatcher.Instance.Post(...) trong UIPlayer.SetChatMessage để đồng bộ an toàn việc gán giá trị UI lên luồng chính của Unity. |
| Bảo trì / Mở rộng | Gắn chặt UI với Data Network. Nếu UI thay đổi, sửa Network code. | Decoupled (Giảm kết dính). Việc hiển thị UI chat (khung Chat Panel lẫn Head Bubble trên đầu nhân vật) hoạt động độc lập và chỉ "lắng nghe" dữ liệu. |
Ví Dụ Chi Tiết (Luồng Head Bubble Player):
- C++: Trong
OnPrtcChatMessage, code thực hiện:pPlayer->SetLastSaidWords(strTemp, p->emotion, pItem);Tức là bắt ép đối tượng thực hiện cập nhật UI. - C#: Trong
GameSession.cs(hoặc bên trongAddChatMessage), hệ thống gọi:EventBus.PublishChannel(p.Srcroleid, new EventChatMessageOnTopPlayer(p.Srcroleid, strMsg));Tương ứng bên kia, trongUIPlayer.cschạy ở hàmStart():EventBus.SubscribeChannel<EventChatMessageOnTopPlayer>(hostplayer.GetCharacterID(), SetChatMessage);Khi event kích hoạt, hàmSetChatMessagesẽ nhận Context và gỡ nó ra gán vào Text Mesh UI một cách an toàn.