TH1/Unity/Assets/Scripts/TH1_UI/View/Bottom/UIBottomNetChatAreaMono.cs
2026-04-13 16:09:33 +08:00

334 lines
8.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using TH1_Logic.Chat;
using TH1_Logic.Net;
using TH1_Logic.Steam;
using TH1_UI.View.Bottom.Chat;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace TH1_UI.View.Bottom
{
/// <summary>
/// 纯MonoBehaviour聊天区域挂在UIBottomNet prefab上。
/// 拉取模式:订阅 ChatManager.OnChatUpdated对比 _displayedCount 增量显示新消息。
/// </summary>
public class UIBottomNetChatAreaMono : MonoBehaviour
{
// ---- 对外事件 ----
public Action<string> OnSendMessage;
// ---- 静态访问,供 InputLogic 查询/操作输入框 ----
public static bool IsInputFieldFocused => _instance != null
&& _instance._inputAreaVisible
&& _instance.TextInputArea != null
&& _instance.TextInputArea.isFocused;
public static bool IsInputAreaVisible => _instance != null
&& _instance._inputAreaVisible;
public static bool IsActive => _instance != null && _instance._initialized;
public static void HideInputArea()
{
if (_instance != null)
_instance.SetInputAreaVisible(false);
}
public static void ShowInputArea()
{
if (_instance != null)
_instance.SetInputAreaVisible(true);
}
public static void FocusInputField()
{
if (_instance != null && _instance.TextInputArea != null)
_instance.TextInputArea.ActivateInputField();
}
/// <summary>
/// Enter键专用有文字就发送没文字就关闭。
/// </summary>
public static void SendOrClose()
{
if (_instance == null) return;
if (_instance.TextInputArea == null) return;
string text = _instance.TextInputArea.text;
if (!string.IsNullOrWhiteSpace(text))
{
_instance.OnSendMessage?.Invoke(text.Trim());
_instance.TextInputArea.text = string.Empty;
_instance.TextInputArea.ActivateInputField();
}
else
{
_instance.SetInputAreaVisible(false);
}
}
private static UIBottomNetChatAreaMono _instance;
// ---- Inspector 绑定 ----
[Header("ChatButton - 控制输入区显隐")]
public Button ChatButton;
[Header("ChatShowArea - 常驻消息显示区")]
public RectTransform ChatShowArea;
public GameObject ChatPrefab; // NetChat_ChatPrefab
[Header("ChatInputArea - 输入区")]
public GameObject ChatInputArea;
public TMP_InputField TextInputArea;
public Button SendButton;
[Header("配置")]
public int MaxChatCount = 50;
public float MessageLifetime = 10f;
// ---- 内部数据 ----
private readonly List<ChatRowEntry> _chatRows = new List<ChatRowEntry>();
private bool _inputAreaVisible;
private bool _initialized;
private struct ChatRowEntry
{
public ChatMessageRowMono Row;
public float SpawnTime;
}
// ------------------------------------------------------------------
// Lifecycle
// ------------------------------------------------------------------
private void OnDestroy()
{
Shutdown();
if (_instance == this) _instance = null;
}
private void Update()
{
if (!_initialized || _chatRows.Count == 0) return;
// 从头部检查过期消息
float now = Time.time;
while (_chatRows.Count > 0)
{
var entry = _chatRows[0];
if (now - entry.SpawnTime < MessageLifetime) break;
_chatRows.RemoveAt(0);
if (entry.Row != null)
Destroy(entry.Row.gameObject);
}
}
/// <summary>
/// 外部调用的初始化,跟随 NetInfo 一起触发。
/// ChatButton + ChatShowArea 显示并保持活跃InputArea 隐藏。
/// </summary>
public void Init()
{
if (_initialized) return;
_initialized = true;
_instance = this;
// 标记现有消息为已显示,不回显历史
foreach (var item in ChatManager.Instance.ChatItems)
item.AlreadyUIShow = true;
// 确保 ChatButton 和 ChatShowArea 可见
if (ChatButton != null)
ChatButton.gameObject.SetActive(true);
if (ChatShowArea != null)
ChatShowArea.gameObject.SetActive(true);
// 隐藏输入区
SetInputAreaVisible(false);
// 绑定按钮事件
if (ChatButton != null)
ChatButton.onClick.AddListener(ToggleInputArea);
if (SendButton != null)
SendButton.onClick.AddListener(OnSendButtonClick);
if (TextInputArea != null)
TextInputArea.onSubmit.AddListener(OnInputSubmit);
// 订阅 ChatManager 变更回调
ChatManager.Instance.OnChatUpdated += RefreshChatBox;
}
/// <summary>
/// 关闭并清理,可由外部或 OnDestroy 调用。
/// </summary>
public void Shutdown()
{
if (!_initialized) return;
_initialized = false;
// 取消订阅
ChatManager.Instance.OnChatUpdated -= RefreshChatBox;
if (ChatButton != null)
ChatButton.onClick.RemoveListener(ToggleInputArea);
if (SendButton != null)
SendButton.onClick.RemoveListener(OnSendButtonClick);
if (TextInputArea != null)
TextInputArea.onSubmit.RemoveListener(OnInputSubmit);
ClearAll();
}
// ------------------------------------------------------------------
// Public API
// ------------------------------------------------------------------
/// <summary>
/// 添加一条系统消息不经过ChatManager直接显示
/// </summary>
public void AddSystemMessage(string message)
{
var data = new ChatMessageData
{
SenderName = string.Empty,
Message = message,
IsSelf = false,
IsSystem = true,
Timestamp = DateTime.Now
};
CreateChatRow(data);
}
/// <summary>
/// 清空所有已显示的消息行
/// </summary>
public void ClearAll()
{
for (int i = _chatRows.Count - 1; i >= 0; i--)
{
if (_chatRows[i].Row != null)
Destroy(_chatRows[i].Row.gameObject);
}
_chatRows.Clear();
}
// ------------------------------------------------------------------
// Core: 拉取 ChatManager 增量刷新
// ------------------------------------------------------------------
private void RefreshChatBox()
{
if (!_initialized) return;
var items = ChatManager.Instance.ChatItems;
var lobby = LobbyManager.Instance.Lobby;
ulong selfId = lobby.GetSelfMemberId();
for (int i = 0; i < items.Count; i++)
{
var item = items[i];
if (item.AlreadyUIShow) continue;
item.AlreadyUIShow = true;
// 解析发言者名称
var memberInfo = lobby.GetMemberInfo(item.SenderId);
string senderName = memberInfo?.Name;
if (string.IsNullOrEmpty(senderName))
senderName = $"玩家{item.SenderId}";
bool isSelf = item.SenderId == selfId;
var data = new ChatMessageData
{
SenderName = senderName,
Message = item.Message,
IsSelf = isSelf,
IsSystem = false,
Timestamp = DateTime.Now
};
CreateChatRow(data);
}
}
// ------------------------------------------------------------------
// Internal
// ------------------------------------------------------------------
private void ToggleInputArea()
{
SetInputAreaVisible(!_inputAreaVisible);
}
private void SetInputAreaVisible(bool visible)
{
_inputAreaVisible = visible;
if (ChatInputArea != null)
ChatInputArea.SetActive(visible);
if (visible && TextInputArea != null)
TextInputArea.ActivateInputField();
}
private void OnSendButtonClick()
{
TrySendMessage();
}
private void OnInputSubmit(string _)
{
TrySendMessage();
}
private void TrySendMessage()
{
if (TextInputArea == null) return;
string text = TextInputArea.text;
if (string.IsNullOrWhiteSpace(text)) return;
OnSendMessage?.Invoke(text.Trim());
TextInputArea.text = string.Empty;
TextInputArea.ActivateInputField();
}
private void CreateChatRow(ChatMessageData data)
{
if (ChatPrefab == null || ChatShowArea == null) return;
// 超出上限,移除最早的消息
while (_chatRows.Count >= MaxChatCount)
{
var oldest = _chatRows[0];
_chatRows.RemoveAt(0);
if (oldest.Row != null)
Destroy(oldest.Row.gameObject);
}
var go = Instantiate(ChatPrefab, ChatShowArea);
var row = go.GetComponent<ChatMessageRowMono>();
if (row != null)
{
row.SetContent(data);
_chatRows.Add(new ChatRowEntry { Row = row, SpawnTime = Time.time });
}
else
{
Destroy(go);
}
}
}
/// <summary>
/// 单条聊天消息数据
/// </summary>
public class ChatMessageData
{
public string SenderName;
public string Message;
public bool IsSelf;
public bool IsSystem;
public DateTime Timestamp;
}
}