原文来自: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;
}
}