You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 = $"&lt;color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}&gt;{typedText}&lt;/color&gt;";

                    // Append the current line with colored typed text
                    testText += coloredTypedText + remainingText;
                }
                else if (i < currentLineIndex)
                {
                    // Previous lines, fully typed
                    testText += $"&lt;color=#{ColorUtility.ToHtmlStringRGBA(typedColor)}&gt;{allLines[i]}&lt;/color&gt;";
                }
                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();
    }
}

关键修改说明

  1. 字符级截断逻辑:当整行添加导致溢出时,回退到添加前的文本状态,逐字符尝试添加当前行内容,找到刚好不溢出的最大长度,保留该部分显示。
  2. 剩余文本拆分:将当前行未显示的纯文本内容插入到allLines的下一个索引位置,后续会作为新行继续加载显示。
  3. 富文本兼容:针对不同状态的行(已完成、正在输入、未输入),准确计算显示的纯文本字符数,避免富文本标签干扰剩余文本的提取。
  4. 索引稳定性:截断后插入的新行不会影响当前打字的索引逻辑,游戏流程可以正常推进。

内容的提问来源于stack exchange,提问作者Luke Piazza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:08:10