游戏中的设计模式之优化模式 笔记

原文来自:https://github.com/TYJia/GameDesignPattern_U3D_Version

优化模式中包含了四种不同的模式,分别是:

这里实现了对象池和空间分区,四种模式的说明详见笔记部分。

基本思想:

  • 数据局部性
    • CPU缓存读写速度大于内存读写速度,所以要尽量减少缓存不命中(CPU从内存读取信息)的次数
    • 用连续队列代替指针的不断跳转
    • 不过此模式会让代码更复杂,并伤害其灵活性
  • 脏标识模式
    • 需要结果时才去执行工作——避免不必要的计算或传输开销
    • 一种是被动状态变化时才计算,否则使用缓存;另一种是主动变化标识,否则不执行(例如存盘)
  • 对象池模式
    • 对象池就像一包不同颜色的水彩笔,当我们使用时就拿出来,不用时就放回去——而不是使用时就买一只,不用时就扔进垃圾桶
    • 可以减少内存碎片,减少实例化与回收对象所面临的开销
  • 空间分区
    • 建立细分空间用于存储数据(对象),可以帮助告诉定位对象,降低算法复杂度
    • 例如邮局寄信,如果只按身份证号邮寄,那就麻烦了,每封信平均要拿给几亿人确认是否是ta的;但是按空间分区后,就简单了——省份、城市、街道、小区、楼栋、单元、房号,于是很快就能定位到个人。

 

对象池 项目说明

Game模式下,点击鼠标可以设置多个目标点,Player(方块)会依次移向目标点;

  • 层级列表中会显示对象池中的所有目标点,若未激活列表不为空,则先使用未激活的目标点
  • 若未激活列表为空,则实例化目标点并加入对象池

ObjPool

对象池类,包含了已激活列表和未激活列表,可用方法:

  • AddObj:添加对象
  • GetObj:获取对象,若无法获取则返回null
  • DisableObj:用以替代销毁
  • RemoveObj:彻底删除单个对象
  • CleanPool:清空对象池

ObjPool.cs

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

public class ObjPool {

    public List<GameObject> ActiveObjs;
    public List<GameObject> InactiveObjs;

    public ObjPool()
    {
        ActiveObjs = new List<GameObject>();
        InactiveObjs = new List<GameObject>();
    }

    public void AddObj(GameObject obj)
    {
        ActiveObjs.Add(obj);
    }

    public object GetObj()
    {
        if (InactiveObjs.Count > 0)
        {
            GameObject obj = InactiveObjs[0];
            ActiveObjs.Add(obj);
            InactiveObjs.Remove(obj);
            obj.SetActive(true);
            return obj;
        }
        return null;
    }

    public bool DisableObj(GameObject obj)
    {
        if (ActiveObjs.Contains(obj))
        {
            obj.SetActive(false);
            ActiveObjs.Remove(obj);
            InactiveObjs.Add(obj);
            return true;
        }
        return false;
    }

    public bool RemoveObj(GameObject obj)
    {
        if (ActiveObjs.Contains(obj))
        {
            ActiveObjs.Remove(obj);
            GameObject.Destroy(obj);
            return true;
        }
        else if (InactiveObjs.Contains(obj))
        {
            InactiveObjs.Remove(obj);
            GameObject.Destroy(obj);
            return true;
        }
        return false;
    }

    public void CleanPool()
    {
        for (int i = 0; i < ActiveObjs.Count; i++)
        {
            GameObject.Destroy(ActiveObjs[i]);
        }
        for (int i = 0; i < ActiveObjs.Count; i++)
        {
            GameObject.Destroy(InactiveObjs[i]);
        }
        ActiveObjs = new List<GameObject>();
        InactiveObjs = new List<GameObject>();
    }
}

EventQueueObjPoolVersion.cs

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

public class EventQueueObjPoolVersion : MonoBehaviour
{
    public GameObject PosIndicator;
    private Queue<Transform> mDestinationQueue;
    private Transform Destination;
    public float Speed = 1;
    private ObjPool mPool = new ObjPool(); 

    void Start()
    {
        MouseInputManager.Instance.OnMouseClick += AddDestination;
        mDestinationQueue = new Queue<Transform>();
    }

    private void AddDestination()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Vector3 destination = ray.GetPoint(10);

        GameObject desIndi = (GameObject)mPool.GetObj();
        if (desIndi == null)
        {
            desIndi = GameObject.Instantiate(PosIndicator);
            mPool.AddObj(desIndi);
        }

        desIndi.transform.position = destination;
        mDestinationQueue.Enqueue(desIndi.transform);
    }

    void Update()
    {
        if (Destination == null && mDestinationQueue.Count > 0)
        {
            Destination = mDestinationQueue.Dequeue();
        }
        if (Destination != null)
        {
            float distance = Vector3.Distance(transform.position, Destination.position);
            transform.position = Vector3.Lerp(transform.position, Destination.position,Mathf.Clamp01(Time.deltaTime * Speed / distance));
            if (distance < 0.01f)
            {
                mPool.DisableObj(Destination.gameObject);
                Destination = null;
            }
        }
    }
}

 

空间分区 项目说明

随机生成500个点,临近点之间可以连线

Game模式下,点选OctTreeManager

  • LineGenerator

    • 勾选UseOctTree,使用空间八叉树计算距离,反之用for循环500*500计算距离

      • 静态模式下,使用八叉树计算FPS能达到 86
      • 普通for循环,FPS只有 26

       

    • 勾选Animated,点会发生移动,这时会动态更新八叉树内容,帧率比静态降低,但同样高于普通for循环

  • OctTree

    • 勾选Show,则显示八叉树,反之隐藏

     

 

OtcTree

八叉树类

  • 属性
    • MaxNum,单个节点最多能承受的点数
    • Show,显示八叉树
  • 公开方法
    • ShowBox,显示八叉树
    • GenerateTree,生成八叉树
    • FindCloset,寻找最近的点
    • UpdateTree,更新八叉树内容

 

LineGenerator

生成随机点,并在相近点之间连线

  • CommonMethod,普通for循环计算点之间的距离,复杂度O(n²)
  • OctTreeMethod,八叉树计算距离,复杂度下降到O(n)

LineGenerator.cs

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

public class LineGenerator : MonoBehaviour
{
    public bool UseOctTree = true;
    public bool Animated = false;
    private OctTree OT;
    public GameObject Point;
    public int PointNum = 1000;
    public float SpaceScale = 1;

    private List<Transform> mPointList;
    private List<LineRenderer> mLineList;

    // Use this for initialization
    void Start()
    {
        mPointList = new List<Transform>();
        mLineList = new List<LineRenderer>();
        OT = FindObjectOfType<OctTree>();
        for (int i = 0; i < PointNum; i++)
        {
            mPointList.Add(GameObject.Instantiate(Point, new Vector3(RandomValue(SpaceScale), 
                                                                    RandomValue(SpaceScale), 
                                                                    RandomValue(SpaceScale)), Quaternion.identity).transform);
            mLineList.Add(mPointList[i].GetComponent<LineRenderer>());
        }
        OT.GenerateTree(mPointList);
    }

    private float RandomValue(float value)
    {
        return Random.Range(-value, value);
    }

    // Update is called once per frame
    void Update()
    {
        if (Animated)
        {
            for (int i = 0; i < PointNum; i++)
            {
                mPointList[i].position += new Vector3(RandomValue(SpaceScale / 100),
                                                    RandomValue(SpaceScale / 100),
                                                    RandomValue(SpaceScale / 100));

            }
        }

        if (UseOctTree)
        {
            OctTreeMethod();
        }
        else
        {
            CommonMethod();
        }
    }

    private void OctTreeMethod()
    {
        if (Animated)
        {
            OT.UpdateTree(mPointList);
        }
        for (int i = 0; i < PointNum; i++)
        {
            mLineList[i].SetPosition(0, mPointList[i].position);
            mLineList[i].SetPosition(1, OT.FindCloset(mPointList[i]).position);
        }
    }

    private void CommonMethod()
    {
        for (int i = 0; i < PointNum; i++)
        {
            float min = float.MaxValue;
            Vector3 closet = Vector3.zero;
            for (int j = 0; j < PointNum; j++)
            {
                if (i != j)
                {
                    float dis = Vector3.Distance(mPointList[i].position, mPointList[j].position);
                    if (dis < min)
                    {
                        min = dis;
                        closet = mPointList[j].position;
                    }
                }
            }
            mLineList[i].SetPosition(0, mPointList[i].position);
            mLineList[i].SetPosition(1, closet);
        }
    }
}

OctTree.cs

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

public class OctTree : MonoBehaviour
{
    public TreeNode TheTree;
    [Range(1, 1000)]
    public int MaxNum = 2;
    private float Extents = 0.01f;
    public bool Show = true;
    private bool mShow;
    private Dictionary<Transform, TreeNode> trDic;

    public GameObject SpaceBoxPrefab;

    private void Update()
    {
        if (Show != mShow)
        {
            mShow = Show;
            SpaceBoxPrefab.SetActive(Show);
            ShowBox(TheTree, Show);
        }
    }

    private void ShowBox(TreeNode theTree, bool show)
    {
        theTree.SpaceBox.gameObject.SetActive(show);
        if (theTree.Children != null)
        {
            for (int i = 0; i < 8; i++)
            {
                ShowBox(theTree.Children[i], show);
            }
        }
    }

    public class TreeNode
    {
        public TreeNode Parent = null;
        public TreeNode[] Children;
        public Transform SpaceBox;
        public Bounds BoxBounds;
        public List<Transform> Objs;
        public Vector3 BorderMin, BorderMax;
        public TreeNode()
        {
            Objs = new List<Transform>();
        }
        public TreeNode(TreeNode parent)
        {
            Objs = new List<Transform>();
            Parent = parent;
        }
        public void GenerateChildren()
        {
            Children = new TreeNode[8];
            for (int i = 0; i < 8; i++)
            {
                Children[i] = new TreeNode(this);
            }
        }

    }

    private void Awake()
    {
        TheTree = new TreeNode();
        trDic = new Dictionary<Transform, TreeNode>();
        SpaceBoxPrefab.SetActive(Show);
    }

    public void UpdateTree(List<Transform> objs)
    {
        for (int i = 0; i < objs.Count; i++)
        {
            UpdateObj(objs[i]);
        }
    }

    private void UpdateObj(Transform tr)
    {
        if (!trDic[tr].BoxBounds.Contains(tr.position))
        {
            trDic[tr].Objs.Remove(tr);
            FindInParent(trDic[tr], tr);
        }
    }

    private void FindInParent(TreeNode tn, Transform tr)
    {
        if (tn.Parent != null)
        {
            if (tn.Parent.BoxBounds.Contains(tr.position))
            {
                trDic[tr] = tn.Parent;
                FindInChildren(tn.Parent, tr);
            }
            else
            {
                tn.Parent.Objs.Remove(tr);
                FindInParent(tn.Parent, tr);
            }
        }
        else
        {
            TheTree.Objs.Add(tr);
            trDic[tr] = TheTree;
        }
    }

    private void FindInChildren(TreeNode tn, Transform tr)
    {
        if (tn.Children != null)
        {
            for (int i = 0; i < 8; i++)
            {
                if (tn.Children[i].BoxBounds.Contains(tr.position))
                {
                    if (!tn.Children[i].Objs.Contains(tr))
                    {
                        tn.Children[i].Objs.Add(tr);
                    }
                    trDic[tr] = tn.Children[i];
                    FindInChildren(tn.Children[i], tr);
                }
            }
        }
    }

    void NodeInit(TreeNode tn, Transform spaceBox, List<Transform> objs)
    {
        tn.SpaceBox = spaceBox;
        tn.BoxBounds.center = spaceBox.position;
        tn.BoxBounds.extents = spaceBox.lossyScale;
        tn.BorderMin = spaceBox.position - spaceBox.lossyScale * 0.5f;
        tn.BorderMax = spaceBox.position + spaceBox.lossyScale * 0.5f;

        for (int i = 0; i < objs.Count; i++)
        {
            if (tn.BoxBounds.Contains(objs[i].position))
            {
                tn.Objs.Add(objs[i]);
                if (trDic.ContainsKey(objs[i]))
                {
                    trDic[objs[i]] = tn;
                }
                else
                {
                    trDic.Add(objs[i], tn);
                }
                //objs.Remove(objs[i]);
                //i--;
            }
        }
    }

    public void GenerateTree(List<Transform> objs)
    {
        GenerateRoot(objs);
        CutSpace(TheTree);
    }

    public Transform FindCloset(Transform tr)
    {
        return FindCloset(trDic[tr], tr);
    }

    private Transform FindCloset(TreeNode treeNode, Transform tr)
    {
        TreeNode tn = TheTree;
        if (treeNode.Parent != null)
        {
            tn = treeNode.Parent;
        }
        float min = float.MaxValue;
        Transform closet = tr;
        for (int j = 0; j < tn.Objs.Count; j++)
        {
            if (tr != tn.Objs[j])
            {
                float dis = Vector3.Distance(tr.position, tn.Objs[j].position);
                if (dis < min)
                {
                    min = dis;
                    closet = tn.Objs[j];
                }
            }
        }
        return (closet);
    }

    private void CutSpace(TreeNode theTree)
    {
        if (theTree.Objs.Count > MaxNum)
        {
            Vector3 borderMin, borderMax;
            Vector3 halfBorder = (theTree.BorderMax - theTree.BorderMin) * 0.5f;

            theTree.GenerateChildren();

            for (int x = 0; x < 2; x++)
            {
                for (int y = 0; y < 2; y++)
                {
                    for (int z = 0; z < 2; z++)
                    {
                        int index = x * 4 + y * 2 + z;

                        //Debug.Log(index);

                        Transform spaceBox = Instantiate(SpaceBoxPrefab).transform;

                        borderMin.x = theTree.BorderMin.x + halfBorder.x * x;
                        borderMin.y = theTree.BorderMin.y + halfBorder.y * y;
                        borderMin.z = theTree.BorderMin.z + halfBorder.z * z;
                        borderMax.x = theTree.BorderMax.x - halfBorder.x * (1 - x);
                        borderMax.y = theTree.BorderMax.y - halfBorder.y * (1 - y);
                        borderMax.z = theTree.BorderMax.z - halfBorder.z * (1 - z);


                        spaceBox.position = (borderMin + borderMax) * 0.5f;
                        spaceBox.localScale = borderMax - borderMin;

                        spaceBox.SetParent(theTree.SpaceBox);

                        NodeInit(theTree.Children[index], spaceBox, theTree.Objs);
                    }
                }
            }
            for (int i = 0; i < 8; i++)
            {
                CutSpace(theTree.Children[i]);
            }
        }
    }

    private void GenerateRoot(List<Transform> objs)
    {
        Vector3 borderMin, borderMax;

        if (objs.Count > 0)
        {
            borderMin = borderMax = objs[0].position;
            for (int i = 1; i < objs.Count; i++)
            {
                borderMin.x = Min(objs[i].position.x - Extents, borderMin.x);
                borderMin.y = Min(objs[i].position.y - Extents, borderMin.y);
                borderMin.z = Min(objs[i].position.z - Extents, borderMin.z);
                borderMax.x = Max(objs[i].position.x + Extents, borderMax.x);
                borderMax.y = Max(objs[i].position.y + Extents, borderMax.y);
                borderMax.z = Max(objs[i].position.z + Extents, borderMax.z);
            }
            Transform spaceBox = Instantiate(SpaceBoxPrefab).transform;
            if (borderMin != borderMax)
            {
                spaceBox.position = (borderMin + borderMax) * 0.5f;
                spaceBox.localScale = borderMax - borderMin;
            }
            else
            {
                spaceBox.position = borderMin;
                spaceBox.localScale = Vector3.one * 2;
            }
            spaceBox.SetParent(transform);
            NodeInit(TheTree, spaceBox, objs);
        }
    }

    private static float Min(float a, float b)
    {
        return a < b ? a : b;
    }

    private static float Max(float a, float b)
    {
        return a > b ? a : b;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值