SFX added
This commit is contained in:
@@ -1,287 +1,287 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
public partial class CFXR_Effect : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class CameraShake
|
||||
{
|
||||
public enum ShakeSpace
|
||||
{
|
||||
Screen,
|
||||
World
|
||||
}
|
||||
|
||||
static public bool editorPreview = true;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public bool enabled = false;
|
||||
[Space]
|
||||
public bool useMainCamera = true;
|
||||
public List<Camera> cameras = new List<Camera>();
|
||||
[Space]
|
||||
public float delay = 0.0f;
|
||||
public float duration = 1.0f;
|
||||
public ShakeSpace shakeSpace = ShakeSpace.Screen;
|
||||
public Vector3 shakeStrength = new Vector3(0.1f, 0.1f, 0.1f);
|
||||
public AnimationCurve shakeCurve = AnimationCurve.Linear(0, 1, 1, 0);
|
||||
[Space]
|
||||
[Range(0, 0.1f)] public float shakesDelay = 0;
|
||||
|
||||
[System.NonSerialized] public bool isShaking;
|
||||
Dictionary<Camera, Vector3> camerasPreRenderPosition = new Dictionary<Camera, Vector3>();
|
||||
Vector3 shakeVector;
|
||||
float delaysTimer;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// STATIC
|
||||
// Use static methods to dispatch the Camera callbacks, to ensure that ScreenShake components are called in an order in PreRender,
|
||||
// and in the _reverse_ order for PostRender, so that the final Camera position is the same as it is originally (allowing concurrent
|
||||
// screen shake to be active)
|
||||
|
||||
static bool s_CallbackRegistered;
|
||||
static List<CameraShake> s_CameraShakes = new List<CameraShake>();
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
static void OnPreRenderCamera_Static_URP(ScriptableRenderContext context, Camera cam)
|
||||
{
|
||||
OnPreRenderCamera_Static(cam);
|
||||
}
|
||||
static void OnPostRenderCamera_Static_URP(ScriptableRenderContext context, Camera cam)
|
||||
{
|
||||
OnPostRenderCamera_Static(cam);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void OnPreRenderCamera_Static(Camera cam)
|
||||
{
|
||||
int count = s_CameraShakes.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var ss = s_CameraShakes[i];
|
||||
ss.onPreRenderCamera(cam);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnPostRenderCamera_Static(Camera cam)
|
||||
{
|
||||
int count = s_CameraShakes.Count;
|
||||
for (int i = count-1; i >= 0; i--)
|
||||
{
|
||||
var ss = s_CameraShakes[i];
|
||||
ss.onPostRenderCamera(cam);
|
||||
}
|
||||
}
|
||||
|
||||
static void RegisterStaticCallback(CameraShake cameraShake)
|
||||
{
|
||||
s_CameraShakes.Add(cameraShake);
|
||||
|
||||
if (!s_CallbackRegistered)
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
if (GraphicsSettings.currentRenderPipeline == null)
|
||||
#else
|
||||
if (GraphicsSettings.renderPipelineAsset == null)
|
||||
#endif
|
||||
{
|
||||
// Built-in Render Pipeline
|
||||
Camera.onPreRender += OnPreRenderCamera_Static;
|
||||
Camera.onPostRender += OnPostRenderCamera_Static;
|
||||
}
|
||||
else
|
||||
{
|
||||
// URP
|
||||
RenderPipelineManager.beginCameraRendering += OnPreRenderCamera_Static_URP;
|
||||
RenderPipelineManager.endCameraRendering += OnPostRenderCamera_Static_URP;
|
||||
}
|
||||
#else
|
||||
Camera.onPreRender += OnPreRenderCamera_Static;
|
||||
Camera.onPostRender += OnPostRenderCamera_Static;
|
||||
#endif
|
||||
|
||||
s_CallbackRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void UnregisterStaticCallback(CameraShake cameraShake)
|
||||
{
|
||||
s_CameraShakes.Remove(cameraShake);
|
||||
|
||||
if (s_CallbackRegistered && s_CameraShakes.Count == 0)
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
if (GraphicsSettings.currentRenderPipeline == null)
|
||||
#else
|
||||
if (GraphicsSettings.renderPipelineAsset == null)
|
||||
#endif
|
||||
{
|
||||
// Built-in Render Pipeline
|
||||
Camera.onPreRender -= OnPreRenderCamera_Static;
|
||||
Camera.onPostRender -= OnPostRenderCamera_Static;
|
||||
}
|
||||
else
|
||||
{
|
||||
// URP
|
||||
RenderPipelineManager.beginCameraRendering -= OnPreRenderCamera_Static_URP;
|
||||
RenderPipelineManager.endCameraRendering -= OnPostRenderCamera_Static_URP;
|
||||
}
|
||||
#else
|
||||
Camera.onPreRender -= OnPreRenderCamera_Static;
|
||||
Camera.onPostRender -= OnPostRenderCamera_Static;
|
||||
#endif
|
||||
|
||||
s_CallbackRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void onPreRenderCamera(Camera cam)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
//add scene view camera if necessary
|
||||
if (SceneView.currentDrawingSceneView != null && SceneView.currentDrawingSceneView.camera == cam && !camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
camerasPreRenderPosition.Add(cam, cam.transform.localPosition);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (isShaking && camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
camerasPreRenderPosition[cam] = cam.transform.localPosition;
|
||||
|
||||
if (Time.timeScale <= 0) return;
|
||||
|
||||
switch (shakeSpace)
|
||||
{
|
||||
case ShakeSpace.Screen: cam.transform.localPosition += cam.transform.rotation * shakeVector; break;
|
||||
case ShakeSpace.World: cam.transform.localPosition += shakeVector; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onPostRenderCamera(Camera cam)
|
||||
{
|
||||
if (camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
cam.transform.localPosition = camerasPreRenderPosition[cam];
|
||||
}
|
||||
}
|
||||
|
||||
public void fetchCameras()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var cam in cameras)
|
||||
{
|
||||
if (cam == null) continue;
|
||||
|
||||
camerasPreRenderPosition.Remove(cam);
|
||||
}
|
||||
|
||||
cameras.Clear();
|
||||
|
||||
if (useMainCamera && Camera.main != null)
|
||||
{
|
||||
cameras.Add(Camera.main);
|
||||
}
|
||||
|
||||
foreach (var cam in cameras)
|
||||
{
|
||||
if (cam == null) continue;
|
||||
|
||||
if (!camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
camerasPreRenderPosition.Add(cam, Vector3.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartShake()
|
||||
{
|
||||
if (isShaking)
|
||||
{
|
||||
StopShake();
|
||||
}
|
||||
|
||||
isShaking = true;
|
||||
RegisterStaticCallback(this);
|
||||
}
|
||||
|
||||
public void StopShake()
|
||||
{
|
||||
isShaking = false;
|
||||
shakeVector = Vector3.zero;
|
||||
UnregisterStaticCallback(this);
|
||||
}
|
||||
|
||||
public void animate(float time)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!editorPreview && !EditorApplication.isPlaying)
|
||||
{
|
||||
shakeVector = Vector3.zero;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
float totalDuration = duration + delay;
|
||||
if (time < totalDuration)
|
||||
{
|
||||
if (time < delay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isShaking)
|
||||
{
|
||||
this.StartShake();
|
||||
}
|
||||
|
||||
// duration of the camera shake
|
||||
float delta = Mathf.Clamp01(time/totalDuration);
|
||||
|
||||
// delay between each camera move
|
||||
if (shakesDelay > 0)
|
||||
{
|
||||
delaysTimer += Time.deltaTime;
|
||||
if (delaysTimer < shakesDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (delaysTimer >= shakesDelay)
|
||||
{
|
||||
delaysTimer -= shakesDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var randomVec = new Vector3(Random.value, Random.value, Random.value);
|
||||
var shakeVec = Vector3.Scale(randomVec, shakeStrength) * (Random.value > 0.5f ? -1 : 1);
|
||||
shakeVector = shakeVec * shakeCurve.Evaluate(delta) * GLOBAL_CAMERA_SHAKE_MULTIPLIER;
|
||||
}
|
||||
else if (isShaking)
|
||||
{
|
||||
StopShake();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
public partial class CFXR_Effect : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class CameraShake
|
||||
{
|
||||
public enum ShakeSpace
|
||||
{
|
||||
Screen,
|
||||
World
|
||||
}
|
||||
|
||||
static public bool editorPreview = true;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
public bool enabled = false;
|
||||
[Space]
|
||||
public bool useMainCamera = true;
|
||||
public List<Camera> cameras = new List<Camera>();
|
||||
[Space]
|
||||
public float delay = 0.0f;
|
||||
public float duration = 1.0f;
|
||||
public ShakeSpace shakeSpace = ShakeSpace.Screen;
|
||||
public Vector3 shakeStrength = new Vector3(0.1f, 0.1f, 0.1f);
|
||||
public AnimationCurve shakeCurve = AnimationCurve.Linear(0, 1, 1, 0);
|
||||
[Space]
|
||||
[Range(0, 0.1f)] public float shakesDelay = 0;
|
||||
|
||||
[System.NonSerialized] public bool isShaking;
|
||||
Dictionary<Camera, Vector3> camerasPreRenderPosition = new Dictionary<Camera, Vector3>();
|
||||
Vector3 shakeVector;
|
||||
float delaysTimer;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// STATIC
|
||||
// Use static methods to dispatch the Camera callbacks, to ensure that ScreenShake components are called in an order in PreRender,
|
||||
// and in the _reverse_ order for PostRender, so that the final Camera position is the same as it is originally (allowing concurrent
|
||||
// screen shake to be active)
|
||||
|
||||
static bool s_CallbackRegistered;
|
||||
static List<CameraShake> s_CameraShakes = new List<CameraShake>();
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
static void OnPreRenderCamera_Static_URP(ScriptableRenderContext context, Camera cam)
|
||||
{
|
||||
OnPreRenderCamera_Static(cam);
|
||||
}
|
||||
static void OnPostRenderCamera_Static_URP(ScriptableRenderContext context, Camera cam)
|
||||
{
|
||||
OnPostRenderCamera_Static(cam);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void OnPreRenderCamera_Static(Camera cam)
|
||||
{
|
||||
int count = s_CameraShakes.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var ss = s_CameraShakes[i];
|
||||
ss.onPreRenderCamera(cam);
|
||||
}
|
||||
}
|
||||
|
||||
static void OnPostRenderCamera_Static(Camera cam)
|
||||
{
|
||||
int count = s_CameraShakes.Count;
|
||||
for (int i = count-1; i >= 0; i--)
|
||||
{
|
||||
var ss = s_CameraShakes[i];
|
||||
ss.onPostRenderCamera(cam);
|
||||
}
|
||||
}
|
||||
|
||||
static void RegisterStaticCallback(CameraShake cameraShake)
|
||||
{
|
||||
s_CameraShakes.Add(cameraShake);
|
||||
|
||||
if (!s_CallbackRegistered)
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
if (GraphicsSettings.currentRenderPipeline == null)
|
||||
#else
|
||||
if (GraphicsSettings.renderPipelineAsset == null)
|
||||
#endif
|
||||
{
|
||||
// Built-in Render Pipeline
|
||||
Camera.onPreRender += OnPreRenderCamera_Static;
|
||||
Camera.onPostRender += OnPostRenderCamera_Static;
|
||||
}
|
||||
else
|
||||
{
|
||||
// URP
|
||||
RenderPipelineManager.beginCameraRendering += OnPreRenderCamera_Static_URP;
|
||||
RenderPipelineManager.endCameraRendering += OnPostRenderCamera_Static_URP;
|
||||
}
|
||||
#else
|
||||
Camera.onPreRender += OnPreRenderCamera_Static;
|
||||
Camera.onPostRender += OnPostRenderCamera_Static;
|
||||
#endif
|
||||
|
||||
s_CallbackRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void UnregisterStaticCallback(CameraShake cameraShake)
|
||||
{
|
||||
s_CameraShakes.Remove(cameraShake);
|
||||
|
||||
if (s_CallbackRegistered && s_CameraShakes.Count == 0)
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
if (GraphicsSettings.currentRenderPipeline == null)
|
||||
#else
|
||||
if (GraphicsSettings.renderPipelineAsset == null)
|
||||
#endif
|
||||
{
|
||||
// Built-in Render Pipeline
|
||||
Camera.onPreRender -= OnPreRenderCamera_Static;
|
||||
Camera.onPostRender -= OnPostRenderCamera_Static;
|
||||
}
|
||||
else
|
||||
{
|
||||
// URP
|
||||
RenderPipelineManager.beginCameraRendering -= OnPreRenderCamera_Static_URP;
|
||||
RenderPipelineManager.endCameraRendering -= OnPostRenderCamera_Static_URP;
|
||||
}
|
||||
#else
|
||||
Camera.onPreRender -= OnPreRenderCamera_Static;
|
||||
Camera.onPostRender -= OnPostRenderCamera_Static;
|
||||
#endif
|
||||
|
||||
s_CallbackRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
void onPreRenderCamera(Camera cam)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
//add scene view camera if necessary
|
||||
if (SceneView.currentDrawingSceneView != null && SceneView.currentDrawingSceneView.camera == cam && !camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
camerasPreRenderPosition.Add(cam, cam.transform.localPosition);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (isShaking && camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
camerasPreRenderPosition[cam] = cam.transform.localPosition;
|
||||
|
||||
if (Time.timeScale <= 0) return;
|
||||
|
||||
switch (shakeSpace)
|
||||
{
|
||||
case ShakeSpace.Screen: cam.transform.localPosition += cam.transform.rotation * shakeVector; break;
|
||||
case ShakeSpace.World: cam.transform.localPosition += shakeVector; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onPostRenderCamera(Camera cam)
|
||||
{
|
||||
if (camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
cam.transform.localPosition = camerasPreRenderPosition[cam];
|
||||
}
|
||||
}
|
||||
|
||||
public void fetchCameras()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var cam in cameras)
|
||||
{
|
||||
if (cam == null) continue;
|
||||
|
||||
camerasPreRenderPosition.Remove(cam);
|
||||
}
|
||||
|
||||
cameras.Clear();
|
||||
|
||||
if (useMainCamera && Camera.main != null)
|
||||
{
|
||||
cameras.Add(Camera.main);
|
||||
}
|
||||
|
||||
foreach (var cam in cameras)
|
||||
{
|
||||
if (cam == null) continue;
|
||||
|
||||
if (!camerasPreRenderPosition.ContainsKey(cam))
|
||||
{
|
||||
camerasPreRenderPosition.Add(cam, Vector3.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void StartShake()
|
||||
{
|
||||
if (isShaking)
|
||||
{
|
||||
StopShake();
|
||||
}
|
||||
|
||||
isShaking = true;
|
||||
RegisterStaticCallback(this);
|
||||
}
|
||||
|
||||
public void StopShake()
|
||||
{
|
||||
isShaking = false;
|
||||
shakeVector = Vector3.zero;
|
||||
UnregisterStaticCallback(this);
|
||||
}
|
||||
|
||||
public void animate(float time)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!editorPreview && !EditorApplication.isPlaying)
|
||||
{
|
||||
shakeVector = Vector3.zero;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
float totalDuration = duration + delay;
|
||||
if (time < totalDuration)
|
||||
{
|
||||
if (time < delay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isShaking)
|
||||
{
|
||||
this.StartShake();
|
||||
}
|
||||
|
||||
// duration of the camera shake
|
||||
float delta = Mathf.Clamp01(time/totalDuration);
|
||||
|
||||
// delay between each camera move
|
||||
if (shakesDelay > 0)
|
||||
{
|
||||
delaysTimer += Time.deltaTime;
|
||||
if (delaysTimer < shakesDelay)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (delaysTimer >= shakesDelay)
|
||||
{
|
||||
delaysTimer -= shakesDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var randomVec = new Vector3(Random.value, Random.value, Random.value);
|
||||
var shakeVec = Vector3.Scale(randomVec, shakeStrength) * (Random.value > 0.5f ? -1 : 1);
|
||||
shakeVector = shakeVec * shakeCurve.Evaluate(delta) * GLOBAL_CAMERA_SHAKE_MULTIPLIER;
|
||||
}
|
||||
else if (isShaking)
|
||||
{
|
||||
StopShake();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,419 +1,419 @@
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// Cartoon FX
|
||||
// (c) 2012-2022 Jean Moreno
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
[RequireComponent(typeof(ParticleSystem))]
|
||||
public class CFXR_ParticleText : MonoBehaviour
|
||||
{
|
||||
[Header("Dynamic")]
|
||||
[Tooltip("Allow changing the text at runtime with the 'UpdateText' method. If disabled, this script will be excluded from the build.")]
|
||||
public bool isDynamic;
|
||||
|
||||
[Header("Text")]
|
||||
[SerializeField] string text;
|
||||
[SerializeField] float size = 1f;
|
||||
[SerializeField] float letterSpacing = 0.44f;
|
||||
|
||||
[Header("Colors")]
|
||||
[SerializeField] Color backgroundColor = new Color(0, 0, 0, 1);
|
||||
[SerializeField] Color color1 = new Color(1, 1, 1, 1);
|
||||
[SerializeField] Color color2 = new Color(0, 0, 1, 1);
|
||||
|
||||
[Header("Delay")]
|
||||
[SerializeField] float delay = 0.05f;
|
||||
[SerializeField] bool cumulativeDelay = false;
|
||||
[Range(0f, 2f)] [SerializeField] float compensateLifetime = 0;
|
||||
|
||||
[Header("Misc")]
|
||||
[SerializeField] float lifetimeMultiplier = 1f;
|
||||
[Range(-90f, 90f)] [SerializeField] float rotation = -5f;
|
||||
[SerializeField] float sortingFudgeOffset = 0.1f;
|
||||
#pragma warning disable 0649
|
||||
[SerializeField] CFXR_ParticleTextFontAsset font;
|
||||
#pragma warning restore 0649
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[HideInInspector] [SerializeField] bool autoUpdateEditor = true;
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (text == null || font == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// parse text to only allow valid characters
|
||||
List<char> allowed = new List<char>(font.CharSequence.ToCharArray());
|
||||
allowed.Add(' ');
|
||||
|
||||
char[] chars;
|
||||
switch (font.letterCase)
|
||||
{
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Lower: chars = text.ToLowerInvariant().ToCharArray(); break;
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Upper: chars = text.ToUpperInvariant().ToCharArray(); break;
|
||||
default:
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Both: chars = text.ToCharArray(); break;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// delay so that we are allowed to destroy GameObjects
|
||||
if (autoUpdateEditor && !EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
EditorApplication.delayCall += () => { UpdateText(null); };
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (!isDynamic)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeFirstParticle();
|
||||
}
|
||||
|
||||
float baseLifetime;
|
||||
float baseScaleX;
|
||||
float baseScaleY;
|
||||
float baseScaleZ;
|
||||
Vector3 basePivot;
|
||||
|
||||
void InitializeFirstParticle()
|
||||
{
|
||||
if (isDynamic && this.transform.childCount == 0)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] A disabled GameObject with a ParticleSystem component is required as the first child when 'isDyanmic' is enabled, so that its settings can be used as a base for the generated characters.");
|
||||
}
|
||||
|
||||
var ps = isDynamic ? this.transform.GetChild(0).GetComponent<ParticleSystem>() : this.GetComponent<ParticleSystem>();
|
||||
var main = ps.main;
|
||||
baseLifetime = main.startLifetime.constant;
|
||||
baseScaleX = main.startSizeXMultiplier;
|
||||
baseScaleY = main.startSizeYMultiplier;
|
||||
baseScaleZ = main.startSizeZMultiplier;
|
||||
basePivot = ps.GetComponent<ParticleSystemRenderer>().pivot;
|
||||
if (isDynamic)
|
||||
{
|
||||
basePivot.x = 0; // make sure to not offset the text horizontally
|
||||
ps.gameObject.SetActive(false); // ensure first child is inactive
|
||||
ps.gameObject.name = "MODEL";
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateText(
|
||||
string newText = null,
|
||||
float? newSize = null,
|
||||
Color? newColor1 = null, Color? newColor2 = null, Color? newBackgroundColor = null,
|
||||
float? newLifetimeMultiplier = null
|
||||
)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Only allow updating text for GameObjects that aren't prefabs, since we are possibly destroying/adding GameObjects
|
||||
if (this == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prefabInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(this);
|
||||
var prefabAssetType = PrefabUtility.GetPrefabAssetType(this);
|
||||
if (!(prefabInstanceStatus == PrefabInstanceStatus.NotAPrefab && prefabAssetType == PrefabAssetType.NotAPrefab))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
InitializeFirstParticle();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Application.isPlaying && !isDynamic)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] You cannot update the text at runtime if it's not marked as dynamic.");
|
||||
}
|
||||
|
||||
if (newText != null)
|
||||
{
|
||||
switch (font.letterCase)
|
||||
{
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Lower:
|
||||
newText = newText.ToLowerInvariant();
|
||||
break;
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Upper:
|
||||
newText = newText.ToUpperInvariant();
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify that new text doesn't contain invalid characters
|
||||
foreach (char c in newText)
|
||||
{
|
||||
if (char.IsWhiteSpace(c)) continue;
|
||||
if (font.CharSequence.IndexOf(c) < 0)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] Invalid character supplied for the dynamic text: '" + c + "'\nThe allowed characters from the selected font are: " + font.CharSequence);
|
||||
}
|
||||
}
|
||||
|
||||
this.text = newText;
|
||||
}
|
||||
|
||||
if (newSize != null) this.size = newSize.Value;
|
||||
if (newColor1 != null) this.color1 = newColor1.Value;
|
||||
if (newColor2 != null) this.color2 = newColor2.Value;
|
||||
if (newBackgroundColor != null) this.backgroundColor = newBackgroundColor.Value;
|
||||
if (newLifetimeMultiplier != null) this.lifetimeMultiplier = newLifetimeMultiplier.Value;
|
||||
|
||||
if (text == null || font == null || !font.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.transform.childCount == 0)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] A disabled GameObject with a ParticleSystem component is required as the first child when 'isDyanmic' is enabled, so that its settings can be used as a base for the generated characters.");
|
||||
}
|
||||
|
||||
// process text and calculate total width offset
|
||||
float totalWidth = 0f;
|
||||
int charCount = 0;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace(text[i]))
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
totalWidth += letterSpacing * size;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
charCount++;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// delete all children in editor, to make sure we refresh the particle systems based on the first one
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
int length = this.transform.childCount;
|
||||
int overflow = 0;
|
||||
while (this.transform.childCount > 1)
|
||||
{
|
||||
Object.DestroyImmediate(this.transform.GetChild(this.transform.childCount - 1).gameObject);
|
||||
overflow++;
|
||||
if (overflow > 1000)
|
||||
{
|
||||
// just in case...
|
||||
Debug.LogError("Overflow!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (charCount > 0)
|
||||
{
|
||||
// calculate needed instances
|
||||
int childCount = this.transform.childCount - (isDynamic ? 1 : 0); // first one is the particle source and always deactivated
|
||||
if (childCount < charCount)
|
||||
{
|
||||
// instantiate new letter GameObjects if needed
|
||||
GameObject model = isDynamic ? this.transform.GetChild(0).gameObject : null;
|
||||
for (int i = childCount; i < charCount; i++)
|
||||
{
|
||||
var newLetter = isDynamic ? Instantiate(model, this.transform) : new GameObject();
|
||||
if (!isDynamic)
|
||||
{
|
||||
newLetter.transform.SetParent(this.transform);
|
||||
newLetter.AddComponent<ParticleSystem>();
|
||||
}
|
||||
|
||||
newLetter.transform.localPosition = Vector3.zero;
|
||||
newLetter.transform.localRotation = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
// update each letter
|
||||
float offset = totalWidth / 2f;
|
||||
totalWidth = 0f;
|
||||
int currentChild = isDynamic ? 0 : -1;
|
||||
|
||||
// when not dynamic, we use CopySerialized to propagate the settings to the instances
|
||||
var sourceParticle = isDynamic ? null : this.GetComponent<ParticleSystem>();
|
||||
var sourceParticleRenderer = this.GetComponent<ParticleSystemRenderer>();
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
var letter = text[i];
|
||||
if (char.IsWhiteSpace(letter))
|
||||
{
|
||||
totalWidth += letterSpacing * size;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentChild++;
|
||||
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;
|
||||
|
||||
// update particle system for this letter
|
||||
var letterObj = this.transform.GetChild(currentChild).gameObject;
|
||||
letterObj.name = letter.ToString();
|
||||
var ps = letterObj.GetComponent<ParticleSystem>();
|
||||
#if UNITY_EDITOR
|
||||
if (!isDynamic)
|
||||
{
|
||||
EditorUtility.CopySerialized(sourceParticle, ps);
|
||||
ps.gameObject.SetActive(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
var mainModule = ps.main;
|
||||
mainModule.startSizeXMultiplier = baseScaleX * ratio;
|
||||
mainModule.startSizeYMultiplier = baseScaleY * ratio;
|
||||
mainModule.startSizeZMultiplier = baseScaleZ * ratio;
|
||||
|
||||
ps.textureSheetAnimation.SetSprite(0, sprite);
|
||||
|
||||
mainModule.startRotation = Mathf.Deg2Rad * rotation;
|
||||
mainModule.startColor = backgroundColor;
|
||||
|
||||
var customData = ps.customData;
|
||||
customData.enabled = true;
|
||||
customData.SetColor(ParticleSystemCustomData.Custom1, color1);
|
||||
customData.SetColor(ParticleSystemCustomData.Custom2, color2);
|
||||
|
||||
if (cumulativeDelay)
|
||||
{
|
||||
mainModule.startDelay = delay * i;
|
||||
mainModule.startLifetime = Mathf.LerpUnclamped(baseLifetime, baseLifetime + (delay * (text.Length - i)), compensateLifetime / lifetimeMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
mainModule.startDelay = delay;
|
||||
}
|
||||
|
||||
mainModule.startLifetime = mainModule.startLifetime.constant * lifetimeMultiplier;
|
||||
|
||||
// particle system renderer parameters
|
||||
var particleRenderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
#if UNITY_EDITOR
|
||||
if (!isDynamic)
|
||||
{
|
||||
EditorUtility.CopySerialized(sourceParticleRenderer, particleRenderer);
|
||||
}
|
||||
#endif
|
||||
|
||||
particleRenderer.enabled = true;
|
||||
particleRenderer.pivot = new Vector3(basePivot.x + position, basePivot.y, basePivot.z);
|
||||
particleRenderer.sortingFudge += i * sortingFudgeOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set active state for needed letters only
|
||||
for (int i = 1, l = this.transform.childCount; i < l; i++)
|
||||
{
|
||||
this.transform.GetChild(i).gameObject.SetActive(i <= charCount);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// automatically play the effect in Editor
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
this.GetComponent<ParticleSystem>().Clear(true);
|
||||
this.GetComponent<ParticleSystem>().Play(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CFXR_ParticleText))]
|
||||
public class ParticleTextEditor : Editor
|
||||
{
|
||||
CFXR_ParticleText CastTarget
|
||||
{
|
||||
get { return (CFXR_ParticleText) this.target; }
|
||||
}
|
||||
|
||||
GUIContent GUIContent_AutoUpdateToggle = new GUIContent("Auto-update", "Automatically regenerate the text when a property is changed.");
|
||||
GUIContent GUIContent_UpdateTextButton = new GUIContent(" Update Text ", "Regenerate the text and create new letter GameObjects if needed.");
|
||||
|
||||
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\" to make it an independent Game Object.",
|
||||
MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnInspectorGUI();
|
||||
|
||||
serializedObject.Update();
|
||||
SerializedProperty autoUpdateBool = serializedObject.FindProperty("autoUpdateEditor");
|
||||
|
||||
GUILayout.Space(8);
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
autoUpdateBool.boolValue = GUILayout.Toggle(autoUpdateBool.boolValue, GUIContent_AutoUpdateToggle, GUILayout.Height(30));
|
||||
if (GUILayout.Button(GUIContent_UpdateTextButton, GUILayout.Height(30)))
|
||||
{
|
||||
CastTarget.UpdateText(null);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// Cartoon FX
|
||||
// (c) 2012-2022 Jean Moreno
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
[RequireComponent(typeof(ParticleSystem))]
|
||||
public class CFXR_ParticleText : MonoBehaviour
|
||||
{
|
||||
[Header("Dynamic")]
|
||||
[Tooltip("Allow changing the text at runtime with the 'UpdateText' method. If disabled, this script will be excluded from the build.")]
|
||||
public bool isDynamic;
|
||||
|
||||
[Header("Text")]
|
||||
[SerializeField] string text;
|
||||
[SerializeField] float size = 1f;
|
||||
[SerializeField] float letterSpacing = 0.44f;
|
||||
|
||||
[Header("Colors")]
|
||||
[SerializeField] Color backgroundColor = new Color(0, 0, 0, 1);
|
||||
[SerializeField] Color color1 = new Color(1, 1, 1, 1);
|
||||
[SerializeField] Color color2 = new Color(0, 0, 1, 1);
|
||||
|
||||
[Header("Delay")]
|
||||
[SerializeField] float delay = 0.05f;
|
||||
[SerializeField] bool cumulativeDelay = false;
|
||||
[Range(0f, 2f)] [SerializeField] float compensateLifetime = 0;
|
||||
|
||||
[Header("Misc")]
|
||||
[SerializeField] float lifetimeMultiplier = 1f;
|
||||
[Range(-90f, 90f)] [SerializeField] float rotation = -5f;
|
||||
[SerializeField] float sortingFudgeOffset = 0.1f;
|
||||
#pragma warning disable 0649
|
||||
[SerializeField] CFXR_ParticleTextFontAsset font;
|
||||
#pragma warning restore 0649
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[HideInInspector] [SerializeField] bool autoUpdateEditor = true;
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (text == null || font == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// parse text to only allow valid characters
|
||||
List<char> allowed = new List<char>(font.CharSequence.ToCharArray());
|
||||
allowed.Add(' ');
|
||||
|
||||
char[] chars;
|
||||
switch (font.letterCase)
|
||||
{
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Lower: chars = text.ToLowerInvariant().ToCharArray(); break;
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Upper: chars = text.ToUpperInvariant().ToCharArray(); break;
|
||||
default:
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Both: chars = text.ToCharArray(); break;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// delay so that we are allowed to destroy GameObjects
|
||||
if (autoUpdateEditor && !EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
EditorApplication.delayCall += () => { UpdateText(null); };
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (!isDynamic)
|
||||
{
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
InitializeFirstParticle();
|
||||
}
|
||||
|
||||
float baseLifetime;
|
||||
float baseScaleX;
|
||||
float baseScaleY;
|
||||
float baseScaleZ;
|
||||
Vector3 basePivot;
|
||||
|
||||
void InitializeFirstParticle()
|
||||
{
|
||||
if (isDynamic && this.transform.childCount == 0)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] A disabled GameObject with a ParticleSystem component is required as the first child when 'isDyanmic' is enabled, so that its settings can be used as a base for the generated characters.");
|
||||
}
|
||||
|
||||
var ps = isDynamic ? this.transform.GetChild(0).GetComponent<ParticleSystem>() : this.GetComponent<ParticleSystem>();
|
||||
var main = ps.main;
|
||||
baseLifetime = main.startLifetime.constant;
|
||||
baseScaleX = main.startSizeXMultiplier;
|
||||
baseScaleY = main.startSizeYMultiplier;
|
||||
baseScaleZ = main.startSizeZMultiplier;
|
||||
basePivot = ps.GetComponent<ParticleSystemRenderer>().pivot;
|
||||
if (isDynamic)
|
||||
{
|
||||
basePivot.x = 0; // make sure to not offset the text horizontally
|
||||
ps.gameObject.SetActive(false); // ensure first child is inactive
|
||||
ps.gameObject.name = "MODEL";
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateText(
|
||||
string newText = null,
|
||||
float? newSize = null,
|
||||
Color? newColor1 = null, Color? newColor2 = null, Color? newBackgroundColor = null,
|
||||
float? newLifetimeMultiplier = null
|
||||
)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Only allow updating text for GameObjects that aren't prefabs, since we are possibly destroying/adding GameObjects
|
||||
if (this == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var prefabInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(this);
|
||||
var prefabAssetType = PrefabUtility.GetPrefabAssetType(this);
|
||||
if (!(prefabInstanceStatus == PrefabInstanceStatus.NotAPrefab && prefabAssetType == PrefabAssetType.NotAPrefab))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
InitializeFirstParticle();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Application.isPlaying && !isDynamic)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] You cannot update the text at runtime if it's not marked as dynamic.");
|
||||
}
|
||||
|
||||
if (newText != null)
|
||||
{
|
||||
switch (font.letterCase)
|
||||
{
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Lower:
|
||||
newText = newText.ToLowerInvariant();
|
||||
break;
|
||||
case CFXR_ParticleTextFontAsset.LetterCase.Upper:
|
||||
newText = newText.ToUpperInvariant();
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify that new text doesn't contain invalid characters
|
||||
foreach (char c in newText)
|
||||
{
|
||||
if (char.IsWhiteSpace(c)) continue;
|
||||
if (font.CharSequence.IndexOf(c) < 0)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] Invalid character supplied for the dynamic text: '" + c + "'\nThe allowed characters from the selected font are: " + font.CharSequence);
|
||||
}
|
||||
}
|
||||
|
||||
this.text = newText;
|
||||
}
|
||||
|
||||
if (newSize != null) this.size = newSize.Value;
|
||||
if (newColor1 != null) this.color1 = newColor1.Value;
|
||||
if (newColor2 != null) this.color2 = newColor2.Value;
|
||||
if (newBackgroundColor != null) this.backgroundColor = newBackgroundColor.Value;
|
||||
if (newLifetimeMultiplier != null) this.lifetimeMultiplier = newLifetimeMultiplier.Value;
|
||||
|
||||
if (text == null || font == null || !font.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.transform.childCount == 0)
|
||||
{
|
||||
throw new System.Exception("[CFXR_ParticleText] A disabled GameObject with a ParticleSystem component is required as the first child when 'isDyanmic' is enabled, so that its settings can be used as a base for the generated characters.");
|
||||
}
|
||||
|
||||
// process text and calculate total width offset
|
||||
float totalWidth = 0f;
|
||||
int charCount = 0;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (char.IsWhiteSpace(text[i]))
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
totalWidth += letterSpacing * size;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
charCount++;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// delete all children in editor, to make sure we refresh the particle systems based on the first one
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
int length = this.transform.childCount;
|
||||
int overflow = 0;
|
||||
while (this.transform.childCount > 1)
|
||||
{
|
||||
Object.DestroyImmediate(this.transform.GetChild(this.transform.childCount - 1).gameObject);
|
||||
overflow++;
|
||||
if (overflow > 1000)
|
||||
{
|
||||
// just in case...
|
||||
Debug.LogError("Overflow!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (charCount > 0)
|
||||
{
|
||||
// calculate needed instances
|
||||
int childCount = this.transform.childCount - (isDynamic ? 1 : 0); // first one is the particle source and always deactivated
|
||||
if (childCount < charCount)
|
||||
{
|
||||
// instantiate new letter GameObjects if needed
|
||||
GameObject model = isDynamic ? this.transform.GetChild(0).gameObject : null;
|
||||
for (int i = childCount; i < charCount; i++)
|
||||
{
|
||||
var newLetter = isDynamic ? Instantiate(model, this.transform) : new GameObject();
|
||||
if (!isDynamic)
|
||||
{
|
||||
newLetter.transform.SetParent(this.transform);
|
||||
newLetter.AddComponent<ParticleSystem>();
|
||||
}
|
||||
|
||||
newLetter.transform.localPosition = Vector3.zero;
|
||||
newLetter.transform.localRotation = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
// update each letter
|
||||
float offset = totalWidth / 2f;
|
||||
totalWidth = 0f;
|
||||
int currentChild = isDynamic ? 0 : -1;
|
||||
|
||||
// when not dynamic, we use CopySerialized to propagate the settings to the instances
|
||||
var sourceParticle = isDynamic ? null : this.GetComponent<ParticleSystem>();
|
||||
var sourceParticleRenderer = this.GetComponent<ParticleSystemRenderer>();
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
var letter = text[i];
|
||||
if (char.IsWhiteSpace(letter))
|
||||
{
|
||||
totalWidth += letterSpacing * size;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentChild++;
|
||||
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;
|
||||
|
||||
// update particle system for this letter
|
||||
var letterObj = this.transform.GetChild(currentChild).gameObject;
|
||||
letterObj.name = letter.ToString();
|
||||
var ps = letterObj.GetComponent<ParticleSystem>();
|
||||
#if UNITY_EDITOR
|
||||
if (!isDynamic)
|
||||
{
|
||||
EditorUtility.CopySerialized(sourceParticle, ps);
|
||||
ps.gameObject.SetActive(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
var mainModule = ps.main;
|
||||
mainModule.startSizeXMultiplier = baseScaleX * ratio;
|
||||
mainModule.startSizeYMultiplier = baseScaleY * ratio;
|
||||
mainModule.startSizeZMultiplier = baseScaleZ * ratio;
|
||||
|
||||
ps.textureSheetAnimation.SetSprite(0, sprite);
|
||||
|
||||
mainModule.startRotation = Mathf.Deg2Rad * rotation;
|
||||
mainModule.startColor = backgroundColor;
|
||||
|
||||
var customData = ps.customData;
|
||||
customData.enabled = true;
|
||||
customData.SetColor(ParticleSystemCustomData.Custom1, color1);
|
||||
customData.SetColor(ParticleSystemCustomData.Custom2, color2);
|
||||
|
||||
if (cumulativeDelay)
|
||||
{
|
||||
mainModule.startDelay = delay * i;
|
||||
mainModule.startLifetime = Mathf.LerpUnclamped(baseLifetime, baseLifetime + (delay * (text.Length - i)), compensateLifetime / lifetimeMultiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
mainModule.startDelay = delay;
|
||||
}
|
||||
|
||||
mainModule.startLifetime = mainModule.startLifetime.constant * lifetimeMultiplier;
|
||||
|
||||
// particle system renderer parameters
|
||||
var particleRenderer = ps.GetComponent<ParticleSystemRenderer>();
|
||||
#if UNITY_EDITOR
|
||||
if (!isDynamic)
|
||||
{
|
||||
EditorUtility.CopySerialized(sourceParticleRenderer, particleRenderer);
|
||||
}
|
||||
#endif
|
||||
|
||||
particleRenderer.enabled = true;
|
||||
particleRenderer.pivot = new Vector3(basePivot.x + position, basePivot.y, basePivot.z);
|
||||
particleRenderer.sortingFudge += i * sortingFudgeOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set active state for needed letters only
|
||||
for (int i = 1, l = this.transform.childCount; i < l; i++)
|
||||
{
|
||||
this.transform.GetChild(i).gameObject.SetActive(i <= charCount);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// automatically play the effect in Editor
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
this.GetComponent<ParticleSystem>().Clear(true);
|
||||
this.GetComponent<ParticleSystem>().Play(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CFXR_ParticleText))]
|
||||
public class ParticleTextEditor : Editor
|
||||
{
|
||||
CFXR_ParticleText CastTarget
|
||||
{
|
||||
get { return (CFXR_ParticleText) this.target; }
|
||||
}
|
||||
|
||||
GUIContent GUIContent_AutoUpdateToggle = new GUIContent("Auto-update", "Automatically regenerate the text when a property is changed.");
|
||||
GUIContent GUIContent_UpdateTextButton = new GUIContent(" Update Text ", "Regenerate the text and create new letter GameObjects if needed.");
|
||||
|
||||
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\" to make it an independent Game Object.",
|
||||
MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnInspectorGUI();
|
||||
|
||||
serializedObject.Update();
|
||||
SerializedProperty autoUpdateBool = serializedObject.FindProperty("autoUpdateEditor");
|
||||
|
||||
GUILayout.Space(8);
|
||||
GUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.FlexibleSpace();
|
||||
autoUpdateBool.boolValue = GUILayout.Toggle(autoUpdateBool.boolValue, GUIContent_AutoUpdateToggle, GUILayout.Height(30));
|
||||
if (GUILayout.Button(GUIContent_UpdateTextButton, GUILayout.Height(30)))
|
||||
{
|
||||
CastTarget.UpdateText(null);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1,128 +1,128 @@
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// Cartoon FX
|
||||
// (c) 2012-2020 Jean Moreno
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
public class CFXR_ParticleTextFontAsset : ScriptableObject
|
||||
{
|
||||
public enum LetterCase
|
||||
{
|
||||
Both,
|
||||
Upper,
|
||||
Lower
|
||||
}
|
||||
|
||||
public string CharSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?-.#@$ ";
|
||||
public LetterCase letterCase = LetterCase.Upper;
|
||||
public Sprite[] CharSprites;
|
||||
public Kerning[] CharKerningOffsets;
|
||||
|
||||
[System.Serializable]
|
||||
public class Kerning
|
||||
{
|
||||
public string name = "A";
|
||||
public float pre = 0f;
|
||||
public float post = 0f;
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
this.hideFlags = HideFlags.None;
|
||||
|
||||
if (CharKerningOffsets == null || CharKerningOffsets.Length != CharSequence.Length)
|
||||
{
|
||||
CharKerningOffsets = new Kerning[CharSequence.Length];
|
||||
for (int i = 0; i < CharKerningOffsets.Length; i++)
|
||||
{
|
||||
CharKerningOffsets[i] = new Kerning() { name = CharSequence[i].ToString() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
bool valid = !string.IsNullOrEmpty(CharSequence) && CharSprites != null && CharSprites.Length == CharSequence.Length && CharKerningOffsets != null && CharKerningOffsets.Length == CharSprites.Length;
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
Debug.LogError(string.Format("Invalid ParticleTextFontAsset: '{0}'\n", this.name), this);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// [MenuItem("Tools/Create font asset")]
|
||||
static void CreateFontAsset()
|
||||
{
|
||||
var instance = CreateInstance<CFXR_ParticleTextFontAsset>();
|
||||
AssetDatabase.CreateAsset(instance, "Assets/Font.asset");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CFXR_ParticleTextFontAsset))]
|
||||
public class ParticleTextFontAssetEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Export Kerning"))
|
||||
{
|
||||
var ptfa = this.target as CFXR_ParticleTextFontAsset;
|
||||
var path = EditorUtility.SaveFilePanel("Export Kerning Settings", Application.dataPath, ptfa.name + " kerning", ".txt");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
string output = "";
|
||||
foreach (var k in ptfa.CharKerningOffsets)
|
||||
{
|
||||
output += k.name + "\t" + k.pre + "\t" + k.post + "\n";
|
||||
}
|
||||
System.IO.File.WriteAllText(path, output);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Import Kerning"))
|
||||
{
|
||||
var path = EditorUtility.OpenFilePanel("Import Kerning Settings", Application.dataPath, "txt");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var text = System.IO.File.ReadAllText(path);
|
||||
var split = text.Split(new string[] { "\n" }, System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var ptfa = this.target as CFXR_ParticleTextFontAsset;
|
||||
Undo.RecordObject(ptfa, "Import Kerning Settings");
|
||||
List<CFXR_ParticleTextFontAsset.Kerning> kerningList = new List<CFXR_ParticleTextFontAsset.Kerning>(ptfa.CharKerningOffsets);
|
||||
for (int i = 0; i < split.Length; i++)
|
||||
{
|
||||
var data = split[i].Split('\t');
|
||||
|
||||
foreach (var cko in kerningList)
|
||||
{
|
||||
if (cko.name == data[0])
|
||||
{
|
||||
cko.pre = float.Parse(data[1]);
|
||||
cko.post = float.Parse(data[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ptfa.CharKerningOffsets = kerningList.ToArray();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// Cartoon FX
|
||||
// (c) 2012-2020 Jean Moreno
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
public class CFXR_ParticleTextFontAsset : ScriptableObject
|
||||
{
|
||||
public enum LetterCase
|
||||
{
|
||||
Both,
|
||||
Upper,
|
||||
Lower
|
||||
}
|
||||
|
||||
public string CharSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?-.#@$ ";
|
||||
public LetterCase letterCase = LetterCase.Upper;
|
||||
public Sprite[] CharSprites;
|
||||
public Kerning[] CharKerningOffsets;
|
||||
|
||||
[System.Serializable]
|
||||
public class Kerning
|
||||
{
|
||||
public string name = "A";
|
||||
public float pre = 0f;
|
||||
public float post = 0f;
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
this.hideFlags = HideFlags.None;
|
||||
|
||||
if (CharKerningOffsets == null || CharKerningOffsets.Length != CharSequence.Length)
|
||||
{
|
||||
CharKerningOffsets = new Kerning[CharSequence.Length];
|
||||
for (int i = 0; i < CharKerningOffsets.Length; i++)
|
||||
{
|
||||
CharKerningOffsets[i] = new Kerning() { name = CharSequence[i].ToString() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
bool valid = !string.IsNullOrEmpty(CharSequence) && CharSprites != null && CharSprites.Length == CharSequence.Length && CharKerningOffsets != null && CharKerningOffsets.Length == CharSprites.Length;
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
Debug.LogError(string.Format("Invalid ParticleTextFontAsset: '{0}'\n", this.name), this);
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// [MenuItem("Tools/Create font asset")]
|
||||
static void CreateFontAsset()
|
||||
{
|
||||
var instance = CreateInstance<CFXR_ParticleTextFontAsset>();
|
||||
AssetDatabase.CreateAsset(instance, "Assets/Font.asset");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CFXR_ParticleTextFontAsset))]
|
||||
public class ParticleTextFontAssetEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("Export Kerning"))
|
||||
{
|
||||
var ptfa = this.target as CFXR_ParticleTextFontAsset;
|
||||
var path = EditorUtility.SaveFilePanel("Export Kerning Settings", Application.dataPath, ptfa.name + " kerning", ".txt");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
string output = "";
|
||||
foreach (var k in ptfa.CharKerningOffsets)
|
||||
{
|
||||
output += k.name + "\t" + k.pre + "\t" + k.post + "\n";
|
||||
}
|
||||
System.IO.File.WriteAllText(path, output);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Import Kerning"))
|
||||
{
|
||||
var path = EditorUtility.OpenFilePanel("Import Kerning Settings", Application.dataPath, "txt");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var text = System.IO.File.ReadAllText(path);
|
||||
var split = text.Split(new string[] { "\n" }, System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var ptfa = this.target as CFXR_ParticleTextFontAsset;
|
||||
Undo.RecordObject(ptfa, "Import Kerning Settings");
|
||||
List<CFXR_ParticleTextFontAsset.Kerning> kerningList = new List<CFXR_ParticleTextFontAsset.Kerning>(ptfa.CharKerningOffsets);
|
||||
for (int i = 0; i < split.Length; i++)
|
||||
{
|
||||
var data = split[i].Split('\t');
|
||||
|
||||
foreach (var cko in kerningList)
|
||||
{
|
||||
if (cko.name == data[0])
|
||||
{
|
||||
cko.pre = float.Parse(data[1]);
|
||||
cko.post = float.Parse(data[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ptfa.CharKerningOffsets = kerningList.ToArray();
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user