3D世界宠物围绕主角旋转飞行的脚本

发表于2017-11-30
评论0 887浏览

这个脚本的功能主要是宠物围绕角色不定期的顺逆时针交替旋转,交替的过程是宠物自身旋转,围绕主角旋转的同时宠物在一定范围内上下浮动,脚本里面用到了比较多的协程,不懂的同学可以看下这里点击打开链接


在比较复杂的运动中,“分而治之”是一个很重要很有用的解决方案。之前把前面所述的几种运动都集合在脚本中,用代码进行控制,结果发现他们之间的运动有好多是有冲突的,于是各种鬼畜现象就出现了。调试了好久没办法,后来开窍了,使用父子物体的形式把不同的运动放到不同的层级物体上面实现。于是问题迎刃而解了。


首先看一下层级面板里面的结构:


我们要做的是图里面的上面三个物体,其中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;  
    }  
}  

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论