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;
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值