BendText.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using System;
  2. using System.Linq;
  3. using TMPro;
  4. using UnityEngine;
  5. namespace TweenableObject
  6. {
  7. [DefaultExecutionOrder(100)]
  8. [ExecuteInEditMode]
  9. [RequireComponent(typeof(TextMeshPro))]
  10. public class BendText : MonoBehaviour
  11. {
  12. [SerializeField] private bool isActive;
  13. [SerializeField] private bool flip;
  14. [SerializeField] private float radius;
  15. [SerializeField, Range(0, 360)] private float spread;
  16. private RectTransform _rectTransform;
  17. private TextMeshPro _textMeshPro;
  18. private bool _isActive;
  19. private bool _flip;
  20. private float _lastRadius;
  21. private float _lastSpread;
  22. private Rect _lastRect;
  23. private float _maxY;
  24. private float _minY;
  25. private void Awake()
  26. {
  27. _rectTransform = GetComponent<RectTransform>();
  28. _textMeshPro = GetComponent<TextMeshPro>();
  29. }
  30. private void OnEnable()
  31. {
  32. ReCalculate();
  33. }
  34. private void OnDisable()
  35. {
  36. _textMeshPro.ForceMeshUpdate();
  37. }
  38. private void Update()
  39. {
  40. // EditModeでも動作するようUpdateで更新を検出
  41. ReCalculate();
  42. }
  43. // パラメータやTextMeshProの変更を監視し,変更があればメッシュの再変形を行う
  44. public void ReCalculate()
  45. {
  46. if (!_textMeshPro.havePropertiesChanged && _isActive == isActive && _flip == flip &&
  47. _lastRadius == radius && _lastSpread == spread && _rectTransform.rect == _lastRect) return;
  48. _isActive = isActive;
  49. _flip = flip;
  50. _lastRadius = radius;
  51. _lastSpread = spread;
  52. _lastRect = _rectTransform.rect;
  53. if (isActive) SetCurve();
  54. else _textMeshPro.ForceMeshUpdate();
  55. var enu = _textMeshPro.textInfo.meshInfo.SelectMany(m => m.vertices).Select(v => v.y).ToArray();
  56. _minY = enu.Min();
  57. _maxY = enu.Max();
  58. }
  59. // Textに対しメッシュの変形を実行
  60. private void SetCurve()
  61. {
  62. _textMeshPro.ForceMeshUpdate();
  63. var textInfo = _textMeshPro.textInfo;
  64. var characterCount = textInfo.characterCount;
  65. var rect = _rectTransform.rect;
  66. var minX = rect.xMin + _textMeshPro.margin.x;
  67. var maxX = rect.xMax - _textMeshPro.margin.y;
  68. for (var i = 0; i < characterCount; i++)
  69. {
  70. if (!textInfo.characterInfo[i].isVisible) continue;
  71. var vertexIndex = textInfo.characterInfo[i].vertexIndex;
  72. var materialIndex = textInfo.characterInfo[i].materialReferenceIndex;
  73. var vertices = textInfo.meshInfo[materialIndex].vertices;
  74. // 文字の中点座標を計算
  75. Vector3 offsetToMidBaseline =
  76. new Vector2((vertices[vertexIndex + 0].x + vertices[vertexIndex + 2].x) / 2,
  77. textInfo.characterInfo[i].baseLine);
  78. // 変形後の文字の中心位置を計算
  79. var val = (offsetToMidBaseline.x - minX) / (maxX - minX);
  80. var pos0 = CalcPositionFromCircle(val);
  81. var pos1 = CalcPositionFromCircle(val + 0.0001f);
  82. var rotation = Quaternion.FromToRotation(Vector3.right, (pos1 - pos0).normalized);
  83. // 各文字の4頂点への処理
  84. for (var j = 0; j < 4; ++j)
  85. {
  86. var point = vertices[vertexIndex + j];
  87. // 中点からの相対ベクトルを算出
  88. point -= offsetToMidBaseline;
  89. // 移動と回転を適用
  90. point = rotation * point + new Vector3(pos0.x, offsetToMidBaseline.y, pos0.z);
  91. vertices[vertexIndex + j] = point;
  92. }
  93. }
  94. _textMeshPro.UpdateVertexData();
  95. }
  96. // 0~1のvalueに対し,円周上の位置を返す
  97. private Vector3 CalcPositionFromCircle(float val)
  98. {
  99. var angle = (float)(1.5 * Math.PI + (val - 0.5f) * spread * Mathf.Deg2Rad);
  100. var x = radius * Mathf.Cos(angle);
  101. var z = (flip ? -1 : 1) * radius * (1 + Mathf.Sin(angle));
  102. return new Vector3(x, 0, z);
  103. }
  104. private readonly Color _rectColor = new Color(0.98f, 0.498f, 0.196f);
  105. private readonly Color _circleColor = new Color(0.176f, 0.784f, 1);
  106. private void OnDrawGizmosSelected()
  107. {
  108. var old = Gizmos.matrix;
  109. var transform1 = transform;
  110. var lossyScale = transform1.lossyScale;
  111. Gizmos.matrix = Matrix4x4.TRS(transform1.position + (flip ? -1 : 1) * transform1.forward * radius * lossyScale.z, transform1.rotation, lossyScale);
  112. const int step = 5;
  113. var rangeMin = (flip ? 0 : 180) - spread / 2;
  114. var rangeMax = (flip ? 0 : 180) + spread / 2;
  115. var from = new Vector3(radius * Mathf.Sin(rangeMin * Mathf.Deg2Rad), 0, radius * Mathf.Cos(rangeMin * Mathf.Deg2Rad));
  116. for (var i = rangeMin; i <= 360 + rangeMin; i += step)
  117. {
  118. // 境界部での丸め込み
  119. var deg = i;
  120. if (deg - rangeMin > 0 && deg - rangeMin <= step) deg = rangeMin;
  121. else if (deg - rangeMax > 0 && deg - rangeMax <= step) deg = rangeMax;
  122. var to = new Vector3(radius * Mathf.Sin(deg * Mathf.Deg2Rad), 0, radius * Mathf.Cos(deg * Mathf.Deg2Rad));
  123. if (deg == rangeMin || deg == rangeMax)
  124. {
  125. Gizmos.color = _rectColor;
  126. Gizmos.DrawLine(new Vector3(to.x, _minY, to.z), new Vector3(to.x, _maxY, to.z));
  127. Gizmos.DrawLine(new Vector3(to.x, _minY, to.z), new Vector3(to.x, _maxY, to.z));
  128. }
  129. if (from != to && rangeMin <= deg && deg <= rangeMax)
  130. {
  131. Gizmos.color = _rectColor;
  132. Gizmos.DrawLine(new Vector3(from.x, _minY, from.z), new Vector3(to.x, _minY, to.z));
  133. Gizmos.DrawLine(new Vector3(from.x, _maxY, from.z), new Vector3(to.x, _maxY, to.z));
  134. }
  135. else
  136. {
  137. Gizmos.color = _circleColor;
  138. Gizmos.DrawLine(from, to);
  139. }
  140. from = to;
  141. }
  142. Gizmos.matrix = old;
  143. }
  144. }
  145. }