現在グレンジでUnityを用いたゲーム開発を行っているみくりやと申します。
UIを実装する際にテキストエリアに関する話題はよく出るかと思います。
「文字数制限超えてテキストがレイアウトからはみ出てます」
とか
「仕様変更で文字数収まらなくなったのでレイアウトの調整お願いします」
とか
そんな話のさなか、デザイナーさんから長体かけたいという言葉が出ました。
Unityだけ触ってる自分には馴染みのない言葉でしたが言い換えると
指定幅に収まるようにテキストにスケールをかける
ことです。
参考:“長体(文字)” の意味・解説
ということで作ってみた
というライトな感じではいけず苦戦しました。
Textクラスを継承しOnPopulateMeshでゴニョゴニョする方針です。
private Matrix4x4 _scaleMatrix = Matrix4x4.identity;
protected override void OnPopulateMesh(VertexHelper toFill) {
#region Textクラス処理
if (font == null) {
return;
}
// We don't care if we the font Texture changes while we are doing our Update.
// The end result of cachedTextGenerator will be valid for this instance.
// Otherwise we can get issues like Case 619238.
m_DisableFontTextureRebuiltCallback = true;
Vector2 extents = rectTransform.rect.size;
var settings = GetGenerationSettings(extents);
#region 追記箇所
float overRate = preferredWidth / rectTransform.rect.width;
if (overRate > 1f) {
switch (alignment) {
case TextAnchor.LowerLeft:
case TextAnchor.LowerRight:
settings.textAnchor = TextAnchor.LowerCenter;
break;
case TextAnchor.MiddleLeft:
case TextAnchor.MiddleRight:
settings.textAnchor = TextAnchor.MiddleCenter;
break;
case TextAnchor.UpperLeft:
case TextAnchor.UpperRight:
settings.textAnchor = TextAnchor.UpperCenter;
break;
}
// 変換行列を作成
_scaleMatrix = Matrix4x4.identity;
// scale x
_scaleMatrix.m00 = 1f / overRate;
// scale y
_scaleMatrix.m11 = 1f;
// scale z
_scaleMatrix.m22 = 1f;
// テキストが切られないようにOverflow指定
settings.horizontalOverflow = HorizontalWrapMode.Overflow;
}
#endregion
cachedTextGenerator.PopulateWithErrors(text, settings, gameObject);
// Apply the offset to the vertices
IList<UIVertex> verts = cachedTextGenerator.verts;
float unitsPerPixel = 1 / pixelsPerUnit;
int vertCount = verts.Count;
// We have no verts to process just return (case 1037923)
if (vertCount <= 0) {
toFill.Clear();
return;
}
Vector2 roundingOffset = new Vector2(verts[0].position.x, verts[0].position.y) * unitsPerPixel;
roundingOffset = PixelAdjustPoint(roundingOffset) - roundingOffset;
toFill.Clear();
if (roundingOffset != Vector2.zero) {
for (int i = 0; i < vertCount; ++i) {
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
m_TempVerts[tempVertsIndex].position.x += roundingOffset.x;
m_TempVerts[tempVertsIndex].position.y += roundingOffset.y;
#region 追記箇所
if (overRate > 1f) {
m_TempVerts[tempVertsIndex].position = _scaleMatrix.MultiplyPoint3x4(m_TempVerts[tempVertsIndex].position);
}
#endregion
if (tempVertsIndex == 3) {
toFill.AddUIVertexQuad(m_TempVerts);
}
}
} else {
for (int i = 0; i < vertCount; ++i) {
int tempVertsIndex = i & 3;
m_TempVerts[tempVertsIndex] = verts[i];
m_TempVerts[tempVertsIndex].position *= unitsPerPixel;
#region 追記箇所
if (overRate > 1f) {
m_TempVerts[tempVertsIndex].position = _scaleMatrix.MultiplyPoint3x4(m_TempVerts[tempVertsIndex].position);
}
#endregion
if (tempVertsIndex == 3) {
toFill.AddUIVertexQuad(m_TempVerts);
}
}
}
m_DisableFontTextureRebuiltCallback = false;
#endregion
}
まずTextクラスのOnPopulateMesh処理をゴリッと持ってきます (ここがイケてないところですが…)
参考:https://bitbucket.org/Unity-Technologies/ui/src/2019.1/
そして「#region 追記箇所」の位置に処理を追加しています。
最初の追記箇所ではTextの設定値に関する計算とどのくらい縮めるかの計算を行います。
以下の計算式でどのくらいはみ出してるかを割り出しました。
float overRate = preferredWidth / rectTransform.rect.width;
この値を用いてどのくらい縮めるかの変換行列を作成します。
// 変換行列を作成
_scaleMatrix = Matrix4x4.identity;
// scale x
_scaleMatrix.m00 = 1f / overRate;
// scale y
_scaleMatrix.m11 = 1f;
// scale z
_scaleMatrix.m22 = 1f;
今回は横方向のみに長体をかけますが、m11を使用することで縦に対しても反映できます。
次にGetGenerationSettingsで取得した設定値に関する処理です。
まずtextAnchorをCenterにしている箇所は LeftかRightだと頂点計算時の処理に手を加える必要があり ややこしくなりそうだったのですが、長体をかけるときはどうせrectの幅いっぱいの状態なので割り切って上書くようにしました。
horizontalOverflowも Wrapだと文字が切れる のでOverflowで上書きします。
また、設定値を直接上書きすると長体をかけないでいいときにもとに戻す処理がややこしくなるのでGetGenerationSettingsで得た値を書き換えます。
ややこしいんです。
あとは頂点計算後に_scaleMatrix.MultiplyPoint3x4してやればスケールをかけることが出来ます。
このように一文字の横幅が収まるサイズに縮んでくれます。やったね!
まとめ
Textの処理内容に直接手をかけることになりましたが一応希望の機能は作ることが出来ました。デフォであっていいような気もするのでUnityさん作ってくれないかな~。もうちょっとスマートな方法を模索したい今日このごろです。