44// https://github.com/coposuke/TextMeshProAnimator
55//
66// ===================================
7-
7+ //#define TMPA_OUTPUT_LOG
88
99using UnityEngine ;
1010using TMPro ;
@@ -38,15 +38,13 @@ public class TextMeshProGeometryAnimator : MonoBehaviour
3838 /// ループするかどうか
3939 /// </summary>
4040 [ SerializeField ]
41- private bool isLoop = false ;
42-
41+ private bool isLoop = false ;
42+
4343 /// <summary>
4444 /// アニメーション中かどうか
4545 /// </summary>
46- public bool isAnimating { get { return isPlaying && time < maxTime ; } }
47- /// <summary>再生フラグ</summary>
48- private bool isPlaying = false ;
49-
46+ public bool isAnimating { get ; private set ; } = false ;
47+
5048 /// <summary>
5149 /// 文字送りアニメーションデータ
5250 /// </summary>
@@ -61,8 +59,8 @@ public class TextMeshProGeometryAnimator : MonoBehaviour
6159 /// <summary>アニメーション時間</summary>
6260 private float time = 0f ;
6361 /// <summary>アニメーション最大時間</summary>
64- private float maxTime = 0f ;
65-
62+ private float maxTime = 0f ;
63+
6664 /// <summary>頂点座標のキャッシュ</summary>
6765 private Vector3 [ ] [ ] baseVertices = default ;
6866 /// <summary>頂点カラーのキャッシュ</summary>
@@ -75,29 +73,52 @@ public class TextMeshProGeometryAnimator : MonoBehaviour
7573 private int characterCount = 0 ;
7674
7775
76+ #region Unity Events
7877#if UNITY_EDITOR
7978 /// <summary>
8079 /// Unity Event OnValidate
8180 /// </summary>
8281 private void OnValidate ( )
8382 {
83+ if ( Application . isPlaying )
84+ return ;
85+
8486 if ( this . textComponent == null )
85- {
86- this . textComponent = GetComponent < TMP_Text > ( ) ;
87- }
87+ this . textComponent = GetComponent < TMP_Text > ( ) ;
88+
89+ if ( this . textComponent == null )
90+ return ;
8891
89- if ( ! Application . isPlaying )
90- this . time = this . maxTime * this . progress ;
92+ if ( this . gameObject . activeSelf )
93+ OnValidateChild ( ) ;
94+ }
9195
92- UpdateMaxVisibleCharacters ( ) ;
93- this . textComponent . ForceMeshUpdate ( true ) ;
96+ /// <summary>
97+ /// OnValidateの再帰メソッド
98+ /// </summary>
99+ private void OnValidateChild ( )
100+ {
101+ if ( Application . isPlaying )
102+ return ;
94103
95- UpdateText ( ) ;
96- UpdateCachedVertex ( true ) ;
97- UpdateAnimation ( ) ;
98- }
104+ // シーンロード後およびコンパイル直後はTextInfoが準備できていないので強制更新する
105+ this . textComponent . ForceMeshUpdate ( true ) ;
106+
107+ if ( ! Refresh ( true ) )
108+ {
109+ // ゲームプレイ終了時はTextInfoが異常値な為,遅らせて処理する
110+ UnityEditor . EditorApplication . delayCall += ( ) =>
111+ {
112+ // ゲームプレイ終了時にDestroyされているケースを回避
113+ if ( this . textComponent == null )
114+ return ;
115+
116+ OnValidateChild ( ) ;
117+ } ;
118+ }
119+ }
99120#endif
100-
121+
101122 /// <summary>
102123 /// Unity Event Awake
103124 /// </summary>
@@ -114,10 +135,13 @@ private void OnEnable()
114135 {
115136 TMPro_EventManager . TEXT_CHANGED_EVENT . Add ( OnChangedText ) ;
116137
138+ // シーンロード時はTextInfoが異常値な為,強制更新する
139+ this . textComponent ? . ForceMeshUpdate ( true ) ;
140+
117141 if ( this . playOnEnable )
118142 Play ( ) ;
119143 else
120- Finish ( ) ;
144+ Refresh ( true ) ;
121145 }
122146
123147 /// <summary>
@@ -126,60 +150,51 @@ private void OnEnable()
126150 private void OnDisable ( )
127151 {
128152 TMPro_EventManager . TEXT_CHANGED_EVENT . Remove ( OnChangedText ) ;
129-
130- Finish ( ) ;
131- }
132-
153+ }
154+
133155 /// <summary>
134156 /// Unity Event Update
135157 /// </summary>
136158 private void Update ( )
137159 {
160+ if ( this . textComponent == null ) { return ; }
161+ if ( ! this . textComponent . isActiveAndEnabled ) { return ; }
138162 if ( null == animationData ) { return ; }
139- if ( null == textInfo ) { return ; }
140-
141- if ( playByProgress )
142- {
143- time = maxTime * progress ;
144- }
145- else if ( 0f < maxTime )
146- {
147- if ( ! isPlaying ) { return ; }
148-
149- time += Time . deltaTime * animationData . speed ;
150-
151- if ( isLoop )
152- {
153- time += ( time < 0f ? maxTime : 0f ) ;
154- time %= maxTime ;
155- }
156- else
157- {
158- time = Mathf . Clamp ( time , 0f , maxTime ) ;
159- }
160-
161- #if UNITY_EDITOR
162- progress = time / maxTime ;
163- #endif
164- }
165-
166- UpdateAnimation ( ) ;
163+ if ( null == textInfo ) { return ; }
164+
165+ if ( this . isAnimating || this . playByProgress )
166+ {
167+ UpdateTime ( this . playByProgress , Time . deltaTime ) ;
168+ UpdateAnimation ( ) ;
169+
170+ if ( this . isAnimating )
171+ {
172+ if ( ( this . animationData . speed > 0 && this . time >= this . maxTime ) ||
173+ ( this . animationData . speed < 0 && this . time <= 0f ) )
174+ {
175+ this . isAnimating = false ;
176+ Log ( $ "TMPA Stop: { this . textComponent . name } ") ;
177+ }
178+ }
179+ }
167180 }
168181
169182 /// <summary>
170183 /// TextMesh Proのtext変更時に呼び出されるメソッドです
171184 /// OnEnableとOnDisableにてTMPro_EventManagerに登録しています
172185 /// </summary>
173- /// <param name="obj"></param>
174186 private void OnChangedText ( Object obj )
175187 {
176188 if ( obj == this . textComponent )
177189 {
178- UpdateText ( ) ;
179- UpdateAnimation ( ) ;
190+ Log ( $ "TMPA OnChangedText: { this . textComponent . name } " ) ;
191+ Refresh ( this . playByProgress ) ;
180192 }
181193 }
194+ #endregion
182195
196+
197+ #region For User
183198 /// <summary>
184199 /// アニメーションデータの上書き設定
185200 /// </summary>
@@ -193,32 +208,111 @@ public void SetAnimation(TextMeshProGeometryAnimation animation)
193208 /// </summary>
194209 public void Play ( )
195210 {
196- this . isPlaying = true ;
197- this . time = 0.0f ;
198- UpdateText ( ) ;
199- UpdateAnimation ( ) ;
211+ Log ( $ "TMPA Play: { this . textComponent . name } ") ;
212+ this . isAnimating = true ;
213+ Refresh ( true ) ;
200214 }
201215
202216 /// <summary>
203217 /// 強制終了
204218 /// </summary>
205- public void Finish ( )
219+ public void Finish ( float normalizedTime = 0f )
206220 {
207- this . isPlaying = false ;
208- this . time = this . maxTime ;
209- UpdateText ( ) ;
210- UpdateAnimation ( ) ;
221+ Log ( $ "TMPA Finish: { this . textComponent . name } ") ;
222+ this . isAnimating = false ;
223+ this . time = this . maxTime * normalizedTime ;
224+ Refresh ( false ) ;
225+ }
226+ #endregion
227+
228+
229+ #region Private Methods
230+ private bool Refresh ( bool useProgress )
231+ {
232+ UpdateText ( ) ;
233+
234+ if ( VerifyTextInfo ( ) )
235+ {
236+ UpdateTime ( useProgress , 0f ) ;
237+
238+ // MaxVisibleCharactersに変動があった場合はForceMeshUpdateでメッシュを更新する必要がある
239+ if ( UpdateMaxVisibleCharacters ( ) )
240+ {
241+ this . textComponent ? . ForceMeshUpdate ( true ) ;
242+ UpdateCachedVertex ( true ) ;
243+ }
244+
245+ UpdateAnimation ( ) ;
246+
247+ return true ;
248+ }
249+
250+ return false ;
251+ }
252+
253+ /// <summary>
254+ /// 時間の更新
255+ /// </summary>
256+ private void UpdateTime ( bool useProgress , float elapsedTime )
257+ {
258+ if ( useProgress )
259+ {
260+ this . time = this . maxTime * this . progress ;
261+ }
262+ else if ( 0f < this . maxTime )
263+ {
264+ if ( ! this . isAnimating ) { return ; }
265+
266+ this . time += elapsedTime * this . animationData . speed ;
267+
268+ if ( this . isLoop )
269+ {
270+ this . time += ( this . time < 0f ? this . maxTime : 0f ) ;
271+ this . time %= this . maxTime ;
272+ }
273+ else
274+ {
275+ this . time = Mathf . Clamp ( this . time , 0f , this . maxTime ) ;
276+ }
277+ }
278+ }
279+
280+ /// <summary>
281+ /// TMPro Textの情報が有効かどうか(主に開始時に異常値が発生する)
282+ /// </summary>
283+ private bool VerifyTextInfo ( )
284+ {
285+ if ( this . textComponent == null )
286+ return false ;
287+
288+ if ( ! this . textComponent . isActiveAndEnabled )
289+ return false ;
290+
291+ if ( this . textInfo == null )
292+ return false ;
293+
294+ if ( this . textInfo . characterCount <= 0 )
295+ return false ;
296+
297+ for ( int i = 0 ; i < this . textInfo . materialCount ; i ++ )
298+ {
299+ if ( this . textInfo . meshInfo [ i ] . vertices == null ||
300+ this . textInfo . meshInfo [ i ] . colors32 == null )
301+ return false ;
302+ }
303+
304+ return true ;
211305 }
212306
213307 /// <summary>
214308 /// TMPro Textの情報更新
215309 /// </summary>
216310 private void UpdateText ( )
217311 {
218- this . textInfo = textComponent . textInfo ;
312+ this . textInfo = this . textComponent . textInfo ;
219313
220314 // 各アニメーション要素で、一番時間がかかるものを最大時間として計算
221- maxTime = Mathf . Max (
315+ this . maxTime = Mathf . Max (
222316 CalcAnimationTotalTime ( this . textInfo . characterCount , this . animationData . position ) ,
223317 CalcAnimationTotalTime ( this . textInfo . characterCount , this . animationData . rotation ) ,
224318 CalcAnimationTotalTime ( this . textInfo . characterCount , this . animationData . scale ) ,
@@ -235,7 +329,7 @@ private void UpdateText()
235329 /// <summary>
236330 /// 頂点データのキャッシュ
237331 /// </summary>
238- /// <returns>成功判定</returns>
332+ /// <returns>成功判定(失敗時は異常なTextInfoである, 主に開始時に発生) </returns>
239333 private bool UpdateCachedVertex ( bool forceCopy )
240334 {
241335 if ( this . textInfo == null )
@@ -286,10 +380,14 @@ private bool UpdateCachedVertex(bool forceCopy)
286380 return true ;
287381 }
288382
289- private bool UpdateMaxVisibleCharacters ( )
290- {
291- // マーカーや下線等の追加描画物はどうしても描画されてしまうので、
292- // 表示最大数も合わせてアニメーションすることで対応しています。
383+ /// <summary>
384+ /// MaxVisibleCharactersの更新
385+ /// </summary>
386+ /// <returns></returns>
387+ private bool UpdateMaxVisibleCharacters ( )
388+ {
389+ // マーカーや下線等の追加描画物はどうしても描画されてしまうので、
390+ // 表示最大数も合わせてアニメーションすることで対応しています。
293391 if ( this . animationData . useMaxVisibleCharacter )
294392 {
295393 var visibleCharacters = CalcAnimationCharacterCount ( time , this . animationData . alpha ) ;
@@ -298,19 +396,20 @@ private bool UpdateMaxVisibleCharacters()
298396 this . textComponent . maxVisibleCharacters = visibleCharacters ;
299397 return true ;
300398 }
301- }
302-
303- return false ;
399+ }
400+
401+ return false ;
304402 }
305403
306404 /// <summary>
307405 /// TMPro Textの頂点情報の編集
308406 /// </summary>
309407 private void UpdateAnimation ( )
310408 {
409+ // Wave位置(テキストの動き出し)に合わせて,MaxVisibleCharactersの最大値を更新する
311410 bool forceCacheCopy = UpdateMaxVisibleCharacters ( ) ;
312-
313- // アニメーション用の頂点キャッシュ更新
411+
412+ // アニメーション用の頂点キャッシュ更新
314413 if ( ! UpdateCachedVertex ( forceCacheCopy ) )
315414 return ;
316415
@@ -526,4 +625,13 @@ static private int CalcAnimationCharacterCount(float time, TextMeshProGeometryAn
526625 if ( item . wave <= 0.0f ) { return int . MaxValue ; }
527626 return ( int ) ( ( time - item . delay ) / item . wave ) + 1 ;
528627 }
628+
629+ [ System . Diagnostics . Conditional ( "TMPA_OUTPUT_LOG" ) ]
630+ static private void Log ( string str )
631+ {
632+ #if TMPA_OUTPUT_LOG
633+ Debug . Log ( str ) ;
634+ #endif
635+ }
636+ #endregion
529637}
0 commit comments