类似元气骑士的九宫格随机地图实现

类似元气骑士的九宫格随机地图实现

主要思路

情况1.九宫格中随机选择7个方格出来必定可联通(可斜向联通)。
在这里插入图片描述
情况2.九宫格中随机选择7个方格出来大部分情况可联通,特殊情况时排除掉不可横纵联通那个方格(只可横纵联通)。
在这里插入图片描述

特殊情况:
在这里插入图片描述
该情况下红色格子无法与其他6个格子横纵联通。
解决方法:只联通6个黄色格子

文章将以情况2进行说明,情况1同理。

即先预制出一定数量的单个房间地图,然后随机选择7个方格去放置房间地图,再使用洗牌算法(非必须)保证选出的房间的不重复性,再使用四领域填充的洪水填充算法(BFS实现)进行房间格子的联通。

代码部分

1.洗牌算法:非必须,如果不介意格子会出现重复地图的可能性,可不需要使用。

   /// <summary>
    /// 洗牌算法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="_shuffleList">洗牌前list</param>
    /// <returns>洗牌后list</returns>
    public static List<T> ShuffleAlgotithm<T>(List<T> _shuffleList, int _startNum = 0)
    {
        for (int i = _startNum; i < _shuffleList.Count; i++)
        {
            int randomNum = UnityEngine.Random.Range(i, _shuffleList.Count);
            //Debug.Log(randomNum);
            T temp = _shuffleList[randomNum];
            _shuffleList[randomNum] = _shuffleList[i];
            _shuffleList[i] = temp;
        }
        return _shuffleList;
    }

2.洪水填充算法(BFS):

using System;
using System.Collections.Generic;
using UnityEngine;

public class MapGenerator : SingletonMono<MapGenerator> //这里只是通过继承实现的单例模式,删掉继承自己重写个单例模式就行
{
    [Header("MAP")]
    public float nineGridSpace; //地图间隔


    [Header("COMMON")]
    public bool previewLine;  //辅助线,可删
    public GameObject pointPrefab;  //辅助标记,可删

    [SerializeField] public List<MapData> actualGeneratorPointList = new List<MapData>();

    private List<MapData> generatorPointList = new List<MapData>();
    private MapData[] initialMap = new MapData[2]; //应对极端情况:设置一个备用点
    private bool[,] MapActive = new bool[3, 3]; //是否激活

    protected override void Awake()
    {
        base.Awake();

        MapBuild();

        MapConnect();//洪水填充,BFS
    }

    private void Start()
    {
        GameManager.GetInstance().MapFinal(nineGridSpace, actualGeneratorPointList);
    }


    private void MapConnect() //洪水填充主体部分
    {
        bool[,] MapFlag = new bool[3, 3]; //是否检测
        MapFlag[(int)initialMap[0].x, (int)initialMap[0].y] = true; //初始点检测过了

        Queue<MapData> queue = new Queue<MapData>();
        queue.Enqueue(initialMap[0]);  //初始点入队
        actualGeneratorPointList.Add(initialMap[0]); //实际点位,极端情况会和GeneratorPointList不一样

        while (queue.Count > 0)
        {
            MapData tempMap = queue.Dequeue();
            bool isInitialOne = tempMap.x == initialMap[0].x && tempMap.y == initialMap[0].y;
            for (int x = -1; x <= 1; x++) //获取相邻瓦片,四领域填充。左下上右
            {
                for (int y = -1; y <= 1; y++)
                {
                    int neightborX = tempMap.x + x;
                    int neightborY = tempMap.y + y;

                    if (x == 0 || y == 0)
                    {
                        if (neightborX >= 0 && neightborX < 3
                            && neightborY >= 0 && neightborY < 3)
                        {
                            if (MapFlag[neightborX, neightborY] == false && MapActive[neightborX, neightborY] == true) //没有检测过且已经激活
                            {
                                MapFlag[neightborX, neightborY] = true;
                                MapData mapData = new MapData(neightborX, neightborY);
                                queue.Enqueue(mapData); //添加到队列准备下次遍历
                                actualGeneratorPointList.Add(mapData);

                                if (previewLine)
                                {
                                    Debug.DrawLine(new Vector3(tempMap.x, tempMap.y, 0) * nineGridSpace, new Vector3(neightborX, neightborY, 0) * nineGridSpace, Color.green, 1000);
                                }
                            }
                        }

                        if (isInitialOne && queue.Count == 0)
                        {
                            if (x == 1 && y == 0) //四方向检测完毕
                            {
                                actualGeneratorPointList.RemoveAt(0); //移除第一初始点
                                actualGeneratorPointList.Add(initialMap[1]);  //放入第二初始点
                                MapFlag[(int)initialMap[1].x, (int)initialMap[1].y] = true; //初始点检测过了
                                queue.Enqueue(initialMap[1]);
                            }
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    /// 九宫格点,再选7个点生成地图
    /// </summary>
    private void MapBuild()
    {
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                generatorPointList.Add(new MapData(i, j));
            }
        }

        generatorPointList = UtilityTools.ShuffleAlgotithm<MapData>(generatorPointList);

        for (int i = 0; i < 7; i++)
        {
            MapData tempMap = generatorPointList[i];
            if(previewLine)
            {
                GameObject go = Instantiate(pointPrefab, new Vector3(tempMap.x, tempMap.y, 0) * nineGridSpace, Quaternion.identity); //画线用
                go.transform.SetParent(this.gameObject.transform);
                go.name = tempMap.x + "," + tempMap.y;
            }


            MapActive[(int)tempMap.x, (int)tempMap.y] = true;

            if (i <= 1) //将0号设置为第一初始点,1号生成第二初始点
            {
                initialMap[i] = tempMap;
            }
        }
    }

    [Serializable]
    public struct MapData
    {
        public int x;
        public int y;

        public MapData(int _x, int _y)
        {
            this.x = _x;
            this.y = _y;
        }
    }
}

3.管理脚本:

using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class GameManager : SingletonMono<GameManager>
{
    public List<Transform> realMapsList = new List<Transform>();
    public string mapsPath;
    public string startMapPath;
    public string bossMapPath;


    public void MapFinal(float _nineGridSpace, List<MapGenerator.MapData> _actualGeneratorPointList)
    {
        List<GameObject> mapPrefabs = new List<GameObject>(); //把地图资源加载到内存中,创建个public List直接拖地图也行

        GameObject[] FG = Resources.LoadAll<GameObject>(mapsPath); //动态加载的部分,直接拖也行
        foreach (var map in FG)
        {
            mapPrefabs.Add(map);
        }

        mapPrefabs = UtilityTools.ShuffleAlgotithm<GameObject>(mapPrefabs, 1); //调用洗牌

        GameObject go = Instantiate(Resources.Load<GameObject>(startMapPath));

        go.transform.position = new Vector3(_actualGeneratorPointList[0].x * _nineGridSpace, _actualGeneratorPointList[0].y * _nineGridSpace, 0);
        realMapsList.Add(go.transform);
        go.transform.SetParent(MapGenerator.GetInstance().gameObject.transform);

        for (int i = 1, j = _actualGeneratorPointList.Count; i < j-1; i++) //用加载得到的资源对象,实例化游戏对象,实现游戏物体的动态加载
        {
            //初始房间
            go = Instantiate(mapPrefabs[i % mapPrefabs.Count]);

            go.transform.position = new Vector3(_actualGeneratorPointList[i].x * _nineGridSpace, _actualGeneratorPointList[i].y * _nineGridSpace, 0);
            realMapsList.Add(go.transform);
            go.transform.SetParent(MapGenerator.GetInstance().gameObject.transform);
        }

        go = Instantiate(Resources.Load<GameObject>(bossMapPath));

        go.transform.position = new Vector3(_actualGeneratorPointList[_actualGeneratorPointList.Count-1].x * _nineGridSpace, _actualGeneratorPointList[_actualGeneratorPointList.Count - 1].y * _nineGridSpace, 0);
        realMapsList.Add(go.transform);
        go.transform.SetParent(MapGenerator.GetInstance().gameObject.transform);
    }
}

效果演示

在这里插入图片描述
在这里插入图片描述

补充

1.上述代码是我直接从自己的工程文件拖出来的,可能没法直接使用,但是稍微改改绝对能用的
2.上述方法宫格大一点应该也可行,但是由于算法的原因,过大的话效率会变低。
3.本人水平有限,写的不是很好,只是想分享分享个人浅薄的见解,恳请原谅,谢谢

参考

1.2D随机地图的生成
2.参考洪水填充算法实现绝对通畅的随机地图生成器

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值