固定视角的游戏有个特点就是摄像机的角度是一致的,不会有变化。
但因为我们的某款游戏在游戏过程中会有大量的粒子特效出现,overdraw会非常高,drawcall数量同样也会很高。
这样的话在手机上会由于带宽问题而产生消耗过大的问题。如果是比较低端的机型还可能会导致卡死等严重的情况。
我们要避免这样的情况,除了上一章说的特效rt化的方式(只能解决固定的效果),我们还得控制特效的数量。
在固定视角下我们可以知道角度不会变的,这个时候我们可以考虑用屏幕空间的区域特效展示限制来控制特效的数量。
也就是说屏幕空间下分格子,每个格子保存有一定数量的特效池。超过了就不让显示,让他在当前屏幕数量少了的时候再显示。
总体规则是:
1.屏幕等分格子。
2.每次产生特效,利用二分查找找到确定的格子,存入到指定的格子缓存池内。
3.格子缓存池达到了上限,再来特效则比较优先级,把优先级低的特效放到hide的layer内(不让摄像机渲染)。放入隐藏特效池子里。
4.间隔一段时间查看特效播放完成,播放完成则把对应的对象池内的内容置空。
5.间隔一段时间查看是否有空余的格子缓存,隐藏特效池子的特效是否播放完成?没播放完成的特效则让他从hide到可见的layer内。
大体上是上面的步骤。
下面贴出关键代码:
MapTileObject是对象的基础类:
using UnityEngine;
public class MapTileObject
{
public MapTileObject(Transform trans, int priority)
{
ObjectTransform = trans;
Priority = priority;
}
public Transform ObjectTransform;
public int Priority = 0;
}
MapTile是一个池子的基础结构:
using System.Collections.Generic;
using UnityEngine;
public class MapTile where T : MapTileObject
{
public Rect TileRect;
public T[] ObjectList = new T[MapTileMgr.TileMapCount];
}
MapTileMgr是统一的格子管理与池子管理,包括二分搜索都再这里:
using UnityEngine;
using System.Collections.Generic;
using System;
using Game;
///
/// by llsansun
/// 控制屏幕上每个区域的对象显示数量,不让他同一区域数量太多
/// 例如粒子如果一个区域数量太多,overdraw会很高
/// 应用场景
/// 1.并且我们假设这个区域数量太多,多余的TileMapCount之外的数量对显示影响不大时可以用
/// 2.对不同性能的机型控制显示数量时可以用
/// 3.不想同区域overdraw太多时可以用,比如有些显示的物体设置了tranparent的渲染,但是在这个区域内下面的物体不影响显示时可以用。
/// 总之就是屏幕区域的对象显示限制
///
public class MapTileMgr : MonoBehaviour
{
struct sHideObject
{
public int mDefaultLayer;
public MapTileObject mMapTileObject;
}
///
/// 主摄像机,如果有需要可以改
///
public Camera mMainCamera = null;
///
/// 一个格子的宽度
///
public int TileWidth = 100;
///
/// 一个格子的高度
///
public int TileHeight = 100;
///
/// 每个格子的数量上限
///
public static int TileMapCount = 8;
int currentFrame = 0;
///
/// 检查特效是否能显示的时间
///
public int CheckInternalFrame = 5;
private int widthCount;
private int heightCount;
private List mMapTiles = new List();
///
/// 隐藏对象判断的最大数量
///
public static int TotalHideObjectNumber = 150;
//暂时隐藏的对象
private sHideObject[] mHideObjects = new sHideObject[TotalHideObjectNumber];
private int mHideLayer;
private int mShowLayer;
///
/// 创建屏幕格子
///
public void Start()
{
mHideLayer = LayerMask.NameToLayer("Hide");
mShowLayer = LayerMask.NameToLayer("TransparentFX");
widthCount = (Screen.width / TileWidth) + 1;//额外多一个,避免边缘问题
heightCount = (Screen.height / TileHeight) + 1;//额外多一个,避免边缘问题
for (int j = 0; j < heightCount; j++)
{
for (int i = 0; i < widthCount; i++)
{
MapTile tile = new MapTile();
Vector2 pos = new Vector2(i * TileWidth, j * TileWidth);
Vector2 size = new Vector2(TileWidth + 2, TileWidth + 2);//这里预防刚好踩到中间无格子地带
tile.TileRect = new Rect(pos, size);
mMapTiles.Add(tile);
}
}
}
///
/// 放入一个对象到格子池,没有回调则默认把他移到很远的地方
///
///
///
public virtual void AddToMap(Transform go)
{
if (go == null)
{
return;
}
MapTileObject mapObj = new MapTileObject(go.transform, 100);
AddToMap(mapObj, null);
}
///
/// 放入一个对象到格子池,没有回调则默认把他移到很远的地方
///
///
/// 返回哪个对象需要移除
public virtual void AddToMap(MapTileObject go)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
AddToMap(go, null);
}
///
/// 放入一个对象到格子池,这个版本不需要关注优先级
///
///
///
public virtual void AddToMap(Transform go, Action OverflowCallBack)
{
if (go == null)
{
return;
}
MapTileObject mapObj = new MapTileObject(go.transform, 100);
AddToMap(mapObj, OverflowCallBack);
}
///
/// 放入一个对象到格子池,这个版本通过MapTileObject的Priority控制优先级
///
///
/// 返回哪个对象需要移除
public virtual void AddToMap(MapTileObject go, Action OverflowCallBack)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
if (mMainCamera == null)
{
mMainCamera = Camera.main;
}
Vector3 parPos = go.ObjectTransform.position;
Vector3 screenPos = mMainCamera.WorldToScreenPoint(parPos);
MapTile mapTile = FindMapTile(screenPos);
if (mapTile != null)
HandleGO(mapTile, go, OverflowCallBack);
}
void Update()
{
currentFrame++;
if (currentFrame >= CheckInternalFrame)
{
for (int i = mHideObjects.Length - 1; i >= 0; i--)
{
//被隐藏了或者layer已经不是隐藏层了则认为他被释放了
if (mHideObjects[i].mMapTileObject == null ||
mHideObjects[i].mMapTileObject.ObjectTransform == null ||
!mHideObjects[i].mMapTileObject.ObjectTransform.gameObject.activeSelf ||
mHideObjects[i].mMapTileObject.ObjectTransform.gameObject.layer != mHideLayer)
{
mHideObjects[i].mMapTileObject = null;
continue;
}
AddToMap(mHideObjects[i].mMapTileObject);
}
currentFrame = 0;
}
}
///
/// 处理显示
///
///
///
///
private void HandleGO(MapTile mapTile, MapTileObject go, Action OverflowCallBack)
{
if (mapTile != null)
{
int currentHaveActiveObject = 0;
int noUseIndex = -1;
int cuPriority = int.MaxValue;
for (int i = TileMapCount - 1; i >= 0; i--)
{
var obj = mapTile.ObjectList[i];
if (obj == null ||
obj.ObjectTransform == null
|| !obj.ObjectTransform.gameObject.activeSelf)
{
noUseIndex = i;
break;
}
else
{
currentHaveActiveObject++;
if (obj.Priority < cuPriority)
{
cuPriority = obj.Priority;
noUseIndex = i;
}
}
}
//如果上面都没赋值给他,就容错,随机一个
if (noUseIndex == -1)
{
noUseIndex = UnityEngine.Random.Range(0, TileMapCount - 1);
}
if (currentHaveActiveObject >= TileMapCount)
{
AddToHideObjects(mapTile.ObjectList[noUseIndex]);
if (OverflowCallBack != null)
{
OverflowCallBack(mapTile.ObjectList[noUseIndex]);
}
else
{
if (mapTile.ObjectList[noUseIndex].ObjectTransform != null)
{
SetLayer(mapTile.ObjectList[noUseIndex].ObjectTransform, true);
}
}
}
SetLayer(go.ObjectTransform.transform, false);
mapTile.ObjectList[noUseIndex] = go;
}
}
private void AddToHideObjects(MapTileObject obj)
{
int noUseIndex = -1;
for (int i = 0; i < mHideObjects.Length; i++)
{
if (mHideObjects[i].mMapTileObject == obj)
{
return;
}
if (mHideObjects[i].mMapTileObject == null ||
mHideObjects[i].mMapTileObject.ObjectTransform == null)
{
//找到没在用的
noUseIndex = i;
}
}
//如果全都用上则不再管理该特效
if (noUseIndex == -1)
{
return;
}
if (obj == null || obj.ObjectTransform == null)
{
return;
}
sHideObject hideObject;
hideObject.mDefaultLayer = obj.ObjectTransform.gameObject.layer;
hideObject.mMapTileObject = obj;
mHideObjects[noUseIndex] = hideObject;
}
private void SetLayer(Transform objectTransform, bool isHide)
{
var layer = isHide ? mHideLayer : mShowLayer;
if (objectTransform.gameObject.layer == layer)
return;
GameUtils.SetTransformLayer(objectTransform, layer);
}
///
/// 横向纵向二分查找格子
///
///
///
private MapTile FindMapTile(Vector3 screenPos)
{
if (screenPos.x < 0 || screenPos.y < 0 ||
screenPos.x >= Screen.width || screenPos.y >= Screen.height)
return null;
if (mMapTiles == null ||
mMapTiles.Count == 0)
{
return null;
}
//for (int i = (mMapTiles.Count / 2); i < mMapTiles.Count; i++)
int index = Mathf.FloorToInt(mMapTiles.Count / 2);
XPosHandle(ref index, screenPos);
YPosHandle(ref index, screenPos);
return mMapTiles[index];
}
///
/// 横向查找
///
///
///
private void XPosHandle(ref int index, Vector3 screenPos)
{
bool hasData = false;
int horLeft = 0;
int horRight = 0;
while (!hasData)
{
Rect centerRect = mMapTiles[index].TileRect;
float centerRectWidth = centerRect.x + centerRect.width;
if (!(screenPos.x >= centerRect.x && screenPos.x < centerRectWidth))
{
int currentHor = Mathf.CeilToInt(index / widthCount) + 1;
if (horLeft == 0)
horLeft = (currentHor - 1) * widthCount;
if (horRight == 0)
horRight = currentHor * widthCount;
//上限下限的缩小
if (screenPos.x >= centerRectWidth)
{
horLeft = index;
}
else
{
horRight = index;
}
//下一个二分查找对象
index = horLeft + Mathf.FloorToInt((horRight - horLeft) / 2);
}
else
{
hasData = true;
}
}
}
///
/// 纵向查找
///
///
///
private void YPosHandle(ref int index, Vector3 screenPos)
{
bool hasData = false;
int horTop = 0;
int horBottom = 0;
while (!hasData)
{
Rect centerRect = mMapTiles[index].TileRect;
float centerRectHeight = centerRect.y + centerRect.height;
int hor = index % widthCount;//横向位置(----)
int ver = Mathf.FloorToInt(index / widthCount);//纵向位置(||||)
if (screenPos.y >= centerRect.y && screenPos.y < centerRectHeight)
{
hasData = true;
}
else
{
if (horTop == 0)
horTop = 0;
if (horBottom == 0)
horBottom = heightCount;
//上限下限的缩小
if (screenPos.y >= centerRectHeight)
{
//horTop = ver * widthCount + hor;
horTop = ver;
}
else
{
horBottom = ver;
//horBottom = ver * widthCount + hor;
}
//下一个二分查找对象
index = (horTop + Mathf.FloorToInt((horBottom - horTop) / 2)) * widthCount + hor;
}
}
}
}
然后因为我们现在针对特效,所以我们需要用一个ParticleMapTileMgr继承MapTileMgr来做特效的格子处理的特殊部分:
using UnityEngine;
using System.Collections;
using System;
public class ParticleMapTileMgr : MapTileMgr
{
///
/// 循环播放的粒子不管理
///
///
///
private bool isLoopParticle(GameObject go)
{
var particle = go.GetComponentInChildren();
if (particle == null)
return false;
return particle.main.loop;
}
///
/// 放入一个对象到格子池,没有回调则默认把他移到很远的地方
///
///
///
public override void AddToMap(Transform go)
{
if (go == null)
{
return;
}
if (isLoopParticle(go.gameObject))
{
return;
}
base.AddToMap(go);
}
///
/// 放入一个对象到格子池,没有回调则默认把他移到很远的地方
///
///
/// 返回哪个对象需要移除
public override void AddToMap(MapTileObject go)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
if (isLoopParticle(go.ObjectTransform.gameObject))
{
return;
}
base.AddToMap(go);
}
///
/// 放入一个对象到格子池,这个版本不需要关注优先级
///
///
///
public override void AddToMap(Transform go, Action OverflowCallBack)
{
if (go == null)
{
return;
}
if (isLoopParticle(go.gameObject))
{
return;
}
base.AddToMap(go, OverflowCallBack);
}
///
/// 放入一个对象到格子池,这个版本通过MapTileObject的Priority控制优先级
///
///
/// 返回哪个对象需要移除
public override void AddToMap(MapTileObject go, Action OverflowCallBack)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
if (isLoopParticle(go.ObjectTransform.gameObject))
{
return;
}
base.AddToMap(go, OverflowCallBack);
}
}
然后使用的时候需要我们把组件加进去,在播放特效的时候调用相应的AddToMap就好了。
mMapTileMgr = m_CacheMgr.RootGO.AddComponent();
加的时候是:
MapTileObject mapTileMgr = new MapTileObject(sfxCtrl.transform, sfxCtrl.m_Priority); mMapTileMgr.AddToMap(mapTileMgr);
这样的话从之前dc可以到四五百个控制在100个左右,而且效果也能接受。