Boids鸟群

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

简介

鸟群生成删除,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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值