要求一个地图上具有若干个区域,每个区域由左下角坐标和右上角坐标定义
SpawnRegionSetting.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "SO_SpawnRegionSetting",
menuName = "Chess/SpawnRegionSetting")]
public class SpawnRegionSetting : ScriptableObject
{
public List<SpawnRegion> regions;
}
[Serializable]
public class SpawnRegion
{
public Vector2Int bottomleft;
public Vector2Int upright;
public bool IsInRegion(Vector2 pos)
{
if (pos.x >= bottomleft.x &&
pos.y >= bottomleft.y &&
pos.x <= upright.x &&
pos.y <= upright.y)
return true;
return false;
}
}
需要有一个脚本控制各个区域,每个区域对应一个空位列表
有一个统一的函数添加或者移除空位,因为各个区域之间可能会重叠,所以这个函数需要负责将空位从各个区域的空位列表添加或者移除
为了获得空位,也需要直到哪个区域里面存在空位
RegionsController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Universal;
using Random = UnityEngine.Random;
public class RegionsController : Singleton<RegionsController>
{
[Header("Spawn Setting")]
public SpawnRegionSetting regionSetting;
[SerializeField, ReadOnly]
public List<Positions> emptyPosInRegions = new List<Positions>();
[SerializeField]
private int showEmptyPosInRegionsIndex = -1;
protected override void Awake()
{
base.Awake();
for (int i = 0; i < regionSetting.regions.Count; i++)
{
emptyPosInRegions.Add(new Positions());
}
}
/// <summary>
/// 输入的左下角坐标和右上角坐标需要是在网格坐标系中的
/// 例如网格坐标系下的 (0, 0) 对应世界坐标系下的 (0.5, 0.5) * GridSize + basePos 位置
/// 网格坐标系下的 (3,3) 对应世界坐标系下的 (2.5, 2.5) * GridSize + basePos 位置
/// </summary>
/// <param name="bottomleft">网格坐标系中的左下角坐标</param>
/// <param name="upright">网格坐标系中的右上角坐标</param>
public void AddRectEmptyPosToAllRegions(Vector2Int bottomleft, Vector2Int upright)
{
for (int i = 0; i < regionSetting.regions.Count; i++)
{
Vector3 basePos = Chessboard.Instance.transform.position;
Vector3 tmpPos;
Vector2Int bottomleftIntersectPart = new Vector2Int(Mathf.Max(regionSetting.regions[i].bottomleft.x, bottomleft.x),
Mathf.Max(regionSetting.regions[i].bottomleft.y, bottomleft.y));
Vector2Int uprightIntersectPart = new Vector2Int(Mathf.Min(regionSetting.regions[i].upright.x, upright.x),
Mathf.Min(regionSetting.regions[i].upright.y, upright.y));
for (int x = bottomleftIntersectPart.x; x <= uprightIntersectPart.x; x++)
{
for (int y = bottomleftIntersectPart.y; y <= uprightIntersectPart.y; y++)
{
tmpPos = new Vector3(x + 0.5f, y + 0.5f, 0) * ChessboardImageController.Instance.GridSize + basePos;
if (!emptyPosInRegions[i].list.Contains(tmpPos))
{
emptyPosInRegions[i].list.Add(tmpPos);
}
}
}
}
}
/// <summary>
/// 判断输入的世界坐标系中的坐标是否对应某个区域中的空位
/// </summary>
/// <param name="pos">属于世界坐标系的坐标</param>
/// <returns></returns>
public bool IsEmptyPosInAnyRegions(Vector3 pos)
{
foreach (var emptyPosInRegion in emptyPosInRegions)
{
if (emptyPosInRegion.list.Contains(pos))
{
return true;
}
}
return false;
}
/// <summary>
/// 将输入的世界坐标系中的坐标,添加到所有能够包含这个坐标的区域的空位列表中
/// </summary>
/// <param name="pos">属于世界坐标系的坐标</param>
public void AddEmptyPosToAllRegions(Vector3 pos)
{
for (int i = 0; i < regionSetting.regions.Count; i++)
{
Vector3 relativeGridPos = ChessPosConverter.WorldPosToRelativeGridPos(pos);
if (regionSetting.regions[i].IsInRegion(relativeGridPos))
{
if (!emptyPosInRegions[i].list.Contains(pos))
{
emptyPosInRegions[i].list.Add(pos);
}
}
}
}
/// <summary>
/// 将输入的世界坐标系中的坐标,从所有区域的空位列表中移除
/// </summary>
/// <param name="pos">属于世界坐标系的坐标</param>
public void RemoveEmptyPosFromAllRegions(Vector3 pos)
{
for (int i = 0; i < regionSetting.regions.Count; i++)
{
emptyPosInRegions[i].list.Remove(pos);
}
}
public int GetAvailableRegionIndex()
{
// Get available regions
if (regionSetting.regions.Count == 0)
{
Debug.LogError("There is not an available region setting");
return -1;
}
List<int> availableRegionIndexs = Enumerable.Range(0, regionSetting.regions.Count).ToList();
for (int i = availableRegionIndexs.Count - 1; i >= 0; i--)
{
if (emptyPosInRegions[i].list.Count == 0)
{
availableRegionIndexs.RemoveAt(i);
}
}
if (availableRegionIndexs.Count == 0)
{
Debug.LogError("There is not an available region with empty pos");
return -1;
}
// Choose a regions from available regions randomly
int randRegionIndex = Random.Range(0, availableRegionIndexs.Count);
return availableRegionIndexs[randRegionIndex];
}
public void OnDrawGizmos()
{
Gizmos.color = new Color(0.3f, 0.3f, 1f, 0.5f);
if(showEmptyPosInRegionsIndex >= 0 && showEmptyPosInRegionsIndex < regionSetting.regions.Count)
{
foreach (Vector3 emptyPos in emptyPosInRegions[showEmptyPosInRegionsIndex].list)
{
Gizmos.DrawCube(emptyPos, Vector3.one * ChessboardImageController.Instance.GridSize);
}
}
}
}
[Serializable]
public class Positions
{
public List<Vector3> list = new List<Vector3>();
}
使用示例:
public static GameObject TryRandomSpawn()
{
// Choose a regions from available regions randomly
int randRegionIndex = RegionsController.Instance.GetAvailableRegionIndex();
if (randRegionIndex == -1)
return null;
if (RegionsController.Instance.emptyPosInRegions.Count <= randRegionIndex)
{
Debug.LogError("传入的有效区域序号不存在!请检查传入的 AvailableRegionIndex!");
return null;
}
if (RegionsController.Instance.emptyPosInRegions[randRegionIndex].list.Count() == 0)
{
Debug.LogError("传入的有效区域中实际上没有空位!请检查传入的 AvailableRegionIndex!");
return null;
}
// Choose a empty pos from selected region
int randPosIndex = Random.Range(0, RegionsController.Instance.emptyPosInRegions[randRegionIndex].list.Count());
Vector3 spawnPos = RegionsController.Instance.emptyPosInRegions[randRegionIndex].list[randPosIndex];
// Spawn
return Spawn(spawnPos);
}
维护示例:
如果一个棋子从 oldPos 移动到 newPos,那么
// 我怎么在棋盘中生成一个棋子?为了确保不出错,我是已经在棋盘中创建并且在维护一个空位列表,这样每次取空位只需要 o(1) 时间复杂度
// 但是麻烦的就是维护这个列表,需要注意
// 主要的逻辑是要注意调整棋盘中的空位列表
// 空位列表都是世界坐标
// 为什么是 ToAllRegions 因为现在的空位列表是一个区域一个空位列表
// 因为策划里面是先选区域再选区域里的空位,所以一个区域有一个空位列表
// 这样的话不同的区域的空位列表可能就会重合
// 所以在处理空位列表的时候要看一遍所有区域
RegionsController.Instance.AddEmptyPosToAllRegions(oldPos);
RegionsController.Instance.RemoveEmptyPosFromAllRegions(newPos);
如果棋盘的大小发生变化,那么需要添加两个拓展矩形的空位到所有区间的空位列表中
private void RefreshEmptyPosInRegions()
{
Vector2Int gridPosUpRight = GetGridPosUpRight();
Vector2Int rectBottomLeft1 = new Vector2Int(lastGridPosUpRight.x + 1, 0);
Vector2Int rectBottomLeft2 = new Vector2Int(0, lastGridPosUpRight.y + 1);
Vector2Int rectUpRight2 = new Vector2Int(lastGridPosUpRight.x, gridPosUpRight.y);
RegionsController.Instance.AddRectEmptyPosToAllRegions(rectBottomLeft1, gridPosUpRight);
RegionsController.Instance.AddRectEmptyPosToAllRegions(rectBottomLeft2, rectUpRight2);
lastGridPosUpRight = gridPosUpRight;
}
这个方法的难点就在于维护……主要是这个空位列表的维护很容易出错
始终要注意棋子移动的时候有没有调用 AddEmptyPosToAllRegions 和 RemoveEmptyPosFromAllRegions,不然别人在取空位的时候,可能取到的空位上面已经有棋子了,就会发生棋子叠加棋子的情况