Skip to content

Instantly share code, notes, and snippets.

@iwashihead
Created April 4, 2017 09:55
Show Gist options
  • Save iwashihead/d11e8e88255cf929cf5e2ecb334e87e9 to your computer and use it in GitHub Desktop.
Save iwashihead/d11e8e88255cf929cf5e2ecb334e87e9 to your computer and use it in GitHub Desktop.
RichTextのようにタグ設定してルビを振るコンポーネント
using UnityEngine;
using System;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
/// <summary>
/// ルビ付きテキスト.
/// "ふわふわ[時間:タイム]" のようにタグを設定することでルビが振られます
/// </summary>
[RequireComponent(typeof(Text))]
public class RubyText : UIBehaviour, IMeshModifier
{
private const int RUBY_OBJECT_MAX_COUNT = 16;//ルビの最大数 ( ルビタグを設定できる最大数 )
private const int TEXT_LENGTH_MAX = 200;//本文の最大文字数
private const int VERTEX_COUNT_PER_CHARACTER = 6;
private const char TAG_RUBY_START = '[';
private const char TAG_RUBY_END = ']';
private const char TAG_RUBY_SEPARETOR = ':';
/// <summary>
/// 各ルビ文字のデータ.
/// </summary>
public struct RubyData
{
/// <summary>ルビ文字の内容.</summary>
public string Text;
/// <summary>本文の何文字目から何文字目にルビを降るか?</summary>
public int StartIndex;
/// <summary>本文の何文字目から何文字目にルビを降るか?</summary>
public int LastIndex;
public RubyData(string text, int startIndex, int lastIndex)
{
this.Text = text;
this.StartIndex = startIndex;
this.LastIndex = lastIndex;
}
};
private Text selfText;
private Text SelfText {
get {
if (!selfText) selfText = GetComponent<Text> ();
return selfText;
}
}
[Header("ルビ文字列の設定")]
public Font RubyFont;
public int RubyFontSize = 18;
public FontStyle RubyFontStyle = FontStyle.Normal;
public bool SupportRichText = true;
public float RubyHeight = 0.8f;
//ルビ文字表示用のテキストオブジェクト
Text[] rubyTextObjs = new Text[RUBY_OBJECT_MAX_COUNT];
//ルビ情報
RubyData[] rubyDatas = new RubyData[RUBY_OBJECT_MAX_COUNT];
//各文字の座標データ
Vector2[] chrPosition = new Vector2[TEXT_LENGTH_MAX];
//ルビ箇所の総数
int rubyCount;
/// <summary>
/// 初期化
/// </summary>
public void Initialize()
{
rubyCount = 0;
// 本文からルビ文字部分をカットして、ルビ文字データを作る。
SelfText.text = ParseRuby (SelfText.text);
// ルビ文字の生成
for (int i = 0; i < rubyCount; ++i) {
if (rubyTextObjs [i] != null) {
// text 設定
rubyTextObjs [i].font = RubyFont ?? SelfText.font;
rubyTextObjs [i].fontSize = RubyFontSize;
rubyTextObjs [i].fontStyle = RubyFontStyle;
rubyTextObjs [i].supportRichText = SupportRichText;
rubyTextObjs [i].text = rubyDatas [i].Text;
continue;
}
var go = new GameObject (i.ToString ());
rubyTextObjs [i] = go.AddComponent<Text> ();
// transform 設定
rubyTextObjs [i].transform.SetParent (base.transform);
rubyTextObjs [i].transform.localRotation = Quaternion.identity;
rubyTextObjs [i].transform.localScale = Vector3.one;
// text 設定
rubyTextObjs [i].font = RubyFont ? RubyFont : SelfText.font;
rubyTextObjs [i].fontSize = RubyFontSize;
rubyTextObjs [i].fontStyle = RubyFontStyle;
rubyTextObjs [i].supportRichText = SupportRichText;
rubyTextObjs [i].text = rubyDatas [i].Text;
rubyTextObjs [i].alignment = TextAnchor.MiddleCenter;
// text adjuster 設定
var textAdjust = go.AddComponent<TextSizeAdjuster> ();
textAdjust.Mode = TextSizeAdjuster.AdjustMode.HeightAndWidth;
textAdjust.AlwaysUpdate = false;
textAdjust.Adjust ();
}
}
/// <summary>
/// ルビ記号を解析して、ルビデータと本文を分離する。
/// </summary>
/// <returns>ルビ無しの文字列.</returns>
/// <param name="rubyText">ルビのタグが設定されている文字列.</param>
public string ParseRuby (string rubyText)
{
int n1 = rubyText.IndexOf (TAG_RUBY_START); //記号が何文字目にあるか?
int n2 = rubyText.IndexOf (TAG_RUBY_SEPARETOR); //記号が何文字目にあるか?
int n3 = rubyText.IndexOf (TAG_RUBY_END); //記号が何文字目にあるか?
//記号が発見できなかったら何もしない。
if (n1 == -1 || n2 == -1 || n3 == -1) {
return rubyText;//そのまま本文を返す。
}
string sub1 = rubyText.Substring (0, n1); // 先頭から'['までの部分
string sub2 = rubyText.Substring (n1+1,n2-n1-1);// '['〜':'の部分
string sub3 = rubyText.Substring (n2+1,n3-n2-1);// ':'〜']'の部分 (ルビ文字の部分)
string sub4 = rubyText.Substring (n3+1); // ']'以降の部分
rubyDatas [rubyCount].StartIndex = n1;//何文字目にルビを降るか?
rubyDatas [rubyCount].LastIndex = n2-2;//何文字目にルビを降るか?
rubyDatas [rubyCount].Text = sub3;
rubyCount++;
return ParseRuby (sub1+sub2+sub4);//再帰的に処理する。
}
#region UnityEvents
private new void OnEnable()
{
base.OnEnable();
Initialize ();
}
private new void OnDisable()
{
base.OnDisable();
for (int i = 0; i < rubyCount; i++) {
if (rubyTextObjs [i])
Destroy (rubyTextObjs [i].gameObject);
}
rubyCount = 0;
}
private new void OnDestroy()
{
base.OnDestroy();
for (int i = 0; i < rubyCount; i++) {
if (rubyTextObjs [i])
Destroy (rubyTextObjs [i].gameObject);
}
rubyCount = 0;
}
#if UNITY_EDITOR
public new void OnValidate()
{
base.OnValidate();
var graphics = base.GetComponent<Graphic>();
if (graphics != null)
{
graphics.SetVerticesDirty();
}
}
#endif
#endregion
#region IMeshModifier
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);
}
private void ModifyVertices(List<UIVertex> vertexList)
{
// 1文字が6頂点で構成されてる
// chrPosition[] に各文字の中心座標を格納している
int cnt = 0;//本文の文字数
for (int i = 0; i < vertexList.Count; i += VERTEX_COUNT_PER_CHARACTER)
{
chrPosition [cnt] = new Vector2 (
(vertexList [i].position.x + vertexList [i + 2].position.x) * 0.5f,
(vertexList [i].position.y + vertexList [i + 2].position.y) * 0.5f
);
cnt++;
}
//ルビ文字の準備 その2
//表示位置を決める(ルビ文字の表示位置は rubyDatas [i].StartIndex 番目の文字と rubyDatas [i].LastIndex 番目の文字の中間地点としている)
//どれだけ上にルビを表示するか。(フォントサイズの大きさxRubyHeight)分上にずらして表示
float dy = SelfText.fontSize * RubyHeight;
for (int i = 0; i < rubyCount; ++i) {
rubyTextObjs [i].transform.localPosition = new Vector3 (
(chrPosition [rubyDatas [i].StartIndex].x + chrPosition [rubyDatas [i].LastIndex].x) * 0.5f,
(chrPosition [rubyDatas [i].StartIndex].y + chrPosition [rubyDatas [i].LastIndex].y) * 0.5f + dy,
0
);
}
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment