参考自《游戏设计原型与开发》
简介
鸟群生成删除,BoidMgr单例管理所有鸟群,BoidSpawner管理某一个具体鸟群,Boid管理某一只鸟
调用
//基于固定点
BoidMgr.GetIns().Create("firstboid", 20,Vector3.zero, gameObject.transform.position);
//基于可移动
BoidMgr.GetIns().Create("secondboid", Vector3.one*100, gameObject.transform);
BoidMgr
生成有很多参数可选,自由添加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BoidMgr
{
private static BoidMgr _ins;
public static BoidMgr GetIns()
{
if (_ins == null)
_ins = new BoidMgr();
return _ins;
}
private Dictionary<string, BoidSpawner> _spawnerDic = new Dictionary<string, BoidSpawner>();
/// <summary>
/// 鸟群跟随目标
/// </summary>
public void Create(string name, Vector3 initpos, Transform target)
{
Create(name,0,initpos,target);
}
public void Create(string name, int boidsNum, Vector3 initpos, Transform target)
{
BoidSpawner spawner = new GameObject().AddComponent<BoidSpawner>();
spawner.Init(BoidTargetType.Obj, name, boidsNum,initpos, target, Vector3.zero, () => { _spawnerDic.Add(name, spawner); });
}
/// <summary>
/// 鸟群固定点
/// </summary>
public void Create(string name, Vector3 initpos, Vector3 target)
{
Create(name,0,initpos,target);
}
public void Create(string name, int boidsNum, Vector3 initpos, Vector3 target)
{
BoidSpawner spawner = new GameObject().AddComponent<BoidSpawner>();
spawner.Init(BoidTargetType.Point, name, boidsNum, initpos, null, target, () => { _spawnerDic.Add(name, spawner); });
}
/// <summary>
/// 删除
/// </summary>
public void Delete(string name)
{
if (_spawnerDic.Count <= 0)
return;
if (_spawnerDic.ContainsKey(name))
{
BoidSpawner spawner = _spawnerDic[name];
//删除
_spawnerDic.Remove(name);
spawner.Delete();
}
}
public void DeleteAll()
{
if (_spawnerDic.Count <= 0)
return;
foreach (var item in _spawnerDic)
{
_spawnerDic.Remove(item.Key);
item.Value.Delete();
}
_spawnerDic.Clear();
}
/// <summary>
/// 索引
/// </summary>
public BoidSpawner GetSpawner(string name)
{
if (_spawnerDic.ContainsKey(name))
return _spawnerDic[name];
return null;
}
}
BoidSpawner
参数自由添加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class BoidSpawner : MonoBehaviour
{
private Vector3 _targetPos;
private Transform _targetTrans;
private Vector3 _target;
private bool _isTargetTrans;
public string Name { get => gameObject.name; }
public Vector3 TargetPos { get => _targetPos; }
public Transform TargetTrans { get => _targetTrans; }
public Vector3 Target { get => _target; set => _target = value; }
public bool IsTargetTrans { get => _isTargetTrans; set => _isTargetTrans = value; }
//配置参数,调整Boid对象的行为
public int _numBoids = 100; //boid 的个数
public GameObject boidPrefab; //boid 在unity中的预制体
public float spawnRadius = 100f; //实例化 boid 的位置范围
public float spawnVelcoty = 10f; //boid 的速度
public float minVelocity = 0f;
public float maxVelocity = 30f;
public float nearDist = 30f; //判定为附近的 boid 的最小范围值
public float collisionDist = 5f; //判定为最近的 boid 的最小范围值(具有碰撞风险)
public float velocityMatchingAmt = 0.01f; //与 附近的boid 的平均速度 乘数(影响新速度)
public float flockCenteringAmt = 0.15f; //与 附近的boid 的平均三维间距 乘数(影响新速度)
public float collisionAvoidanceAmt = -0.5f; //与 最近的boid 的平均三维间距 乘数(影响新速度)
public float mouseAtrractionAmt = 0.01f; //当 目标距离 过大时,与其间距的 乘数(影响新速度)
public float mouseAvoidanceAmt = 0.75f; //当 目标距离 过小时,与其间距的 乘数(影响新速度)
public float mouseAvoiddanceDsit = 15f;
public float velocityLerpAmt = 0.25f; //线性插值法计算新速度的 乘数
/// <summary>
/// 自定义添加更多参数
/// </summary>
public void Init(BoidTargetType type, string name, int boidsNum, Vector3 initpos, Transform targetTrans, Vector3 targetPos, UnityAction action)
{
_isTargetTrans = (type == BoidTargetType.Point) ? false : true;
gameObject.name = name;
_numBoids = boidsNum;
gameObject.transform.position = initpos;
_targetPos = targetPos;
_targetTrans = targetTrans;
for (int i = 0; i < _numBoids; i++)
{
GameObject obj = Instantiate(Resources.Load<GameObject>("Boid"));
obj.GetComponent<Boid>().InitData(this);
}
action();
}
public void Init(BoidTargetType type, string name, Vector3 initpos, Transform targetTrans, Vector3 targetPos, UnityAction action)
{
Init(type, name, _numBoids, initpos, targetTrans, targetPos, action);
}
public void Delete()
{
StartCoroutine(IDelete());
}
IEnumerator IDelete()
{
yield return null;
foreach (var item in boidsList)
{
item.gameObject.SetActive(false);
}
for (int i = boidsList.Count - 1; i >= 0; i--)
{
Boid boid = boidsList[i];
boidsList.RemoveAt(i);
Destroy(boid);
}
boidsList.Clear();
Destroy(gameObject);
}
private void LateUpdate()
{
_target = (_isTargetTrans) ? _targetTrans.position : _targetPos;
}
/// <summary>
/// 该Spawner管理的所有鸟
/// </summary>
public List<Boid> boidsList = new List<Boid>();
public void AddBoid(Boid boid)
{
boidsList.Add(boid);
}
public List<Boid> GetBoids()
{
return boidsList;
}
}
/// <summary>
/// 固定点还是跟随目标
/// </summary>
public enum BoidTargetType
{
Point,
Obj
}
Boid
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Boid : MonoBehaviour
{
private BoidSpawner _spawnerF;
private List<Boid> boids; //实例化Boid 的表
private Vector3 velocity; //当前速度
private Vector3 newVelocity; //下一帧中的速度
private Vector3 newPosition; //下一帧中的位置
private List<Boid> neighbors; //附近所有的 Boid 的表
private List<Boid> collisionRisks; //距离过近的所有 Boid 的表(具有碰撞风险,需要处理)
private Boid closest; //最近的 Boid
public void InitData(BoidSpawner spawner)
{
_spawnerF = spawner;
_spawnerF.AddBoid(this);
boids = _spawnerF.GetBoids();
Vector3 randPos = Random.insideUnitSphere * _spawnerF.spawnRadius;
//只让Boid在xz平面上移动,并设定起始坐标
randPos.y = 0;
this.transform.position = randPos;
//Random.onUnitSphere 返回 一个半径为1的 球体表面的点
velocity = Random.onUnitSphere;
velocity *= _spawnerF.spawnVelcoty;
//初始化两个List
neighbors = new List<Boid>();
collisionRisks = new List<Boid>();
//让this.transform成为Boid游戏对象的子对象
this.transform.parent = _spawnerF.transform;
给Boid设置一个随机的颜色
//Color randColor = Color.black;
设置颜色的颜色要 较深,非透明
//while (randColor.r + randColor.g + randColor.b < 1.0f)
// randColor = new Color(Random.value, Random.value, Random.value);
渲染 boid
//Renderer[] rends = gameObject.GetComponentsInChildren<Renderer>();
//foreach (Renderer r in rends)
// r.material.color = randColor;
}
private void Update()
{
boids = _spawnerF.GetBoids();
//获取到 当前boid 附近所有的Boids 的表
List<Boid> neighbors = GetNeighbors(this);
//使用当前位置和速度初始化新位置和新速度
newVelocity = velocity;
newPosition = this.transform.position;
//速度匹配
//取得于 当前Boid 的速度接近的 所有邻近Boid对象 的平均速度
Vector3 neighborVel = GetAverageVelocity(neighbors);
//将 新速度 += 邻近boid的平均速度*velocityMatchingAmt
newVelocity += neighborVel * _spawnerF.velocityMatchingAmt;
/*
凝聚向心性:使 当前boid 向 邻近Boid对象 的中心 移动
*/
//取得于 当前Boid 的三位坐标接近的 所有邻近Boid对象 的平均三位间距
Vector3 neighborCenterOffset = GetAveragePosition(neighbors) - this.transform.position;
//将 新速度 += 邻近boid的平均间距*flockCenteringAmt
newVelocity += neighborCenterOffset * _spawnerF.flockCenteringAmt;
/*
排斥性:避免撞到 邻近的Boid
*/
Vector3 dist;
if (collisionRisks.Count > 0) //处理 最近的boid 表
{
//取得 最近的所有boid 的平均位置
Vector3 collisionAveragePos = GetAveragePosition(collisionRisks);
dist = collisionAveragePos - this.transform.position;
//将 新速度 += 与最近boid的平均间距*flockCenteringAmt
newVelocity += dist * _spawnerF.collisionAvoidanceAmt;
}
//跟随鼠标光标:无论距离多远都向鼠标光标移动
dist = _spawnerF.Target - this.transform.position;
//若距离鼠标光标太远,则靠近;反之离开(修改新速度)
if (dist.magnitude > _spawnerF.mouseAvoiddanceDsit)
newVelocity += dist * _spawnerF.mouseAtrractionAmt;
else
newVelocity -= dist.normalized * _spawnerF.mouseAvoidanceAmt;
}
private void LateUpdate()
{
//使用线性插值法
//基于计算出的新速度 进而修改 当前速度
velocity = (1 - _spawnerF.velocityLerpAmt) * velocity + _spawnerF.velocityLerpAmt * newVelocity;
//确保 速度值 在上下限范围内(超过范围就设定为范围值)
if (velocity.magnitude > _spawnerF.maxVelocity)
velocity = velocity.normalized * _spawnerF.maxVelocity;
if (velocity.magnitude < _spawnerF.minVelocity)
velocity = velocity.normalized * _spawnerF.minVelocity;
//确定新位置(附加新方向),相当于1s移动 velocity 的距离
newPosition = this.transform.position + velocity * Time.deltaTime;
//将所有对象限制在XZ平面
//修改当前boid的方向:从原有位置看向新位置newPosition
this.transform.LookAt(newPosition);
//position移动方式,移动到新位置
this.transform.position = newPosition;
}
//查找那些Boid距离当前Boid距离足够近,可以被当作附近对象
private List<Boid> GetNeighbors(Boid boi)
{
float closesDist = float.MaxValue; //最小间距,MaxValue 为浮点数的最大值
Vector3 delta; //当前 boid 与其他某个 boid 的三维间距
float dist; //三位间距转换为的 实数间距
neighbors.Clear(); //清理上次表的数据
collisionRisks.Clear(); //清理上次表的数据
//遍历目前所有的 boid,依据设定的范围值筛选出 附近的boid 与 最近的boid 于各自表中
foreach (Boid b in boids)
{
if (b == boi) //跳过自身
continue;
delta = b.transform.position - boi.transform.position; //遍历到的 b 与当前持有的 boi(都为boid) 的三维间距
dist = delta.magnitude; //实数间距
if (dist < closesDist)
{
closesDist = dist; //更新最小间距
closest = b; //更新最近的 boid 为 b
}
if (dist < _spawnerF.nearDist) //处在附近的 boid 范围
neighbors.Add(b);
if (dist < _spawnerF.collisionDist) //处在最近的 boid 范围(有碰撞风险)
collisionRisks.Add(b);
}
if (neighbors.Count == 0) //若没有其他满足邻近范围的boid,则将自身boid纳入附近的boid表中
neighbors.Add(closest);
return (neighbors);
}
//获取 List<Boid>当中 所有Boid 的平均位置
private Vector3 GetAveragePosition(List<Boid> someBoids)
{
Vector3 sum = Vector3.zero;
foreach (Boid b in someBoids)
sum += b.transform.position;
Vector3 center = sum / someBoids.Count;
return (center);
}
//获取 List<Boid> 当中 所有Boid 的平均速度
private Vector3 GetAverageVelocity(List<Boid> someBoids)
{
Vector3 sum = Vector3.zero;
foreach (Boid b in someBoids)
sum += b.velocity;
Vector3 avg = sum / someBoids.Count;
return (avg);
}
}