技术标签: 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; // 返回地板坐标集合
}
}
挂载脚本
配置点击事件
效果
/// <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);
}
https://pixel-poem.itch.io/dungeon-assetpuck
挂载脚本,配置参数
效果
每次测试都要运行程序再执行生成地图,非常的麻烦,我们可以实现不运行也可以执行程序化生成地牢的方法
新增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);
}
//。。。
}
修改点击事件
运行测试,一切正常
/// <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(); // 调用地牢生成器的方法生成地牢
}
}
}
效果
新增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;
}
配置不同的参数
修改SimpleRandomWalkDungeonGenerator,调用前面定义的参数
[SerializeField]
private SimpleRandomWalkSO randomWalkParameters;
大地牢
小地牢
岛
新增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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
文章浏览阅读475次。请求参数:num_iid=0070517287/000000012389328898。参数说明:num_iid:店铺ID/商品ID。异常示例相关资料错误码解释。_props_name
文章浏览阅读1k次。用break语句智能跳出本层循环,但是有的时候要求跳出两层或者更多层的循环该如何处理?我想一般有两种办法。第一种办法就是使用goto语句,这样的话可以进行大范围的转移,跳出多少层循环都是可以的。但是众所周知,goto语句被大多数程序员所抵触,因为它违反了结构化?第二种办法,我想就比较麻烦一点,就是在跳出之前设定一个标志(比如说给一个bool型的变量flag赋值),然后在第二层循环中判断,选择跳出第_跳出标记循环的优劣
文章浏览阅读2.9k次,点赞30次,收藏33次。ipmitool命令详解_ipmitool
文章浏览阅读1.1k次,点赞18次,收藏25次。DISKPART> select disk #默认一块磁盘这里为 0,即:select disk 0DISKPART> select partition #即:select partition 4。_windows server2022 c盘扩容
文章浏览阅读7.5k次,点赞4次,收藏7次。最近做毕设,需要用到一个国外的数据集LANL异常检测数据集。本来以为不过是下载个数据集,能有多麻烦,结果自己下载的时候差点没被整吐。当然这也跟我平时不怎么关注那些下载提速用的软件和插件有关。后来同学跟我推荐了IDM这个加速器,下载的时候也耗费了一些神气。因为网上的相关博客很多,但是很多博客只是介绍IDM安装过程的其中一部分。所以尽管看了很多篇博客,还是遇到了一些问题,主要应该是网上大部分都是直接..._idm怎么火绒扩展
文章浏览阅读1.9k次。ndk build fastrtps_fast-rtps-1.5.0-1
文章浏览阅读2.5k次。解决ERROR: Cause: invalid type code: 2D自己在新建Kotlin工程文件时遇见了这个错误,通过在build.gradle(Project) 中添加了第三方maven解决了这个错误。 maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } maven { url "https://jitpack.io" }build.gradle(Project) 代码:/_android studio cause: invalid type code: b6
文章浏览阅读927次。小程序音视频性能测试_环境搭建一.逻辑视图二.环境资源准备测试环境:梳理出所有相关的服务器和涉及到的测试工具,并估算出需要的测试资源量。如:软硬件(外网)作用 数量小程序后台服务器 连接业务支撑数据库,校验小程序的用户等; 1Web代理服务器 消息转发和音频编解码,音视频转发 3Zookeeper服务 Zookeeper处理代理,使多台代理不会重复去会议服务器取流 1Mysql..._音视频代理 测试方法
文章浏览阅读185次。HDU-2546 01背包问题题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2546题意:这其实就是一个简单地01背包问题关于01背包:有N件物品和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。每个物品只能往背包中装最多一次的操作;注意:因为这个题要求我们要使最后的饭卡的钱最小,我..._0_10_饭卡01背包
文章浏览阅读140次。题干给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。注意:答案中不可以包含重复的四元组。示例:给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。满足要求的四元组集合为:[ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2]]_给定一个包含n个整数的数组nums和一个目标值target java
文章浏览阅读78次。大学毕业也有一个多月了,你找到工作了吗?周围同学陆陆续续都有了归宿,自己却还在迷茫无措、醉生梦死当鸵鸟,你甘心吗?或许还有考研失利,工作被虐,毕业遭遇分手季,你满腔的洪荒之力找不到拼搏的出口?何不来试试学习传智播客UI设计? 不逼自己一把,你永远不知道自己有多优秀。4个半月,锻造...
文章浏览阅读2.4k次。慕客网标明的用法是适用type为text和passswod的当我input框设置type为number的时候怎么限制输入长度呢?使用outinput就可以啦<input type="number" name="phone" id="phone" value="phone" oninput="if(value.length>11)value=value.slice(0,11)"_html inputy=number 限制长度没用