using System.Collections.Generic; using System.Linq; using TMPro; using UnityEngine; using UnityEngine.UI; namespace Convai.Scripts.Utils { /// /// Manages the chat UI for displaying messages from characters and the player. /// public class ChatBoxUI : ChatUIBase { private const int MAX_MESSAGES = 25; private readonly List _messageList = new(); private GameObject _chatPanel, _textObject; private ScrollRect _chatScrollRect; private Speaker _currentSpeaker; private bool _isFirstMessage = true; /// /// Initializes the chat UI with the specified prefab. /// /// The UI prefab to instantiate. public override void Initialize(GameObject uiPrefab) { UIInstance = Instantiate(uiPrefab); _chatPanel = UIInstance.transform.GetChild(0).GetChild(0).GetChild(0).GetChild(0).gameObject; _textObject = _chatPanel.transform.GetChild(0).gameObject; _chatScrollRect = UIInstance.transform.GetChild(0).GetChild(0).GetComponent(); UIInstance.SetActive(false); } /// /// Sends a message as a character. /// /// The name of the character. /// The message text. /// The color of the character's text. public override void SendCharacterText(string charName, string text, Color characterTextColor) { BroadcastCharacterDialogue(charName, text, characterTextColor); } /// /// Sends a message as the player. /// /// The name of the player. /// The message text. /// The color of the player's text. public override void SendPlayerText(string playerName, string text, Color playerTextColor) { BroadcastPlayerDialogue(playerName, text, playerTextColor); } /// /// Clears all messages from the UI. /// public void ClearUI() { foreach (Message message in _messageList) Destroy(message.TextObject.gameObject); _messageList.Clear(); } // Helper methods and private functions are below. These are not part of the public API // and are used internally by the ChatBoxUI class to manage chat messages. /// /// Broadcasts a dialogue message from a character. /// /// Name of the character. /// Text of the dialogue message. /// Color of the character's text. private void BroadcastCharacterDialogue(string characterName, string text, Color characterTextColor) { string trimmedText = text.Trim(); if (_currentSpeaker != Speaker.Character || _isFirstMessage) { _isFirstMessage = false; _currentSpeaker = Speaker.Character; HandleNewCharacterMessage(characterName, trimmedText, characterTextColor); } else { AppendToExistingMessage(trimmedText); } _currentSpeaker = Speaker.Character; ScrollToBottom(); } /// /// Handles a new dialogue message from a character. /// /// Name of the character. /// Text of the dialogue message. /// Color of the character's text. private void HandleNewCharacterMessage(string characterName, string text, Color characterTextColor) { if (_messageList.Count >= MAX_MESSAGES) DestroyOldestMessage(); CreateNewMessage(text, characterName, characterTextColor); } /// /// Broadcasts a dialogue message from a player. /// /// Name of the player. /// Text of the dialogue message. /// Color of the player's text. private void BroadcastPlayerDialogue(string playerName, string text, Color playerTextColor) { string trimmedText = text.Trim(); if (_currentSpeaker != Speaker.Player || !_messageList.Any()) { _currentSpeaker = Speaker.Player; HandleNewPlayerMessage(playerName, trimmedText, playerTextColor); } else ReplaceExistingPlayerMessage(playerName, trimmedText, playerTextColor); _currentSpeaker = Speaker.Player; ScrollToBottom(); } /// /// Handles a new dialogue message from a player. /// /// Name of the player. /// Text of the dialogue message. /// Color of the player's text. private void HandleNewPlayerMessage(string playerName, string text, Color playerTextColor) { if (_messageList.Count >= MAX_MESSAGES) DestroyOldestMessage(); CreateNewMessage(text, playerName, playerTextColor); } /// /// Replaces an existing player message with a new one. /// /// Name of the player. /// New text of the dialogue message. /// Color of the player's text. private void ReplaceExistingPlayerMessage(string playerName, string text, Color playerTextColor) { Message lastMessage = _messageList[^1]; lastMessage.Text = text; lastMessage.TextObject.text = FormatDialogueText(playerName, text, playerTextColor); } /// /// Appends a text to the existing message. /// /// Text which needs to append to the existing message. private void AppendToExistingMessage(string text) { if (_messageList.Count > 0) { Message lastMessage = _messageList[^1]; lastMessage.Text += " " + text; lastMessage.TextObject.text += " " + text; } } /// /// private void ScrollToBottom() { Canvas.ForceUpdateCanvases(); _chatScrollRect.verticalNormalizedPosition = 0f; } /// /// Formats the dialogue text. /// /// Name of the speaker. /// Text of the dialogue message. /// Color of the speaker's text. /// Formatted dialogue text with color tag and speaker's name. private static string FormatDialogueText(string speakerName, string text, Color speakerColor) { string speakerColorHtml = ColorUtility.ToHtmlStringRGB(speakerColor); return $"{speakerName}: {text}"; } /// /// Destroys the oldest message in the chat UI. /// private void DestroyOldestMessage() { Destroy(_messageList[0].TextObject.gameObject); _messageList.RemoveAt(0); } /// /// Creates a new dialogue message. /// /// Text of the dialogue message. /// Name of the speaker. /// Color of the speaker's text. private void CreateNewMessage(string text, string speakerName, Color speakerColor) { Message newMessage = new() { Text = text, TextObject = Instantiate(_textObject, _chatPanel.transform).GetComponent() }; // If the speaker is the player, disable the feedback icon GameObject feedbackButtons = newMessage.TextObject.transform.GetChild(0).gameObject; feedbackButtons.SetActive(_currentSpeaker == Speaker.Character); newMessage.TextObject.text = FormatDialogueText(speakerName, text, speakerColor); _messageList.Add(newMessage); } /// /// Enumeration of the possible speakers. /// private enum Speaker { Player, Character } /// /// Class to keep track of individual chat messages. /// private class Message { public string Text { get; set; } public TMP_Text TextObject { get; set; } } } }