这个脚本的功能主要是宠物围绕角色不定期的顺逆时针
交替旋转,交替的过程是宠物自身旋转,围绕主角旋转的同时
宠物在一定范围内上下浮动,脚本里面用到了比较多的协程,
不懂的同学可以看下这里点击打开链接。
在比较复杂的运动中,“分而治之”是一个很重要很有用的解决方案。
之前把前面所述的几种运动都集合在脚本中,用代码进行控制,结果发现
他们之间的运动有好多是有冲突的,于是各种鬼畜现象就出现了。
调试了好久没办法,后来开窍了,使用父子物体的形式把不同的运动放到不同的层级物体
上面实现。于是问题迎刃而解了。
首先看一下层级面板里面的结构:
我们要做的是图里面的上面三个物体,其中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;
}
}