這個腳本的功能主要是寵物圍繞角色不定期的順逆時針
交替旋轉,交替的過程是寵物自身旋轉,圍繞主角旋轉的同時
寵物在一定範圍內上下浮動,腳本里面用到了比較多的協程,
不懂的同學可以看下這裏點擊打開鏈接。
在比較複雜的運動中,“分而治之”是一個很重要很有用的解決方案。
之前把前面所述的幾種運動都集合在腳本中,用代碼進行控制,結果發現
他們之間的運動有好多是有衝突的,於是各種鬼畜現象就出現了。
調試了好久沒辦法,後來開竅了,使用父子物體的形式把不同的運動放到不同的層級物體
上面實現。於是問題迎刃而解了。
首先看一下層級面板裏面的結構:
我們要做的是圖裏面的上面三個物體,其中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;
}
}