Files
2026-04-22 16:32:58 +07:00

410 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using CSNetwork;
using CSNetwork.GPDataType;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
namespace BrewMonster.Scripts.ChatUI
{
[Serializable]
public struct ChatMessageData
{
public string message;
public byte channel;
}
public class ChatSystemlUI : MonoBehaviour
{
[Header("ChatPanelUI")] public ScrollRect scrollRect;
[Tooltip("Nền/fullscreen block — bật/tắt cùng lúc với chatPanelUIGO (có thể để null).")]
public GameObject BgGameObject;
public GameObject chatPanelUIGO;
public RectTransform content;
public ChatMessageView messagePrefab;
public Button closeChatPanelButton;
public Button closeBGChatPanelButton;
public Button emojiButton;
public Button sendButton;
[Header("EmojiPanelUI")]
public GameObject EmojiPanelUI;
public Button closeEmojiPanelButton;
[Header("Config")] public int maxVisibleMessages = 30;
public int maxStoredMessages = 2000;
[Header("Chat System Data")]
public ChatSystemSO chatSystemSO;
private Dictionary<byte, Sprite> _iconCache;
private List<ChatMessageData> _messages = new();
private List<ChatMessageView> _visibleViews = new();
private List<ChatMessageData> _filteredMessagesCache = new();
private ObjectPool<ChatMessageView> _pool;
private bool _userAtBottom = true;
private ChatChannel _currentFilterChannel = ChatChannel.GP_CHAT_LOCAL;
ChatInputHandler _chatInput;
void Awake()
{
ClearChat();
_iconCache = new Dictionary<byte, Sprite>();
if (chatSystemSO != null && chatSystemSO.channelIcons != null)
{
foreach (var mapping in chatSystemSO.channelIcons)
{
_iconCache[(byte)mapping.channel] = mapping.icon;
}
}
EventBus.Subscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Subscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Subscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
EventBus.Subscribe<OpenChatPanelRequestedEvent>(OnOpenChatPanelRequested);
_pool = new ObjectPool<ChatMessageView>(
CreateItem,
OnGetItem,
OnReleaseItem,
OnDestroyItem,
false,
10,
100
);
scrollRect.onValueChanged.AddListener(OnScrollChanged);
SetChatPanelAndBgVisible(false);
}
/// <summary>Bật/tắt panel chat và BG cùng trạng thái (2 GO tách nhưng luồng giống nhau).</summary>
void SetChatPanelAndBgVisible(bool visible)
{
if (chatPanelUIGO != null)
chatPanelUIGO.SetActive(visible);
if (BgGameObject != null)
BgGameObject.SetActive(visible);
}
void OnEnable()
{
if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf || _pool == null || content == null)
return;
RefreshVisible();
}
void Start()
{
_chatInput = GetComponent<ChatInputHandler>();
WireUiButtons();
}
void WireUiButtons()
{
if (closeChatPanelButton != null)
closeChatPanelButton.onClick.AddListener(CloseChatPanel);
if (closeBGChatPanelButton != null)
closeBGChatPanelButton.onClick.AddListener(CloseChatPanel);
if (emojiButton != null)
emojiButton.onClick.AddListener(ToggleEmojiPanel);
if (closeEmojiPanelButton != null)
closeEmojiPanelButton.onClick.AddListener(CloseEmojiPanel);
if (sendButton != null)
sendButton.onClick.AddListener(OnSendButtonClicked);
}
private void OnDestroy()
{
if (closeChatPanelButton != null)
closeChatPanelButton.onClick.RemoveListener(CloseChatPanel);
if (closeBGChatPanelButton != null)
closeBGChatPanelButton.onClick.RemoveListener(CloseChatPanel);
if (emojiButton != null)
emojiButton.onClick.RemoveListener(ToggleEmojiPanel);
if (closeEmojiPanelButton != null)
closeEmojiPanelButton.onClick.RemoveListener(CloseEmojiPanel);
if (sendButton != null)
sendButton.onClick.RemoveListener(OnSendButtonClicked);
EventBus.Unsubscribe<OnEventClearChat>(OnChatMessageClear);
EventBus.Unsubscribe<GameSession.ChatMessageEvent>(OnChatMessageReceived);
EventBus.Unsubscribe<ChatChannelFilterChangedEvent>(OnChannelFilterChanged);
EventBus.Unsubscribe<OpenChatPanelRequestedEvent>(OnOpenChatPanelRequested);
if (scrollRect != null)
scrollRect.onValueChanged.RemoveListener(OnScrollChanged);
if (_pool != null)
{
_pool.Clear();
_pool.Dispose();
}
}
private void OnChannelFilterChanged(ChatChannelFilterChangedEvent e)
{
if (this == null) return;
_currentFilterChannel = e.channel;
if (chatPanelUIGO != null && chatPanelUIGO.activeSelf)
RefreshVisible();
}
void OnOpenChatPanelRequested(OpenChatPanelRequestedEvent _)
{
if (this == null) return;
OpenChatPanel(_.focusInputOnOpen);
}
private bool ShouldShowMessage(ChatMessageData data)
{
if (_currentFilterChannel == ChatChannel.GP_CHAT_LOCAL) return true;
if (data.channel == (byte)ChatChannel.GP_CHAT_MISC) return true;
return data.channel == (byte)_currentFilterChannel;
}
private void OnChatMessageReceived(GameSession.ChatMessageEvent x)
{
ChatThreadDispatcher.Instance.Post(() =>
{
if (this == null) return;
AddMessage(x.context, x.channel);
});
}
private void OnChatMessageClear(OnEventClearChat obj)
{
ChatThreadDispatcher.Instance.Post(() =>
{
if (this == null) return;
ClearChat();
});
}
ChatMessageView CreateItem()
{
if (messagePrefab == null || content == null) return null;
var item = Instantiate(messagePrefab);
item.transform.SetParent(content, false);
return item;
}
void OnGetItem(ChatMessageView item)
{
if (item != null)
item.gameObject.SetActive(true);
}
void OnReleaseItem(ChatMessageView item)
{
if (item != null)
item.gameObject.SetActive(false);
}
void OnDestroyItem(ChatMessageView item)
{
if (item != null)
Destroy(item.gameObject);
}
void OnScrollChanged(Vector2 pos)
{
_userAtBottom = scrollRect.verticalNormalizedPosition <= 0.001f;
}
bool IsAtBottom()
{
return scrollRect.verticalNormalizedPosition <= 0.001f;
}
public void AddMessage(string msg, byte channel)
{
if (this == null) return;
var data = new ChatMessageData { message = msg, channel = channel };
_messages.Add(data);
if (_messages.Count > maxStoredMessages)
_messages.RemoveAt(0);
if (chatPanelUIGO == null || !chatPanelUIGO.activeSelf)
return;
if (ShouldShowMessage(data))
{
AddMessageView(data);
if (_userAtBottom)
ScrollToBottom();
}
}
void AddMessageView(ChatMessageData data)
{
if (this == null) return;
var view = _pool.Get();
if (view == null) view = CreateItem();
if (view == null) return;
view.transform.SetParent(content, false);
view.transform.SetAsLastSibling();
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
view.Bind(icon, data.message);
_visibleViews.Add(view);
if (_visibleViews.Count > maxVisibleMessages)
{
var old = _visibleViews[0];
_visibleViews.RemoveAt(0);
if (old != null)
{
_pool.Release(old);
}
}
Canvas.ForceUpdateCanvases();
}
void RefreshVisible()
{
foreach (var view in _visibleViews)
{
if (view != null)
_pool.Release(view);
}
_visibleViews.Clear();
_filteredMessagesCache.Clear();
foreach (var msg in _messages)
{
if (ShouldShowMessage(msg))
{
_filteredMessagesCache.Add(msg);
}
}
int start = Mathf.Max(0, _filteredMessagesCache.Count - maxVisibleMessages);
for (int i = start; i < _filteredMessagesCache.Count; i++)
{
var view = _pool.Get();
if (view == null) view = CreateItem();
if (view == null) continue;
view.transform.SetParent(content, false);
view.transform.SetAsLastSibling();
var data = _filteredMessagesCache[i];
Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null;
view.Bind(icon, data.message);
_visibleViews.Add(view);
}
Canvas.ForceUpdateCanvases();
ScrollToBottom();
}
/// <summary>Cuộn log chat xuống dòng cuối (sau khi layout xong).</summary>
public void ScrollToBottom()
{
if (scrollRect == null) return;
if (content != null)
LayoutRebuilder.ForceRebuildLayoutImmediate(content);
Canvas.ForceUpdateCanvases();
scrollRect.verticalNormalizedPosition = 0f;
}
public void ClearChat()
{
foreach (var view in _visibleViews)
{
if (view != null)
_pool.Release(view);
}
_visibleViews.Clear();
_messages.Clear();
}
public void OnHandlerChatButton()
{
if (chatPanelUIGO == null) return;
bool open = !chatPanelUIGO.activeSelf;
SetChatPanelAndBgVisible(open);
if (open)
RefreshVisible();
else
SetEmojiPanelVisible(false);
}
public void OpenChatPanel()
{
OpenChatPanel(true);
}
public void OpenChatPanel(bool focusInputOnOpen)
{
if (chatPanelUIGO == null) return;
SetChatPanelAndBgVisible(true);
RefreshVisible();
_chatInput ??= GetComponent<ChatInputHandler>();
if (focusInputOnOpen && _chatInput != null && _chatInput.inputField != null)
_chatInput.inputField.ActivateInputField();
}
public void CloseChatPanel()
{
SetChatPanelAndBgVisible(false);
SetEmojiPanelVisible(false);
}
public void ToggleEmojiPanel()
{
if (EmojiPanelUI == null) return;
SetEmojiPanelVisible(!EmojiPanelUI.activeSelf);
}
public void CloseEmojiPanel()
{
SetEmojiPanelVisible(false);
}
void SetEmojiPanelVisible(bool visible)
{
if (EmojiPanelUI != null)
EmojiPanelUI.SetActive(visible);
}
void OnSendButtonClicked()
{
_chatInput ??= GetComponent<ChatInputHandler>();
_chatInput?.SubmitFromSendButton();
}
}
public struct OnEventClearChat { }
/// <summary>
/// Mini chat (hoặc HUD) publish để mở panel chat đầy đủ; <see cref="ChatSystemlUI"/> subscribe.
/// </summary>
public struct OpenChatPanelRequestedEvent
{
public bool focusInputOnOpen;
public OpenChatPanelRequestedEvent(bool focusInputOnOpen)
{
this.focusInputOnOpen = focusInputOnOpen;
}
}
}