目前参与一款在研SLG研发,目前在开发实时战斗部分的表现,今天要分享的是实时战斗头像自动排布算法的实现。
需求分析
- 部队头像的自动跟随、
- 自动监测碰撞以避免头像重叠、
- 需要满足一定性能要求,不能产生过多的DrawCall和其他CPU开销、
- 参考效果为万国觉醒实时战斗表现,当前方案基本实现相同效果。
设计思路
- 建立Root Canvas空间下的屏幕空间映射,进行虚拟网格拆分,作为基础Map<mapIndex,Area>,和单个头像图标的坐标系。
- 提供1所建立的坐标系的注册和反注册,用来记录改空间内网格的占用信息。
- 周期性进行Available检测,不满足则进行头像位置变换。
- Available算法基于对象和Root的Relative Bounds 进行索引变换,查询Area占用情况。
- 每个对象内置固定个数的Available Points,运行时根据部队朝向进行最优排序,周期选取最优点为对象跟随点。
实现方案要点
- 建立Map<Index,Area>空间索引映射关系,作为坐标系管理和查询的基础
- 可以直接通过Bounds和AreaSize计算对应坐标,近似Hash算法的查询复杂度
- 周期性的Available检测,避免高并发的运算
- Vector.Dot对AvailablePoints进行排序
- 使用缓存变量避免GC Alloc
PotraitBoard 可以放在UI任意节点,指定好控制参数,即可在Enable Disable时自动注册和反注册
PotraitManager挂载于场景常驻节点
PotraitManager._root需要为UI节点下AnchorPreset为Strench/Strench(铺满全屏)
PotraitManager.areasCount为屏幕空间拆分的行列数
代码略长,不过注释充分,直接贴上了……
using System.Collections.Generic;
using System.Text;
using DG.Tweening;
using UnityEngine;
using XLua;
public class PotraitBoard : MonoBehaviour
{
[LuaCallCSharp]
public enum DefaultPos
{
Left = 1,
Right = 2,
}
/// <summary>
/// controll target
/// </summary>
public RectTransform target;
/// <summary>
/// available points
/// </summary>
public List<RectTransform> availablePoints = new List<RectTransform>();
/// <summary>
/// best sort of points
/// </summary>
public List<int> bestSortOfPoints = new List<int>();
/// <summary>
/// position illegle check duration
/// </summary>
private float checkDuration = 2f;
/// <summary>
/// pos of potrait sets
/// </summary>
private int curPos = 0;
/// <summary>
/// pos of potrait sets
/// </summary>
private int curIndex = 0;
/// <summary>
/// troop`s direction
/// </summary>
private Vector2 direction = Vector2.left;
/// <summary>
/// move to next pos
/// </summary>
/// <param name="nextPos"></param>
private void MoveToNext(int nextPos)
{
target.DOLocalMove(availablePoints[nextPos].localPosition, 0.5f).SetEase(Ease.OutQuad);
}
/// <summary>
/// move to next available point in availablePoints in default sequence, if cur is not available
/// </summary>
private void MoveToNextIfNotAvailable()
{
if (PotraitBoardManager.Instance.IsPointAvailable(availablePoints[curPos]))
return;
int sign = curPos;
while (!PotraitBoardManager.Instance.IsPointAvailable(availablePoints[curPos]))
{
curPos++;
curPos = curPos % availablePoints.Count;
if (sign == curPos)
return;
}
//Debug.Log("PotraitBoard MoveToNextIfNotAvailable Move From [" + sign + "] To Pos: [" + curPos + "]");
PotraitBoardManager.Instance.UnRegisterPotrait(curIndex,availablePoints[sign]);
curIndex = PotraitBoardManager.Instance.RegisterPotrait(availablePoints[curPos]);
MoveToNext(curPos);
}
/// <summary>
/// move to next best point available
/// </summary>
private void MoveToNextBestAvailable()
{
if (bestSortOfPoints.Count <= 0)
ReSortPoints();
int pIndex = 0;
for (int i = 0; i < bestSortOfPoints.Count; i++)
{
if (Potra