3D Game Pr ramming Design(五):与游戏世界交互(对象池)

把核心框架实现了,没有做界面

PS:让我想起来以前用XNA写了半个暑假的简陋版抢滩登陆,老辛苦还丑,现在的可视化引擎比以前的强大太多了

框架是以前写的老框架的延伸,直接看UML图吧


TotalManager,LevelManager,UIManager我前面的文章已经做过一次了,标准的MVC中负责VC的部分,M就是下面的Level_N了。

Unity已经帮我们实现了组件模式,所以我们只需要建一个空物体,然后把EnemyCreate,Shoot,UIController三个脚本挂上去,再制成Prefab,在LevelManager调用即可。

因为这些工具类我都希望他们只会生成出一个,所以都做成了单例模式(多打一行重复代码都是罪孽)。细节上就是继承了一个UnitySingleton泛型,实现如下:

UnitySingleton.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UnitySingleton<T> : MonoBehaviour 
    where T : Component 
    //约束T需要继承Component
{
    //proteced保证了继承的子类可以访问
    //不过也导致同样继承了UnitySingleton的别的子类也可以访问,不过问题不大
    protected static T _instance;

    public static T Instance() {
        if (_instance == null) { 
            //找到我们在场景里已经实例化的实例
            _instance = FindObjectOfType(typeof(T)) as T;
            //如果我们场景里没有实例化,就创建一个空物体增加脚本返回这个实例
            if (_instance == null) {
                GameObject obj = new GameObject();
                obj.name = typeof(T).ToString();
                _instance = obj.AddComponent<T>();
            }
        }
        return _instance;
    }
}

直接继承即可。注意我在泛型里并没有声明构造,需要在子类私有化防止被外部new实例化

public class UIController : UnitySingleton<UIController>{
    private UIController() { }
}

Shoot的逻辑处理没什么好说的,老样子一个raycast解决,当然其实这里还能玩很多东西的,例如不用射线检测,而是做成FreeFPS用子弹来打飞碟(直说了吧,还是抢滩登陆)

Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit; //存储碰撞到的物体的信息
bool isCollider = Physics.Raycast(ray,out hit); //被检测物体要有collider才能检测到
if (isCollider) {
	hit.transform
	hit.collider
	hit.point
}

ray里储存了方向变量,我们可以很方便地得到子弹的射击角度。

不过子弹也好,飞碟也好,都是频繁创建的物体,如果不断的实例化+销毁,一是会让内存碎片化(当然UNITY会帮你处理好这一块的内容),二是对性能的损耗是极高的,因为创建实例的复杂度是O(n)的(算法课没开小差吧?)

这种时候,我们就可以使用一种叫做“对象池”的类解决这个问题(工厂模式的衍生)。我们提前实例好一定数量的对象,使用的时候就拿出来,用完了就塞回对象池,避开了实例化和释放的操作,这时复杂度是O(1)的

对象池存储对象的方法可以任意选择,List,Stack,Dictionary,数组都没有问题,根据自己的需要选择合适的存储方式

我下面给出一个使用Stack(栈)的例子

public class ObjectPool<T> where T : class, new()
{
    private Stack<T> m_objectStack = new Stack<T>();

    public T Get()
    {
        return (m_objectStack.Count == 0) ? new T() : m_objectStack.Pop();
    }

    public void Return(T t)
    {
        m_objectStack.Push(t);
    }
}

很简单的代码,需要对象就调用Get方法,从Stack里拿一个实例去用,Stack里的对象如果已经用完了就当场实例化一个。

对象用完了就Return回来,压回Stack里等待下次使用

这段代码没有提前实例化,需要提前实例化的自己补一个Init方法,Return一定数量的对象实例即可

使用Stack的好处有两个,1.简单,用标准库,两分钟就能写好一个这样的对象池,2.对象池的大小是可拓展的,不会因为越界而报错。缺点也是因为可拓展,导致无法处理内存碎片化的问题(当然,使用UNITY就不用考虑这个了)

注意在多线程里使用对象池需要加上锁,不然无法保证线程安全(Unity依然不需要担心这个问题)

最后Unity有一个叫PoolManager的脚本插件,功能很强大,实际应用中用这个插件就可以满足九成九的需求了


github链接:https://github.com/keven2148/3D-Game-Programming-Design-Lesson-Work/tree/master/Lesson5

版本:Unity2017.3

猜你喜欢

转载自blog.csdn.net/keven2148/article/details/79923384
PR
今日推荐