需求:以单字为单位的透明度递进与播放。

继承BaseMeshEffect制作UI特效。

制作思路:

  1. 声明一个与文字数量同样长度的数组,用于储存每个文字的透明度。
  2. 用索引标记当前显示的文字。
  3. 每帧对从开始处到索引标记的位置进行透明度增加,并储存更新数组。
  4. 调用graphic.SetVerticesDirty,这样会触发ModifyMesh方法。
  5. 在ModifyMesh设置顶点透明度即可更新渲染。

注意:

一个文字由两个三角形构成,而在操作顶点流时,获取的顶点是两个三角的顶点,也就是说,一个文字的顶点数是6而非4,需要丢弃多余的顶点。


效果:

代码:

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[AddComponentMenu("UI/Effects/Typewriter")]
[RequireComponent(typeof(Text))]
public class UIEff_Typewriter : BaseMeshEffect
{
    //当前播放的字
    [SerializeField]
    private int currentIndex = 0;
    private int renderDoneIndex = 0;

    //每个字完成的透明度
    [SerializeField]
    private byte advanceInterval = 16;
    public byte AdvanceInterval { get => advanceInterval; }

    [SerializeField]
    private bool isPlaying = false;
    public bool IsPlaying { get => isPlaying; }

    //每个网格的透明度
    private byte[] opacity;

    //播放结束
    public event Action EndHandler;

    [SerializeField]
    private float speed = 0.5f;
    public float Speed
    {
        get => speed;
        set
        {
            speed = Math.Min(Math.Max(value, 0f), 1f);
        }
    }

    protected override void Awake()
    {
#if UNITY_EDITOR
        if (!UnityEditor.EditorApplication.isPlaying) return;
#endif
        if (!IsActive()) return;
        base.Awake();
        Refresh();
    }

    public void Refresh()
    {
        currentIndex = 0;
        renderDoneIndex = 0;
        opacity = null;
    }

    private void FixedUpdate()
    {
        if (!IsActive()) return;
        if (!isPlaying) return;
        if (opacity == null || opacity.Length == 0)
        {
            graphic.SetVerticesDirty();
            return;
        }
        for (int w = 0; w < Mathf.Lerp(1, 10, speed); w++)
        {
            for (int i = renderDoneIndex; i < opacity.Length; i++)
            {
                //文字索引超过当前渲染文字
                if (i > currentIndex) break;
                //如果当前渲染字超过设定的完成度则前进
                if (opacity[currentIndex] >= this.advanceInterval)
                {
                    //不超过最大字数就前进一格字符
                    if (currentIndex < opacity.Length - 1)
                        ++currentIndex;
                }
                if (opacity[i] < 255)
                {
                    byte opacityStep = 2;
                    if ((int)opacity[i] + opacityStep > 255)
                    {
                        opacity[i] = 255;
                    }
                    else
                    {
                        opacity[i] += opacityStep;
                    }
                }
                if (opacity[i] == 255)
                {
                    ++renderDoneIndex;
                }
            }

            graphic.SetVerticesDirty();

            //最后一个透明度为1时结束
            if (opacity[opacity.Length - 1] == 255)
            {
                Stop();
            }
        }
    }

    public void Play()
    {
        Refresh();
        isPlaying = true;
    }
    public void Stop()
    {
        isPlaying = false;
        if(opacity != null)
        {
            //显示全部文字
            for (int i = 0; i < opacity.Length; i++)
            {
                opacity[i] = 255;
            }
            currentIndex = opacity.Length - 1;
        }

        graphic.SetVerticesDirty();

        EndHandler?.Invoke();
    }

    public override void ModifyMesh(VertexHelper vh)
    {
#if UNITY_EDITOR
        if (!UnityEditor.EditorApplication.isPlaying) return;
#endif
        if (!IsActive()) return;

        List<UIVertex> verts = new List<UIVertex>();
        vh.GetUIVertexStream(verts);

        //6点转4点
        List<UIVertex> vs = new List<UIVertex>();

        for (int i = 1; i <= verts.Count; i += 3)
        {
            vs.Add(verts[i - 1]);
            vs.Add(verts[i]);
        }

        //初始化每个字的数组长度
        if (opacity == null) opacity = new byte[vs.Count / 4];

        //设置透明
        for (int i = 0; i < vs.Count; i += 4)
        {
            UIVertex v1 = vs[i + 0];
            UIVertex v2 = vs[i + 1];
            UIVertex v3 = vs[i + 2];
            UIVertex v4 = vs[i + 3];

            v1.color.a = opacity[i / 4];
            v2.color.a = opacity[i / 4];
            v3.color.a = opacity[i / 4];
            v4.color.a = opacity[i / 4];

            vh.SetUIVertex(v1, i + 0);
            vh.SetUIVertex(v2, i + 1);
            vh.SetUIVertex(v3, i + 2);
            vh.SetUIVertex(v4, i + 3);
        }

    }

}