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 ChatPanelUI : MonoBehaviour { [Header("UI")] public ScrollRect scrollRect; public GameObject chatPanelUIGO; public RectTransform content; public ChatMessageView messagePrefab; //public GameObject newMessageIndicator; [Header("Config")] public int maxVisibleMessages = 30; public int maxStoredMessages = 2000; [Header("Chat System Data")] public ChatSystemSO chatSystemSO; private Dictionary _iconCache; private List _messages = new(); private List _visibleViews = new(); private ObjectPool _pool; private bool _userAtBottom = true; void Awake() { _iconCache = new Dictionary(); if (chatSystemSO != null && chatSystemSO.channelIcons != null) { foreach (var mapping in chatSystemSO.channelIcons) { _iconCache[(byte)mapping.channel] = mapping.icon; } } EventBus.Subscribe(OnChatMessageReceived); _pool = new ObjectPool( CreateItem, OnGetItem, OnReleaseItem, OnDestroyItem, false, 10, 100 ); scrollRect.onValueChanged.AddListener(OnScrollChanged); } private void OnDestroy() { EventBus.Unsubscribe(OnChatMessageReceived); } private void OnChatMessageReceived(GameSession.ChatMessageEvent x) { ChatThreadDispatcher.Instance.Post(() => { AddMessage(x.context, x.channel); }); } ChatMessageView CreateItem() { var item = Instantiate(messagePrefab); item.transform.SetParent(content, false); return item; } void OnGetItem(ChatMessageView item) { item.gameObject.SetActive(true); } void OnReleaseItem(ChatMessageView item) { item.gameObject.SetActive(false); } void OnDestroyItem(ChatMessageView item) { Destroy(item.gameObject); } void OnScrollChanged(Vector2 pos) { _userAtBottom = scrollRect.verticalNormalizedPosition <= 0.001f; if (_userAtBottom) { //newMessageIndicator.SetActive(false); } } bool IsAtBottom() { return scrollRect.verticalNormalizedPosition <= 0.001f; } public void AddMessage(string msg, byte channel) { var data = new ChatMessageData { message = msg, channel = channel }; _messages.Add(data); if (_messages.Count > maxStoredMessages) _messages.RemoveAt(0); if (!chatPanelUIGO.activeSelf) return; AddMessageView(data); if (_userAtBottom) ScrollToBottom(); } void AddMessageView(ChatMessageData data) { var view = _pool.Get(); 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); _pool.Release(old); } Canvas.ForceUpdateCanvases(); } void RefreshVisible() { foreach (var view in _visibleViews) _pool.Release(view); _visibleViews.Clear(); int start = Mathf.Max(0, _messages.Count - maxVisibleMessages); for (int i = start; i < _messages.Count; i++) { var view = _pool.Get(); view.transform.SetParent(content, false); view.transform.SetAsLastSibling(); var data = _messages[i]; Sprite icon = _iconCache.ContainsKey(data.channel) ? _iconCache[data.channel] : null; view.Bind(icon, data.message); _visibleViews.Add(view); } Canvas.ForceUpdateCanvases(); ScrollToBottom(); } public void ScrollToBottom() { Canvas.ForceUpdateCanvases(); scrollRect.verticalNormalizedPosition = 0f; //newMessageIndicator.SetActive(false); } public void ClearChat() { foreach (var view in _visibleViews) _pool.Release(view); _visibleViews.Clear(); _messages.Clear(); } public void OnHandlerChatButton() { bool open = !chatPanelUIGO.activeSelf; chatPanelUIGO.SetActive(open); if (open) RefreshVisible(); } } }