Unity曲线编辑器和bezier曲线插值
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 }