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

2023-11-20 20:04

本文主要是介绍【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏1(附项目源码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 先看看最终效果
  • 前言
  • 随机游走算法
  • 使用随机游走算法
  • 添加地板瓦片
    • 1. 新增TilemapVisualizer,用于可视化地图
    • 2. 瓦片素材
  • 不运行执行程序化生成地牢方法
    • 1. 先简单重构代码
    • 2. 新增Editor脚本RandomDungeonGeneratorEditor
  • 将参数保存到可编辑脚本对象(ScriptableObject)
    • 1. 定义简单随机行走数据的 ScriptableObject 类
    • 2. 配置不同的地形参数
    • 3. 调用
    • 4. 效果
  • 生成墙壁
  • 补充
  • 源码
  • 完结

先看看最终效果

在这里插入图片描述

前言

关于使用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){// 创建路径 HashSetHashSet<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;}// 返回路径 HashSetreturn 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>(); // 地板坐标的 HashSetfor (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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

这篇关于【用unity实现100个游戏之16】Unity程序化生成随机2D地牢游戏1(附项目源码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


原文地址:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.chinasem.cn/article/397109

相关文章

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

Python使用python-can实现合并BLF文件

《Python使用python-can实现合并BLF文件》python-can库是Python生态中专注于CAN总线通信与数据处理的强大工具,本文将使用python-can为BLF文件合并提供高效灵活... 目录一、python-can 库:CAN 数据处理的利器二、BLF 文件合并核心代码解析1. 基础合

Python使用OpenCV实现获取视频时长的小工具

《Python使用OpenCV实现获取视频时长的小工具》在处理视频数据时,获取视频的时长是一项常见且基础的需求,本文将详细介绍如何使用Python和OpenCV获取视频时长,并对每一行代码进行深入解析... 目录一、代码实现二、代码解析1. 导入 OpenCV 库2. 定义获取视频时长的函数3. 打开视频文

golang版本升级如何实现

《golang版本升级如何实现》:本文主要介绍golang版本升级如何实现问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录golanwww.chinasem.cng版本升级linux上golang版本升级删除golang旧版本安装golang最新版本总结gola

SpringBoot中SM2公钥加密、私钥解密的实现示例详解

《SpringBoot中SM2公钥加密、私钥解密的实现示例详解》本文介绍了如何在SpringBoot项目中实现SM2公钥加密和私钥解密的功能,通过使用Hutool库和BouncyCastle依赖,简化... 目录一、前言1、加密信息(示例)2、加密结果(示例)二、实现代码1、yml文件配置2、创建SM2工具