【unity】特效

特效控制

驱动
  • 自动播放
    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();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值