You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
7.0 KiB
C#

//--------------------------------------------------------------------------------------------------------------------------------
// 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<char> allowed = new List<char>(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<ParticleSystem>();
var sourceParticle = this.GetComponent<ParticleSystem>();
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<ParticleSystemRenderer>();
var sourceParticleRenderer = this.GetComponent<ParticleSystemRenderer>();
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<ParticleSystem>();
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
}