理解 Boids

Boids 可以理解为类似鸟群的东西,就是多个个体之间的相互作用。在游戏开发中经常会用到。例如 RTS 游戏,控制一个坦克战队,如何保持行进方向的一致性,以及坦克之间互相有一定有间隔,又不会间隔太大,这里就可以使用 Boids 相关的理论来实现。

在众多这方面的文章中,基本上会涉及到三个方面,跟随分离聚合。跟随,就是说整个群体有一个行进的大方向。分离,则是个体与个体之间有一定的间隔,不至于发生碰撞。而聚合,就是个体不能离群体太远,不能脱离群体。

对于上面提到的三个方面,简单来说,就是一个力的叠加。

跟随

一个鸟群往哪个方向飞,可以假设有一只领头的鸟,其他的鸟跟随这只鸟的方向。知道领头的鸟的方向,知道自己当前的飞行方向,就可以计算出应该向中个方向施加一个力,可以使自己的方向,偏向于领头的鸟的方向。

分离

分离,是要保证个体之间不要离的太近,不要发生碰撞。先考虑两个物体的情况,假设要使物体 A 远离物体 B,只要从 B,向 A 施加一个推力,就可以将 A 推离 B。那如果 A 要同时和 B 与 C 保持距离呢?一样的,只需要从 B 和 C 分别向 A 施加一个推力,这两个的合力,就是 A 远离 B 和 C 的方向。同理,不管 A 要与多少个物体保持距离,只需要从每个物体出发,向 A 的方向施加一个力,就可以将 A 推开。

上面只考虑了 A 远离其他物体的情况,如果每一个物体都要与其他物体保持距离呢?一样的,只需要从每一个其他物体,向自己的方向施加一个力,这个合力,就是自己运动的方向。

聚合

为了保证个体不脱离群体,还需要一个聚合力。就是将个体自身,推向群体中心的力。

分离的力和聚合的力一定程度上抵消,从而达到个体之间即保持了距离,又保证了每一个体不脱离群体。

下面的代码是我的一个小游戏项目中的,其中只用到了分离和聚合。只要懂了原理,就可以根据具体的情况灵活变通,达到自己想要的效果即可。

Boids

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

Author: Moeif Studio

Permalink: http://blog.moeif.com/2020/11/30/understanding-boids/

任何技术问题,可加微信交流,微信: ifloop

搜索并关注微信公众号 [ 萌一小栈 ] 可及时订阅最新技术文章

Comments