Boids鸟群

it2025-09-22  9

参考自《游戏设计原型与开发》

简介

鸟群生成删除,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); } }
最新回复(0)