仅为自己在游戏制作过程中对耗时较多的思考总结,代码均为片段。
需求
战棋角色在移动,攻击等行为时,会只能对周围指定范围内的格子进行操作,同时,在相对复杂的地形,包含如消耗多点行动力,不能移动等地形格时播放移动动画的路径使用迭代进行,得出行动范围与最短路径。
思路
在四边形战棋格中,每地图格有最多4个相邻地图格,认为玩家所在即开始计算的地图格坐标(x,y),相邻地图格为x,y±1的4格,对xy地图设定最大行动力,然后向周围扩展,对相邻4格进行模拟移动,减去行动力消耗并设置新的行动力,以新的行动力与新的中心格进行扩展,通过迭代扩展到指定的范围即可,扩展过程中需注意避免重复扩展与地图边缘。
代码
地图格代码,在后续代码候为Map类
public int step = -1;//行动力
public int cost = 1;//行动力消费
工具方法类
/// <summary>
/// 三个参数分别是该移动对象当前移动力,该移动对象当前位置x坐标,该移动对象当前位置的z坐标
/// </summary>
/// <param name="MaxStep"></param>
/// <param name="X"></param>
/// <param name="Z"></param>
/// <param name="isResetColor">是否刷新颜色</param>
public static void ColorChange(int MaxStep, float X, float Z, bool isResetColor)
{
List<List<Map>> map = GameManager.GetInstance().GetMapList();//获取所有地图格
//重置步数
foreach (List<Map> mt in map)
{
foreach (Map mm in mt)
{
mm.step = -1;
}
}
int x = (int)(X + 0.5f % 1);//将坐标转换为int,四舍五入
int z = (int)(Z + 0.5f % 1);
map[x][z].step = MaxStep;
//为地图方块设置步数
SetStep(x, z);
//至此所有map的step属性已经设置完毕,根据需求进行后续处理即可
if (isResetColor)
{
//渲染改色
foreach (List<Map> ml in map)
{
foreach (Map n in ml)
{
//在此根据实际情况对map进行处理,如变色等
if (n.step >= 0)
{
n.SetColor(1);
}
}
}
}
}
/// <summary>
/// 在已经设置好xz的step属性的基础上对其周围进行扩散性的设置step,到0为止,注意此处大量与判断为去除了地图边缘,切若有更近路径可以到被设置的地图格时,跳过此格,trycatch也是用于此迭代中流程控制的一部分,请注意不要删除
/// </summary>
/// <param name="x"></param>
/// <param name="z"></param>
private static void SetStep(int x, int z)
{
try
{
GameManager gameManager = GameManager.GetInstance();
if (gameManager.GetMapList()[x][z] != null && gameManager.GetMapList()[x][z].step < 0)
{
return;
}
if (z > 0 && gameManager.GetMapList()[x][z - 1] != null
&& gameManager.GetMapList()[x][z - 1].step < gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x][z - 1].cost)
{
gameManager.GetMapList()[x][z - 1].step = gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x][z - 1].cost;
SetStep(x, z - 1);
}
if (z < gameManager.GetMapList()[x].Count - 1 && gameManager.GetMapList()[x][z + 1] != null
&& gameManager.GetMapList()[x][z + 1].step < gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x][z + 1].cost)
{
gameManager.GetMapList()[x][z + 1].step = gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x][z + 1].cost;
SetStep(x, z + 1);
}
if (x > 0 && gameManager.GetMapList()[x - 1][z] != null
&& gameManager.GetMapList()[x - 1][z].step < gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x - 1][z].cost)
{
gameManager.GetMapList()[x - 1][z].step = gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x - 1][z].cost;
SetStep(x - 1, z);
}
if (x < gameManager.GetMapList().Count - 1 && gameManager.GetMapList()[x + 1][z] != null
&& gameManager.GetMapList()[x + 1][z].step < gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x + 1][z].cost)
{
gameManager.GetMapList()[x + 1][z].step = gameManager.GetMapList()[x][z].step - gameManager.GetMapList()[x + 1][z].cost;
SetStep(x + 1, z);
}
}
catch
{
}
}
补充GameMananger中的部分代码,对未使用部分进行了删除,非完整代码
private static GameManager _instance;
private List<List<Map>> map = null;
void Awake()
{
_instance = this;
}
public static GameManager GetInstance()
{
while (_instance == null)
{
Thread.Sleep(100);
}
return _instance;
}
/// <summary>
/// 获取带有map脚本的所有map格子
/// </summary>
/// <returns></returns>
public List<List<Map>> GetMapList()
{
if (map == null)
{
map = new List<List<Map>>();
m = GameObject.FindGameObjectsWithTag("Map");
for (int i = 0; i < MAXX; i++)
{
map.Add(new List<Map>());
for (int j = 0; j < MAXZ; j++)
{
SetToMapList(i, j);
}
}
}
return map;
}
在上面通过对map的step属性进行修改对指定范围内的map进行改色,此时step属性我们只对其使用了step>0这么一个判断,但step属性是由maxstep属性逐渐减去cost扩散而来,即(maxstep - step)为此map到初始map的最短距离,最短路径的生成就基于此实现。
大体思路为从终点开始,向起点扩散式判断到起点距离,取合适值取用,因为找出一条最短路径即可,所以一旦找到一个合适的map之后即可跳出循环。(感觉写的自己都看不懂还是看代码吧)
移动方法代码片段,此移动方法一般挂在player上,原项目中使用itween与itweenpath控制移动,稍有删改,传入的map为要移动到的终点map
/// <summary>
/// 移动方法
/// </summary>
/// <param name="m">终点</param>
public void Move(Map m)
{
Map mt = m;
List<Vector3> vl = new List<Vector3>();//创建list用于存储路径点
vl.Add(m.transform.position);
int iii = 0;
while (mt.step < MaxStep && iii < MaxStep)
{
iii++;
int x = (int)mt.transform.position.x;
int z = (int)mt.transform.position.z;
if (z > 0 && gameManager.GetMapList()[x][z - 1] != null)
{
if (gameManager.GetMapList()[x][z - 1].step == mt.step + mt.cost)
{
mt = gameManager.GetMapList()[x][z - 1];
vl.Add(mt.transform.position);
continue;
}
}
if (z < gameManager.GetMapList()[x].Count - 1 && gameManager.GetMapList()[x][z + 1] != null)
{
if (gameManager.GetMapList()[x][z + 1].step == mt.step + mt.cost)
{
mt = gameManager.GetMapList()[x][z + 1];
vl.Add(mt.transform.position);
continue;
}
}
if (x > 0 && gameManager.GetMapList()[x - 1][z] != null)
{
if (gameManager.GetMapList()[x - 1][z].step == mt.step + mt.cost)
{
mt = gameManager.GetMapList()[x - 1][z];
vl.Add(mt.transform.position);
continue;
}
}
if (x < gameManager.GetMapList().Count - 1 && gameManager.GetMapList()[x + 1][z] != null)
{
if (gameManager.GetMapList()[x + 1][z].step == mt.step + mt.cost)
{
mt = gameManager.GetMapList()[x + 1][z];
vl.Add(mt.transform.position);
continue;
}
}
}
//使用itweenpath确定行动路径
iTweenPath ipath = path.GetComponent<iTweenPath>();
ipath.nodes.Clear();
ipath.nodes.Add(transform.position);
if (vl.Count > 2)
{
vl.Remove(vl[vl.Count - 1]);
}
//此时,vl为反向路径
vl.Reverse();
ipath.nodes.AddRange(vl);
// x y z 标示移动的位置。
ipath.nodeCount = ipath.nodes.Count;
if (args["path"] == null)
{
args.Add("path", iTweenPath.GetPath("123"));
}
else
{
args["path"] = iTweenPath.GetPath("123");
}
iTween.MoveTo(gameObject, args);
}
扩展
若是六边形战棋,通过对坐标系的处理大概也是可以使用的。。大概。。没处理过6变形呢。。
最后
第一次编写,真实感觉自己都看不懂,但是代码姑且是贴上了。。欢迎交流学习。。
当时查又查不到,大概纠结了一周左右出了这堆东西(趴。。
扩展
2019/10/11
深度遍历改为广度遍历,思路重新参考了A*,使用了迭代,没有递归
注意因为实际需求,这里设置路径点数时候铺满了全图,若仅作可变路径,更推荐找到寻路点即停
代码部分
class Util{
public static Vector2Int[] AspectPriority = new Vector2Int[4] { Vector2Int.up, Vector2Int.down, Vector2Int.right, Vector2Int.left };
private static Vector2Int mStreetStart;
/// <summary>
/// 获取路径,若无可用路径,使用开始与结束两点,结束点可以超界
/// </summary>
/// <param name="rEnd"></param>
/// <returns></returns>
public static List<Vector2Int> GetRoute(Vector2Int rEnd, List<List<BuildingMap>> rBuildingMaps)
{
List<Vector2Int> rRoute = new List<Vector2Int>();
Vector2Int rMapIndex = rEnd;
do
{
rRoute.Add(rMapIndex);
rMapIndex = InfrastructureUtil.GetLastRoute(rMapIndex, rBuildingMaps);
} while (rMapIndex.x >= 0 && rMapIndex.y >= 0);
if (rRoute.Count < 2 && mStreetStart.x >= 0 && mStreetStart.y >= 0)
{
rRoute.Add(mStreetStart);
}
return rRoute;
}
/// <summary>
/// 根据优先级找到上一步的位置
/// </summary>
/// <param name="rNow"></param>
/// <returns></returns>
private static Vector2Int GetLastRoute(Vector2Int rNow, List<List<BuildingMap>> rBuildingMaps)
{
int x = (int)rNow.x, y = (int)rNow.y;
if (x < 0 || y < 0 || x >= rBuildingMaps.Count || y >= rBuildingMaps[x].Count)
return Vector2Int.one * -1;
if (rBuildingMaps[x][y].StepIndex == 0)
return Vector2Int.one * -1;
for (int i = 0; i < AspectPriority.Length; i++)
{
Vector2Int rLastNum = rNow + AspectPriority[i];
if (rLastNum.x < 0 || rLastNum.x >= rBuildingMaps.Count || rLastNum.y < 0 || rLastNum.y >= rBuildingMaps[0].Count)
continue;
if (rBuildingMaps[(int)rLastNum.x][(int)rLastNum.y].StepIndex == (rBuildingMaps[x][y].StepIndex - 1))
return rLastNum;
}
//Debug.LogError("未找到上一级" + mBuildingMaps[x][y].StepIndex);
return Vector2Int.one * -1;
}
/// <summary>
/// 指定起点,设置地图格step属性用于寻路
/// </summary>
/// <param name="nIndex"></param>
/// <param name="nStep"></param>
public static void SetMapStep(Vector2Int nIndex, int nStep, List<List<BuildingMap>> rBuildingMaps)
{
int x = (int)nIndex.x, y = (int)nIndex.y;
mStreetStart = nIndex;
rBuildingMaps[x][y].StepIndex = nStep;
LinkedList<Vector2> rOpenList = new LinkedList<Vector2>();
List<Vector2> rCloseList = new List<Vector2>();
rOpenList.AddLast(new Vector2(x, y));
int nMapStep = 0;
while (rOpenList.Count > 0)
{
Vector2 rV2 = rOpenList.First.Value;
nMapStep = rBuildingMaps[(int)rV2.x][(int)rV2.y].StepIndex;
for (int i = 0; i < AspectPriority.Length; i++)
{
Vector2 rNextV2 = rV2 + AspectPriority[i];
if (!rCloseList.Contains(rNextV2) && !rOpenList.Contains(rNextV2))
{
x = (int)rNextV2.x;
y = (int)rNextV2.y;
if (x < 0 || x >= rBuildingMaps.Count || y < 0 || y >= rBuildingMaps[0].Count)
continue;
if (rBuildingMaps[x][y].MapType == MapType.Building || rBuildingMaps[x][y].MapType == MapType.Disable)
{
rBuildingMaps[x][y].StepIndex = 100;
rCloseList.Add(rNextV2);
}
else if (rBuildingMaps[x][y].StepIndex < 0 || rBuildingMaps[x][y].StepIndex > nMapStep)
{
rBuildingMaps[x][y].StepIndex = nMapStep + 1;
rOpenList.AddLast(rNextV2);
}
}
}
rOpenList.Remove(rV2);
rCloseList.Add(rV2);
}
}
/// <summary>
/// 重置所有地图格的step属性
/// </summary>
public static void ReSetMapStep(List<List<BuildingMap>> rBuildingMaps)
{
for (int i = 0; i < rBuildingMaps.Count; i++)
{
for (int j = 0; j < rBuildingMaps[i].Count; j++)
{
rBuildingMaps[i][j].StepIndex = -1;
}
}
mStreetStart = Vector2Int.one * -1;
}
private static Vector2Int[] mNorAspect = new Vector2Int[4] { Vector2Int.up, Vector2Int.right, Vector2Int.down, Vector2Int.left };
/// <summary>
/// 判断是否符合优先级来确认是否需要旋转
/// 注意,二维数组的xy与坐标的xy相反
/// </summary>
/// <param name="rAround"></param>
/// <returns></returns>
private static int IsAllowStreet(List<List<bool>> rAround)
{
int nAspNum = 0;
for (int i = 0; i < mNorAspect.Length; i++)
{
if (rAround[1 - mNorAspect[i].y][mNorAspect[i].x + 1])
{
nAspNum++;
if (i == 3)
{
if (nAspNum != 4)
return -1;
}
else if (i != 0)
{
if (nAspNum == 1)
return -1;
}
}
}
return nAspNum;
}
}
public class BuildingMap
{
public MapType MapType = MapType.Disable;
public int BuildingNum;
private int nStepIndex;//用于寻路
public int StepIndex
{
get { return nStepIndex; }
set
{
if (nStepIndex > value || value <= 0 || nStepIndex < 0)
{
nStepIndex = value;
}
}
}
}
//扩展方法,好像并不是每个都用到了
static class InfrastructureExtand
{
/// <summary>
/// Vec3剔除Y轴坐标后转为Vec2
/// </summary>
/// <param name="rVec3"></param>
/// <returns></returns>
public static Vector2Int Vec2(this Vector3 rVec3)
{
int rVec2X = Mathf.FloorToInt(rVec3.x);
int rVec2Y = Mathf.FloorToInt(rVec3.z);
return new Vector2Int(rVec2X, rVec2Y);
}
/// <summary>
/// Vec2加入y轴坐标转为Vec3
/// </summary>
/// <param name="rVec2"></param>
/// <param name="fY"></param>
/// <param name="bIsInt"></param>
/// <returns></returns>
public static Vector3 Vec3(this Vector2Int rVec2, float fY = 0, bool bIsInt = false)
{
if (bIsInt)
return new Vector3(rVec2.x, fY, rVec2.y);
return new Vector3(rVec2.x + 0.5f, fY, rVec2.y + 0.5f);
}
/// <summary>
/// Vec2加入y轴坐标转为Vec3
/// </summary>
/// <param name="rVec2"></param>
/// <param name="fY"></param>
/// <param name="bIsInt"></param>
/// <returns></returns>
public static Vector3 Vec3(this Vector2 rVec2, float fY = 0, bool bIsInt = false)
{
if (bIsInt)
return new Vector3(rVec2.x, fY, rVec2.y);
return new Vector3(rVec2.x + 0.5f, fY, rVec2.y + 0.5f);
}
/// <summary>
/// 使用Vec2的xy获取二维list的元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="rList"></param>
/// <param name="rV2"></param>
/// <returns></returns>
public static T GetValue<T>(this List<List<T>> rList, Vector2Int rV2)
{
int nX = rV2.x, nY = rV2.y;
if (nX < 0 || nX >= rList.Count || nY < 0 || nY >= rList[nX].Count)
return default(T);
return rList[nX][nY];
}
}