[Unity] UI.Text (uGUI) で縦書き(のようなもの)を作る

## 前置き
- ゆるっと対応する
- ほんとにゆるやかに対応する (念押し)
- 小説等の対応を行う為のガチなアプローチはここでは行わない

![image](https://qiita-image-store.s3.amazonaws.com/0/102664/9c984fcb-483b-1ed3-aa32-73e4ff81f85b.png)

### 縦書き対応で満たされて欲しい案件
- (1) レイアウト
  - (1-1) テキストボックス入力時に縦に文が伸びる
  - (1-2) 改行語、既存の文の左に文が追加出来る
- (2) 文字
  - (2-1) 一部文字の回転
  - (2-2) 一部位置の調整
  - 該当文字: http://opentype.jp/fontguide_doc4.htm

### フォントの縦書き設定
- 通常は上記の縦書き対応はフォント側に設定が埋め込まれる
  - 縦書きと横書きで異なる[GID](http://www.morisawa.co.jp/culture/dictionary/1919)が用いられる
  - 参考: [フォントのしくみ](http://www.iwatafont.co.jp/news/img/about_font.pdf)
  - 参考: [Webと文字 - はいはい縦書き縦書き・・・・・ッ！？](http://d.hatena.ne.jp/project_the_tower2/20100509/1273370298)

### Unity (UI.Text) と縦書き設定
- Unity (UI.Textコンポーネント) では現在 (5.4.1f) 時点でフォントの縦書き設定は読み込めない

## 対応
### RectTransformを回転させる

![rotate_text.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/4de1dab6-9654-65d9-f1a8-18791ee8b63b.gif)

- 文字を入力した際に縦に文章が伸びる用にRectTransformのZを90°回転させる
- これによりレイアウトの条件を満たす

### テキストの頂点を90°回転させる

![image](https://qiita-image-store.s3.amazonaws.com/0/102664/c980cc24-d610-0569-51c4-2ad393357fdf.png)

-  IMeshModifier を継承したScriptを実装する
  - `CanvasRenderer に渡す前に Graphic の頂点の修正のために許可されるインターフェース`
- ModifyMeshメソッドをoverrideすることでUI要素の頂点を加工することが出来る
- テキストを90°回転させることで縦書きのように見せる
- (以下のコードをベースにしています)
  - [テラシュールブログ - 【Unity】UIで表現するテキストを１文字ずつ”動かす”](http://tsubakit1.hateblo.jp/entry/2016/04/15/230553)
  - [KEN KENTAN BLOG - UnityのuGUIで文字間隔（カーニング）を行う方法](https://blog.kentan.jp/2016/02/24/unity%E3%81%AEugui%E3%81%A7%E6%96%87%E5%AD%97%E9%96%93%E9%9A%94%EF%BC%88%E3%82%AB%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0%EF%BC%89%E3%82%92%E8%A1%8C%E3%81%86%E6%96%B9%E6%B3%95/)

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

public class RotateText : UIBehaviour, IMeshModifier
{
    public new void OnValidate()
    {
        base.OnValidate();

        var graphics = base.GetComponent<Graphic>();
        if (graphics != null)
        {
            graphics.SetVerticesDirty();
        }
    }

    public void ModifyMesh (Mesh mesh) {}
    public void ModifyMesh (VertexHelper verts)
    {
        if (!this.IsActive())
        {
            return;
        }

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

        ModifyVertices(vertexList);

        verts.Clear();
        verts.AddUIVertexTriangleStream(vertexList);
    }

    void ModifyVertices(List<UIVertex> vertexList)
    {
        // memo: １テキスト６頂点
        for (int i = 0, vertexListCount = vertexList.Count; i < vertexListCount; i += 6)
        {
            var center = Vector2.Lerp(vertexList[i].position, vertexList[i + 3].position, 0.5f);
            for (int r = 0; r < 6; r++)
            {
                var element = vertexList[i + r];
                var pos = element.position - (Vector3)center;
                var newPos = new Vector2(
                    pos.x * Mathf.Cos(90 * Mathf.Deg2Rad) - pos.y * Mathf.Sin(90 * Mathf.Deg2Rad),
                    pos.x * Mathf.Sin(90 * Mathf.Deg2Rad) + pos.y * Mathf.Cos(90 * Mathf.Deg2Rad)
                );

                element.position = (Vector3)(newPos + center);
                vertexList[i + r] = element;
            }
        }
    }
}

```

### 一部文字は回転させない

![image](https://qiita-image-store.s3.amazonaws.com/0/102664/9c984fcb-483b-1ed3-aa32-73e4ff81f85b.png)


- 上記のままだと一部文字 (e.g: ー ) が正常に見えない
- 一部の文字に対して、回転を施さないような処理を加える

```csharp
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
using System.Linq;

[RequireComponent(typeof(Text))]
public class RotateText : UIBehaviour, IMeshModifier
{
    private Text textComponent;
    private char[] characters;

    // 回転させない文字群
    // XXX 別の設定ファイルなりcsvにまとめて最初に読み込んでしまうのが良さそう
    public List<char> nonrotatableCharacters;

    public new void OnValidate()
    {
        base.OnValidate();
        textComponent = this.GetComponent<Text>();

        var graphics = base.GetComponent<Graphic>();
        if (graphics != null)
        {
            graphics.SetVerticesDirty();
        }
    }

    public void ModifyMesh (Mesh mesh) {}
    public void ModifyMesh (VertexHelper verts)
    {
        if (!this.IsActive())
        {
            return;
        }

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

        ModifyVertices(vertexList);

        verts.Clear();
        verts.AddUIVertexTriangleStream(vertexList);
    }

    void ModifyVertices(List<UIVertex> vertexList)
    {
        characters = textComponent.text.ToCharArray();
        if (characters.Length == 0)
        {
            return;
        }

        for (int i = 0, vertexListCount = vertexList.Count; i < vertexListCount; i += 6)
        {
            int index = i / 6;
            if (IsNonrotatableCharactor(characters[index]))
            {
                continue;
            }

            var center = Vector2.Lerp(vertexList[i].position, vertexList[i + 3].position, 0.5f);
            for (int r = 0; r < 6; r++)
            {
                var element = vertexList[i + r];
                var pos = element.position - (Vector3)center;
                var newPos = new Vector2(
                    pos.x * Mathf.Cos(90 * Mathf.Deg2Rad) - pos.y * Mathf.Sin(90 * Mathf.Deg2Rad),
                    pos.x * Mathf.Sin(90 * Mathf.Deg2Rad) + pos.y * Mathf.Cos(90 * Mathf.Deg2Rad)
                );

                element.position = (Vector3)(newPos + center);
                vertexList[i + r] = element;
            }
        }
    }

    bool IsNonrotatableCharactor(char character)
    {
        return nonrotatableCharacters.Any(x => x == character);
    }
}
```

### TODO 一部文字位置の調整

![image](https://qiita-image-store.s3.amazonaws.com/0/102664/15656539-fa40-dc85-dd47-7500ccd8eb0c.png)

- 上記のコードのままだと「。」「っ」等の文字の位置が適切でない
- 字を半文字分横にズラす対応を入れると良さそう
  - 良き時に追記する(遠い目)

### TODO その他頂点が増える場合の対応

- 上記コードのままだと、UIComponentのShadowやOutline等、頂点の数が追加される場合に正常に動かないので注意
  - 具体的には `vertexList.Count` (頂点数) が文字列数*6より多くなってしまうため

### パフォーマンス対策
- 上記のコードでは `List<UIVertex> vertexList = new List<UIVertex>();` にて都度インスタンス生成が行われてパフォーマンスが悪い
- 宴のマニュアルで紹介されているListPoolのアプローチを使うと良さそう
  - [UIの頂点をカスタムする～ModifyVertices、ModifyMeshのバージョンアップ対応～](http://madnesslabo.net/utage/?page_id=6042)
- 具体的には以下のコードを自プロダクトに配置する
  - (internal (実装の隠蔽) しているため、自プロダクトのコードからは直接アクセス出来ないため)
  - [ListPool](https://bitbucket.org/Unity-Technologies/ui/src/0155c39e05ca5d7dcc97d9974256ef83bc122586/UnityEngine.UI/UI/Core/Utility/ListPool.cs?at=5.2&fileviewer=file-view-default)
  - [ObjectPool](https://bitbucket.org/Unity-Technologies/ui/src/0155c39e05ca5d7dcc97d9974256ef83bc122586/UnityEngine.UI/UI/Core/Utility/ObjectPool.cs?at=5.2&fileviewer=file-view-default)

## 最後に
- 諸々細かい調整入れるの大変なのでUnity側で縦書き対応していただきたいところ....!!

---

<div><img src="http://unity-chan.com/images/imageLicenseLogo.png" alt="ユニティちゃんライセンス"><p>この作品は<a href="http://unity-chan.com/contents/license_jp/" target="_blank">ユニティちゃんライセンス条項</a>の元に提供されています</p></div>