うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】uGUI の Text をスクロールさせる

あらまし

電光掲示板を見ていて uGUI の Text も文字スクロールができるのではと思い立って作成してみる

テキストを左に動かしてみる

今回 Text の頂点を動かすことで、文字スクロールを実装するため IMeshModifier を継承したクラスを利用する
IMeshModifier を利用することで、Graphic 継承クラスの頂点を操作することができる

f:id:hacchi_man:20200122004241p:plain

例えば「New Text」という文字の場合メッシュは上図のようになっており
とりあえずこの頂点座標を左にずらす処理を書いてみる

f:id:hacchi_man:20200122005014g:plain

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

[RequireComponent(typeof(Text))]
public class TextScroll : BaseMeshEffect
{
    [SerializeField]
    private float _offsetX;
    
    private List<UIVertex> uiVertexList = new List<UIVertex>();
    
#if UNITY_EDITOR

    public new void OnValidate()
    {
        gameObject.GetComponent<Graphic>().SetVerticesDirty();
    }

#endif

    public override void ModifyMesh(VertexHelper vertex)
    {
        uiVertexList.Clear();
        vertex.GetUIVertexStream(uiVertexList);

        var count = uiVertexList.Count;

        for (var i = 0; i < count; ++i)
        {
            var vert = uiVertexList[i];
            vert.position.x -= _offsetX;
            uiVertexList[i] = vert;
        }

        vertex.Clear();
        vertex.AddUIVertexTriangleStream(uiVertexList);
    }
}

左に移動した文字を右に移動させる

スクロールさせるためには、左に移動した文字を右に移動させる必要がある
左に隠れたかの判定には position.x を見て判断する
RectTransform の Pivot によって左端の値は変わってくるので判定に利用する値も変える必要がある

1文字メッシュ構成

1文字のメッシュ構成は下図のようになっており、(1, 2, 3)番目の頂点のxを見れば良い f:id:hacchi_man:20200123001808p:plain:w300

左端にいったら右側に移動する処理

※わかりやすくするために Mask 処理をはずしている
f:id:hacchi_man:20200123002900g:plain:w400

var offset = _offset % (textWidth);
// 左端の座標
var leftValue = Mathf.Lerp(0, _rectWidth * -1, _pivotX);
// 文字ごとにループする
for (var i = 0; i < count; i += 6)
{
    // 右下
    var checkVert = _uiVertexes[i + 3];
    checkVert.position.x -= offset;
    // 文字が隠れているか
    var isAddValue = checkVert.position.x < leftValue;
    // 隠れていた場合は右端に移動
    if (isAddValue)
        checkVert.position.x += textWidth;

    _uiVertexes[i + 3] = checkVert;

    foreach (var index in new[]{0, 1, 2, 4, 5})
    {
        var vert = _uiVertexes[i + index];
        vert.position.x -= offset;
        // 隠れていた場合は右端に移動
        if (isAddValue)
            vert.position.x += textWidth;

        _uiVertexes[i + index] = vert;
    }
}

完成コード

スクロール時のスペースやスクロールのスピードを調整できるように対応したコードが以下になります

f:id:hacchi_man:20200123003636g:plain

またOutlineShadow にも対応していますが結構な数の頂点を動かすことになるので注意が必要です

f:id:hacchi_man:20200123004916g:plain

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

[RequireComponent(typeof(Text))]
public class TextScroll : BaseMeshEffect
{
    [SerializeField]
    private float _space;
    [SerializeField]
    private float _speed;
    
    private Graphic _cacheGraphic;
    private List<UIVertex> _uiVertexes = new List<UIVertex>();
    
    private float _offset;
    private float _rectWidth;
    private float _pivotX;

    protected override void Awake()
    {
        Init();
    }

#if UNITY_EDITOR

    protected override void OnValidate()
    {
        Init();
        _cacheGraphic.SetVerticesDirty();
    }

#endif

    private void Init()
    {
        var text = GetComponent<Text>();
        text.horizontalOverflow = HorizontalWrapMode.Overflow;
        _cacheGraphic = GetComponent<Graphic>();
        _pivotX = (transform as RectTransform).pivot.x;
        _rectWidth = (transform as RectTransform).rect.width;
    }

    private void Update()
    {
        _offset += Time.deltaTime * _speed;
        _cacheGraphic.SetVerticesDirty();
    }

    public override void ModifyMesh(VertexHelper vertex)
    {
        _uiVertexes.Clear();
        vertex.GetUIVertexStream(_uiVertexes);

        var count = _uiVertexes.Count;
        if (count > 5)
        {
            var textWidth = _uiVertexes[count - 3].position.x - _uiVertexes[0].position.x;
            // テキスト数
            var textCount = _uiVertexes.Count / 6;
            // 一文字あたりのサイズ
            var charaWidth = textWidth / textCount;
            if (textWidth - charaWidth + _space > _rectWidth)
            {
                var offset = _offset % (textWidth + _space);
                var leftValue = Mathf.Lerp(0, _rectWidth * -1, _pivotX);
                for (var i = 0; i < count; i += 6)
                {
                    var checkVert = _uiVertexes[i + 3];
                    checkVert.position.x -= offset;
                    var isAddValue = checkVert.position.x < leftValue;
                    if (isAddValue)
                        checkVert.position.x += textWidth + _space;
                
                    _uiVertexes[i + 3] = checkVert;

                    foreach (var index in new[]{0, 1, 2, 4, 5})
                    {
                        var vert = _uiVertexes[i + index];
                        vert.position.x -= offset;
                        if (isAddValue)
                            vert.position.x += textWidth + _space;

                        _uiVertexes[i + index] = vert;
                    }
                }
            }
        }
        
        vertex.Clear();
        vertex.AddUIVertexTriangleStream(_uiVertexes);
    }
}