天天看點

Unity 使用貝塞爾曲線實作抛物線運動

在遊戲中使用Tween+貝塞爾曲線實作一個抛物線的效果。

1、線性貝塞爾曲線公式:

給定點P0、P1,線性貝茲曲線隻是一條兩點之間的直線。這條線由下式給出:

Unity 使用貝塞爾曲線實作抛物線運動
Unity 使用貝塞爾曲線實作抛物線運動

2、二次貝塞爾曲線公式:

二次方貝茲曲線的路徑由給定點P0、P1、P2控制,這條線由下式給出:

Unity 使用貝塞爾曲線實作抛物線運動
Unity 使用貝塞爾曲線實作抛物線運動

3、三次貝塞爾曲線公式:

P0、P1、P2、P3四個點在平面或在三維空間中定義了三次方貝茲曲線。曲線起始于P0走向P1,并從P2的方向來到P3。一般不會經過P1或P2;這兩個點隻是用來充當控制點。P0和P1之間的間距,決定了曲線在轉而趨進P3之前,走向P2方向的“長度有多長”。

曲線的參數形式為:

Unity 使用貝塞爾曲線實作抛物線運動
Unity 使用貝塞爾曲線實作抛物線運動

這裡使用二次貝塞爾曲線實作抛物線運動:

代碼如下

using DG.Tweening;
using System;
using System.Collections;
using UnityEngine;

public class CurvedMoveEffect : MonoBehaviour
{
    private Action callback = null;
    [SerializeField]
    private GameObject startPos, endPos;

    private void Start()
    {
        MoveTo(startPos.transform.position, endPos.transform.position, 0.6f, 1, null);
    }

    private void OnComplete()
    {
        if (callback != null)
        {
            callback.Invoke();
        }
        Destroy(gameObject);
    }

    /// <param name="centerOffset">中心點偏移量,0.5為基準,調整幅度0.01,根據實際調整</param>
    public void MoveTo(Vector3 startPos, Vector3 endPos, float centerOffset, float time = 0.5f, Action callback = null)
    {
        this.callback = callback;
        gameObject.transform.position = startPos;
        gameObject.SetActive(true);
        StartCoroutine(Move(startPos, endPos, centerOffset, time, OnComplete));
    }

    //如果有拖尾效果,延遲一定時間再顯示,否則尾巴會有殘影
    IEnumerator Move(Vector3 startPos, Vector3 endPos, float centerOffset, float time = 0.5f, Action onComplete = null)
    {
        TrailRenderer trail = gameObject.GetComponentInChildren<TrailRenderer>(true);
        yield return new WaitForSeconds(trail ? trail.time : 0);
        //如果路徑點較少,可以使用CatmullRom曲線插值類型運動,預設PathType.Linear
        //同時可以設定easetype來使用不同的運動方式,其它easetype:https://blog.csdn.net/w0100746363/article/details/83587485
        //如果想讓物體一直朝着路徑方向可以加上SetLookAt(0):DOPath(path, time).SetLookAt(0);
        Tween tween = gameObject.transform.DOPath(Path(startPos, endPos, centerOffset, 6), time, PathType.CatmullRom).SetEase(Ease.OutSine);

        if (onComplete != null)
        {
            tween.OnComplete(new DG.Tweening.Core.TweenCallback(onComplete));
        }
        if (trail)
            trail.enabled = true;
    }

    private Vector3[] Path(Vector3 startTrans, Vector3 endTrans, float centerCtl, int segmentNum)
    {
        var startPoint = startTrans;
        var endPoint = endTrans;
        //中間點:起點和終點的向量相加再乘一個系數,系數可以根據實際調整
        var bezierControlPoint = (startPoint + endPoint) * centerCtl;

        //segmentNum為int類型,路徑點數量,值越大,路徑點越多,曲線越平滑
        Vector3[] path = new Vector3[segmentNum];
        for (int i = 0; i < segmentNum; i++)
        {
            var time = (i + 1) / (float)segmentNum;//歸化到0~1範圍
            path[i] = GetBezierPoint(time, startPoint, bezierControlPoint, endPoint);//使用貝塞爾曲線的公式取得t時的路徑點
        }
        return path;
    }

    /// <param name="t">0到1的值,0擷取曲線的起點,1獲得曲線的終點</param>
    /// <param name="start">曲線的起始位置</param>
    /// <param name="center">決定曲線形狀的控制點</param>
    /// <param name="end">曲線的終點</param>
    private Vector3 GetBezierPoint(float t, Vector3 start, Vector3 center, Vector3 end)
    {
        return (1 - t) * (1 - t) * start + 2 * t * (1 - t) * center + t * t * end;
    }
}
           

另外,相同的中間點偏移系數在場景中Canvas的不同Render Mode和MainCamera的不同Projection時都可能産生不同的效果,需要根據實際情況調整。

繼續閱讀