//-------------------------------------------------------------------------------------------------------------------------------- // Cartoon FX // (c) 2012-2020 Jean Moreno //-------------------------------------------------------------------------------------------------------------------------------- using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace CartoonFX { [RequireComponent(typeof(ParticleSystem))] public class CFXR_ParticleText : MonoBehaviour { #if UNITY_EDITOR [Header("Text")] public string text; public float size = 1f; public float letterSpacing = 0.44f; [Header("Colors")] public Color backgroundColor = new Color(0, 0, 0, 1); public Color color1 = new Color(1, 1, 1, 1); public Color color2 = new Color(0, 0, 1, 1); [Header("Delay")] public float delay = 0.05f; public bool cumulativeDelay = false; [Range(0f, 2f)] public float compensateLifetime = 0; [Header("Misc")] public float lifetimeMultiplier = 1f; [Range(-90f, 90f)] public float rotation = -5f; public float sortingFudgeOffset = 0.1f; public CFXR_ParticleTextFontAsset font; public KeyCode refreshTextKey = KeyCode.R; void OnValidate() { this.hideFlags = HideFlags.DontSaveInBuild; if (text == null || font == null) { return; } // parse text to only allow valid characters List allowed = new List(font.CharSequence.ToCharArray()); allowed.Add(' '); var chars = text.ToUpperInvariant().ToCharArray(); string newText = ""; foreach (var c in chars) { if (allowed.Contains(c)) { newText += c; } } text = newText; // prevent negative or 0 size size = Mathf.Max(0.001f, size); } public void GenerateText() { if (text == null || font == null || !font.IsValid()) return; // delete all children int length = this.transform.childCount; int overflow = 0; while (this.transform.childCount > 0) { Object.DestroyImmediate(this.transform.GetChild(0).gameObject); overflow++; if (overflow > 1000) { // just in case... Debug.LogError("Overflow!"); break; } } // create one particle per character if (!string.IsNullOrEmpty(text)) { // calculate total width offset float totalWidth = 0f; for (int i = 1; i < text.Length; i++) { if (char.IsWhiteSpace(text[i])) { totalWidth += letterSpacing * size; } else { int index = font.CharSequence.IndexOf(text[i]); var sprite = font.CharSprites[index]; float charWidth = sprite.rect.width + font.CharKerningOffsets[index].post + font.CharKerningOffsets[index].pre; totalWidth += (charWidth * 0.01f + letterSpacing) * size; } } float offset = totalWidth/2f; totalWidth = 0f; for (int i = 0; i < text.Length; i++) { var letter = text[i]; if (char.IsWhiteSpace(letter)) { totalWidth += letterSpacing * size; } else { int index = font.CharSequence.IndexOf(text[i]); var sprite = font.CharSprites[index]; // calculate char particle size ratio var ratio = size * sprite.rect.width/50f; // calculate char position totalWidth += font.CharKerningOffsets[index].pre * 0.01f * size; var position = (totalWidth-offset)/ratio; float charWidth = sprite.rect.width + font.CharKerningOffsets[index].post; totalWidth += (charWidth * 0.01f + letterSpacing) * size; // create gameobject with particle system var letterObj = new GameObject(letter.ToString(), typeof(ParticleSystem)); letterObj.transform.SetParent(this.transform); letterObj.transform.localPosition = Vector3.zero; letterObj.transform.localRotation = Quaternion.identity; var ps = letterObj.GetComponent(); var sourceParticle = this.GetComponent(); EditorUtility.CopySerialized(sourceParticle, ps); letterObj.name = letter.ToString(); // set particle system parameters var main = ps.main; main.startSizeXMultiplier *= ratio; main.startSizeYMultiplier *= ratio; main.startSizeZMultiplier *= ratio; ps.textureSheetAnimation.SetSprite(0, sprite); main.startRotation = Mathf.Deg2Rad * rotation; main.startColor = backgroundColor; var cd = ps.customData; cd.enabled = true; cd.SetColor(ParticleSystemCustomData.Custom1, color1); cd.SetColor(ParticleSystemCustomData.Custom2, color2); if (cumulativeDelay) { main.startDelay = delay * i; main.startLifetime = Mathf.LerpUnclamped(main.startLifetime.constant, main.startLifetime.constant + (delay * (text.Length-i)), compensateLifetime); } else { main.startDelay = delay; } main.startLifetime = main.startLifetime.constant * lifetimeMultiplier; // particle system renderer parameters var psr = ps.GetComponent(); var sourceParticleRenderer = this.GetComponent(); EditorUtility.CopySerialized(sourceParticleRenderer, psr); psr.enabled = true; psr.pivot = new Vector3(psr.pivot.x + position, psr.pivot.y, psr.pivot.z); psr.sortingFudge += i * sortingFudgeOffset; } } } // restart particle system updateFrame = 0; if (!editorUpdateSubscribed) { editorUpdateSubscribed = true; EditorApplication.update += RestartParticleSystem; } } int updateFrame = 0; bool editorUpdateSubscribed = false; void RestartParticleSystem() { var particleSystem = this.GetComponent(); if (updateFrame == 0) { particleSystem.Play(true); } else if (updateFrame == 5) { particleSystem.time = 0f; EditorApplication.update -= RestartParticleSystem; editorUpdateSubscribed = false; } updateFrame++; } #endif } #if UNITY_EDITOR [CustomEditor(typeof(CFXR_ParticleText))] public class ParticleTextEditor : Editor { public override void OnInspectorGUI() { var prefab = PrefabUtility.GetPrefabInstanceStatus(target); if (prefab != PrefabInstanceStatus.NotAPrefab) { EditorGUILayout.HelpBox("Cartoon FX Particle Text doesn't work on Prefab Instances, as it needs to destroy/create children GameObjects.\nYou can right-click on the object, and select \"Unpack Prefab Completely\" to make it an independent Game Object.", MessageType.Warning); return; } base.OnInspectorGUI(); GUILayout.Space(8); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); bool refresh = GUILayout.Button(" Refresh Text ", GUILayout.Height(30)) || GUI.changed; GUILayout.EndHorizontal(); if (((CFXR_ParticleText)target).refreshTextKey != KeyCode.None && Event.current.type == EventType.KeyDown) { if (Event.current.keyCode == ((CFXR_ParticleText)target).refreshTextKey) { Event.current.Use(); refresh = true; } } if (refresh) { Undo.RecordObject(this.target, "Generate Particle Text"); (this.target as CFXR_ParticleText).GenerateText(); } } } #endif }