固定視角的遊戲有個特點就是攝像機的角度是一致的,不會有變化。
但因爲我們的某款遊戲在遊戲過程中會有大量的粒子特效出現,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<T> where T : MapTileObject
{
public Rect TileRect;
public T[] ObjectList = new T[MapTileMgr.TileMapCount];
}
MapTileMgr是統一的格子管理與池子管理,包括二分搜索都再這裏:
using UnityEngine;
using System.Collections.Generic;
using System;
using Game;
/// <summary>
/// by llsansun
/// 控制屏幕上每個區域的對象顯示數量,不讓他同一區域數量太多
/// 例如粒子如果一個區域數量太多,overdraw會很高
/// 應用場景
/// 1.並且我們假設這個區域數量太多,多餘的TileMapCount之外的數量對顯示影響不大時可以用
/// 2.對不同性能的機型控制顯示數量時可以用
/// 3.不想同區域overdraw太多時可以用,比如有些顯示的物體設置了tranparent的渲染,但是在這個區域內下面的物體不影響顯示時可以用。
/// 總之就是屏幕區域的對象顯示限制
/// </summary>
public class MapTileMgr : MonoBehaviour
{
struct sHideObject
{
public int mDefaultLayer;
public MapTileObject mMapTileObject;
}
/// <summary>
/// 主攝像機,如果有需要可以改
/// </summary>
public Camera mMainCamera = null;
/// <summary>
/// 一個格子的寬度
/// </summary>
public int TileWidth = 100;
/// <summary>
/// 一個格子的高度
/// </summary>
public int TileHeight = 100;
/// <summary>
/// 每個格子的數量上限
/// </summary>
public static int TileMapCount = 8;
int currentFrame = 0;
/// <summary>
/// 檢查特效是否能顯示的時間
/// </summary>
public int CheckInternalFrame = 5;
private int widthCount;
private int heightCount;
private List<MapTile<MapTileObject>> mMapTiles = new List<MapTile<MapTileObject>>();
/// <summary>
/// 隱藏對象判斷的最大數量
/// </summary>
public static int TotalHideObjectNumber = 150;
//暫時隱藏的對象
private sHideObject[] mHideObjects = new sHideObject[TotalHideObjectNumber];
private int mHideLayer;
private int mShowLayer;
/// <summary>
/// 創建屏幕格子
/// </summary>
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<MapTileObject> tile = new MapTile<MapTileObject>();
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);
}
}
}
/// <summary>
/// 放入一個對象到格子池,沒有回調則默認把他移到很遠的地方
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack"></param>
public virtual void AddToMap(Transform go)
{
if (go == null)
{
return;
}
MapTileObject mapObj = new MapTileObject(go.transform, 100);
AddToMap(mapObj, null);
}
/// <summary>
/// 放入一個對象到格子池,沒有回調則默認把他移到很遠的地方
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack">返回哪個對象需要移除</param>
public virtual void AddToMap(MapTileObject go)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
AddToMap(go, null);
}
/// <summary>
/// 放入一個對象到格子池,這個版本不需要關注優先級
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack"></param>
public virtual void AddToMap(Transform go, Action<MapTileObject> OverflowCallBack)
{
if (go == null)
{
return;
}
MapTileObject mapObj = new MapTileObject(go.transform, 100);
AddToMap(mapObj, OverflowCallBack);
}
/// <summary>
/// 放入一個對象到格子池,這個版本通過MapTileObject的Priority控制優先級
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack">返回哪個對象需要移除</param>
public virtual void AddToMap(MapTileObject go, Action<MapTileObject> 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<MapTileObject> 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;
}
}
/// <summary>
/// 處理顯示
/// </summary>
/// <param name="mapTile"></param>
/// <param name="go"></param>
/// <param name="OverflowCallBack"></param>
private void HandleGO(MapTile<MapTileObject> mapTile, MapTileObject go, Action<MapTileObject> 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);
}
/// <summary>
/// 橫向縱向二分查找格子
/// </summary>
/// <param name="screenPos"></param>
/// <returns></returns>
private MapTile<MapTileObject> 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];
}
/// <summary>
/// 橫向查找
/// </summary>
/// <param name="index"></param>
/// <param name="screenPos"></param>
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;
}
}
}
/// <summary>
/// 縱向查找
/// </summary>
/// <param name="index"></param>
/// <param name="screenPos"></param>
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
{
/// <summary>
/// 循環播放的粒子不管理
/// </summary>
/// <param name="go"></param>
/// <returns></returns>
private bool isLoopParticle(GameObject go)
{
var particle = go.GetComponentInChildren<ParticleSystem>();
if (particle == null)
return false;
return particle.main.loop;
}
/// <summary>
/// 放入一個對象到格子池,沒有回調則默認把他移到很遠的地方
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack"></param>
public override void AddToMap(Transform go)
{
if (go == null)
{
return;
}
if (isLoopParticle(go.gameObject))
{
return;
}
base.AddToMap(go);
}
/// <summary>
/// 放入一個對象到格子池,沒有回調則默認把他移到很遠的地方
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack">返回哪個對象需要移除</param>
public override void AddToMap(MapTileObject go)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
if (isLoopParticle(go.ObjectTransform.gameObject))
{
return;
}
base.AddToMap(go);
}
/// <summary>
/// 放入一個對象到格子池,這個版本不需要關注優先級
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack"></param>
public override void AddToMap(Transform go, Action<MapTileObject> OverflowCallBack)
{
if (go == null)
{
return;
}
if (isLoopParticle(go.gameObject))
{
return;
}
base.AddToMap(go, OverflowCallBack);
}
/// <summary>
/// 放入一個對象到格子池,這個版本通過MapTileObject的Priority控制優先級
/// </summary>
/// <param name="go"></param>
/// <param name="OverflowCallBack">返回哪個對象需要移除</param>
public override void AddToMap(MapTileObject go, Action<MapTileObject> OverflowCallBack)
{
if (go == null || go.ObjectTransform == null)
{
return;
}
if (isLoopParticle(go.ObjectTransform.gameObject))
{
return;
}
base.AddToMap(go, OverflowCallBack);
}
}
然後使用的時候需要我們把組件加進去,在播放特效的時候調用相應的AddToMap就好了。
mMapTileMgr = m_CacheMgr.RootGO.AddComponent<ParticleMapTileMgr>();
加的時候是:
MapTileObject mapTileMgr = new MapTileObject(sfxCtrl.transform, sfxCtrl.m_Priority);
mMapTileMgr.AddToMap(mapTileMgr);
這樣的話從之前dc可以到四五百個控制在100個左右,而且效果也能接受。