Stable version
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "CFXRRuntime"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 785156d0baf9e564e92265c9169511bb
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 04efd1cc0f5c31c4da57d931c6665976
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,878 @@
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
// Cartoon FX
|
||||
// (c) 2012-2020 Jean Moreno
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Use the defines below to globally disable features:
|
||||
|
||||
//#define DISABLE_CAMERA_SHAKE
|
||||
//#define DISABLE_LIGHTS
|
||||
//#define DISABLE_CLEAR_BEHAVIOR
|
||||
|
||||
//--------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
[RequireComponent(typeof(ParticleSystem))]
|
||||
[DisallowMultipleComponent]
|
||||
public partial class CFXR_Effect : MonoBehaviour
|
||||
{
|
||||
// Change this value to easily tune the camera shake strength for all effects
|
||||
const float GLOBAL_CAMERA_SHAKE_MULTIPLIER = 1.0f;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[InitializeOnLoadMethod]
|
||||
static void InitGlobalOptions()
|
||||
{
|
||||
AnimatedLight.editorPreview = EditorPrefs.GetBool("CFXR Light EditorPreview", true);
|
||||
#if !DISABLE_CAMERA_SHAKE
|
||||
CameraShake.editorPreview = EditorPrefs.GetBool("CFXR CameraShake EditorPreview", true);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
public enum ClearBehavior
|
||||
{
|
||||
None,
|
||||
Disable,
|
||||
Destroy
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class AnimatedLight
|
||||
{
|
||||
static public bool editorPreview = true;
|
||||
|
||||
public Light light;
|
||||
|
||||
public bool loop;
|
||||
|
||||
public bool animateIntensity;
|
||||
public float intensityStart = 8f;
|
||||
public float intensityEnd = 0f;
|
||||
public float intensityDuration = 0.5f;
|
||||
public AnimationCurve intensityCurve = AnimationCurve.EaseInOut(0f, 1f, 1f, 0f);
|
||||
public bool perlinIntensity;
|
||||
public float perlinIntensitySpeed = 1f;
|
||||
public bool fadeIn;
|
||||
public float fadeInDuration = 0.5f;
|
||||
public bool fadeOut;
|
||||
public float fadeOutDuration = 0.5f;
|
||||
|
||||
public bool animateRange;
|
||||
public float rangeStart = 8f;
|
||||
public float rangeEnd = 0f;
|
||||
public float rangeDuration = 0.5f;
|
||||
public AnimationCurve rangeCurve = AnimationCurve.EaseInOut(0f, 1f, 1f, 0f);
|
||||
public bool perlinRange;
|
||||
public float perlinRangeSpeed = 1f;
|
||||
|
||||
public bool animateColor;
|
||||
public Gradient colorGradient;
|
||||
public float colorDuration = 0.5f;
|
||||
public AnimationCurve colorCurve = AnimationCurve.EaseInOut(0f, 1f, 1f, 0f);
|
||||
public bool perlinColor;
|
||||
public float perlinColorSpeed = 1f;
|
||||
|
||||
public void animate(float time)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!editorPreview && !EditorApplication.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (light != null)
|
||||
{
|
||||
if (animateIntensity)
|
||||
{
|
||||
float delta = loop ? Mathf.Clamp01((time % intensityDuration)/intensityDuration) : Mathf.Clamp01(time/intensityDuration);
|
||||
delta = perlinIntensity ? Mathf.PerlinNoise(Time.time * perlinIntensitySpeed, 0f) : intensityCurve.Evaluate(delta);
|
||||
light.intensity = Mathf.LerpUnclamped(intensityEnd, intensityStart, delta);
|
||||
|
||||
if (fadeIn && time < fadeInDuration)
|
||||
{
|
||||
light.intensity *= Mathf.Clamp01(time / fadeInDuration);
|
||||
}
|
||||
}
|
||||
|
||||
if (animateRange)
|
||||
{
|
||||
float delta = loop ? Mathf.Clamp01((time % rangeDuration)/rangeDuration) : Mathf.Clamp01(time/rangeDuration);
|
||||
delta = perlinRange ? Mathf.PerlinNoise(Time.time * perlinRangeSpeed, 10f) : rangeCurve.Evaluate(delta);
|
||||
light.range = Mathf.LerpUnclamped(rangeEnd, rangeStart, delta);
|
||||
}
|
||||
|
||||
if (animateColor)
|
||||
{
|
||||
float delta = loop ? Mathf.Clamp01((time % colorDuration)/colorDuration) : Mathf.Clamp01(time/colorDuration);
|
||||
delta = perlinColor ? Mathf.PerlinNoise(Time.time * perlinColorSpeed, 0f) : colorCurve.Evaluate(delta);
|
||||
light.color = colorGradient.Evaluate(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void animateFadeOut(float time)
|
||||
{
|
||||
if (fadeOut && light != null)
|
||||
{
|
||||
light.intensity *= 1.0f - Mathf.Clamp01(time / fadeOutDuration);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
if (light != null)
|
||||
{
|
||||
if (animateIntensity)
|
||||
{
|
||||
light.intensity = (fadeIn || fadeOut) ? 0 : intensityEnd;
|
||||
}
|
||||
|
||||
if (animateRange)
|
||||
{
|
||||
light.range = rangeEnd;
|
||||
}
|
||||
|
||||
if (animateColor)
|
||||
{
|
||||
light.color = colorGradient.Evaluate(1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Animated Light Property Drawer
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(AnimatedLight))]
|
||||
public class AnimatedLightDrawer : PropertyDrawer
|
||||
{
|
||||
SerializedProperty light;
|
||||
|
||||
SerializedProperty loop;
|
||||
|
||||
SerializedProperty animateIntensity;
|
||||
SerializedProperty intensityStart;
|
||||
SerializedProperty intensityEnd;
|
||||
SerializedProperty intensityDuration;
|
||||
SerializedProperty intensityCurve;
|
||||
SerializedProperty perlinIntensity;
|
||||
SerializedProperty perlinIntensitySpeed;
|
||||
SerializedProperty fadeIn;
|
||||
SerializedProperty fadeInDuration;
|
||||
SerializedProperty fadeOut;
|
||||
SerializedProperty fadeOutDuration;
|
||||
|
||||
SerializedProperty animateRange;
|
||||
SerializedProperty rangeStart;
|
||||
SerializedProperty rangeEnd;
|
||||
SerializedProperty rangeDuration;
|
||||
SerializedProperty rangeCurve;
|
||||
SerializedProperty perlinRange;
|
||||
SerializedProperty perlinRangeSpeed;
|
||||
|
||||
SerializedProperty animateColor;
|
||||
SerializedProperty colorGradient;
|
||||
SerializedProperty colorDuration;
|
||||
SerializedProperty colorCurve;
|
||||
SerializedProperty perlinColor;
|
||||
SerializedProperty perlinColorSpeed;
|
||||
|
||||
void fetchProperties(SerializedProperty property)
|
||||
{
|
||||
light = property.FindPropertyRelative("light");
|
||||
|
||||
loop = property.FindPropertyRelative("loop");
|
||||
|
||||
animateIntensity = property.FindPropertyRelative("animateIntensity");
|
||||
intensityStart = property.FindPropertyRelative("intensityStart");
|
||||
intensityEnd = property.FindPropertyRelative("intensityEnd");
|
||||
intensityDuration = property.FindPropertyRelative("intensityDuration");
|
||||
intensityCurve = property.FindPropertyRelative("intensityCurve");
|
||||
perlinIntensity = property.FindPropertyRelative("perlinIntensity");
|
||||
perlinIntensitySpeed = property.FindPropertyRelative("perlinIntensitySpeed");
|
||||
fadeIn = property.FindPropertyRelative("fadeIn");
|
||||
fadeInDuration = property.FindPropertyRelative("fadeInDuration");
|
||||
fadeOut = property.FindPropertyRelative("fadeOut");
|
||||
fadeOutDuration = property.FindPropertyRelative("fadeOutDuration");
|
||||
|
||||
animateRange = property.FindPropertyRelative("animateRange");
|
||||
rangeStart = property.FindPropertyRelative("rangeStart");
|
||||
rangeEnd = property.FindPropertyRelative("rangeEnd");
|
||||
rangeDuration = property.FindPropertyRelative("rangeDuration");
|
||||
rangeCurve = property.FindPropertyRelative("rangeCurve");
|
||||
perlinRange = property.FindPropertyRelative("perlinRange");
|
||||
perlinRangeSpeed = property.FindPropertyRelative("perlinRangeSpeed");
|
||||
|
||||
animateColor = property.FindPropertyRelative("animateColor");
|
||||
colorGradient = property.FindPropertyRelative("colorGradient");
|
||||
colorDuration = property.FindPropertyRelative("colorDuration");
|
||||
colorCurve = property.FindPropertyRelative("colorCurve");
|
||||
perlinColor = property.FindPropertyRelative("perlinColor");
|
||||
perlinColorSpeed = property.FindPropertyRelative("perlinColorSpeed");
|
||||
}
|
||||
|
||||
static GUIContent[] ModePopupLabels = new GUIContent[] { new GUIContent("Curve"), new GUIContent("Perlin Noise") };
|
||||
static GUIContent IntensityModeLabel = new GUIContent("Intensity Mode");
|
||||
static GUIContent RangeModeLabel = new GUIContent("Range Mode");
|
||||
static GUIContent ColorModeLabel = new GUIContent("Color Mode");
|
||||
|
||||
const float INDENT_WIDTH = 15f;
|
||||
const float PADDING = 4f;
|
||||
|
||||
void startIndent(ref Rect rect)
|
||||
{
|
||||
EditorGUIUtility.labelWidth -= INDENT_WIDTH;
|
||||
rect.xMin += INDENT_WIDTH;
|
||||
}
|
||||
|
||||
void endIndent(ref Rect rect)
|
||||
{
|
||||
EditorGUIUtility.labelWidth += INDENT_WIDTH;
|
||||
rect.xMin -= INDENT_WIDTH;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
fetchProperties(property);
|
||||
|
||||
Rect rect = EditorGUI.IndentedRect(position);
|
||||
|
||||
//Rect lineRect = rect;
|
||||
//lineRect.height = 1;
|
||||
//lineRect.y -= 2;
|
||||
//EditorGUI.DrawRect(lineRect, Color.gray);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
EditorStyles.helpBox.Draw(rect, GUIContent.none, 0);
|
||||
}
|
||||
|
||||
EditorGUIUtility.labelWidth -= INDENT_WIDTH;
|
||||
|
||||
rect.height = EditorGUIUtility.singleLineHeight;
|
||||
rect.xMax -= PADDING;
|
||||
rect.y += PADDING;
|
||||
float propSpace = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
|
||||
EditorGUI.PropertyField(rect, light); rect.y += propSpace;
|
||||
EditorGUI.PropertyField(rect, loop); rect.y += propSpace;
|
||||
|
||||
EditorGUI.PropertyField(rect, animateIntensity); rect.y += propSpace;
|
||||
if (animateIntensity.boolValue)
|
||||
{
|
||||
startIndent(ref rect);
|
||||
{
|
||||
|
||||
EditorGUI.PropertyField(rect, intensityStart); rect.y += propSpace;
|
||||
EditorGUI.PropertyField(rect, intensityEnd); rect.y += propSpace;
|
||||
|
||||
int val = EditorGUI.Popup(rect, IntensityModeLabel, perlinIntensity.boolValue ? 1 : 0, ModePopupLabels); rect.y += propSpace;
|
||||
if (val == 1 && !perlinIntensity.boolValue)
|
||||
{
|
||||
perlinIntensity.boolValue = true;
|
||||
}
|
||||
else if (val == 0 && perlinIntensity.boolValue)
|
||||
{
|
||||
perlinIntensity.boolValue = false;
|
||||
}
|
||||
|
||||
startIndent(ref rect);
|
||||
{
|
||||
if (perlinIntensity.boolValue)
|
||||
{
|
||||
EditorGUI.PropertyField(rect, perlinIntensitySpeed); rect.y += propSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(rect, intensityDuration); rect.y += propSpace;
|
||||
EditorGUI.PropertyField(rect, intensityCurve); rect.y += propSpace;
|
||||
}
|
||||
}
|
||||
endIndent(ref rect);
|
||||
|
||||
EditorGUI.PropertyField(rect, fadeIn); rect.y += propSpace;
|
||||
if (fadeIn.boolValue)
|
||||
{
|
||||
startIndent(ref rect);
|
||||
EditorGUI.PropertyField(rect, fadeInDuration); rect.y += propSpace;
|
||||
endIndent(ref rect);
|
||||
}
|
||||
|
||||
EditorGUI.PropertyField(rect, fadeOut); rect.y += propSpace;
|
||||
if (fadeOut.boolValue)
|
||||
{
|
||||
startIndent(ref rect);
|
||||
EditorGUI.PropertyField(rect, fadeOutDuration); rect.y += propSpace;
|
||||
endIndent(ref rect);
|
||||
}
|
||||
|
||||
}
|
||||
endIndent(ref rect);
|
||||
}
|
||||
|
||||
EditorGUI.PropertyField(rect, animateRange); rect.y += propSpace;
|
||||
if (animateRange.boolValue)
|
||||
{
|
||||
startIndent(ref rect);
|
||||
{
|
||||
EditorGUI.PropertyField(rect, rangeStart); rect.y += propSpace;
|
||||
EditorGUI.PropertyField(rect, rangeEnd); rect.y += propSpace;
|
||||
|
||||
int val = EditorGUI.Popup(rect, RangeModeLabel, perlinRange.boolValue ? 1 : 0, ModePopupLabels); rect.y += propSpace;
|
||||
if (val == 1 && !perlinRange.boolValue)
|
||||
{
|
||||
perlinRange.boolValue = true;
|
||||
}
|
||||
else if (val == 0 && perlinRange.boolValue)
|
||||
{
|
||||
perlinRange.boolValue = false;
|
||||
}
|
||||
|
||||
startIndent(ref rect);
|
||||
{
|
||||
if (perlinRange.boolValue)
|
||||
{
|
||||
EditorGUI.PropertyField(rect, perlinRangeSpeed); rect.y += propSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(rect, rangeDuration); rect.y += propSpace;
|
||||
EditorGUI.PropertyField(rect, rangeCurve); rect.y += propSpace;
|
||||
}
|
||||
}
|
||||
endIndent(ref rect);
|
||||
}
|
||||
endIndent(ref rect);
|
||||
}
|
||||
|
||||
EditorGUI.PropertyField(rect, animateColor); rect.y += propSpace;
|
||||
if (animateColor.boolValue)
|
||||
{
|
||||
startIndent(ref rect);
|
||||
{
|
||||
|
||||
EditorGUI.PropertyField(rect, colorGradient); rect.y += propSpace;
|
||||
|
||||
int val = EditorGUI.Popup(rect, ColorModeLabel, perlinColor.boolValue ? 1 : 0, ModePopupLabels); rect.y += propSpace;
|
||||
if (val == 1 && !perlinColor.boolValue)
|
||||
{
|
||||
perlinColor.boolValue = true;
|
||||
}
|
||||
else if (val == 0 && perlinColor.boolValue)
|
||||
{
|
||||
perlinColor.boolValue = false;
|
||||
}
|
||||
|
||||
startIndent(ref rect);
|
||||
{
|
||||
|
||||
if (perlinColor.boolValue)
|
||||
{
|
||||
EditorGUI.PropertyField(rect, perlinColorSpeed); rect.y += propSpace;
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.PropertyField(rect, colorDuration); rect.y += propSpace;
|
||||
EditorGUI.PropertyField(rect, colorCurve); rect.y += propSpace;
|
||||
}
|
||||
}
|
||||
endIndent(ref rect);
|
||||
}
|
||||
endIndent(ref rect);
|
||||
}
|
||||
|
||||
EditorGUIUtility.labelWidth += INDENT_WIDTH;
|
||||
|
||||
if (GUI.changed)
|
||||
{
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
fetchProperties(property);
|
||||
|
||||
float propSpace = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||
int count = 5;
|
||||
|
||||
if (animateIntensity.boolValue)
|
||||
{
|
||||
count += 3;
|
||||
count += perlinIntensity.boolValue ? 1 : 2;
|
||||
count += 1;
|
||||
count += fadeIn.boolValue ? 1 : 0;
|
||||
count += 1;
|
||||
count += fadeOut.boolValue ? 1 : 0;
|
||||
}
|
||||
|
||||
if (animateRange.boolValue)
|
||||
{
|
||||
count += 3;
|
||||
count += perlinRange.boolValue ? 1 : 2;
|
||||
}
|
||||
|
||||
if (animateColor.boolValue)
|
||||
{
|
||||
count += 2;
|
||||
count += perlinColor.boolValue ? 1 : 2;
|
||||
}
|
||||
|
||||
return count * propSpace + PADDING * 2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
// ================================================================================================================================
|
||||
|
||||
// Globally disable features
|
||||
public static bool GlobalDisableCameraShake;
|
||||
public static bool GlobalDisableLights;
|
||||
|
||||
// ================================================================================================================================
|
||||
|
||||
[Tooltip("Defines an action to execute when the Particle System has completely finished playing and emitting particles.")]
|
||||
public ClearBehavior clearBehavior = ClearBehavior.Destroy;
|
||||
[Space]
|
||||
public CameraShake cameraShake;
|
||||
[Space]
|
||||
public AnimatedLight[] animatedLights;
|
||||
[Tooltip("Defines which Particle System to track to trigger light fading out.\nLeave empty if not using fading out.")]
|
||||
public ParticleSystem fadeOutReference;
|
||||
|
||||
float time;
|
||||
ParticleSystem rootParticleSystem;
|
||||
[System.NonSerialized] MaterialPropertyBlock materialPropertyBlock;
|
||||
[System.NonSerialized] Renderer particleRenderer;
|
||||
|
||||
// ================================================================================================================================
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
time = 0f;
|
||||
fadingOutStartTime = 0f;
|
||||
isFadingOut = false;
|
||||
|
||||
#if !DISABLE_LIGHTS
|
||||
if (animatedLights != null)
|
||||
{
|
||||
foreach (var animLight in animatedLights)
|
||||
{
|
||||
animLight.reset();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !DISABLE_CAMERA_SHAKE
|
||||
if (cameraShake != null && cameraShake.enabled)
|
||||
{
|
||||
cameraShake.StopShake();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !DISABLE_CAMERA_SHAKE || !DISABLE_CLEAR_BEHAVIOR
|
||||
void Awake()
|
||||
{
|
||||
#if !DISABLE_CAMERA_SHAKE
|
||||
if (cameraShake != null && cameraShake.enabled)
|
||||
{
|
||||
cameraShake.fetchCameras();
|
||||
}
|
||||
#endif
|
||||
#if !DISABLE_CLEAR_BEHAVIOR
|
||||
startFrameOffset = GlobalStartFrameOffset++;
|
||||
#endif
|
||||
// Detect if world position needs to be passed to the shader
|
||||
particleRenderer = this.GetComponent<ParticleSystemRenderer>();
|
||||
if (particleRenderer.sharedMaterial != null && particleRenderer.sharedMaterial.IsKeywordEnabled("_CFXR_LIGHTING_WPOS_OFFSET"))
|
||||
{
|
||||
materialPropertyBlock = new MaterialPropertyBlock();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
foreach (var animLight in animatedLights)
|
||||
{
|
||||
if (animLight.light != null)
|
||||
{
|
||||
#if !DISABLE_LIGHTS
|
||||
animLight.light.enabled = !GlobalDisableLights;
|
||||
#else
|
||||
animLight.light.enabled = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
|
||||
#if !DISABLE_LIGHTS || !DISABLE_CAMERA_SHAKE || !DISABLE_CLEAR_BEHAVIOR
|
||||
const int CHECK_EVERY_N_FRAME = 20;
|
||||
static int GlobalStartFrameOffset = 0;
|
||||
int startFrameOffset;
|
||||
void Update()
|
||||
{
|
||||
#if !DISABLE_LIGHTS || !DISABLE_CAMERA_SHAKE
|
||||
time += Time.deltaTime;
|
||||
|
||||
Animate(time);
|
||||
|
||||
if (fadeOutReference != null && !fadeOutReference.isEmitting && (fadeOutReference.isPlaying || isFadingOut))
|
||||
{
|
||||
FadeOut(time);
|
||||
}
|
||||
#endif
|
||||
#if !DISABLE_CLEAR_BEHAVIOR
|
||||
if (clearBehavior != ClearBehavior.None)
|
||||
{
|
||||
if (rootParticleSystem == null)
|
||||
{
|
||||
rootParticleSystem = this.GetComponent<ParticleSystem>();
|
||||
}
|
||||
|
||||
// Check isAlive every N frame, with an offset so that all active effects aren't checked at once
|
||||
if ((Time.renderedFrameCount + startFrameOffset) % CHECK_EVERY_N_FRAME == 0)
|
||||
{
|
||||
if (!rootParticleSystem.IsAlive(true))
|
||||
{
|
||||
if (clearBehavior == ClearBehavior.Destroy)
|
||||
{
|
||||
GameObject.Destroy(this.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (materialPropertyBlock != null)
|
||||
{
|
||||
particleRenderer.GetPropertyBlock(materialPropertyBlock);
|
||||
materialPropertyBlock.SetVector("_GameObjectWorldPosition", this.transform.position);
|
||||
particleRenderer.SetPropertyBlock(materialPropertyBlock);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !DISABLE_LIGHTS || !DISABLE_CAMERA_SHAKE
|
||||
public void Animate(float time)
|
||||
{
|
||||
#if !DISABLE_LIGHTS
|
||||
if (animatedLights != null && !GlobalDisableLights)
|
||||
{
|
||||
foreach (var animLight in animatedLights)
|
||||
{
|
||||
animLight.animate(time);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !DISABLE_CAMERA_SHAKE
|
||||
if (cameraShake != null && cameraShake.enabled && !GlobalDisableCameraShake)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (!cameraShake.isShaking)
|
||||
{
|
||||
cameraShake.fetchCameras();
|
||||
}
|
||||
#endif
|
||||
cameraShake.animate(time);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !DISABLE_LIGHTS
|
||||
bool isFadingOut;
|
||||
float fadingOutStartTime;
|
||||
public void FadeOut(float time)
|
||||
{
|
||||
if (animatedLights == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFadingOut)
|
||||
{
|
||||
isFadingOut = true;
|
||||
fadingOutStartTime = time;
|
||||
}
|
||||
|
||||
foreach (var animLight in animatedLights)
|
||||
{
|
||||
animLight.animateFadeOut(time - fadingOutStartTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Editor preview
|
||||
// Detect when the Particle System is previewing and trigger this animation too
|
||||
|
||||
[System.NonSerialized] ParticleSystem _parentParticle;
|
||||
ParticleSystem parentParticle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_parentParticle == null)
|
||||
{
|
||||
_parentParticle = this.GetComponent<ParticleSystem>();
|
||||
}
|
||||
return _parentParticle;
|
||||
}
|
||||
}
|
||||
[System.NonSerialized] public bool editorUpdateRegistered;
|
||||
|
||||
[System.NonSerialized] bool particleWasStopped;
|
||||
[System.NonSerialized] float particleTime;
|
||||
[System.NonSerialized] float particleTimeUnwrapped;
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
UnregisterEditorUpdate();
|
||||
}
|
||||
|
||||
public void RegisterEditorUpdate()
|
||||
{
|
||||
var type = PrefabUtility.GetPrefabAssetType(this.gameObject);
|
||||
var status = PrefabUtility.GetPrefabInstanceStatus(this.gameObject);
|
||||
|
||||
// Prefab in Project window
|
||||
if ((type == PrefabAssetType.Regular || type == PrefabAssetType.Variant) && status == PrefabInstanceStatus.NotAPrefab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorUpdateRegistered)
|
||||
{
|
||||
EditorApplication.update += onEditorUpdate;
|
||||
editorUpdateRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterEditorUpdate()
|
||||
{
|
||||
if (editorUpdateRegistered)
|
||||
{
|
||||
editorUpdateRegistered = false;
|
||||
EditorApplication.update -= onEditorUpdate;
|
||||
}
|
||||
ResetState();
|
||||
}
|
||||
|
||||
void onEditorUpdate()
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var renderer = this.GetComponent<ParticleSystemRenderer>();
|
||||
if (renderer.sharedMaterial != null && renderer.sharedMaterial.IsKeywordEnabled("_CFXR_LIGHTING_WPOS_OFFSET"))
|
||||
{
|
||||
if (materialPropertyBlock == null)
|
||||
{
|
||||
materialPropertyBlock = new MaterialPropertyBlock();
|
||||
}
|
||||
|
||||
renderer.GetPropertyBlock(materialPropertyBlock);
|
||||
materialPropertyBlock.SetVector("_GameObjectWorldPosition", this.transform.position);
|
||||
renderer.SetPropertyBlock(materialPropertyBlock);
|
||||
}
|
||||
|
||||
// Need to track unwrapped time when playing back from Editor
|
||||
// because the parentParticle.time will be reset at each loop
|
||||
float delta = parentParticle.time - particleTime;
|
||||
|
||||
if (delta < 0 && parentParticle.isPlaying)
|
||||
{
|
||||
delta = parentParticle.main.duration + delta;
|
||||
if (delta > 0.1 || delta < 0)
|
||||
{
|
||||
// try to detect when "Restart" is pressed
|
||||
ResetState();
|
||||
particleTimeUnwrapped = 0;
|
||||
delta = 0;
|
||||
}
|
||||
}
|
||||
particleTimeUnwrapped += delta;
|
||||
|
||||
if (particleTime != parentParticle.time)
|
||||
{
|
||||
#if !DISABLE_CAMERA_SHAKE
|
||||
if (cameraShake != null && cameraShake.enabled && parentParticle.time < particleTime && parentParticle.time < 0.05f)
|
||||
{
|
||||
cameraShake.StartShake();
|
||||
}
|
||||
#endif
|
||||
#if !DISABLE_LIGHTS || !DISABLE_CAMERA_SHAKES
|
||||
Animate(particleTimeUnwrapped);
|
||||
|
||||
if (!parentParticle.isEmitting)
|
||||
{
|
||||
FadeOut(particleTimeUnwrapped);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (particleWasStopped != parentParticle.isStopped)
|
||||
{
|
||||
if (parentParticle.isStopped)
|
||||
{
|
||||
ResetState();
|
||||
}
|
||||
particleTimeUnwrapped = 0;
|
||||
}
|
||||
|
||||
particleWasStopped = parentParticle.isStopped;
|
||||
particleTime = parentParticle.time;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CFXR_Effect))]
|
||||
[CanEditMultipleObjects]
|
||||
public class CFXR_Effect_Editor : Editor
|
||||
{
|
||||
bool? lightEditorPreview;
|
||||
bool? shakeEditorPreview;
|
||||
|
||||
GUIStyle _PaddedRoundedRect;
|
||||
GUIStyle PaddedRoundedRect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_PaddedRoundedRect == null)
|
||||
{
|
||||
_PaddedRoundedRect = new GUIStyle(EditorStyles.helpBox);
|
||||
_PaddedRoundedRect.padding = new RectOffset(4, 4, 4, 4);
|
||||
}
|
||||
return _PaddedRoundedRect;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
GlobalOptionsGUI();
|
||||
|
||||
#if DISABLE_CAMERA_SHAKE
|
||||
EditorGUILayout.HelpBox("Camera Shake has been globally disabled in the code.\nThe properties remain to avoid data loss but the shaking won't be applied for any effect.", MessageType.Info);
|
||||
#endif
|
||||
|
||||
base.OnInspectorGUI();
|
||||
}
|
||||
|
||||
void GlobalOptionsGUI()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(PaddedRoundedRect);
|
||||
{
|
||||
GUILayout.Label("Editor Preview:", EditorStyles.boldLabel);
|
||||
|
||||
if (lightEditorPreview == null)
|
||||
{
|
||||
lightEditorPreview = EditorPrefs.GetBool("CFXR Light EditorPreview", true);
|
||||
}
|
||||
bool lightPreview = EditorGUILayout.Toggle("Light Animations", lightEditorPreview.Value);
|
||||
if (lightPreview != lightEditorPreview.Value)
|
||||
{
|
||||
lightEditorPreview = lightPreview;
|
||||
EditorPrefs.SetBool("CFXR Light EditorPreview", lightPreview);
|
||||
CFXR_Effect.AnimatedLight.editorPreview = lightPreview;
|
||||
}
|
||||
|
||||
#if !DISABLE_CAMERA_SHAKE
|
||||
if (shakeEditorPreview == null)
|
||||
{
|
||||
shakeEditorPreview = EditorPrefs.GetBool("CFXR CameraShake EditorPreview", true);
|
||||
}
|
||||
bool shakePreview = EditorGUILayout.Toggle("Camera Shake", shakeEditorPreview.Value);
|
||||
if (shakePreview != shakeEditorPreview.Value)
|
||||
{
|
||||
shakeEditorPreview = shakePreview;
|
||||
EditorPrefs.SetBool("CFXR CameraShake EditorPreview", shakePreview);
|
||||
CFXR_Effect.CameraShake.editorPreview = shakePreview;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (this.targets == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var t in this.targets)
|
||||
{
|
||||
var cfxr_effect = t as CFXR_Effect;
|
||||
if (cfxr_effect != null)
|
||||
{
|
||||
if (isPrefabSource(cfxr_effect.gameObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
cfxr_effect.RegisterEditorUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (this.targets == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var t in this.targets)
|
||||
{
|
||||
// Can be null if GameObject has been destroyed
|
||||
var cfxr_effect = t as CFXR_Effect;
|
||||
if (cfxr_effect != null)
|
||||
{
|
||||
if (isPrefabSource(cfxr_effect.gameObject))
|
||||
{
|
||||
return;
|
||||
}
|
||||
cfxr_effect.UnregisterEditorUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool isPrefabSource(GameObject gameObject)
|
||||
{
|
||||
var assetType = PrefabUtility.GetPrefabAssetType(gameObject);
|
||||
var prefabType = PrefabUtility.GetPrefabInstanceStatus(gameObject);
|
||||
return ((assetType == PrefabAssetType.Regular || assetType == PrefabAssetType.Variant) && prefabType == PrefabInstanceStatus.NotAPrefab);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
;
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9205bc1bbacc90040a998067b5643d16
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace CartoonFX
|
||||
{
|
||||
[RequireComponent(typeof(ParticleSystem))]
|
||||
public class CFXR_EmissionBySurface : MonoBehaviour
|
||||
{
|
||||
public bool active = true;
|
||||
public float particlesPerUnit = 10;
|
||||
[Tooltip("This is to avoid slowdowns in the Editor if the value gets too high")] public float maxEmissionRate = 5000;
|
||||
[HideInInspector] public float density = 0;
|
||||
|
||||
bool attachedToEditor;
|
||||
ParticleSystem ps;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
void OnValidate()
|
||||
{
|
||||
this.hideFlags = HideFlags.DontSaveInBuild;
|
||||
CalculateAndUpdateEmission();
|
||||
}
|
||||
|
||||
internal void AttachToEditor()
|
||||
{
|
||||
if (attachedToEditor) return;
|
||||
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
attachedToEditor = true;
|
||||
}
|
||||
|
||||
internal void DetachFromEditor()
|
||||
{
|
||||
if (!attachedToEditor) return;
|
||||
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
attachedToEditor = false;
|
||||
}
|
||||
|
||||
void OnEditorUpdate()
|
||||
{
|
||||
CalculateAndUpdateEmission();
|
||||
|
||||
if (!System.Array.Exists(Selection.gameObjects, item => item == this.gameObject))
|
||||
{
|
||||
DetachFromEditor();
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateAndUpdateEmission()
|
||||
{
|
||||
if (!active) return;
|
||||
if (this == null) return;
|
||||
if (ps == null) ps = this.GetComponent<ParticleSystem>();
|
||||
density = CalculateShapeDensity(ps.shape, ps.main.scalingMode == ParticleSystemScalingMode.Shape, this.transform);
|
||||
if (density == 0) return;
|
||||
float emissionOverTime = density * particlesPerUnit;
|
||||
ParticleSystem.EmissionModule emission = ps.emission;
|
||||
if (Math.Abs(emission.rateOverTime.constant - emissionOverTime) > 0.1f)
|
||||
{
|
||||
emission.rateOverTime = Mathf.Min(maxEmissionRate, emissionOverTime);
|
||||
}
|
||||
}
|
||||
|
||||
float CalculateShapeDensity(ParticleSystem.ShapeModule shapeModule, bool isShapeScaling, Transform transform)
|
||||
{
|
||||
float arcPercentage = Mathf.Max(0.01f, shapeModule.arc / 360f);
|
||||
float thicknessPercentage = Mathf.Max(0.01f, 1.0f - shapeModule.radiusThickness);
|
||||
|
||||
float scaleX = shapeModule.scale.x;
|
||||
float scaleY = shapeModule.scale.y;
|
||||
float scaleZ = shapeModule.scale.z;
|
||||
if (isShapeScaling)
|
||||
{
|
||||
Vector3 localScale = Quaternion.Euler(shapeModule.rotation) * transform.localScale;
|
||||
scaleX = scaleX * localScale.x;
|
||||
scaleY = scaleY * localScale.y;
|
||||
scaleZ = scaleZ * localScale.z;
|
||||
}
|
||||
scaleX = Mathf.Abs(scaleX);
|
||||
scaleY = Mathf.Abs(scaleY);
|
||||
scaleZ = Mathf.Abs(scaleZ);
|
||||
|
||||
switch (shapeModule.shapeType)
|
||||
{
|
||||
case ParticleSystemShapeType.Hemisphere:
|
||||
case ParticleSystemShapeType.Sphere:
|
||||
{
|
||||
float rX = shapeModule.radius * scaleX;
|
||||
float rY = shapeModule.radius * scaleY;
|
||||
float rZ = shapeModule.radius * scaleZ;
|
||||
float rmX = rX * thicknessPercentage;
|
||||
float rmY = rY * thicknessPercentage;
|
||||
float rmZ = rZ * thicknessPercentage;
|
||||
float volume = (rX * rY * rZ - rmX * rmY * rmZ) * Mathf.PI;
|
||||
if (shapeModule.shapeType == ParticleSystemShapeType.Hemisphere)
|
||||
{
|
||||
volume /= 2.0f;
|
||||
}
|
||||
return volume * arcPercentage;
|
||||
}
|
||||
case ParticleSystemShapeType.Cone:
|
||||
{
|
||||
float innerDisk = shapeModule.radius * scaleX * thicknessPercentage * shapeModule.radius * scaleY * thicknessPercentage * Mathf.PI;
|
||||
float outerDisk = shapeModule.radius *scaleX * shapeModule.radius * scaleY * Mathf.PI;
|
||||
return outerDisk - innerDisk;
|
||||
}
|
||||
case ParticleSystemShapeType.ConeVolume:
|
||||
{
|
||||
// cylinder volume, changing the angle doesn't actually extend the area from where the particles are emitted
|
||||
float innerCylinder = shapeModule.radius * scaleX * thicknessPercentage * shapeModule.radius * scaleY * thicknessPercentage * Mathf.PI * shapeModule.length * scaleZ;
|
||||
float outerCylinder = shapeModule.radius * scaleX * shapeModule.radius * scaleY * Mathf.PI * shapeModule.length * scaleZ;
|
||||
return outerCylinder - innerCylinder;
|
||||
}
|
||||
case ParticleSystemShapeType.BoxEdge:
|
||||
case ParticleSystemShapeType.BoxShell:
|
||||
case ParticleSystemShapeType.Box:
|
||||
{
|
||||
return scaleX * scaleY * scaleZ;
|
||||
}
|
||||
case ParticleSystemShapeType.Circle:
|
||||
{
|
||||
float radiusX = shapeModule.radius * scaleX;
|
||||
float radiusY = shapeModule.radius * scaleY;
|
||||
|
||||
float radiusMinX = radiusX * thicknessPercentage;
|
||||
float radiusMinY = radiusY * thicknessPercentage;
|
||||
float area = (radiusX * radiusY - radiusMinX * radiusMinY) * Mathf.PI;
|
||||
return area * arcPercentage;
|
||||
}
|
||||
case ParticleSystemShapeType.SingleSidedEdge:
|
||||
{
|
||||
return shapeModule.radius * scaleX;
|
||||
}
|
||||
case ParticleSystemShapeType.Donut:
|
||||
{
|
||||
float outerDonutVolume = 2 * Mathf.PI * Mathf.PI * shapeModule.donutRadius * shapeModule.donutRadius * shapeModule.radius * arcPercentage;
|
||||
float innerDonutVolume = 2 * Mathf.PI * Mathf.PI * shapeModule.donutRadius * thicknessPercentage * thicknessPercentage * shapeModule.donutRadius * shapeModule.radius * arcPercentage;
|
||||
return (outerDonutVolume - innerDonutVolume) * scaleX * scaleY * scaleZ;
|
||||
}
|
||||
case ParticleSystemShapeType.Rectangle:
|
||||
{
|
||||
return scaleX * scaleY;
|
||||
}
|
||||
case ParticleSystemShapeType.Mesh:
|
||||
case ParticleSystemShapeType.SkinnedMeshRenderer:
|
||||
case ParticleSystemShapeType.MeshRenderer:
|
||||
{
|
||||
Debug.LogWarning( string.Format("[{0}] Calculating volume for a mesh is unsupported.", nameof(CFXR_EmissionBySurface)));
|
||||
this.active = false;
|
||||
return 0;
|
||||
}
|
||||
case ParticleSystemShapeType.Sprite:
|
||||
case ParticleSystemShapeType.SpriteRenderer:
|
||||
{
|
||||
Debug.LogWarning( string.Format("[{0}] Calculating volume for a sprite is unsupported.", nameof(CFXR_EmissionBySurface)));
|
||||
this.active = false;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(CFXR_EmissionBySurface))]
|
||||
class CFXR_EmissionBySurface_Editor : Editor
|
||||
{
|
||||
CFXR_EmissionBySurface Target { get { return target as CFXR_EmissionBySurface; } }
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.HelpBox("This Editor script will adapt the particle emission based on its shape density, so that you can resize it to fit a specific situation and the overall number of particles won't change.\n\nYou can scale the object to change the emission area, and you can open the 'Shape' module in the Particle System to visualize the emission area.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("Calculated Density: " + Target.density, MessageType.None);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Target.AttachToEditor();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Target.DetachFromEditor();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 403cb51292a8b3c4c870cccfc0e68659
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03c8102322ad0d04fa6ae3f17887e967
|
||||
timeCreated: 1535104615
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +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
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffbf5f8b3f8ff3e49bbbf5495becc6b3
|
||||
timeCreated: 1535104615
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user