3D世界寵物圍繞主角旋轉飛行的腳本

這個腳本的功能主要是寵物圍繞角色不定期的順逆時針

交替旋轉,交替的過程是寵物自身旋轉,圍繞主角旋轉的同時

寵物在一定範圍內上下浮動,腳本里面用到了比較多的協程,

不懂的同學可以看下這裏點擊打開鏈接

在比較複雜的運動中,“分而治之”是一個很重要很有用的解決方案。

之前把前面所述的幾種運動都集合在腳本中,用代碼進行控制,結果發現

他們之間的運動有好多是有衝突的,於是各種鬼畜現象就出現了。

調試了好久沒辦法,後來開竅了,使用父子物體的形式把不同的運動放到不同的層級物體

上面實現。於是問題迎刃而解了。



首先看一下層級面板裏面的結構:


我們要做的是圖裏面的上面三個物體,其中RotateSystem和

player是同一級別的,都是最根的GameObject,不存在父物體,

沒有把RotateSystem放到Player裏面是爲了防止角色轉身對

RotateSystem本身的旋轉造成影響。Pet的所有父物體都是空的GameObject。

首先來看一下RotateSystem裏面的自定義腳本:


很簡單,只有一個,下面看下腳本內容:

using UnityEngine;
using System.Collections;

public class Follow : MonoBehaviour {

    Transform player;

	void Start () {
        player = GameObject.FindGameObjectWithTag(Tags.player).transform;
	}
	
    void LateUpdate()
    {
        transform.position = player.position;
    }
}
這裏要注意一下的是位置跟隨的代碼是放在LateUpdate裏面,防止一些不和諧現象產生,原因可以百度LateUpdate的作用。


下面再來看看RotateCenter裏面的自定義腳本:

也只有一個腳本,我解釋一下參數,

從上往下,第一個表示寵物上升和下落的速度,

第二個表示相對於父物體的最低飛行高度,

第三個表示相對於父物體的最高飛行高度,

由於父物體RotateSystem的位置一直跟隨主角,所以

表示相對於主角的最低和最高飛行高度。

第四個表示高度再次變化的最低間隔時間,

第四個表示高度再次變化的最高間隔時間,

在這兩個間隔時間內有一定機率觸發高度變化。

下面來看腳本代碼:

using UnityEngine;
using System.Collections;

public class FloatingUpDown : MonoBehaviour {



    FlyAroundPlayer flyAroundPlayer;

    public float flyUpDownSmoothing;

    public float minRelativeFlyingHeight;

    public float maxRelativeFlyingHeight;

    public float minFlyUpDownDur;

    public float maxFlyUpDownDur;

    Vector3 flyingHeightPos;

    Transform player;

	// Use this for initialization
	void Start () {
        player = GameObject.FindGameObjectWithTag(Tags.player).transform;

        flyAroundPlayer = GetComponentInChildren<FlyAroundPlayer>();
        StartCoroutine(FlyingUpDown());
	}
	
	// Update is called once per frame
	void Update () {
        ControlFlyingHeight();
	}

    IEnumerator FlyingUpDown()
    {
        while (true)
        {
            if (!flyAroundPlayer.changingHeadDir)
            {
                int direction = Random.Range(-1, 2);
                if (direction != 0)
                {
                    flyingHeightPos = new Vector3(transform.localPosition.x, transform.localPosition.y + direction * Random.Range(minRelativeFlyingHeight, maxRelativeFlyingHeight), transform.localPosition.z);
                }
               
            }
            yield return new WaitForSeconds(Random.Range(minFlyUpDownDur, maxFlyUpDownDur));
        }

    }

    void ControlFlyingHeight()
    {
        //控制角色的飛行高度
        transform.localPosition = Vector3.Lerp(transform.localPosition, flyingHeightPos, flyUpDownSmoothing * Time.deltaTime);

        if (transform.localPosition.y > maxRelativeFlyingHeight)
        {
            transform.localPosition = new Vector3(transform.localPosition.x, maxRelativeFlyingHeight, transform.localPosition.z);
        }
        else if (transform.localPosition.y < minRelativeFlyingHeight)
        {
            transform.localPosition = new Vector3(transform.localPosition.x, minRelativeFlyingHeight, transform.localPosition.z);
        }
    }
}
這裏使用的是localPosition而不是position,是因爲如果父物體要運動,要保持相對位置,

如果直接使用position,則位置被腳本控制住了,當父物體移動到別的地方的時候,就沒有保持

相對位置了。



最後是Pet物體,來看下里面的自定義腳本:


只有FlyAroundPlayer這個自定義腳本。

下面解釋參數:

第一個是開始時候的相對位置,當Pet這個物體有父物體時,

它的Transform組件的position就表示localposition,因此在

Scene面板調好了位置之後,可以把transform的position信息

賦值到相對位置裏邊,配合下一個參數旋轉半徑實現飛行的位置控制。

第二個表示飛行旋轉半徑,

第三個表示旋轉速度,

第四個表示寵物調頭往反方向旋轉的最小間隔時間,

第五個表示寵物調頭往反方向旋轉的最大間隔時間,

第六個表示是否正在調頭,調頭的過程觸發,

可以用來做其他運動的控制條件,可以用[HideInInspector]標記。

第七個表示調頭的速度。

下面是代碼:

using UnityEngine;
using System.Collections;

public class FlyAroundPlayer : MonoBehaviour {



    Transform rotateCenter;

    public Vector3 offsetPosition;

    public float rotateRadius;

    public float rotatePlayerSpeed;

    int rotateDir=-1;

    public float changeDirTimeMax, changeDirTimeMin;

    /// <summary>
    /// 正在調頭往反方向旋轉
    /// </summary>
    public bool changingHeadDir;

    /// <summary>
    /// 調頭往反方向的時間
    /// </summary>
    public float changingHeadDirSpeed;

	// Use this for initialization
	void Start () {
        rotateCenter = GameObject.Find("RotateCenter").transform;
        AdjustRadius();
        StartCoroutine(ChangeHeadDir());
            
	}

    IEnumerator ChangeHeadDir()
    {
        while (true)
        {
            yield return new WaitForSeconds(Random.Range(changeDirTimeMin,changeDirTimeMax));

            int selection = Random.Range(0, 2);
            if (!changingHeadDir)
            {
                if (selection == 0)
                {
                    if (rotateDir == 1)
                    {
                        StartCoroutine(ChangingHeadDir());
                    }
                    rotateDir = -1;
                }
                else
                {
                    if (rotateDir == -1)
                    {
                        StartCoroutine(ChangingHeadDir());
                    }
                    rotateDir = 1;
                }
            }
            
        }
        
    }

    IEnumerator ChangingHeadDir()
    {
        Vector3 targetForward = transform.localRotation.eulerAngles;
        targetForward.y -=180;
        changingHeadDir = true;
       
        while (Mathf.Abs((targetForward-transform.localRotation.eulerAngles).y)%360>0.1)
	{
        transform.localRotation = Quaternion.Lerp(transform.localRotation, Quaternion.Euler(targetForward), changingHeadDirSpeed * Time.deltaTime);
            yield return null;
	}  
            
        changingHeadDir = false;
    }

    void AdjustRadius()
    {
        Vector3 newRadius=Vector3.zero;
        Vector3 currentRadius = rotateCenter.position - offsetPosition;
        if (currentRadius.magnitude!=rotateRadius)
        {
            newRadius=currentRadius.normalized* rotateRadius;
        }
        transform.position = rotateCenter.position + newRadius;
    }
	
	// Update is called once per frame
	void Update () {
        if (!changingHeadDir)
        {
            transform.RotateAround(rotateCenter.position, rotateCenter.up, rotateDir * rotatePlayerSpeed);
        }
        
    }

}


上面的代碼,寵物旋轉半徑是固定的,下面介紹一下,寵物旋轉半徑發生變化的

實現方法,同樣採用“分而治之”的解決方案,這裏不直接去改變半徑,

而是改變RotateSystem物體相對主角在xz平面的位移。下面是RotateSystem新的Inspector面板:


下面解釋參數:

從上往下第一個是主角物體的半徑,這個靠實際運行的時候,

來調節,這個參數的目的是使寵物旋轉的時候不會撞到主角身上,

第二個參數是在一次位置變換過後,下一次位置變換有機率觸發的最短時間,

第三個參數是在一次位置變換過後,下一次位置變換有機率觸發的最長時間,

第四個表示在觸發時間內,觸發位置變換的機率,

最後一個表示位置變換的速率。

下面是腳本:

using UnityEngine;
using System.Collections;

public class Follow : MonoBehaviour {

    Transform player;

    FlyAroundPlayer flyAroundPlayer;

    float rotateRadius;

    /// <summary>
    /// RotateSystem在xz平面,x方向和z方向移動的最大距離
    /// </summary>
    public float randomPosOffset;

    public float minChangeDur;

    public float maxChangeDur;

    /// <summary>
    /// 每次到達指定變換時間時,位置變換髮生的概率
    /// </summary>
    public float changeRatio;

    /// <summary>
    /// 位置偏移的速度
    /// </summary>
    public float removeSpeed;


    Vector3 targetPos;

    /// <summary>
    /// 當前的相對於player的position的位置偏移
    /// </summary>
    Vector3 currentPosOffset=Vector3.zero;

	void Start () {
        player = GameObject.FindGameObjectWithTag(Tags.player).transform;
        flyAroundPlayer = GetComponentInChildren<FlyAroundPlayer>();
        rotateRadius = flyAroundPlayer.rotateRadius;

        StartCoroutine(RandomPos());
        
	}

    IEnumerator RandomPos()
    {
        float ratio = Random.Range(0f, 1f);
        if (ratio<changeRatio)
        {
            float newPosZ = Random.Range(-rotateRadius + randomPosOffset, rotateRadius - randomPosOffset);
            float newPosX = Random.Range(-rotateRadius + randomPosOffset, rotateRadius - randomPosOffset);
            targetPos = new Vector3(newPosX, 0, newPosZ);
        }
                StartCoroutine(StartOffset());

                yield return null;
    }

    IEnumerator StartOffset()
    {
        while (currentPosOffset!=targetPos)
        {
            currentPosOffset = Vector3.Lerp(currentPosOffset, targetPos, removeSpeed * Time.deltaTime);
            yield return null;  
        }
        yield return new WaitForSeconds(Random.Range(minChangeDur, minChangeDur));
        StartCoroutine(RandomPos());
    }

    void LateUpdate()
    {
        transform.position = player.position+currentPosOffset;
    }
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章