Unity 实现字幕打字效果

news2024/11/24 3:00:58

Text文本打字效果,TextMeshPro可以对应参考,差距不大,改改参数名就能用。改脚本原本被我集成到其他的程序集中,现在已经分离。

效果

请添加图片描述

实现功能

1.能够设置每行能够容纳的字数和允许的冗余
2.打字效果
3.每行打完上移
4.开头进入,结束弹出
5.行居中

脚本使用

请添加图片描述
请添加图片描述

属性解释
TypingSpeed打字速度(.s 每过多少时间打一个)
RowShowMax一行显示的最大值
AllowRedundancy每行能够允许的冗余
IsUpdateText更新文本,默认不启动,勾选点击运行就更新
Text挂载text文本
OffsetY对每行上移的补偿
SaveMarqueeoriginPosition保存字幕整体的初始位置
TextCloseDelayTime字幕消失的时间(Obsolete)
BottomShow需要挂载下方显示的整体
函数功能
UpdateText更新文本,需要再Update中调用
OnFinish更新完成,更新文本完成后调用的函数
OnTextUpdate(string)文本更新,在打字过程中如果发生文本更新需要调用的方法
OnTextReset文本重置


设置

** 1.物体设置 **
请添加图片描述

** 2.text文本设置 **
请添加图片描述
1.需要文字的size(如果有外描边的话)来计算字占据的行宽,来决定每行上移的距离
2.文本每行居中,但定格显示

代码

using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;

namespace YBZ {

    public class TypewriterEffect : MonoBehaviour 
    {
     
        // 测试文本
        private readonly string text_test = "SVN中我需要维护的东西Scene; Scripts/Engine/WSC SteamingAsset/2DPivots.json Editor/WSCPivotEditor.cs \n 下方提示";

        [Header("文本显示"), Space(10)]
        public float typingSpeed = 0.2f; // 打字速度
        public int rowShowMax = 25; // 一行最大显示数量
        public int allowRedundancy = 6; // 允许的冗余数量
        public bool isUpdateText = false; // 是否播放

        public Text text; // 底部字幕

        [Range(-25, 25), Space(5)]
        public int offsetY = -3; // 第一行Y轴补偿
        public Vector3 saveMarqueeOriginPostion = new Vector3(0, -63, 0); // 下方字幕的原初位置

        private float textTimeCount = 0; // 更新用的计时器
        private string word = ""; //保存字幕
        private int currentPos = 0; // 打字字符索引
        private int LineBreakCount = 0; // 换行符计数

        private Vector3 saveTextLocatePostion;  // 用于保存TextUI位置

        private bool isOriginPosition = true;

        void Update() {
            UpdateText();
            if (Input.GetMouseButtonDown(0)) {
                OnTextUpdate(text_test);
            }
        }
        
        // 文本更新
        private void UpdateText() {
            if (!isUpdateText) {
                return;
            }

            // 检查字幕是否位于原初位置
            if (isOriginPosition) {
                isOriginPosition = !isOriginPosition;
                BottomShow.transform.DOLocalMove(Vector3.zero, 1.0f);
            }

            BottomShow.SetActive(true);

            if (saveTextLocatePostion == Vector3.zero) {
                saveTextLocatePostion = text.rectTransform.localPosition;
            }

            if (word == "") {
                word = text.text;
            }

            textTimeCount += Time.deltaTime;
            if (textTimeCount > typingSpeed) {
                textTimeCount = 0;
                currentPos++;
                if (currentPos >= word.Length) {
                    Debug.Log("播放完成");

                    OnFinish();
                    return;
                }
                text.text = word[..currentPos];//刷新文本显示内容
                if (word[currentPos - 1] == '\n') {
                    Debug.Log("发现换行符");
                    LineBreakCount++;

                    // 每次遇到一个换行符就上移25个单位
                    if (LineBreakCount == 1) {
                        text.rectTransform.DOLocalMoveY(text.rectTransform.localPosition.y + 25 + offsetY, 1f);
                    } else if (LineBreakCount != 1) {
                        text.rectTransform.DOLocalMoveY(text.rectTransform.localPosition.y + 25, 1f);
                    }

                }

                // 每次处理行超限
                int lineCount;
                if (LineBreakCount == 0) {
                    lineCount = text.text.Length;
                } else {
                    lineCount = text.text[text.text.LastIndexOf('\n')..].Length;
                }

                // 行超限
                if (lineCount > rowShowMax + allowRedundancy) {
                    word = text.text + '\n' + word[text.text.Length..];
                }

            }
        }

        [Header("下方显示延迟消失的所需要的时间")]
        public float textCloseDelayTime = 2.0f;

        /// <summary>
        /// 下方提示游戏物体,在使用前预加载
        /// </summary>
        public GameObject BottomShow;

        public IEnumerator IE_TextCloseDelayTime(float time) {
            yield return new WaitForSeconds(time);
            BottomShow.SetActive(false);
            Debug.Log("下方显示已关闭");
        }

        public IEnumerator IE_OnTextReset(float time) {
            yield return new WaitForSeconds(time);
            OnTextReset();
            Debug.Log("文本恢复默认");
            isOriginPosition = true;
            BottomShow.transform.DOLocalMove(saveMarqueeOriginPostion, 1.0f);
        }

        // 文本更新完成 , 一旦确认关闭就不要再更新文本, 否侧会出现逻辑错误,如若在播放完毕后更新文本一定要在下方显示关闭后, 字幕回滚的时候不能更新文本.
        private void OnFinish() {
            isUpdateText = false;
            // 完成之后下方显示 延迟关闭, 位置回调
            // StartCoroutine(IE_TextCloseDelayTime(textCloseDelayTime));
            // 延迟文本重置位置
            StartCoroutine(IE_OnTextReset(textCloseDelayTime));
            text.rectTransform.DOLocalMove(saveTextLocatePostion + new Vector3(0, offsetY, 0), textCloseDelayTime);
        }

        // 文本更新, 一旦更新就是确定要开始播放(╯‵□′)╯︵┻━┻(你更新不是为了播放?)
        private void OnTextUpdate(string newtext) {

            text ??= GameObject.Find("字幕文字").gameObject.GetComponent<Text>();
            OnTextReset();
            word = newtext;
            // StartCoroutine(IE_OnTextReset(0.5f));
            BottomShow.SetActive(true);
            isUpdateText = true;
        }

        // 文本恢復默认: 索引为0, 换行符统计为0, LocalPostion恢复, 文本置空
        private void OnTextReset() {
            if (saveTextLocatePostion == Vector3.zero) {
                saveTextLocatePostion = text.rectTransform.localPosition;
            }
            text.rectTransform.localPosition = saveTextLocatePostion;
            text.text = "";
            LineBreakCount = 0;
            currentPos = 0;
        }

        // 初始化
        public void Init() {
            Debug.Log("文本更新初始化完成");
        }

        public void UnInit() {
            Debug.Log("文本控制结束");
        }
        private void OnDestroy() {
            UnInit();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/845675.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目实战 — 消息队列(5){统一硬盘操作}

前面已经使用数据库管理了交换机、绑定、队列&#xff0c;然后又使用了数据文件管理了消息。 那么&#xff0c;这里就创建一个类&#xff0c;讲之前的两个部分整合起来&#xff0c;对上层提供统一的一套接口&#xff0c;表示硬盘上存储的所有的类的信息。 /* * 用这个类来管理…

C++笔记之enum class和emun的区别

C笔记之enum class和emun的区别 code review! 代码,使用 enum class 的示例&#xff1a; #include <iostream>enum class Month { January, February, March, April, May, June, July, August, September, October, November, December };int main() {Month currentM…

龙迅LT8711H是Type-C/DP1.2转HDMI1.4芯片 -现货来了

1.概述 LT8711H是一款高性能Type-C / DP1.2至HDMI1.4转换器&#xff0c;旨在将USB Type-C源或DP1.2源连接至HDMI1.4接收器。 LT8711H集成了符合DP1.2的接收器和符合HDMI1.4的发送器。此外&#xff0c;还包括两个CC控制器&#xff0c;用于CC通信以实现DP Alt Mode和功率传输功…

Frida 编译(去特征)

Frida 编译&#xff08;去特征&#xff09; 编译最新版server编译往期版server更改特征使用定制库 hluwa本文引用&#xff1a; 本文环境&#xff1a; kali-linux-xfce 编译最新版server 第一步&#xff1a; 下载frida git clone --recurse-submodules https://github.com/fri…

Redis键值设计

1.1、优雅的key结构 Redis的Key虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过44字节不包含特殊字符 例如&#xff1a;我们的登录业务&#xff0c;保存用户信息&#xff0c;其key可以…

模型训练技术指南

目录 引言 1. 模型训练的重要性 2. 数据预处理 3. 特征工程 4. 模型选择与评估 5. 参数调优 6. 模型集成 7. 过拟合与欠拟合 8. 模型保存与加载 9. 分布式训练与加速 10. 最佳实践与常见问题 引言 模型训练是机器学习领域中至关重要的一步&#xff0c;它决定了模型的…

处理该文件没有与之关联的应用来执行该操作,若已经安装应用,请在“默认应用设置”页面中创建关联

一、晚上在睡觉前接到一个删除了注册表导致的错误消息 二、解决方法一&#xff1a; 桌面新建 txt&#xff0c;把下面的代码复制粘贴到 txt 文件&#xff0c;然后重命名为1.bat&#xff0c;右键以管理员身份运行。 taskkill /f /im explorer.exe reg add "HKEY_LOCAL_MA…

【数据结构与算法】二叉排序树(BST)

二叉排序树&#xff08;BST&#xff09; 需求&#xff1a; 给你一个数列{7,3,10,12,5,1,9}&#xff0c;要求能够高效的完成对数据的查询和添加。 解决方案分析 使用数组 数组未排序&#xff0c;优点&#xff1a;直接在数组尾添加&#xff0c;速度快。缺点&#xff1a;查找速…

了解华为(H3C)网络设备和OSI模型基本概念

目录 一&#xff0c;认识华为 1.华为发展史 2.华为网络设备介绍 3.VRP概述 二&#xff0c;OSI七层模型 1.七层模型详细表格 2.各层的作用 3.数据在各层之间的传递过程 4.OSI四层网络模型 一&#xff0c;认识华为 官网&#xff1a;https://www.huawei.com/cn/ 1.华为发…

记录一个CMD命令异常 文件名、目录名或卷标语法不正确。

由git clone下来导致缺少符号 使用文档格式转换-转为windows-CR LF即可。 当前测试的命令内容 >cs 文件名、目录名或卷标语法不正确。 ho 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 系统默认的nodepad好像不能转换&#xff0c;直接新建一个文件&am…

【GTest学习】

1. GTest简介&#xff1a; GTest 就是 Google Test, 它是一个免费开源的测试框架, 用于编写测试用 C语言编写的程序(C 程序也能用, 但是需要用 C编译器编译)。gtest的官方网站是&#xff1a;http://code.google.com/p/googletest/ 2.GTest下载与环境搭建&#xff1a; GTest 下…

【雕爷学编程】Arduino动手做(195)---HT16k33 矩阵 8*8点阵屏模块4

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…

MFC计算分贝

分贝的一种定义是&#xff0c;表示功率量之比的一种单位&#xff0c;等于功率强度之比的常用对数的10倍&#xff1b; 主要用于度量声音强度&#xff0c;常用dB表示&#xff1b; 其计算&#xff0c;摘录网上一段资料&#xff1b; 声音的分贝值可以通过以下公式计算&#xff1…

python爬虫(七)_urllib2:urlerror和httperror

python爬虫(七)_urllib2&#xff1a;urlerror和httperror urllib2的异常错误处理 在我们用urlopen或opener.open方法发出一个请求时&#xff0c;如果urlopen或opener.open不能处理这个response&#xff0c;就产生错误。 这里主要说的是URLError和HTTPError,以及对它们的错误…

Vue Router 的query和params的区别?

区别一&#xff1a; &#xff08;1&#xff09;query相当于get请求&#xff0c;页面跳转的时候可以在地址栏看到请求参数 &#xff08;2&#xff09;params相当于post请求&#xff0c;参数不会在地址栏中显示&#xff0c;所以用params传值相对安全 &#xff08;简记&#xff1…

架构训练营学习笔记:5-1 计算架构模式之多级缓存架构

序 本节主要是计算架构。 多级缓存架构 缓存与缓冲&#xff1a;通常场景是读缓存&#xff0c;写缓冲。 缓存技术的本质&#xff1a;空间换时间&#xff0c;因此缓存架构属于高性能计算 架构。 缓存设计框架 主要考虑存什么&#xff1f;存多久&#xff1f;存哪里&#xff1f;如…

数字图像处理 --- 相机的内参与外参(CV学习笔记)

Pinhole Camera Model&#xff08;针孔相机模型&#xff09; 针孔相机是一种没有镜头、只有一个小光圈的简单相机。 光线穿过光圈并在相机的另一侧呈现倒立的图像。为了建模方便&#xff0c;我们可以把物理成像平面(image plane)上的图像移到实际场景(3D object)和焦点(focal p…

leetcode357周赛

2810. 故障键盘 核心思想&#xff1a;自己想的笨办法&#xff0c;枚举s&#xff0c;然后遇到i就翻转。比较好的方法就是双端队列&#xff0c;遇到i字母原本往后加的就往前加&#xff0c;然后读的时候反过来读&#xff0c;往前加的就往后加&#xff0c;读的话就从前往后&#x…

Java并发系列之八:ThreadPoolExecutor

线程池的意义 在讲解线程池之前&#xff0c;有些读者可能存在这样的疑惑&#xff1a;为什么需要线程池&#xff0c;线程池有什么优越性&#xff1f; 关于这个问题&#xff0c;主要从两个角度来进行解答: 减少开销 在大部分JVM上&#xff0c;用户线程与操作系统内核线程是1:1…

【论文阅读】对抗溯源图主机入侵检测系统的模仿攻击(NDSS-2023)

作者&#xff1a;伊利诺伊大学芝加哥分校-Akul Goyal、Gang Wang、Adam Bates&#xff1b;维克森林大学-Xueyuan Han、 引用&#xff1a;Goyal A, Han X, Wang G, et al. Sometimes, You Aren’t What You Do: Mimicry Attacks against Provenance Graph Host Intrusion Detect…