二. 弹幕框架具体实现

文章目录

  • 摘要
  • 开发思路
  • 具体实现
    • 实体类
    • 对象池
    • 管理器
  • 总结

摘要

  • 本文将会讲解在弹幕框架基本结构下的具体实现,要想阅读本文,建议先阅读前置文章

开发思路

  • 在编写具体代码前,要先思考,在UnityC#开发中,我们希望整个框架的具体逻辑是如何的。
  • 虽然在前一文章中已经阐述了诸如接口方法、对象池、以及三层结构。但它们并不具备完整的解决方案,我们仍要思考完整的解决方案应该是什么样子的。一个很好的方法就是提要求。
  • 对于一切运动实体,我们希望它们自己运动,自己判断是否应该结束运动并回到池子中。这也就意味着我们希望与运动判断相关的逻辑都在实体内部实现,并且对象池的入列方法交给实体自己来执行,即“别人从池子中拿实体,实体自己将自身还到池子中”。
  • 对于对象池,我们希望在UnityEditor窗口中设置好后,就能在Runtime下生效,我们只需要通过API去使用就可以了。这也就意味着对象池在初始化的时候能够自动的加载对象,并且将自己注册到管理类的字典中。
  • 在获取对象的时候,我们希望各种复杂的统一细节交给管理类,我们只要申请,然后得到对象,除了做一些该对象特有的设置外,只需要通过调用一个方法,就能让对象开始运动。

具体实现

  • 在思考了具体需求后,我们就可以依次来实现三层结构各部分了。
  • 需要说明的是,在实际开发中必然不可能如此顺利的逐一实现,而是会在测试中反复横跳,进行取舍,最终得出设计的相对优解。

实体类

  • 先来看看实体类的基本结构:
 	public abstract class EntityBase : MonoBehaviour, ShotBaseInter
    {
        /// 
        /// 入列方法委托
        /// 
        private Action<ShotBaseInter> enqueue { set; get; }

        /// 
        /// 构造器
        /// 
        public EntityBase()
        {
            enqueue = null;
        }

        /// 
        /// Unity回调方法,当对象从对象池中被调出并激活后自动执行,
        /// 是实体执行方法的入口
        /// 
        protected virtual void OnEnable()
        {
            OnShotBehaviour();
        }

        /// 
        /// 抽象方法,是被调用的主体,所有实体行为都应该在这个方法体内
        /// 
        public abstract void OnShotBehaviour();

        /// 
        /// 开始方法,如果已经开始则不能通过重新调用来重新开始
        /// 
        public void START()
        {
            gameObject.SetActive(true);    
        }

        /// 
        /// 停止方法
        /// 
        public void STOP()
        {
            StopAllCoroutines();
            gameObject.SetActive(false);
            if (enqueue != null)
                enqueue.Invoke(this);
        }

        /// 
        /// 判断当前对象是否激活运行
        /// 
        /// 
        public bool IsRunning()
        {
            return gameObject.activeSelf;
        }

        /// 
        /// 注册回收方法,只有在初次注册时才有效,防止因后期替换导致的回收错误
        /// 
        public void RegisterEnqueueMethod(Action<ShotBaseInter> e)
        {
            if (enqueue == null)
                enqueue = e;
        }
    }
  • 可以看出该类是一个继承了Monobehaviour和弹幕实体接口的抽象类。之所以定义为抽象类,是因为它并没有提供完整的解决方案,而是提供了完整解决方案的执行模式。具体的执行逻辑需要根据需求去派生实现。
  • 在代码中声明了对象池返回对象的委托变量,因为弹幕实体本身并不知道自己隶属于哪一个对象池,因此需要用一个委托来接收返回方法,而这个返回方法则是在对象池初始化弹幕的时候进行分配的。
  • 因为在这里我希望利用对象的激活状态来控制对象的运动执行,因此我在OnEnable方法中调用了接口方法,并且定义了两个方法:Start和Stop来控制对象是否运动

对象池

  • 首先实例化对象需要预制体,这里我没有采用AB包或者Resources加载,而是通过直接绑定预制体的方式。
  • 我希望对象池能在Awake阶段进行对象初始化以及对象池注册,并且因为不同类型的实体对数目的需求不同,我希望能够自定义初始化的数量。
  • 根据上述需求,我实现的对象池如下所示:
    public class EntitiesPool : MonoBehaviour, ShotPoolInter
    {
        /// 
        /// 弹幕池队列
        /// 
        protected Queue<ShotBaseInter> queue { set; get; }

        /// 
        /// 生成的预制体
        /// 
        public GameObject entityPrefeb;

        /// 
        /// 子弹池注册到Manager时的键值
        /// 
        public string PoolKey;

        /// 
        /// 初始化数量
        /// 
        public int initialNum;

        /// 
        /// 后期单次补充数量
        /// 
        public int supplyNum;

        /// 
        /// 实例化时执行的各种初始化
        /// 
        private void Awake()
        {
            queue = new Queue<ShotBaseInter>();
            EntitiesManager.Instance.RegisterPool(PoolKey, this);
            Initial();
        }

        /// 
        /// 初始化函数,负责生成指定数量的预制体的实例
        /// 
        public void Initial()
        {
            for (int i = 0; i < initialNum; i++)
            {
                GameObject item = Instantiate(entityPrefeb, transform);
                item.GetComponent<EntityBase>().RegisterEnqueueMethod(Queuing);
                queue.Enqueue(item.GetComponent<ShotBaseInter>());
            }
        }

        /// 
        /// 补充函数,当队列中实例不足且需要实例时生成指定数量实例以满足需求
        /// 
        public void Supply()
        {
            Debug.Log($"弹幕池[{PoolKey}]进行补充,补充时间:{Time.time}");
            for (int i = 0; i < supplyNum; i++)
            {
                GameObject item = Instantiate(entityPrefeb, transform);
                item.GetComponent<EntityBase>().RegisterEnqueueMethod(Queuing);
                queue.Enqueue(item.GetComponent<ShotBaseInter>());
            }
        }

        /// 
        /// 出列方法
        /// 
        /// 返回一个子弹对象
        public ShotBaseInter Queued()
        {
            if (queue == null)
                return null;
            if (queue.Count <= 0)
                Supply();

            return queue.Dequeue();
        }

        /// 
        /// 入列方法
        /// 
        /// 入列的实例
        public void Queuing(ShotBaseInter shot)
        {
            if (queue == null)
                return;
            queue.Enqueue(shot);
        }
    }

管理器

  • 管理器本身比较简单,它的实现就是简单的对字典的存取,这和观察者模式等的设计是类似的。因此这里就不做多赘述
    public class EntitiesManager : ShotManagerBase
    {
        /// 
        /// 单例模式
        /// 
        public static EntitiesManager Instance { get { if (instance == null) { instance = new EntitiesManager(); } return instance; } }
        private static EntitiesManager instance;

        /// 
        /// 对象池
        /// 以字典形式存储每一个子弹池实例
        /// 
        protected override Dictionary<string, ShotPoolInter> pools { get; set; }

        /// 
        /// 构造器,初始化对象池字典
        /// 
        public EntitiesManager()
        {
            pools = new Dictionary<string, ShotPoolInter>();
        }

        /// 
        /// 调用一个子弹。
        /// 
        public override ShotBaseInter CallPool(string poolName)
        {
            if (Instance.pools.ContainsKey(poolName))
            {
                var shot = Instance.pools[poolName].Queued();
                return shot;
            }
            return null;
        }

        /// 
        /// 取消注册进来的子弹池
        /// 一般情况下很少使用,但作为一个池子,还是准备了这个基本功能
        /// 
        public override bool CancelPool(string poolName)
        {
            if (Instance.pools.ContainsKey(poolName))
            {
                Instance.pools.Remove(poolName);
                return true;
            }
            return false;
        }

        /// 
        /// 注册子弹池
        /// 
        public override bool RegisterPool(string poolName, ShotPoolInter pool)
        {
            if (Instance.pools.ContainsKey(poolName))
                return false;
            else
                Instance.pools.Add(poolName, pool);
            return true;
        }
    }

总结

  • 以上是我个人的具体实现,实现方法还有很多,可以根据个人的理解进行实现,我这里仅仅作为参考。
  • 目前为止弹幕框架已经构建完成。这个弹幕框架是一个非常简单的设计,因此体量非常的小,但适合新手前期做出一些成品来。
  • 虽说如此,但仅仅有框架是不足以立即应用的。因此在下一篇文章中,将会就该弹幕框架来讲解基本弹幕的实现,以作为弹幕框架使用的引导。

你可能感兴趣的:(#,弹幕框架,unity,经验分享)