Unity C#打字游戏文本溢出处理:截断超长行而非整行移除
打字游戏文本溢出截断方案
问题描述
我开发的打字游戏从文本文件读取行内容存入字符串数组,文本框会显示尽可能多的内容,当前显示的所有行输入完成后会移除并加载下一批。目前的逻辑是添加行直到某行导致溢出就停止添加整行,但部分行长度超过文本框总长度,需要实现仅截断溢出的文本部分,保留可显示的行内容,且原文本文件不可修改。
原脚本
using UnityEngine; using TMPro; using System.Collections.Generic; public class TypingGameManager : MonoBehaviour { // Reference to the TextMeshProUGUI component public TextMeshProUGUI textMeshPro; // Name of the text file in the Resources folder (without extension) public string fileName = "YourTextFile"; // Color for typed characters public Color32 typedColor = new Color32(0, 255, 0, 255); // Green color public List<string> allLines; public int currentLineIndex = 0; public int currentCharIndex = 0; public int linesDisplayed = 0; public int linesShown; // Typewriter audio components public AudioClip typingSound; public AudioSource audioSource; void Start() { // Load the text asset from the Resources folder TextAsset textAsset = Resources.Load<TextAsset>(fileName); // Check if the text asset was found if (textAsset != null) { // Split the text into lines allLines = new List<string>(textAsset.text.Split(new[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries)); // Set TextMeshPro overflow mode to truncate textMeshPro.overflowMode = TextOverflowModes.Truncate; // Display the first batch of lines in the TextMeshPro component UpdateTextDisplay(); } else { Debug.LogError("File not found in Resources: " + fileName); } // Set the typing sound audioSource.clip = typingSound; } void Update() { if (currentLineIndex < allLines.Count) { if (currentCharIndex < allLines[currentLineIndex].Length) { // Check if a key was pressed if (Input.anyKeyDown) { // Get the pressed key string input = Input.inputString; if (input.Length > 0) { char typedChar = input[0]; // Check if the typed character matches the current character in the line if (typedChar == allLines[currentLineIndex][currentCharIndex]) { audioSource.Play(); // Move to the next character currentCharIndex++; // Update the display UpdateTextDisplay(); } } } } else { // Move to the next line or refill if all characters in the current lines have been typed if (currentLineIndex + 1 < allLines.Count) { currentLineIndex++; currentCharIndex = 0; UpdateTextDisplay(); } if (currentLineIndex == linesShown) { // If all lines have been typed, refill the text box with new lines RefillTextBox(); } } } } void UpdateTextDisplay() { // Combine all lines into a single string with typed characters colored string displayedText = ""; string testText = ""; linesShown = 0; for (int i = 0; i < allLines.Count; i++) { if (i >= linesDisplayed) { if (i == currentLineIndex) { // Separate the typed part and the remaining part of the current line string typedText = allLines[i].Substring(0, currentCharIndex); string remainingText = allLines[i].Substring(currentCharIndex); // Create a colored string for the typed part string coloredTypedText = $"<color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}>{typedText}</color>"; // Append the current line with colored typed text testText += coloredTypedText + remainingText; } else if (i < currentLineIndex) { // Previous lines, fully typed testText += $"<color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}>{allLines[i]}</color>"; } else { // Future lines, not yet typed testText += allLines[i]; } // Add a newline character between lines if (i < allLines.Count - 1) { testText += "\n"; } // Temporarily set text to calculate height textMeshPro.text = testText; textMeshPro.ForceMeshUpdate(); // Check if text overflows if (textMeshPro.isTextOverflowing) { break; } else { displayedText = testText; linesShown++; } } } // Update the TextMeshPro text textMeshPro.text = displayedText; } void RefillTextBox() { Debug.Log("To Refill"); // Clear the current text textMeshPro.text = ""; // Reset line and character indices currentLineIndex = 0; currentCharIndex = 0; // linesDisplayed = 0; // Remove typed lines from the list allLines.RemoveRange(0, linesShown); // Display the next batch of lines in the TextMeshPro component UpdateTextDisplay(); } }
修改后的实现方案
using UnityEngine; using TMPro; using System.Collections.Generic; public class TypingGameManager : MonoBehaviour { public TextMeshProUGUI textMeshPro; public string fileName = "YourTextFile"; public Color32 typedColor = new Color32(0, 255, 0, 255); public List<string> allLines; public int currentLineIndex = 0; public int currentCharIndex = 0; public int linesShown; public AudioClip typingSound; public AudioSource audioSource; void Start() { TextAsset textAsset = Resources.Load<TextAsset>(fileName); if (textAsset != null) { allLines = new List<string>(textAsset.text.Split(new[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries)); textMeshPro.overflowMode = TextOverflowModes.Truncate; UpdateTextDisplay(); } else { Debug.LogError("File not found in Resources: " + fileName); } audioSource.clip = typingSound; } void Update() { if (currentLineIndex >= allLines.Count) return; if (currentCharIndex < allLines[currentLineIndex].Length) { if (Input.anyKeyDown && Input.inputString.Length > 0) { char typedChar = Input.inputString[0]; if (typedChar == allLines[currentLineIndex][currentCharIndex]) { audioSource.Play(); currentCharIndex++; UpdateTextDisplay(); } } } else { if (currentLineIndex + 1 < allLines.Count) { currentLineIndex++; currentCharIndex = 0; UpdateTextDisplay(); } if (currentLineIndex == linesShown) { RefillTextBox(); } } } void UpdateTextDisplay() { string displayedText = ""; string testText = ""; linesShown = 0; for (int i = 0; i < allLines.Count; i++) { string lineToAdd = ""; if (i == currentLineIndex) { string typedText = allLines[i].Substring(0, currentCharIndex); string remainingText = allLines[i].Substring(currentCharIndex); string coloredTypedText = $"<color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}>{typedText}</color>"; lineToAdd = coloredTypedText + remainingText; } else if (i < currentLineIndex) { lineToAdd = $"<color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}>{allLines[i]}</color>"; } else { lineToAdd = allLines[i]; } string prevTestText = testText; string tempTest = i < allLines.Count - 1 ? testText + lineToAdd + "\n" : testText + lineToAdd; textMeshPro.text = tempTest; textMeshPro.ForceMeshUpdate(); if (textMeshPro.isTextOverflowing) { // 整行溢出,逐字符截断处理 testText = prevTestText; string truncatedLine = ""; bool overflowed = false; for (int c = 0; c < lineToAdd.Length; c++) { string charTest = i < allLines.Count - 1 ? testText + lineToAdd.Substring(0, c + 1) + "\n" : testText + lineToAdd.Substring(0, c + 1); textMeshPro.text = charTest; textMeshPro.ForceMeshUpdate(); if (textMeshPro.isTextOverflowing) { overflowed = true; break; } truncatedLine = lineToAdd.Substring(0, c + 1); } if (overflowed && truncatedLine.Length > 0) { testText += truncatedLine; displayedText = testText; linesShown++; // 提取剩余纯文本插入列表 int displayedCharCount = 0; if (i == currentLineIndex) { displayedCharCount = currentCharIndex + (truncatedLine.Length - $"<color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}>{allLines[i].Substring(0, currentCharIndex)}</color>".Length); } else if (i < currentLineIndex) { displayedCharCount = allLines[i].Length; } else { displayedCharCount = truncatedLine.Length; } if (displayedCharCount < allLines[i].Length) { string remainingText = allLines[i].Substring(displayedCharCount); allLines.Insert(i + 1, remainingText); } } break; } else { testText = tempTest; displayedText = testText; linesShown++; } } textMeshPro.text = displayedText; } void RefillTextBox() { Debug.Log("To Refill"); textMeshPro.text = ""; currentLineIndex = 0; currentCharIndex = 0; allLines.RemoveRange(0, linesShown); UpdateTextDisplay(); } }
关键修改说明
- 字符级截断逻辑:当整行添加导致溢出时,回退到添加前的文本状态,逐字符尝试添加当前行内容,找到刚好不溢出的最大长度,保留该部分显示。
- 剩余文本拆分:将当前行未显示的纯文本内容插入到
allLines的下一个索引位置,后续会作为新行继续加载显示。 - 富文本兼容:针对不同状态的行(已完成、正在输入、未输入),准确计算显示的纯文本字符数,避免富文本标签干扰剩余文本的提取。
- 索引稳定性:截断后插入的新行不会影响当前打字的索引逻辑,游戏流程可以正常推进。
内容的提问来源于stack exchange,提问作者Luke Piazza
相关产品推荐
相关产品推荐

