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); } } }
如果直接使用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; } }