Unity曲线编辑器和bezier曲线插值

梦想游戏人
目录:
Unity

Unity曲线编辑器和bezier曲线插值 在上一篇的基础上扩展,

还有一个类似功能的插件SWS

曲线编辑,可用于不规则路线,N个2阶bezier 替代高阶,达到曲线插值 的额性能和速度平衡

为了曲线公衡平滑可以用N个3阶的特性调节细节

 

 

BezierMgrEditor.cs

using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;

[CustomEditor(typeof(BezierMgr))]
public class BezierMgrEditor : Editor
{
    private SerializedObject m_Object;
    private SerializedProperty m_ShowWayPointInGame;
    private SerializedProperty m_IsTest;
    private SerializedProperty m_Id;


    //called whenever this inspector window is loaded 
    public void OnEnable()
    {
        //we create a reference to our script object by passing in the target
        m_Object = new SerializedObject(target);
        m_ShowWayPointInGame = m_Object.FindProperty("showWayPoint");
        m_IsTest = m_Object.FindProperty("isWayPointTest");
        m_Id = m_Object.FindProperty("id");

    }

    //called whenever the inspector gui gets rendered
    public override void OnInspectorGUI()
    {
        //this pulls the relative variables from unity runtime and stores them in the object
        m_Object.Update();

        this.RenameAll();

        GUILayout.BeginHorizontal();
        GUILayout.Label("--------------游戏设置--------------");
        GUILayout.EndHorizontal();
        this.m_Id.intValue = EditorGUILayout.IntField("该路点id:", m_Id.intValue, GUILayout.Width(300.0f));

        this.m_ShowWayPointInGame.boolValue = EditorGUILayout.Toggle("游戏中显示路点", m_ShowWayPointInGame.boolValue, GUILayout.Width(300.0f));

        this.m_IsTest.boolValue = EditorGUILayout.Toggle("开启路点测试", m_IsTest.boolValue, GUILayout.Width(300.0f));

        //waypoint index header
        GUILayout.Label("Bezier Points: ", EditorStyles.boldLabel);
        GUILayout.BeginHorizontal();

        if (GUILayout.Button("add"))
        {
            AddPoint();
        }
        if (GUILayout.Button("remove"))
        {
            RemovePoint();
        }
        if (GUILayout.Button("remove all"))
        {
            this.RemoveAllPoints();
        }

        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        GUILayout.Label("---------------------------------");
        GUILayout.EndHorizontal();

        if (points.Count % 3 != 0)
        {
            GUILayout.BeginHorizontal();
            GUILayout.Label("Error:当前路点不为3的整数倍");
            GUILayout.EndHorizontal();
        }

        GUILayout.BeginHorizontal();
        GUILayout.Label("----------信---息----------");
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        GUILayout.Label("当前二阶Bezier曲线数量:" + points.Count / 3);
        GUILayout.EndHorizontal();


        for (int i = 0; i < 3; i++)
        {
            GUILayout.BeginHorizontal();
            //indicate each array slot with index number in front of it
            //create an object field for every waypoint
            // EditorGUILayout.ObjectField(waypoints[i], typeof(Transform), true);

            GUILayout.EndHorizontal();
        }

        //we push our modified variables back to our serialized object
        m_Object.ApplyModifiedProperties();
    }

    ArrayList points = new ArrayList();
    public void AddPoint()
    {
        this.RenameAll();
        int count = 3 - points.Count % 3;

        for (int j = 0; j < count; j++)
        {
            GameObject p = GameObject.Instantiate<GameObject>(GameObject.Find("__BezierTemplatePoint"));

            if (points.Count > 0)
            {
                p.transform.position = (points[points.Count - 1] as GameObject).transform.position + Vector3.left * 10.0f;
            }
            points.Add(p);
            p.name = "BezierPoint " + (points.Count);
            p.transform.SetParent((m_Object.targetObject as BezierMgr).gameObject.transform);
            //   p.transform.SetSiblingIndex(1);
            this.RenameAll();
        }
    }
    void SyncAll()
    {
        this.RenameAll();
    }
    private void RenameAll()
    {
        //先删除不在记录中的点
        var objj = new SerializedObject(target);
        var parent = (objj.targetObject as BezierMgr).gameObject;
        var pp = parent.GetComponentsInChildren<Transform>();

        ArrayList real = new ArrayList();
        for (int i = 1; i < pp.Length; i++)
        {
            Transform tr = pp[i] as Transform;
            real.Add(tr.gameObject);
        }
        // rename all
        int idx = 0;
        bool swap = (real.Count) != points.Count && points.Count != 0; //只有不足3的倍数时 才交换,因为可能会删除某个点来作为

        points.Clear();
        foreach (GameObject obj in real)
        {
            points.Add(obj);
            string point_name_tag = "";

            const string Name_Tag_Head = "_Head";
            const string Name_Tag_Mid = "_Mid";
            const string Name_Tag_End = "_End";

            if (idx % 3 == 0)
            {
                point_name_tag = Name_Tag_Head;
            }
            else if (idx % 3 == 1)
            {
                point_name_tag = Name_Tag_Mid;
            }
            else
            {
                point_name_tag = Name_Tag_End;
            }
            string newname = "BezierPoint Rename " + idx;
            Transform oldObj = parent.transform.Find(newname + Name_Tag_Head);
            if (oldObj == null)
            {
                oldObj = parent.transform.Find(newname + Name_Tag_Mid);
            }
            if (oldObj == null)
            {
                oldObj = parent.transform.Find(newname + Name_Tag_End);
            }
            if (oldObj != null && swap)
            {//replace new position to old position avoid miss info
                obj.transform.position = oldObj.position;
            }
            obj.name = newname + point_name_tag;
            ++idx;
        }
    }
    bool HasObject(GameObject obj)
    {
        foreach (GameObject ob in points)
        {
            if (ob == obj) return true;
        }
        return false;
    }
    public void RemovePoint()
    {
        this.RenameAll();
        if (points.Count <= 0) return;
        int offset = 1;
        if (points.Count % 3 == 0) offset = 3;
        for (int i = 0; i < offset; i++)
        {
            GameObject p = points[points.Count - 1] as GameObject;
            GameObject.DestroyImmediate(p);
            points.Remove(p);
        }

        this.RenameAll();
    }
    public void RemoveAllPoints()
    {
        var parent = (this.m_Object.targetObject as BezierMgr).gameObject;
        var pp = parent.GetComponentsInChildren<Transform>();

        ArrayList real = new ArrayList();
        for (int i = 1; i < pp.Length; i++)
        {
            Transform tr = pp[i] as Transform;
            GameObject.DestroyImmediate(tr.gameObject);
        }
        points.Clear();
    }

}

BezierMgr.cs

/*
* Author:  caoshanshan
* Email:   me@dreamyouxi.com

 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 2次 贝塞尔 曲线管理器,用于拼接多个低阶 组合成平滑路径,
public class BezierMgr : MonoBehaviour
{
    [Header("生成可显示路点否")]
    [SerializeField]
    public bool showWayPoint = true;

    [Header("显示路点的精度")]
    [SerializeField]
    public float tolerance = 0.001f;

    [Header("显示路点的缩放")]
    [SerializeField]
    public float scale = 0.1f;
    [Header("开启路点测试后,汽车前进速度不会变化,只是测试路径平滑度使用")]
    [SerializeField]
    public bool isWayPointTest = true;

    public int id = 0;//改路点id 一个赛道允许多个路点,用id表示
    [HideInInspector]
    public static BezierMgr ins;
    public BezierMgr()
    {
        ins = this;
    }

    private int CURRENT_INDEX = 0;
    public Bezier3 GetNextBezier()
    {
        if (bezier_queue.Count <= 0)
        {
            this.Awake();
        }
        if (CURRENT_INDEX >= bezier_queue.Count)
        {
            CURRENT_INDEX = 0; // for loop
        }
        int index = CURRENT_INDEX;
        CURRENT_INDEX++;
        var b = bezier_queue[index] as Bezier3;
        return b;
    }

    // n阶段 bezier points
    public static List<Vector3> GetBezierPoints(List<Vector3> pathToCurve, int interpolations)
    {
        List<Vector3> tempPoints;
        List<Vector3> curvedPoints;
        int pointsLength = 0;
        int curvedLength = 0;

        if (interpolations < 1)
            interpolations = 1;

        pointsLength = pathToCurve.Count;
        curvedLength = (pointsLength * Mathf.RoundToInt(interpolations)) - 1;
        curvedPoints = new List<Vector3>(curvedLength);

        float t = 0.0f;
        for (int pointInTimeOnCurve = 0; pointInTimeOnCurve < curvedLength + 1; pointInTimeOnCurve++)
        {
            t = Mathf.InverseLerp(0, curvedLength, pointInTimeOnCurve);
            tempPoints = new List<Vector3>(pathToCurve);
            for (int j = pointsLength - 1; j > 0; j--)
            {
                for (int i = 0; i < j; i++)
                {
                    tempPoints[i] = (1 - t) * tempPoints[i] + t * tempPoints[i + 1];
                }
            }
            curvedPoints.Add(tempPoints[0]);
        }

        return curvedPoints;
    }
    public void Init()
    {
        bezier_queue.Clear();
        foreach (GameObject obj in show_queue)
        {
            GameObject.DestroyImmediate(obj);
        }
        show_queue.Clear();
        CURRENT_INDEX = 0;
    }
    ArrayList show_queue = new ArrayList();
    ArrayList bezier_queue = new ArrayList();
    void Awake()
    {
        this.Init();
        this.bezier_queue = this.GetBezierQueue();
    }

    bool hasShow = false;
    ArrayList GetBezierQueue(bool editorMode = false)
    {
        var points = this.GetComponentsInChildren<Transform>();
        ArrayList ret = new ArrayList();
        ArrayList bezier_points = new ArrayList();
        int i = 0;
        foreach (Transform point in points)
        {
            if (i != 0 && editorMode == false)
            {
                //  point.gameObject.SetActive(false);
            }
            bezier_points.Add(point.position);
            i++;
        }
        i--;
        bezier_points.RemoveAt(0);
        if (i % 3 != 0 && editorMode == false)
        {
            Debug.LogError("error of number of way point   " + bezier_points.Count);
        }
        for (int ii = 0; ii < bezier_points.Count; ii += 3)
        {
            //根据路点 生成 2阶bezier 集合
            Vector3 point = (Vector3)bezier_points[ii];
            var bezier = new Bezier3();
            bezier.id = ii / 3;
            if (editorMode)
            {
                try
                {
                    bezier.p0 = (Vector3)bezier_points[ii];
                    bezier.p1 = (Vector3)bezier_points[ii + 1];
                    bezier.p2 = (Vector3)bezier_points[ii + 2];
                    ret.Add(bezier);
                }
                catch (System.Exception e)
                {

                }
            }
            else
            {
                bezier.p0 = (Vector3)bezier_points[ii];
                bezier.p1 = (Vector3)bezier_points[ii + 1];
                bezier.p2 = (Vector3)bezier_points[ii + 2];
                ret.Add(bezier);
            }
        }

        if (editorMode == false && showWayPoint && hasShow == false)
        {
            this.bezier_queue = ret;
            GameObject cube = GameObject.Find("__WayPointShowCube");
            GameObject p = GameObject.Find("__DrawShowPoints");
            if (Application.isPlaying)
            {
                hasShow = true;
                foreach (Bezier3 be in bezier_queue)
                {
                    Vector3 p0 = be.p0;
                    Vector3 p1 = be.p1;
                    Vector3 p2 = be.p2;
                    for (float time = 0f; time < 1.0f; time += tolerance)
                    {
                        float t = time;
                        Vector3 pos = be.GetPoint(t);
                        var obj = GameObject.Instantiate<GameObject>(cube, p.transform);
                        obj.transform.position = pos;
                        obj.name = "clone";
                        obj.transform.localScale = new Vector3(scale, scale, scale);

                        show_queue.Add(obj);
                    }
                }
            }
        }
        return ret;
    }
    void Start()
    {

    }

    void Update()
    {

    }

    void OnDrawGizmos()
    {
        ArrayList bezier_queue = this.GetBezierQueue(true); ;
        ArrayList pointss = new ArrayList();
        foreach (Bezier3 be in bezier_queue)
        {
            for (float time = 0f; time < 1.0f; time += 0.001f)
            {
                float t = time;
                Vector3 pos = be.GetPoint(t);
                pointss.Add(pos);
            }
        }
        for (int ii = 0; ii < pointss.Count - 3; ii += 2)
        {
            Gizmos.DrawLine((Vector3)(pointss[ii]), (Vector3)(pointss[ii + 1]));
        }
    }

}

Bezier3.cs

/*
* Author:  caoshanshan
* Email:   me@dreamyouxi.com

 */
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//2次贝塞尔曲线
//为了计算性能和精度 暂不用高阶生成,用多个2阶 实时计算 拼接出完整赛道
// 额外的工作是 每个2阶的粘合处平稳过渡 
public class Bezier3
{
    public Vector3 GetPoint(float t)
    {
 
        Vector3 pos = (1 - t) * (1 - t) * p0 + 2 * t * (1 - t) * p1 + t * t * p2;
        return pos;
    }
    public Bezier3()
    {

    }
    public Vector3 p0 = Vector3.zero;
    public Vector3 p1 = Vector3.zero;
    public Vector3 p2 = Vector3.zero;

    public int id = 0;//id
}
Scroll Up