Boids 可以理解为类似鸟群的东西,就是多个个体之间的相互作用。在游戏开发中经常会用到。例如 RTS 游戏,控制一个坦克战队,如何保持行进方向的一致性,以及坦克之间互相有一定有间隔,又不会间隔太大,这里就可以使用 Boids 相关的理论来实现。
在众多这方面的文章中,基本上会涉及到三个方面,跟随、分离、聚合。跟随,就是说整个群体有一个行进的大方向。分离,则是个体与个体之间有一定的间隔,不至于发生碰撞。而聚合,就是个体不能离群体太远,不能脱离群体。
对于上面提到的三个方面,简单来说,就是一个力的叠加。
跟随
一个鸟群往哪个方向飞,可以假设有一只领头的鸟,其他的鸟跟随这只鸟的方向。知道领头的鸟的方向,知道自己当前的飞行方向,就可以计算出应该向中个方向施加一个力,可以使自己的方向,偏向于领头的鸟的方向。
分离
分离,是要保证个体之间不要离的太近,不要发生碰撞。先考虑两个物体的情况,假设要使物体 A 远离物体 B,只要从 B,向 A 施加一个推力,就可以将 A 推离 B。那如果 A 要同时和 B 与 C 保持距离呢?一样的,只需要从 B 和 C 分别向 A 施加一个推力,这两个的合力,就是 A 远离 B 和 C 的方向。同理,不管 A 要与多少个物体保持距离,只需要从每个物体出发,向 A 的方向施加一个力,就可以将 A 推开。
上面只考虑了 A 远离其他物体的情况,如果每一个物体都要与其他物体保持距离呢?一样的,只需要从每一个其他物体,向自己的方向施加一个力,这个合力,就是自己运动的方向。
聚合
为了保证个体不脱离群体,还需要一个聚合力。就是将个体自身,推向群体中心的力。
分离的力和聚合的力一定程度上抵消,从而达到个体之间即保持了距离,又保证了每一个体不脱离群体。
下面的代码是我的一个小游戏项目中的,其中只用到了分离和聚合。只要懂了原理,就可以根据具体的情况灵活变通,达到自己想要的效果即可。
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
public class GamePlayFlock : MonoBehaviour
{
public static List<GamePlayFlock> flockList = new List<GamePlayFlock>();
public static void StartAllFlock()
{
for(int i = 0; i < flockList.Count; ++i)
{
flockList[i].StartFlock();
}
}
public float avoidForce;
public float randomForce;
public float toCenterForce;
public float randomFreq;
public Vector3 centerPos;
public float borderDist;
public float avoidDist;
public Vector2 borderRect;
private Vector3 randomVelocity;
private bool started = false;
public void InitFlock(Vector3 centerPos, float xBorderDist,float yBorderDist, float avoidDist)
{
this.centerPos = centerPos;
this.borderDist = xBorderDist;
this.borderRect = new Vector2(xBorderDist, yBorderDist);
this.avoidDist = avoidDist;
flockList.Add(this);
}
private void StartFlock()
{
StartCoroutine(UpdateRandomVelocity());
started = true;
}
// 随机方向的力,这个保证了个体不至于完全一样
private IEnumerator UpdateRandomVelocity()
{
while (true)
{
randomVelocity = Random.insideUnitSphere * randomForce;
randomVelocity.z = 0;
float wait = randomFreq + Random.Range(-randomFreq / 2.0f, randomFreq / 2.0f);
yield return new WaitForSeconds(wait);
}
}
private void Update()
{
if (!started)
{
return;
}
Vector3 avoidVelocity = Vector3.zero;
float dist = 0;
foreach (GamePlayFlock flock in flockList)
{
if (flock.transform != transform)
{
Vector3 otherPos = flock.transform.position;
Vector3 dir = transform.position - otherPos;
dist = dir.magnitude;
if (dist < avoidDist)
{
float f = 1.0f - (dist / avoidDist);
if (dist > 0)
{
avoidVelocity += (dir / dist) * f * avoidForce;
}
}
}
}
Vector3 toCenterDir = centerPos - transform.position;
Vector3 centerVelocity = Vector3.zero;
float xDist = Mathf.Abs(toCenterDir.x);
float yDist = Mathf.Abs(toCenterDir.y);
if(xDist > borderRect.x)
{
float f = xDist / borderRect.x - 1.0f;
centerVelocity.x = (toCenterDir.x / xDist) * f * toCenterForce;
}
if(yDist > borderRect.y)
{
float f = yDist / borderRect.y - 1.0f;
centerVelocity.y = (toCenterDir.y / yDist) * f * toCenterForce;
}
Vector3 velocity = Vector3.zero;
velocity += avoidVelocity;
velocity += randomVelocity;
velocity += centerVelocity;
transform.position += velocity * Time.deltaTime;
}
}