一、介绍
本篇的主要内容是解决大量物体运动时候不发生相互碰撞和重叠等的算法,本篇使用的Unity2018.2.14,算法的脚本主要就两个,一个创建大量鱼并进行初始化的脚本,还有一个就是核心的鱼集群运动的算法。效果如图所示
二、实现
1、首先,需要再指定的区域随机的创建大量的鱼,代码如下,其中goalPos会再集群运动的单个鱼中会用到,这里只是再随机的时
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Global_FlockManage : BaseGameObj {
/// <summary>
/// 集群运动的的范围
/// </summary>
public int M_SizeArea
{
get
{
return sizeArea;
}
}
/// <summary>
/// 显示的集群物体的数量
/// </summary>
public int M_NumShowFlockObj
{
get
{
return numShowFlockObj;
}
}
/// <summary>
/// 所有显示的集群的物体
/// </summary>
public List<FlockEntity> M_ListShowAllFlockObj
{
get
{
return listAllShowFlockObjs;
}
}
/// <summary>
/// 集群的目标位置
/// </summary>
public Vector3 M_GoalPos
{
get
{
return goalPos;
}
}
public static Global_FlockManage M_Instance
{
get
{
if(null==_instance)
{
_instance = FindObjectOfType<Global_FlockManage>();
}
return _instance;
}
}
[SerializeField] FlockEntity[] prefabFlockObj;
[SerializeField] int sizeArea;
[SerializeField] int numShowFlockObj;
private List<FlockEntity> listAllShowFlockObjs = new List<FlockEntity>();
private Vector3 goalPos = Vector3.zero;
private static Global_FlockManage _instance;
protected override void FixedUpdate_State()
{
}
protected override void LateUpdate_State()
{
}
protected override void Update_State()
{
///随机时间改变目标位置,并且目标位置为随机值
if (Random.Range(0, 10000) < 50)
{
goalPos = GetRandPos();
}
}
// Use this for initialization
void Start () {
Init();
}
/// <summary>
/// 根据区域大小获取随机的位置
/// </summary>
/// <returns></returns>
public Vector3 GetRandPos()
{
Vector3 tempVec = Vector3.zero;
float tempX = Random.Range(-sizeArea, sizeArea);
float tempY = Random.Range(0, sizeArea);
float tempZ = Random.Range(-sizeArea, sizeArea);
tempVec = new Vector3(tempX, tempY, tempZ);
return tempVec;
}
public override void Init()
{
base.Init();
goalPos = transform.position;
for (int i = 0; i < numShowFlockObj; i++)
{
//随机的位置
Vector3 tempPos = goalPos + GetRandPos();
//Debug.Log(tempPos);
//创建随机物体
int tempIndex = Random.Range(0, prefabFlockObj.Length);
FlockEntity tempFE = Instantiate(prefabFlockObj[tempIndex], tempPos, Quaternion.identity,transform);
tempFE.Init();
listAllShowFlockObjs.Add(tempFE);
}
}
}
间里随机的改变其值。
2、然后是集群运动的单个鱼的控制,在初始化的时候先让其运动的速度和游动的动画速度随机。
public override void Init()
{
base.Init();
parentPos = Global_FlockManage.M_Instance.transform.position;
moveSpeed = Random.Range(1f, 2);
Animation tempAnim = GetComponent<Animation>();
tempAnim[moveAnimName].speed = Random.Range(0f, 1.0f);
}
然后,其转弯的逻辑上做两种处理,第一种是鱼和边界之间,如果当前鱼游动的位置超过了预定的边界,则立马掉头
protected override void Update_State()
{
//如果距离母物体的距离超过了指定的范围就设置开始掉头为true;
float tempDis2ParentPos = Vector3.Distance(transform.position, parentPos);
turning = (Global_FlockManage.M_Instance.M_SizeArea <= tempDis2ParentPos);
if(turning)
{
Vector3 tempDir = parentPos - transform.position;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(tempDir), rotationSpeed * Time.deltaTime);
moveSpeed = Random.Range(0.5f, 1);
}
transform.Translate(0, 0, Time.deltaTime * moveSpeed);
}
还有一种是鱼和鱼之间,其基本思路就是鱼和其他所有的鱼的位置进行计算,如果有其他鱼的位置在设定的范围内,则将其和当前控制鱼的位置计算方向向量。让后将所有的这个方向向量进行相加,就可以得到它应该去到所有其他在需要规避范围内的鱼的位置的中心点,该中心点和鱼当前位置的向量为向量1,向量1再和总控中的goalPos位置与当前鱼位置的方向向量2,组成方向向量3,然后在计算控制鱼与其他鱼的相反向量为向量4,最后向量4和向量3计算当前鱼的转动方向。其运动的速度取所有范围内的鱼的平均值。完整代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FlockEntity : BaseGameObj {
[SerializeField] string moveAnimName = "Motion";
[SerializeField] float moveSpeed = 1.0f;
private float rotationSpeed = 4.0f;
Vector3 avarageHeading;
Vector3 avaragePosition;
float neighbourDistance = 1.0f;
//public bool startUpdate = false;
bool turning = false;
private Vector3 parentPos;
protected override void FixedUpdate_State()
{
}
protected override void LateUpdate_State()
{
}
protected override void Update_State()
{
//如果距离母物体的距离超过了指定的范围就设置开始掉头为true;
float tempDis2ParentPos = Vector3.Distance(transform.position, parentPos);
turning = (Global_FlockManage.M_Instance.M_SizeArea <= tempDis2ParentPos);
if(turning)
{
Vector3 tempDir = parentPos - transform.position;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(tempDir), rotationSpeed * Time.deltaTime);
moveSpeed = Random.Range(0.5f, 1);
}
else
{
if(Random.Range(0,5)<1)
{
ApplyAIFlockRules();
}
}
transform.Translate(0, 0, Time.deltaTime * moveSpeed);
}
/// <summary>
/// 应用集群规则,包括不碰撞、重叠等
/// </summary>
private void ApplyAIFlockRules()
{
Vector3 tempVcentre = parentPos;
Vector3 tempVavoid = parentPos;
float tempGSpeed = 0.1f;
float tempDis;
int tempGroupSize = 0;
for (int i = 0; i < Global_FlockManage.M_Instance.M_ListShowAllFlockObj.Count; i++)
{
FlockEntity tempAnotherObj = Global_FlockManage.M_Instance.M_ListShowAllFlockObj[i];
//如果是其他物体,那么应该规避它
if (this != tempAnotherObj)
{
tempDis = Vector3.Distance(tempAnotherObj.transform.position, transform.position);
//找到所有小于预定距离的物体,然后计算它们所有的位置与本物体的位置的方向向量
//所有的向量相加就可以知道它最后应该朝什么方向向量走就可以规避所有距离小于指定值的方向
if (tempDis <= neighbourDistance)
{
//计算所有其他范围内鱼的位置,以便计算所有鱼的中心点位置
tempVcentre += tempAnotherObj.transform.position;
tempGroupSize++;
//计算相反的向量,作为规避向量
tempVavoid = tempVavoid + transform.position - tempAnotherObj.transform.position;
tempGSpeed = tempGSpeed + tempAnotherObj.moveSpeed;
}
}
}
if(0<tempGroupSize)
{
tempVcentre = tempVcentre / tempGroupSize + (Global_FlockManage.M_Instance.M_GoalPos - transform.position);
moveSpeed = tempGSpeed / tempGroupSize;
Vector3 tempDir = (tempVcentre + tempVavoid) - transform.position;
if(Vector3.zero!=tempDir)
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(tempDir), rotationSpeed * Time.deltaTime);
}
}
}
public override void Init()
{
base.Init();
parentPos = Global_FlockManage.M_Instance.transform.position;
moveSpeed = Random.Range(1f, 2);
Animation tempAnim = GetComponent<Animation>();
tempAnim[moveAnimName].speed = Random.Range(0f, 1.0f);
}
}
3、最后还有一个我自己写的基类代码BaseGameObj
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class BaseGameObj : MonoBehaviour {
private bool isInitSucceed = false;
// Use this for initialization
void Start()
{
// Init_Start();
}
// Update is called once per frame
void Update()
{
if (!isInitSucceed) return;
Update_State();
}
void LateUpdate()
{
if (!isInitSucceed) return;
LateUpdate_State();
}
void FixedUpdate()
{
if (!isInitSucceed) return;
FixedUpdate_State();
}
/// <summary>
/// 初始化成功之后执行的Update方法
/// </summary>
protected abstract void Update_State();
/// <summary>
/// 初始化成功之后执行的LateUpdate方法
/// </summary>
protected abstract void LateUpdate_State();
/// <summary>
/// 初始化成功之后执行的固定帧率刷新方法
/// </summary>
protected abstract void FixedUpdate_State();
/// <summary>
/// 初始化成功之后执行的开始方法
/// </summary>
public virtual void Init()
{
isInitSucceed = true;
}
}
三、总结
1、真的模型的大小没有考虑,在模型的大小和它们应该规避的最小距离之间没有进行处理
2、运动的随机性比较符合预期