• 【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏1(附项目源码)


    先看看最终效果

    在这里插入图片描述

    前言

    关于使用TileMap生成随机2D地图,其实之前已经有做过类似的,感兴趣可以看看:
    【unity实战】随机地下城生成
    【unity小技巧】Unity2D TileMap+柏林噪声生成随机地图

    但是随着学习深入,发现之前做的比较粗糙和不够全面,最近又在外网看到一个程序化生成2D地牢的视频,觉得不错,所以写了这一篇学习笔记,记录分享一下。

    本项目可能比较长,会分几期来讲,感兴趣的可以关注一下,方便获取后续内容,本期主要是使用随机游走算法生成随机的地牢房间。

    随机游走算法

    新增

    /// 
    /// 静态类,包含用于二维环境的程序化生成算法。
    /// 
    public static class ProceduralGenerationAlgorithms
    {
        /// 
        /// 在二维空间中生成简单的随机行走路径。
        /// 
        /// 行走的起始位置。
        /// 行走步数。
        /// 表示所走路径的 Vector2Int 的 HashSet。
        public static HashSet<Vector2Int> SimpleRandomWalk(Vector2Int startPosition, int walkLength)
        {
            // 创建路径 HashSet
            HashSet<Vector2Int> path = new HashSet<Vector2Int>();
    
            // 将起始位置添加到路径 HashSet 中
            path.Add(startPosition);
    
            // 将当前位置设置为起始位置
            var previousPosition = startPosition;
    
            // 沿着随机方向移动,将每个新位置添加到路径 HashSet 中
            for (int i = 0; i < walkLength; i++)
            {
                var newPosition = previousPosition + Direction2D.GetRandomCardinalDirection();
                path.Add(newPosition);
                previousPosition = newPosition;
            }
    
            // 返回路径 HashSet
            return path;
        }
    }
    
    /// 
    /// 静态类,包含二维方向性工具。
    /// 
    public static class Direction2D
    {
        /// 
        /// 二维空间中基本方向的列表。
        /// 
        public static List<Vector2Int> cardinalDirectionsList = new List<Vector2Int>
        {
            new Vector2Int(0,1), //上
            new Vector2Int(1,0), //右
            new Vector2Int(0, -1), // 下
            new Vector2Int(-1, 0) //左
        };
        
        /// 
        /// 从列表中返回一个随机的基本方向。
        /// 
        /// 表示随机基本方向的 Vector2Int。
        public static Vector2Int GetRandomCardinalDirection()
        {
            return cardinalDirectionsList[UnityEngine.Random.Range(0, cardinalDirectionsList.Count)];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    使用随机游走算法

    新增SimpleRandomWalkDungeonGenerator, 用于生成简单随机行走地牢的类

    /// 
    /// 用于生成简单随机行走地牢的类,继承自 MonoBehaviour。
    /// 
    public class SimpleRandomWalkDungeonGenerator : MonoBehaviour
    {
        [SerializeField, Header("地牢生成的起始位置")]
        protected Vector2Int startPosition = Vector2Int.zero;
        [SerializeField, Header("迭代次数")]
        private int iterations = 10;
        [SerializeField, Header("每次行走的步数")]
        public int walkLength = 10;
        [SerializeField, Header("每次迭代是否随机起始位置")]
        public bool startRandomlyEachIteration = true;
    
        /// 
        /// 执行程序化生成地牢的方法。
        /// 
        public void RunProceduralGeneration()
        {
            HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
            foreach (var position in floorPositions)
            {
                Debug.Log(position); // 输出地板坐标信息
            }
        }
    
        /// 
        /// 执行随机行走算法生成地牢地板坐标的方法。
        /// 
        /// 地牢地板坐标的 HashSet。
        protected HashSet<Vector2Int> RunRandomWalk()
        {
            var currentPosition = startPosition; // 当前位置初始化为起始位置
            HashSet<Vector2Int> floorPositions = new HashSet<Vector2Int>(); // 地板坐标的 HashSet
            for (int i = 0; i < iterations; i++)
            {
                var path = ProceduralGenerationAlgorithms.SimpleRandomWalk(currentPosition, walkLength); // 生成随机行走路径
                floorPositions.UnionWith(path); // 将路径添加到地板坐标集合中
                if (startRandomlyEachIteration)
                {
                    currentPosition = floorPositions.ElementAt(Random.Range(0, floorPositions.Count)); // 如果需要每次迭代随机起始位置,则随机选择一个已生成的位置
                }
            }
            return floorPositions; // 返回地板坐标集合
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    挂载脚本
    在这里插入图片描述
    配置点击事件
    在这里插入图片描述

    效果
    在这里插入图片描述

    添加地板瓦片

    1. 新增TilemapVisualizer,用于可视化地图

    /// 
    /// 用于可视化地图的 TilemapVisualizer 类,继承自 MonoBehaviour。
    /// 
    public class TilemapVisualizer : MonoBehaviour
    {
        [SerializeField]
        private Tilemap floorTilemap; // 地板瓦片地图
        [SerializeField]
        private TileBase floorTile; // 地板瓦片
    
        /// 
        /// 绘制地板瓦片的方法。
        /// 
        /// 地板位置的坐标集合。
        public void PaintFloorTiles(IEnumerable<Vector2Int> floorPositions)
        {
            PaintTiles(floorPositions, floorTilemap, floorTile);
        }
    
        /// 
        /// 绘制瓦片的方法。
        /// 
        /// 瓦片位置的坐标集合。
        /// 瓦片地图。
        /// 要绘制的瓦片。
        private void PaintTiles(IEnumerable<Vector2Int> positions, Tilemap tilemap, TileBase tile)
        {
            foreach (var position in positions)
            {
                PaintSingleTile(tilemap, tile, position);
            }
        }
    
        /// 
        /// 绘制单个瓦片的方法。
        /// 
        /// 瓦片地图。
        /// 要绘制的瓦片。
        /// 瓦片的位置坐标。
        private void PaintSingleTile(Tilemap tilemap, TileBase tile, Vector2Int position)
        {
            var tilePosition = tilemap.WorldToCell((Vector3Int)position); // 将位置坐标转换为瓦片地图上的单元格坐标
            tilemap.SetTile(tilePosition, tile); // 在指定位置绘制瓦片
        }
        
    	//  清空瓦片地图
        public void Clear()
        {
            floorTilemap.ClearAllTiles();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    修改SimpleRandomWalkDungeonGenerator,执行程序化生成地牢的方法

    [SerializeField]
    private TilemapVisualizer tilemapVisualizer;
    
    /// 
    /// 执行程序化生成地牢的方法。
    /// 
    public void RunProceduralGeneration()
    {
        HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
        tilemapVisualizer.Clear();//  清空瓦片地图
        tilemapVisualizer.PaintFloorTiles(floorPositions);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2. 瓦片素材

    https://pixel-poem.itch.io/dungeon-assetpuck
    在这里插入图片描述

    挂载脚本,配置参数
    在这里插入图片描述
    在这里插入图片描述

    效果
    在这里插入图片描述

    不运行执行程序化生成地牢方法

    每次测试都要运行程序再执行生成地图,非常的麻烦,我们可以实现不运行也可以执行程序化生成地牢的方法

    1. 先简单重构代码

    新增AbstractDungeonGenerator,定义抽象地牢生成器的基类

    /// 
    /// 抽象地牢生成器的基类,继承自 MonoBehaviour。
    /// 
    public abstract class AbstractDungeonGenerator : MonoBehaviour
    {
        [SerializeField, Header("瓦片可视化器")]
        protected TilemapVisualizer tilemapVisualizer = null;
        [SerializeField, Header("地牢生成的起始位置")]
        protected Vector2Int startPosition = Vector2Int.zero;
    
        /// 
        /// 生成地牢的方法。
        /// 
        public void GenerateDungeon()
        {
            tilemapVisualizer.Clear(); // 清空瓦片可视化器
            RunProceduralGeneration(); // 执行程序化生成
        }
    
        /// 
        /// 执行程序化生成地牢的抽象方法,需要在子类中实现具体逻辑。
        /// 
        protected abstract void RunProceduralGeneration();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    修改SimpleRandomWalkDungeonGenerator

    public class SimpleRandomWalkDungeonGenerator : AbstractDungeonGenerator
    {
    	//。。。
    	
    	// [SerializeField, Header("地牢生成的起始位置")]
        // protected Vector2Int startPosition = Vector2Int.zero;
        // [SerializeField]
        // private TilemapVisualizer tilemapVisualizer;
        
    	/// 
       	/// 执行程序化生成地牢的方法。
        /// 
        protected override void RunProceduralGeneration()
        {
            HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
            tilemapVisualizer.Clear();
            tilemapVisualizer.PaintFloorTiles(floorPositions);
        }
    
    	//。。。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    修改点击事件
    在这里插入图片描述
    运行测试,一切正常
    在这里插入图片描述

    2. 新增Editor脚本RandomDungeonGeneratorEditor

    在这里插入图片描述

    /// 
    /// 自定义编辑器类,用于 RandomDungeonGenerator。
    /// 
    [CustomEditor(typeof(AbstractDungeonGenerator), true)]
    public class RandomDungeonGeneratorEditor : Editor
    {
        private AbstractDungeonGenerator generator; // 地牢生成器对象
    
        private void Awake()
        {
            generator = (AbstractDungeonGenerator)target; // 获取目标对象并转换为地牢生成器类型
        }
    
        /// 
        /// 在 Inspector 窗口中绘制自定义的 GUI。
        /// 
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI(); // 绘制基类的默认 Inspector 界面
    
            if (GUILayout.Button("Create Dungeon")) // 创建地牢的按钮
            {
                generator.GenerateDungeon(); // 调用地牢生成器的方法生成地牢
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    效果
    在这里插入图片描述

    将参数保存到可编辑脚本对象(ScriptableObject)

    1. 定义简单随机行走数据的 ScriptableObject 类

    新增SimpleRandomWalkSO

    /// 
    /// 简单随机行走数据的 ScriptableObject 类。
    /// 
    [CreateAssetMenu(fileName = "SimpleRandomWalkParameters_", menuName = "PCG/SimpleRandomWalkData")]
    public class SimpleRandomWalkSO : ScriptableObject
    {
        [Header("迭代次数")]
        public int iterations = 10;
        [Header("每次行走的步数")]
        public int walkLength = 10;
        [Header("每次迭代是否随机起点")]
        public bool startRandomlyEachIteration = true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2. 配置不同的地形参数

    配置不同的参数
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    3. 调用

    修改SimpleRandomWalkDungeonGenerator,调用前面定义的参数

    [SerializeField]
    private SimpleRandomWalkSO  randomWalkParameters;
    
    • 1
    • 2

    4. 效果

    大地牢
    在这里插入图片描述
    小地牢
    在这里插入图片描述

    在这里插入图片描述

    生成墙壁

    新增WallGenerator,墙体生成器的静态类

    /// 
    /// 墙体生成器的静态类。
    /// 
    public static class WallGenerator
    {
        /// 
        /// 创建墙体的方法。
        /// 
        /// 地板位置的集合
        /// 瓦片可视化器
        public static void CreateWalls(HashSet<Vector2Int> floorPositions, TilemapVisualizer tilemapVisualizer)
        {
            var basicWallPositions = FindWallsInDirections(floorPositions, Direction2D.cardinalDirectionsList);
    
            // 在每个墙体位置上绘制基本墙体
            foreach (var position in basicWallPositions)
            {
                tilemapVisualizer.PaintSingleBasicWall(position);
            }
        }
    
        /// 
        /// 在指定方向上查找墙体的方法。
        /// 
        /// 地板位置的集合
        /// 方向列表
        /// 墙体位置的集合
        private static HashSet<Vector2Int> FindWallsInDirections(HashSet<Vector2Int> floorPositions, List<Vector2Int> directionList)
        {
            HashSet<Vector2Int> wallPositions = new HashSet<Vector2Int>();
    
            foreach (var position in floorPositions)
            {
                foreach (var direction in directionList)
                {
                    var neighbourPosition = position + direction;
    
                    // 如果邻居位置不在地板位置集合中,则认为是墙体位置
                    if (!floorPositions.Contains(neighbourPosition))
                    {
                        wallPositions.Add(neighbourPosition);
                    }
                }
            }
    
            return wallPositions;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    修改TilemapVisualizer

    [SerializeField, Header("墙壁瓦片地图")]
    private Tilemap wallTilemap;
    [SerializeField, Header("墙壁瓦片")]
    private TileBase wallTop;
    
    //绘制墙壁瓦片的方法
    internal void PaintSingleBasicWall(Vector2Int position)
    {
        PaintSingleTile(wallTilemap, wallTop, position);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    修改SimpleRandomWalkDungeonGenerator

    /// 
    /// 执行程序化生成地牢的方法。
    /// 
    protected override void RunProceduralGeneration()
    {
        HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
        tilemapVisualizer.PaintFloorTiles(floorPositions);//绘制地板瓦片
        WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);//创建墙体
    }
    
    //  清空瓦片地图
    public void Clear()
    {
        floorTilemap.ClearAllTiles();
        wallTilemap.ClearAllTiles();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    配置参数
    在这里插入图片描述
    效果
    在这里插入图片描述

    补充

    想要优化墙壁的显示,可以选择使用rule tile绘制墙壁内容,不懂得可以看我这篇文章,写的比较详细:【Unity小技巧】Unity2D TileMap的探究

    还不懂的也可以看我后面的文章,后面会讲到

    源码

    源码会放在本项目最后一篇

    完结

    赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,以便我第一时间收到反馈,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

    好了,我是向宇https://xiangyu.blog.csdn.net

    一位在小公司默默奋斗的开发者,出于兴趣爱好,于是最近才开始自习unity。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
    在这里插入图片描述

  • 相关阅读:
    Web APIs 学习归纳1---Web API概述&简单的元素获取
    echarts折线图如何设置X轴的起始和末端刻度范围
    水果店圈子:小型水果店用什么保鲜,经营水果店的保鲜方法
    C++11 lambda表达式 已完成未发布
    CustomTkinter:创建现代、可定制的Python UI
    华清远见-html-js-jquery学习总结
    文档课堂作业1
    Android 扫码枪输入时屏蔽软键盘和顶部状态栏
    组合预测 | MATLAB实现基于BP-Adaboost强分类器多特征分类预测
    国际版腾讯云/阿里云:云解析DNS是什么
  • 原文地址:https://blog.csdn.net/qq_36303853/article/details/134511858