【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏1(附项目源码)_unity 单词城堡垒游戏源码-程序员宅基地

技术标签: unity  游戏引擎  # 制作100个unity游戏  游戏  

先看看最终效果

在这里插入图片描述

前言

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

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

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

随机游走算法

新增

/// <summary>
/// 静态类,包含用于二维环境的程序化生成算法。
/// </summary>
public static class ProceduralGenerationAlgorithms
{
    
    /// <summary>
    /// 在二维空间中生成简单的随机行走路径。
    /// </summary>
    /// <param name="startPosition">行走的起始位置。</param>
    /// <param name="walkLength">行走步数。</param>
    /// <returns>表示所走路径的 Vector2Int 的 HashSet。</returns>
    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;
    }
}

/// <summary>
/// 静态类,包含二维方向性工具。
/// </summary>
public static class Direction2D
{
    
    /// <summary>
    /// 二维空间中基本方向的列表。
    /// </summary>
    public static List<Vector2Int> cardinalDirectionsList = new List<Vector2Int>
    {
    
        new Vector2Int(0,1), //上
        new Vector2Int(1,0), //右
        new Vector2Int(0, -1), // 下
        new Vector2Int(-1, 0) //左
    };
    
    /// <summary>
    /// 从列表中返回一个随机的基本方向。
    /// </summary>
    /// <returns>表示随机基本方向的 Vector2Int。</returns>
    public static Vector2Int GetRandomCardinalDirection()
    {
    
        return cardinalDirectionsList[UnityEngine.Random.Range(0, cardinalDirectionsList.Count)];
    }
}

使用随机游走算法

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

/// <summary>
/// 用于生成简单随机行走地牢的类,继承自 MonoBehaviour。
/// </summary>
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;

    /// <summary>
    /// 执行程序化生成地牢的方法。
    /// </summary>
    public void RunProceduralGeneration()
    {
    
        HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
        foreach (var position in floorPositions)
        {
    
            Debug.Log(position); // 输出地板坐标信息
        }
    }

    /// <summary>
    /// 执行随机行走算法生成地牢地板坐标的方法。
    /// </summary>
    /// <returns>地牢地板坐标的 HashSet。</returns>
    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. 新增TilemapVisualizer,用于可视化地图

/// <summary>
/// 用于可视化地图的 TilemapVisualizer 类,继承自 MonoBehaviour。
/// </summary>
public class TilemapVisualizer : MonoBehaviour
{
    
    [SerializeField]
    private Tilemap floorTilemap; // 地板瓦片地图
    [SerializeField]
    private TileBase floorTile; // 地板瓦片

    /// <summary>
    /// 绘制地板瓦片的方法。
    /// </summary>
    /// <param name="floorPositions">地板位置的坐标集合。</param>
    public void PaintFloorTiles(IEnumerable<Vector2Int> floorPositions)
    {
    
        PaintTiles(floorPositions, floorTilemap, floorTile);
    }

    /// <summary>
    /// 绘制瓦片的方法。
    /// </summary>
    /// <param name="positions">瓦片位置的坐标集合。</param>
    /// <param name="tilemap">瓦片地图。</param>
    /// <param name="tile">要绘制的瓦片。</param>
    private void PaintTiles(IEnumerable<Vector2Int> positions, Tilemap tilemap, TileBase tile)
    {
    
        foreach (var position in positions)
        {
    
            PaintSingleTile(tilemap, tile, position);
        }
    }

    /// <summary>
    /// 绘制单个瓦片的方法。
    /// </summary>
    /// <param name="tilemap">瓦片地图。</param>
    /// <param name="tile">要绘制的瓦片。</param>
    /// <param name="position">瓦片的位置坐标。</param>
    private void PaintSingleTile(Tilemap tilemap, TileBase tile, Vector2Int position)
    {
    
        var tilePosition = tilemap.WorldToCell((Vector3Int)position); // 将位置坐标转换为瓦片地图上的单元格坐标
        tilemap.SetTile(tilePosition, tile); // 在指定位置绘制瓦片
    }
    
	//  清空瓦片地图
    public void Clear()
    {
    
        floorTilemap.ClearAllTiles();
    }
}

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

[SerializeField]
private TilemapVisualizer tilemapVisualizer;

/// <summary>
/// 执行程序化生成地牢的方法。
/// </summary>
public void RunProceduralGeneration()
{
    
    HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
    tilemapVisualizer.Clear();//  清空瓦片地图
    tilemapVisualizer.PaintFloorTiles(floorPositions);
}

2. 瓦片素材

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

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

效果
在这里插入图片描述

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

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

1. 先简单重构代码

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

/// <summary>
/// 抽象地牢生成器的基类,继承自 MonoBehaviour。
/// </summary>
public abstract class AbstractDungeonGenerator : MonoBehaviour
{
    
    [SerializeField, Header("瓦片可视化器")]
    protected TilemapVisualizer tilemapVisualizer = null;
    [SerializeField, Header("地牢生成的起始位置")]
    protected Vector2Int startPosition = Vector2Int.zero;

    /// <summary>
    /// 生成地牢的方法。
    /// </summary>
    public void GenerateDungeon()
    {
    
        tilemapVisualizer.Clear(); // 清空瓦片可视化器
        RunProceduralGeneration(); // 执行程序化生成
    }

    /// <summary>
    /// 执行程序化生成地牢的抽象方法,需要在子类中实现具体逻辑。
    /// </summary>
    protected abstract void RunProceduralGeneration();
}

修改SimpleRandomWalkDungeonGenerator

public class SimpleRandomWalkDungeonGenerator : AbstractDungeonGenerator
{
    
	//。。。
	
	// [SerializeField, Header("地牢生成的起始位置")]
    // protected Vector2Int startPosition = Vector2Int.zero;
    // [SerializeField]
    // private TilemapVisualizer tilemapVisualizer;
    
	/// <summary>
   	/// 执行程序化生成地牢的方法。
    /// </summary>
    protected override void RunProceduralGeneration()
    {
    
        HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
        tilemapVisualizer.Clear();
        tilemapVisualizer.PaintFloorTiles(floorPositions);
    }

	//。。。
}

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

2. 新增Editor脚本RandomDungeonGeneratorEditor

在这里插入图片描述

/// <summary>
/// 自定义编辑器类,用于 RandomDungeonGenerator。
/// </summary>
[CustomEditor(typeof(AbstractDungeonGenerator), true)]
public class RandomDungeonGeneratorEditor : Editor
{
    
    private AbstractDungeonGenerator generator; // 地牢生成器对象

    private void Awake()
    {
    
        generator = (AbstractDungeonGenerator)target; // 获取目标对象并转换为地牢生成器类型
    }

    /// <summary>
    /// 在 Inspector 窗口中绘制自定义的 GUI。
    /// </summary>
    public override void OnInspectorGUI()
    {
    
        base.OnInspectorGUI(); // 绘制基类的默认 Inspector 界面

        if (GUILayout.Button("Create Dungeon")) // 创建地牢的按钮
        {
    
            generator.GenerateDungeon(); // 调用地牢生成器的方法生成地牢
        }
    }
}

效果
在这里插入图片描述

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

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

新增SimpleRandomWalkSO

/// <summary>
/// 简单随机行走数据的 ScriptableObject 类。
/// </summary>
[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;
}

2. 配置不同的地形参数

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

3. 调用

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

[SerializeField]
private SimpleRandomWalkSO  randomWalkParameters;

4. 效果

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

在这里插入图片描述

生成墙壁

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

/// <summary>
/// 墙体生成器的静态类。
/// </summary>
public static class WallGenerator
{
    
    /// <summary>
    /// 创建墙体的方法。
    /// </summary>
    /// <param name="floorPositions">地板位置的集合</param>
    /// <param name="tilemapVisualizer">瓦片可视化器</param>
    public static void CreateWalls(HashSet<Vector2Int> floorPositions, TilemapVisualizer tilemapVisualizer)
    {
    
        var basicWallPositions = FindWallsInDirections(floorPositions, Direction2D.cardinalDirectionsList);

        // 在每个墙体位置上绘制基本墙体
        foreach (var position in basicWallPositions)
        {
    
            tilemapVisualizer.PaintSingleBasicWall(position);
        }
    }

    /// <summary>
    /// 在指定方向上查找墙体的方法。
    /// </summary>
    /// <param name="floorPositions">地板位置的集合</param>
    /// <param name="directionList">方向列表</param>
    /// <returns>墙体位置的集合</returns>
    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;
    }
}

修改TilemapVisualizer

[SerializeField, Header("墙壁瓦片地图")]
private Tilemap wallTilemap;
[SerializeField, Header("墙壁瓦片")]
private TileBase wallTop;

//绘制墙壁瓦片的方法
internal void PaintSingleBasicWall(Vector2Int position)
{
    
    PaintSingleTile(wallTilemap, wallTop, position);
}

修改SimpleRandomWalkDungeonGenerator

/// <summary>
/// 执行程序化生成地牢的方法。
/// </summary>
protected override void RunProceduralGeneration()
{
    
    HashSet<Vector2Int> floorPositions = RunRandomWalk(); // 获取地牢地板坐标集合
    tilemapVisualizer.PaintFloorTiles(floorPositions);//绘制地板瓦片
    WallGenerator.CreateWalls(floorPositions, tilemapVisualizer);//创建墙体
}

//  清空瓦片地图
public void Clear()
{
    
    floorTilemap.ClearAllTiles();
    wallTilemap.ClearAllTiles();
}

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

补充

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

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

源码

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

完结

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

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

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_36303853/article/details/134511858

智能推荐

一个简单易用的 Android 导航栏TitleBar_android 导航 title bar-程序员宅基地

文章浏览阅读255次。关注微信号:javalearns 随时随地学Java或扫一扫随时随地学Java一个简单易用的导航栏TitleBar,可以轻松实现IOS导航栏的各种效果整个代码全部集中在TitleBar.java中,所有控件都动态生成,动态布局。不需要引用任何资源文件,拷贝TitleBar.java到自己工程即可使用1. 左边文字,左边返回_android 导航 title bar

Android开发最强模拟器Genymotion的安装及使用教程_genymotion下载教程-程序员宅基地

文章浏览阅读4.9k次,点赞4次,收藏18次。Android开发最强模拟器Genymotion的安装及使用教程一.下载Genymotion软件官网传送门1.登录2.新建账户3.填写信息4.会受到一封邮件,然后点进去5.下载6.下载完成后,双击运行程序7.点击下一步8.点击安装9.装好之后,会自动进行VirtualBox安装10.默认选择,点击下一步11.点击是12.三个都安装13.用刚才注..._genymotion下载教程

reinterpret_cast<T>() static_cast<T>() const_cast<T>() dynamic_cast<T>()_const_pointer_cast<t>() 用法-程序员宅基地

文章浏览阅读592次。reinterpret_cast()从指针类型到一个足够大的整数类型从整数类型或者枚举类型到指针类型从一个指向函数的指针到另一个不同类型的指向函数的指针从一个指向对象的指针到另一个不同类型的指向对象的指针从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针以在任意指针之间进行互相转换,即_const_pointer_cast() 用法

cv2.polylines()-程序员宅基地

文章浏览阅读1.9w次,点赞11次,收藏56次。前言在遥感目标检测中,由于DOTA数据集本身类别的样本数量是极度不均衡的;所以,使用合适的数据增强,可以在一定程度上提升模型的最终检测性能。而在之前的一篇博客中,我提到过使用随机旋转的方法进行增强,但是代码运行完毕之后最好还是需要进行可视化操作验证更为稳妥,所以便有了cv2.polylines这一函数的出现,该函数可以画任意的多边形。正文这里使用python版本的函数举例,可以发现该函数所需要的参数主要为polylines(img, pts, isClosed, color, thickness=N_cv2.polylines

如何使用Git将本地项目推送至代码托管平台?【Gitee、GitLab、GitHub】_怎么把文件交给代码托管平台-程序员宅基地

文章浏览阅读6k次,点赞8次,收藏6次。如何使用Git将本地项目推送至代码托管平台?【Gitee、GitLab、GitHub】_怎么把文件交给代码托管平台

【C1N短网址】微信分享卡片来了-程序员宅基地

文章浏览阅读59次。按如下格式发送消息到公众号,公众号自动回复微信卡片,然后就可以分享啦。关注微信公众号“C1N短网址服务”免费薅。如果你需要自己指定图片,则可以增加第四行。

随便推点

Postgresql系统表pg_attribute_pg_attribute表-程序员宅基地

文章浏览阅读9.6k次。该系统表存储所有表(包括系统表,如pg_class)的字段信息。数据库中的每个表的每个字段在pg_attribute表中都有一行记录。名字类型引用描述attrelidoidpg_class.oid此字段所属的表。attnamename字段名。atttypidoidpg_type.oid字段的数据类型。attstattargetint4attstattarget控制ANALYZE为这个..._pg_attribute表

VUE+webrtc-streamer 实现实时视频播放(监控设备-rtsp)_webrtcstreamer-程序员宅基地

文章浏览阅读3k次,点赞2次,收藏5次。VUE+webrtc-streamer 实现实时视频播放(监控设备-rtsp)_webrtcstreamer

uni-app开发APP上架Apple Store流程记录_uniapp ios applist-程序员宅基地

文章浏览阅读2.9k次,点赞2次,收藏26次。文件名称为“CertificateSigningRequest.certSigningRequest”,选择保存位置,点击 “存储” 将证书请求文件保存到指定路径下,后面申请开发(Development)证书和发布(Production)证书时需要用到。至此,我们已经完成了开发证书的制作(得到了 xxx.p12 证书文件),接下来,继续生成开发阶段所需的描述文件,在生成描述文件之前,需要先添加调试设备(iPhone 、iPad)发布证书打包的 ipa,不可以直接安装到手机上。_uniapp ios applist

el-table格式化el-table-column内容_el-table-column 格式化-程序员宅基地

文章浏览阅读1.2k次。对于格式化,有三种方法:template scope、formatter一、template scope + v-if判断<el-table-column prop="sex" label="性别"> <template slot-scope="scope"> <span v-if="scope.row.sex== 0">男</span> <span v-if="scope.row.sex== 1">女_el-table-column 格式化

Qt知识点汇总——来自网络-程序员宅基地

文章浏览阅读1.3k次。为什么80%的码农都做不了架构师?>>> ..._toutf8和fromutf8

pycharm远程连接linux服务器上的jupyter-程序员宅基地

文章浏览阅读173次。首先得确保linux上安装了jupyter,并能启动服务,在linux命令行输入: jupyter notebook启动后效果。

推荐文章

热门文章

相关标签