以模拟移动为思路的战棋范围及路径生成

以模拟移动为思路的战棋范围及路径生成

仅为自己在游戏制作过程中对耗时较多的思考总结,代码均为片段。

需求

战棋角色在移动,攻击等行为时,会只能对周围指定范围内的格子进行操作,同时,在相对复杂的地形,包含如消耗多点行动力,不能移动等地形格时播放移动动画的路径使用迭代进行,得出行动范围与最短路径。

思路

在四边形战棋格中,每地图格有最多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];
        }
	}
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值