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

news2024/11/14 18:29:27

文章目录

  • 先看看最终效果
  • 前言
  • 随机游走算法
  • 使用随机游走算法
  • 添加地板瓦片
    • 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)
    {
        // 创建路径 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。如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我可能也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信搜一搜有什么意想不到的功能?

其实我们每天都在用的微信&#xff0c;还有很多你意想不到的功能&#xff0c;像是一些每天我们都会看到&#xff0c;但是却不常使用的功能。 “搜一搜”这个功能&#xff0c;其实它已经上线很久了&#xff0c;它不仅是一个搜索入口&#xff0c;还是非常强大的聚合性服务。 生僻…

Navicat DML 操作

在表格种插入 列信息 -- 修改数据 update 表名 set 列名 值1, 列名值2,[where 条件]; -- 注意&#xff1a;如果update语句没有加where 表里对应行的全部信息都会被改; -- 删除数据 delecte from 表名 [where 条件]; 未删除前&#xff1a; 执行删除后为&#xff1a; DQL - 条…

OSCP系列靶场-Esay-DC-1

目录 总结 准备工作 信息收集-端口扫描 目标开放端口收集 目标端口对应服务探测 信息收集-端口测试 22-SSH端口的信息收集 22-SSH端口版本信息与MSF利用(pass) 22-SSH手动登录尝试(失败) 22-SSH弱口令爆破(爆破着玩) 80-HTTP端口的信息收集 信息收集-网站指纹 漏洞…

pikachu靶场-暴力破解攻略

pikachu暴力破解 基于表单的暴力破解 抓包发送到intruder 添加两个变量 下图攻击模式需要选择cluster bomb 用户名处添加几个常见的用户名 密码处则添加密码字典 如图可见有一条密码已经爆出 登录成功 验证码绕过(on server) 输入验证码后提交 抓包 然后发送到repeater先…

【前端学java】java中的日期操作(12)

往期回顾&#xff1a; 【前端学java】JAVA开发的依赖安装与环境配置 &#xff08;0&#xff09;【前端学 java】java的基础语法&#xff08;1&#xff09;【前端学java】JAVA中的packge与import&#xff08;2&#xff09;【前端学java】面向对象编程基础-类的使用 &#xff08…

蓝桥杯每日一题2023.11.20

题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 方法一&#xff1a;暴力枚举&#xff0c;如果说数字不在正确的位置上也就意味着这个数必须要改变&#xff0c;进行改变记录即可 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, a[N], …

江苏专转本考试时,遇到不会的题目该怎么办呢??

有很多同学最近在问&#xff0c;如果专转本考试时遇到 不会的题目怎么办&#xff1f;&#xff1f; 考场上题目太陌生没见过会不会凉凉 以学姐自身经验分享&#xff0c;其实未必会凉凉&#xff0c;当时我在16届计算机考试时&#xff0c;遇上了填空题新题型&#xff0c;当时在考…

微服务学习|Gateway网关:网关作用、快速入门、路由断言工厂、路由过滤器配置、全局过滤器、过滤器执行顺序、跨域问题处理

为什么需要网关 网关功能: 1.身份认证和权限校验 2.服务路由、负载均衡 3.请求限流 网关的技术实现 在SpringCloud中网关的实现包括两种:gateway、zuul Zuul是基于Servlet的实现&#xff0c;属于阻塞式编程。而SprinaCloudGateway则是基于Spring5中提供的WebFlux&#xf…

three.js实现管道漫游

先看效果&#xff1a; <template><div><el-container><el-main><div class"box-card-left"><div id"threejs" style"border: 1px solid red"></div><div class"box-right"><pre s…

【LeetCode】104. 二叉树的最大深度

104. 二叉树的最大深度 难度&#xff1a;简单 题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 …

Java(三)(static,代码块,单例设计模式,继承)

目录 static 有无static修饰的成员变量 有无static修饰的成员方法 static的注意事项 代码块 静态代码块 实例代码块 单例设计模式 饿汉式单例写法 懒汉式单例写法 继承 基本概念 注意事项 权限修饰符 单继承 object 方法重写 子类方法中访问其他成员(成员变量…

Maven工程继承关系,多个模块要使用同一个框架,它们应该是同一个版本,项目中使用的框架版本需要统一管理。

1、父工程pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/PO…

Android studio run 手机或者模拟器安装失败,但是生成了debug.apk

错误信息如下&#xff1a;Error Installation did not succeed. The application could not be installed&#xff1a;List of apks 出现中文乱码&#xff1b; 我首先尝试了打包&#xff0c;能正常安装&#xff0c;再次尝试了debug的安装包&#xff0c;也正常安装&#xff1…

C#基础教程 多线程编程入门 Thread/Task/async/await

private void button1_Click(object sender, EventArgs e){//控制主线程(单线程操作)MessageBox.Show("开始做菜", "友情提示");Thread.Sleep(3000);//主线程休眠MessageBox.Show("素菜做好了","友情提示");Thread.Sleep(5000);Messag…

linux上安装qt creator

linux上安装Qt Creator 1 Qt Creator 的下载 下载地址为&#xff1a;http://download.qt.io/archive/qt/ 根据自己的需求选择Qt Creator版本&#xff0c;这里我下载的是5.12.9&#xff0c;如下图所示&#xff1a; 在ubuntu上可以使用wget命令下载安装包&#xff1a; wget h…

el-table实现表格内嵌套表格

文章目录 一、效果图二、使用场景三、所用组件元素&#xff08;Elementui&#xff09;四、代码部分 一、效果图 二、使用场景 &#x1f6c0;el-form 表单内嵌套el-table表格 &#x1f6c0;el-table 表格内又嵌套el-table表格 三、所用组件元素&#xff08;Elementui&#xff…

BLE协议栈入门学习

蓝牙LE栈 物理层 频带 蓝牙LE在2400MHz到2483.5MHz范围内的2.4GHz免授权频段工作&#xff0c;该频段分为40个信道&#xff0c;每个信道间隔为2MHz。 时分 蓝牙LE是半双工的&#xff0c;可以发送和接收&#xff0c;但不能同时发送和接收&#xff0c;然而&#xff0c;所有的设…

“绵柔的,好喝的”海之蓝畅销20年的经典秘诀:做大众喜爱的好酒

执笔 | 尼 奥 编辑 | 萧 萧 在中国白酒历史长河中&#xff0c;有的品牌如大浪淘沙而灰飞烟灭&#xff0c;也有的白酒品牌因为不断创新而经久不衰。我们时常在思考一个产业命题&#xff1a;白酒品牌常青的秘诀到底是什么&#xff1f; 经过20多年的产业发展&#xff0c;中国…

4-2计算小于1000的正整数的平方根

#include<stdio.h> #include<math.h> int main() {int i;int t;printf("请输入一个数:");scanf("%d",&i);if(i>1000){printf("请重新输入一个数&#xff1a;");scanf("%d",&i);}tsqrt(i);printf("%d的平方…

要事第一:如何通过6个步骤确定项目的优先级

当收到很多项目请求并且每个请求都是重中之重时&#xff0c;该怎么办&#xff1f;从最易完成的开始&#xff1f;还是先解决最大的问题&#xff1f; 实际上两种做法都不对。确定项目优先级的更好方法是评估以下内容&#xff0c;而不是关注项目规模或完成时长&#xff1a; ● 每…