前置き
- ゆるっと対応する
- ほんとにゆるやかに対応する (念押し)
- 小説等の対応を行う為のガチなアプローチはここでは行わない
縦書き対応で満たされて欲しい案件
- (1) レイアウト
- (1-1) テキストボックス入力時に縦に文が伸びる
- (1-2) 改行語、既存の文の左に文が追加出来る
- (2) 文字
- (2-1) 一部文字の回転
- (2-2) 一部位置の調整
- 該当文字: http://opentype.jp/fontguide_doc4.htm
フォントの縦書き設定
- 通常は上記の縦書き対応はフォント側に設定が埋め込まれる
- 縦書きと横書きで異なるGIDが用いられる
- 参考: フォントのしくみ
- 参考: Webと文字 - はいはい縦書き縦書き・・・・・ッ!?
Unity (UI.Text) と縦書き設定
- Unity (UI.Textコンポーネント) では現在 (5.4.1f) 時点でフォントの縦書き設定は読み込めない
対応
RectTransformを回転させる
- 文字を入力した際に縦に文章が伸びる用にRectTransformのZを90°回転させる
- これによりレイアウトの条件を満たす
テキストの頂点を90°回転させる
- IMeshModifier を継承したScriptを実装する
CanvasRenderer に渡す前に Graphic の頂点の修正のために許可されるインターフェース
- ModifyMeshメソッドをoverrideすることでUI要素の頂点を加工することが出来る
- テキストを90°回転させることで縦書きのように見せる
- (以下のコードをベースにしています)
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: 1テキスト6頂点
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;
}
}
}
}
一部文字は回転させない
- 上記のままだと一部文字 (e.g: ー ) が正常に見えない
- 一部の文字に対して、回転を施さないような処理を加える
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 一部文字位置の調整
- 上記のコードのままだと「。」「っ」等の文字の位置が適切でない
- 字を半文字分横にズラす対応を入れると良さそう
- 良き時に追記する(遠い目)
TODO その他頂点が増える場合の対応
- 上記コードのままだと、UIComponentのShadowやOutline等、頂点の数が追加される場合に正常に動かないので注意
- 具体的には
vertexList.Count
(頂点数) が文字列数*6より多くなってしまうため
- 具体的には
パフォーマンス対策
- 上記のコードでは
List<UIVertex> vertexList = new List<UIVertex>();
にて都度インスタンス生成が行われてパフォーマンスが悪い - 宴のマニュアルで紹介されているListPoolのアプローチを使うと良さそう
- 具体的には以下のコードを自プロダクトに配置する
- (internal (実装の隠蔽) しているため、自プロダクトのコードからは直接アクセス出来ないため)
- ListPool
- ObjectPool
最後に
- 諸々細かい調整入れるの大変なのでUnity側で縦書き対応していただきたいところ....!!
この作品はユニティちゃんライセンス条項の元に提供されています