特效控制
驱动
- 自动播放
Unity的特效与动画都有一个自动播放的特性,只要开关Active就会在runtime自动播放 - 时间驱动 ParticleSystem.Simulate
Simulate自带Pause
驱动播放代码请参考了timeline。需要获取root特效来播放,否则某些设计,subParticle会因此而播放有问题,新版的Timeline已经修复了
缩放
与特效本身制作相关
bug
当设置Sub Emitters时,ParticleSystem本身是不允许子粒子不在父级下,不过在scene中可以卡bug卡上去,也可以拉上去后又拉下来,甚至共用一个子粒子。。。
性能
检测
性能检测参考了:【博物纳新】Unity特效性能分析工具
优化思路
用更少的粒子数,更少的模块,达到相似的效果
PS数:
-
只发一个粒子的,使用mesh+动画替代
drawcall:
-
与粒子系统数挂钩
overdraw:
-
减少不必要叠片
-
大面片换成多而小的面片
-
数量多表现效果更好
-
但是顶点会增多 , 不过影响不大
-
prewarm
prewarm就是一个预烘的概念,就是你希望第一眼看到特效就已经有些规模了,而不是看到第一下才慢慢一点点冒出来
默认的tickRate就是普通帧率为0.03(修改引擎后暴漏出来可以直接配置了),5 / 0.03 = 167,prewarm一下需要快速循环167下,所以很卡,是一个很消耗的选项
例如Prewarm Time是5,Prewarm Tick Rate是0.2
Prewarm Tick Count = 5 / 0.2,也就是需要循环遍历25次
第一帧就看到25帧后的样子
精度要求不高的情况下,rate可以设置的大一些
特效曲线编辑器
public class FxParticleSystemCurve : EditorWindow
{
//editor
public static FxParticleSystemCurve window;
private object curveEditorObj;
private static Type curveEditorType;
public delegate bool CurveFieldCallback(int button, SerializedProperty curvePro, SerializedProperty scalarPro);
//gui setting
static Color kCurveBGColor = new Color(0.337f, 0.337f, 0.337f, 1f);
static Rect kSignedRange = new Rect(0f, -1f, 1f, 2f);
//curve data
static SerializedObject serializedObj;
static SerializedProperty scalarPro;
Vector2 scalar = new Vector2(0, 1);
public static void GUICurveField(string name, SerializedObject sObj,
SerializedProperty curvePro, SerializedProperty scalarPro, GUILayoutOption width = null)
{
serializedObj = sObj;
GUILayout.BeginHorizontal();
if (width == null) width = GUILayout.Width(45);
GUILayout.Label(name, width);
var cb = new CurveFieldCallback(OnCurveFieldCallback);
Rect position = GUILayoutUtility.GetLastRect();
position.x += position.width;
position.y += 2;
position.width = 70;
position.height -= 4;
int controlID = GUIUtility.GetControlID(1321321231, FocusType.Keyboard, position);
Event current = Event.current;
EventType typeForControl = current.GetTypeForControl(controlID);
if (typeForControl != EventType.Repaint && typeForControl != EventType.ValidateCommand &&
typeForControl == EventType.MouseDown && position.Contains(current.mousePosition))
{
if (cb != null && cb(current.button, curvePro, scalarPro))
current.Use();
}
else
{
EditorGUIUtility.DrawRegionSwatch(position, curvePro, null, Color.red, kCurveBGColor, kSignedRange);
}
GUILayout.EndHorizontal();
}
public static bool OnCurveFieldCallback(int button, SerializedProperty curvePro, SerializedProperty scalar)
{
if (button == 0)
{
Open();
//处理旧数据 找把valueMax设置到scalarPro
serializedObj.Update();
var scalarValue = 1f;
if (curvePro.animationCurveValue != null &&
curvePro.animationCurveValue.keys.Length > 0)
{
scalarValue = Mathf.Max(curvePro.animationCurveValue.keys.Max(a => Mathf.Abs(a.value)), scalarValue);
if (scalarValue > 1f)//曲线缩到0-1范围
{
var keys = curvePro.animationCurveValue.keys;
for (int i = 0; i < keys.Length; i++)
{
keys[i].value /= scalarValue;
}
curvePro.animationCurveValue = new AnimationCurve(keys);
}
}
scalar.floatValue *= scalarValue;
scalarPro = scalar;
serializedObj.ApplyModifiedProperties();
window.AddCurve(curvePro);
window.SetAxisScalars(new Vector2(0, scalar.floatValue));
}
else
{
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Copy"), false, () => { Copy(curvePro, scalar); });
menu.AddItem(new GUIContent("Paste"), false, () => { Paste(curvePro, scalar); });
menu.AddItem(new GUIContent("Remove"), false, () => { Remove(curvePro); });
menu.ShowAsContext();
}
return false;
}
public void AddCurve(SerializedProperty curve)
{
//delegate
Type curveWrapperType = typeof(Editor).Assembly.GetType("UnityEditor.CurveWrapper");
Type GetAxisScalarsType = curveWrapperType.GetNestedType("GetAxisScalarsCallback", BindingFlags.Public | BindingFlags.NonPublic);
Delegate GetScalarsCb = Delegate.CreateDelegate(GetAxisScalarsType, this, "GetAxisScalars");
Type SetAxisScalarsType = curveWrapperType.GetNestedType("SetAxisScalarsCallback", BindingFlags.Public | BindingFlags.NonPublic);
Delegate SetScalarsCb = Delegate.CreateDelegate(SetAxisScalarsType, this, "SetAxisScalars");
//curve data
var innerType = curveEditorType.GetNestedType("CurveData");
var curveDataObj = Activator.CreateInstance(innerType,
new object[] { "TestCurveData", new GUIContent("倍率"), null, curve,
Color.red, true, GetScalarsCb, SetScalarsCb, true });
//add curve
MethodInfo method = curveEditorType.GetMethod("AddCurve");
method.Invoke(curveEditorObj, new object[] { curveDataObj });
}
public void AddCurve(SerializedProperty mincurveperty, SerializedProperty maxcurveperty)
{
//delegate
Type curveWrapperType = typeof(Editor).Assembly.GetType("UnityEditor.CurveWrapper");
Type GetAxisScalarsType = curveWrapperType.GetNestedType("GetAxisScalarsCallback", BindingFlags.Public | BindingFlags.NonPublic);
Delegate GetAxisScalarsCallbackDelegate = Delegate.CreateDelegate(GetAxisScalarsType, this, "GetAxisScalars");
Type SetAxisScalarsType = curveWrapperType.GetNestedType("SetAxisScalarsCallback", BindingFlags.Public | BindingFlags.NonPublic);
Delegate SetAxisScalarsCallbackDelegate = Delegate.CreateDelegate(SetAxisScalarsType, this, "SetAxisScalars");
//curve data
var innerType = curveEditorType.GetNestedType("CurveData");
var curveDataObj = Activator.CreateInstance(innerType, new object[] { "TestCurveData", new GUIContent("倍率"), mincurveperty, maxcurveperty, Color.red, true, GetAxisScalarsCallbackDelegate, SetAxisScalarsCallbackDelegate, true });
//add curve
MethodInfo method = curveEditorType.GetMethod("AddCurve");
method.Invoke(curveEditorObj, new object[] { curveDataObj });
}
static void Copy(SerializedProperty curvePro, SerializedProperty scalarPro)
{
var clipboardType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ParticleSystemClipboard");
var curve1Property = clipboardType.GetField("m_AnimationCurve1", BindingFlags.Static | BindingFlags.NonPublic);
curve1Property.SetValue(null, curvePro.animationCurveValue);
var curveScalar = clipboardType.GetField("m_AnimationCurveScalar", BindingFlags.Static | BindingFlags.NonPublic);
curveScalar.SetValue(null, scalarPro.floatValue);
}
static void Paste(SerializedProperty curvePro, SerializedProperty scalarPro)
{
var clipboardType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ParticleSystemClipboard");
var curve1Property = clipboardType.GetField("m_AnimationCurve1", BindingFlags.Static | BindingFlags.NonPublic);
object clipboardValue = curve1Property.GetValue(null);
var curveScalar = clipboardType.GetField("m_AnimationCurveScalar", BindingFlags.Static | BindingFlags.NonPublic);
object curveScalarValue = curveScalar.GetValue(null);
serializedObj.Update();
scalarPro.floatValue = (float)curveScalarValue;
curvePro.animationCurveValue = new AnimationCurve((clipboardValue as AnimationCurve).keys);
serializedObj.ApplyModifiedProperties();
}
static void Remove(SerializedProperty curvePro)
{
serializedObj.Update();
curvePro.animationCurveValue = new AnimationCurve();
serializedObj.ApplyModifiedProperties();
}
private void OnGUI()
{
serializedObj?.Update();
MethodInfo onGUIMethod = curveEditorType.GetMethod("OnGUI", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
var width = position.width - 10;
var height = position.height;
onGUIMethod.Invoke(curveEditorObj, new object[] { new Rect(5, 5, width, height) });
EditorGUILayout.Space(height);
serializedObj?.ApplyModifiedProperties();
Event e = Event.current;
if (e.keyCode == FxEditorScriptObject.Instance.exitFocusKey && e.type == EventType.KeyUp)
{
OnClose();
}
}
public Vector2 GetAxisScalars()
{
return scalar;
}
public void SetAxisScalars(Vector2 axisScalars)
{
serializedObj.Update();
scalarPro.floatValue = scalar.y = axisScalars.y;
serializedObj.ApplyModifiedProperties();
}
public static void Open()
{
if (window == null)
window = GetWindow<FxParticleSystemCurve>();
else
OnClose();
}
public static void OnClose()
{
window.Close();
window = null;
}
private void OnEnable()
{
curveEditorType = typeof(EditorWindow).Assembly.GetType("ParticleSystemCurveEditor");
curveEditorObj = Activator.CreateInstance(curveEditorType);
MethodInfo method = curveEditorType.GetMethod("Init", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
method.Invoke(curveEditorObj, null);
}
private void OnDisable()
{
MethodInfo method = curveEditorType.GetMethod("OnDisable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
method.Invoke(curveEditorObj, null);
}
}
颜色曲线编辑器
public class FxGradient
{
public static void EditorGradient(Gradient gradient, string name = null)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(name))
GUILayout.Label(name, GUILayout.Width(50));
Rect rect = GUILayoutUtility.GetLastRect();
rect.x += rect.width;
rect.width = 120; rect.height = 16;
gradient = EditorGUI.GradientField(rect, gradient);
if (Event.current.type == EventType.ContextClick && rect.Contains(Event.current.mousePosition))
{
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Copy"), false, () => { Copy(gradient); });
menu.AddItem(new GUIContent("Paste"), false, () => { Paste(gradient); });
menu.ShowAsContext();
}
GUILayout.EndHorizontal();
}
static void Copy(Gradient gradient)
{
var clipboardType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ParticleSystemClipboard");
var gradient1Field = clipboardType.GetField("m_Gradient1", BindingFlags.Static | BindingFlags.NonPublic);
gradient1Field.SetValue(null, gradient);
}
static void Paste(Gradient gradient)
{
var clipboardType = typeof(EditorWindow).Assembly.GetType("UnityEditor.ParticleSystemClipboard");
var gradient1Field = clipboardType.GetField("m_Gradient1", BindingFlags.Static | BindingFlags.NonPublic);
object gradient1Value = gradient1Field.GetValue(null);
var gradient1 = (Gradient)gradient1Value;
gradient.SetKeys(gradient1.colorKeys, gradient1.alphaKeys);
Assembly editorAssembly = Assembly.GetAssembly(typeof(Editor));
Type gradientPreviewCacheType = editorAssembly.GetType("UnityEditorInternal.GradientPreviewCache");
MethodInfo clearCacheMethod = gradientPreviewCacheType.GetMethod("ClearCache");
clearCacheMethod.Invoke(null, null);
}
}
public class FxGradient
{
public static void EditorGradient(SerializedObject sObj, SerializedProperty property, string name, Gradient gradient)
{
GUILayout.BeginHorizontal();
if (!string.IsNullOrEmpty(name))
GUILayout.Label(name, GUILayout.Width(50));
Rect rect = GUILayoutUtility.GetLastRect();
rect.x += rect.width;
rect.width = 120;
rect.height = 16;
var GradientFieldMethod = typeof(EditorGUI).GetMethod("DoGradientField", BindingFlags.NonPublic | BindingFlags.Static);
if (GradientFieldMethod != null)
{
object[] parameters = new object[] { rect, gradient.GetHashCode(), gradient, property, false };
sObj.Update();
GradientFieldMethod.Invoke(null, parameters);
sObj.ApplyModifiedProperties();
}
GUILayout.EndHorizontal();
}
}