Unity 按照设定路线行走

Unity 按照设定路线行走

这个代码是从官网的一个Demo中扒出来的,Demo中代码较多,好多用不到,不利于查看核心代码

核心代码

using UnityEngine;

[System.Serializable]
public class WaypointList
{
    public WaypointCircuit circuit;
    public Transform[] items = new Transform[0];
}

public struct RoutePoint
{
    public Vector3 position;
    public Vector3 direction;

    public RoutePoint(Vector3 position, Vector3 direction)
    {
        this.position = position;
        this.direction = direction;
    }
}

public class WaypointCircuit : MonoBehaviour
{
    public WaypointList waypointList = new WaypointList();
    [SerializeField] bool smoothRoute = true;
    int numPoints;
    Vector3[] points;
    float[] distances;

    public float editorVisualisationSubsteps = 100;
    public float Length { get; private set; }
    public Transform[] Waypoints { get { return waypointList.items; } }

    //this being here will save GC allocs
    int p0n;
    int p1n;
    int p2n;
    int p3n;

    private float i;
    Vector3 P0;
    Vector3 P1;
    Vector3 P2;
    Vector3 P3;

    // Use this for initialization
    void Awake()
    {
        if (Waypoints.Length > 1)
        {
            CachePositionsAndDistances();
        }
        numPoints = Waypoints.Length;
    }

    public RoutePoint GetRoutePoint(float dist)
    {
        // position and direction
        Vector3 p1 = GetRoutePosition(dist);
        Vector3 p2 = GetRoutePosition(dist + 0.1f);
        Vector3 delta = p2 - p1;
        return new RoutePoint(p1, delta.normalized);
    }

    public Vector3 GetRoutePosition(float dist)
    {
        int point = 0;

        if (Length == 0)
        {
            Length = distances[distances.Length - 1];
        }

        dist = Mathf.Repeat(dist, Length);

        while (distances[point] < dist) { ++point; }


        // get nearest two points, ensuring points wrap-around start & end of circuit
        p1n = ((point - 1) + numPoints) % numPoints;
        p2n = point;

        // found point numbers, now find interpolation value between the two middle points

        i = Mathf.InverseLerp(distances[p1n], distances[p2n], dist);

        if (smoothRoute)
        {
            // smooth catmull-rom calculation between the two relevant points



            // get indices for the surrounding 2 points, because
            // four points are required by the catmull-rom function
            p0n = ((point - 2) + numPoints) % numPoints;
            p3n = (point + 1) % numPoints;

            // 2nd point may have been the 'last' point - a dupe of the first,
            // (to give a value of max track distance instead of zero)
            // but now it must be wrapped back to zero if that was the case.
            p2n = p2n % numPoints;

            P0 = points[p0n];
            P1 = points[p1n];
            P2 = points[p2n];
            P3 = points[p3n];

            return CatmullRom(P0, P1, P2, P3, i);

        }
        else
        {

            // simple linear lerp between the two points:

            p1n = ((point - 1) + numPoints) % numPoints;
            p2n = point;

            return Vector3.Lerp(points[p1n], points[p2n], i);
        }
    }

    Vector3 CatmullRom(Vector3 _P0, Vector3 _P1, Vector3 _P2, Vector3 _P3, float _i)
    {
        // comments are no use here... it's the catmull-rom equation.
        // Un-magic this, lord vector!
        return 0.5f * ((2 * _P1) + (-_P0 + _P2) * _i + (2 * _P0 - 5 * _P1 + 4 * _P2 - _P3) * _i * _i + (-_P0 + 3 * _P1 - 3 * _P2 + _P3) * _i * _i * _i);
    }


    void CachePositionsAndDistances()
    {
        // transfer the position of each point and distances between points to arrays for
        // speed of lookup at runtime
        points = new Vector3[Waypoints.Length + 1];
        distances = new float[Waypoints.Length + 1];

        float accumulateDistance = 0;
        for (int i = 0; i < points.Length; ++i)
        {
            var t1 = Waypoints[(i) % Waypoints.Length];
            var t2 = Waypoints[(i + 1) % Waypoints.Length];
            if (t1 != null && t2 != null)
            {
                Vector3 p1 = t1.position;
                Vector3 p2 = t2.position;
                points[i] = Waypoints[i % Waypoints.Length].position;
                distances[i] = accumulateDistance;
                accumulateDistance += (p1 - p2).magnitude;
            }
        }
    }


    void OnDrawGizmos()
    {
        DrawGizmos(false);
    }

    void OnDrawGizmosSelected()
    {
        DrawGizmos(true);
    }

    void DrawGizmos(bool selected)
    {
        waypointList.circuit = this;
        if (Waypoints.Length > 1)
        {
            numPoints = Waypoints.Length;

            CachePositionsAndDistances();
            Length = distances[distances.Length - 1];

            Gizmos.color = selected ? Color.yellow : new Color(1, 1, 0, 0.5f);
            Vector3 prev = Waypoints[0].position;
            if (smoothRoute)
            {
                for (float dist = 0; dist < Length; dist += Length / editorVisualisationSubsteps)
                {
                    Vector3 next = GetRoutePosition(dist + 1);
                    Gizmos.DrawLine(prev, next);
                    prev = next;
                }
                Gizmos.DrawLine(prev, Waypoints[0].position);
            }
            else
            {

                for (int n = 0; n < Waypoints.Length; ++n)
                {
                    Vector3 next = Waypoints[(n + 1) % Waypoints.Length].position;
                    Gizmos.DrawLine(prev, next);
                    prev = next;
                }
            }
        }
    }
}

using UnityEngine;
using System.Collections;
#if UNITY_EDITOR
using UnityEditor;
#endif

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(WaypointList))]
public class WaypointListDrawer : PropertyDrawer
{
    float lineHeight = 18;
    float spacing = 4;

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.BeginProperty(position, label, property);

        float x = position.x;
        float y = position.y;
        float inspectorWidth = position.width;

        // Draw label


        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        var items = property.FindPropertyRelative("items");
        string[] titles = new string[] { "Transform", "", "", "" };
        string[] props = new string[] { "transform", "^", "v", "-" };
        float[] widths = new float[] { .7f, .1f, .1f, .1f };
        float lineHeight = 18;
        bool changedLength = false;
        if (items.arraySize > 0)
        {

            for (int i = 0; i < items.arraySize; ++i)
            {

                var item = items.GetArrayElementAtIndex(i);

                float rowX = x;
                for (int n = 0; n < props.Length; ++n)
                {
                    float w = widths[n] * inspectorWidth;

                    // Calculate rects
                    Rect rect = new Rect(rowX, y, w, lineHeight);
                    rowX += w;

                    if (i == -1)
                    {
                        EditorGUI.LabelField(rect, titles[n]);
                    }
                    else
                    {
                        if (n == 0)
                        {
                            EditorGUI.ObjectField(rect, item.objectReferenceValue, typeof(Transform), true);
                        }
                        else
                        {
                            if (GUI.Button(rect, props[n]))
                            {
                                switch (props[n])
                                {
                                    case "-":
                                        items.DeleteArrayElementAtIndex(i);
                                        items.DeleteArrayElementAtIndex(i);
                                        changedLength = true;
                                        break;
                                    case "v":
                                        if (i > 0) items.MoveArrayElement(i, i + 1);
                                        break;
                                    case "^":
                                        if (i < items.arraySize - 1) items.MoveArrayElement(i, i - 1);
                                        break;
                                }

                            }
                        }
                    }
                }

                y += lineHeight + spacing;
                if (changedLength)
                {
                    break;
                }
            }

        }
        else
        {

            // add button
            var addButtonRect = new Rect((x + position.width) - widths[widths.Length - 1] * inspectorWidth, y, widths[widths.Length - 1] * inspectorWidth, lineHeight);
            if (GUI.Button(addButtonRect, "+"))
            {
                items.InsertArrayElementAtIndex(items.arraySize);
            }

            y += lineHeight + spacing;
        }

        // add all button
        var addAllButtonRect = new Rect(x, y, inspectorWidth, lineHeight);
        if (GUI.Button(addAllButtonRect, "Assign using all child objects"))
        {
            var circuit = property.FindPropertyRelative("circuit").objectReferenceValue as WaypointCircuit;
            var children = new Transform[circuit.transform.childCount];
            int n = 0; foreach (Transform child in circuit.transform) children[n++] = child;
            System.Array.Sort(children, new TransformNameComparer());
            circuit.waypointList.items = new Transform[children.Length];
            for (n = 0; n < children.Length; ++n)
            {
                circuit.waypointList.items[n] = children[n];
            }


        }
        y += lineHeight + spacing;

        // rename all button
        var renameButtonRect = new Rect(x, y, inspectorWidth, lineHeight);
        if (GUI.Button(renameButtonRect, "Auto Rename numerically from this order"))
        {
            var circuit = property.FindPropertyRelative("circuit").objectReferenceValue as WaypointCircuit;
            int n = 0; foreach (Transform child in circuit.waypointList.items) child.name = "Waypoint " + (n++).ToString("000");

        }
        y += lineHeight + spacing;

        // Set indent back to what it was
        EditorGUI.indentLevel = indent;
        EditorGUI.EndProperty();
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        SerializedProperty items = property.FindPropertyRelative("items");
        float lineAndSpace = lineHeight + spacing;
        return 40 + (items.arraySize * lineAndSpace) + lineAndSpace;
    }

    // comparer for check distances in ray cast hits
    public class TransformNameComparer : IComparer
    {
        public int Compare(object x, object y)
        {
            return ((Transform)x).name.CompareTo(((Transform)y).name);
        }
    }

}
#endif

(1)新建一个Unity 工程,将上面代码复制到脚本中

(2)创建一个空的GameObject,在GameObject下创建四个Cube,作为 4 个路点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DHGK7IO6-1660123460786)(https://img-blog.csdn.net/20150704214342584)]

将 WaypointCircuit.cs 脚本拖拽到 GameObject上
点击 Assign using all child objects 按钮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dafv6yqD-1660123460787)(https://img-blog.csdn.net/20150704214553311)]

自动将GameObject 的子对象添加到路点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpxcuE6R-1660123460787)(https://img-blog.csdn.net/20150704214723641)]

点击 Auto Rename numerically from this order 按钮,将自动修改 GameObject子物体的名字,并且在Scene下绘制出路径
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u285XQrr-1660123460788)(https://img-blog.csdn.net/20150704214923121)]

(3)使用路径,新建一个 Sphere作为移动物体,创建一个 脚本 Follow.cs

using UnityEngine;
using System.Collections;

public class Follow : MonoBehaviour {

    // 路径脚本
    [SerializeField]
    private WaypointCircuit circuit;

    //移动距离
    private float dis;
    //移动速度
    private float speed;
    // Use this for initialization
    void Start()
    {
        dis = 0;
        speed = 10;
    }

    // Update is called once per frame
    void Update()
    {
        //计算距离
        dis += Time.deltaTime * speed;
        //获取相应距离在路径上的位置坐标
        transform.position = circuit.GetRoutePoint(dis).position;
        //获取相应距离在路径上的方向
        transform.rotation = Quaternion.LookRotation(circuit.GetRoutePoint(dis).direction);
    }
}

将 Follow.cs 脚本拖拽到 Sphere 上,将GameObject 拖拽到 Circuit 参数上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W8kznz45-1660123460788)(https://img-blog.csdn.net/20150704215549128)]

(4)运行项目,Sphere在路径上完美行走
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xU58cH8j-1660123460788)(https://img-blog.csdn.net/20150704215729503)]

  • 13
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值