【unity小技巧】Unity2D TileMap+柏林噪声生成随机地图(附源码)

文章目录

  • 前言
  • 柏林噪声
  • 素材导入
  • Rule Tile配置
  • 生成随机地图
  • 问题
  • 扩展问题
  • 添加植被
  • 源码
  • 参考
  • 完结

前言

我的上一篇文章介绍了TileMap的使用,主要是为我这篇做一个铺垫,看过上一篇文章的人,应该已经很好的理解TileMap的使用了,这里我就不需要过多的解释一些繁琐而基础的知识了,省去很多时间。所有没看过上一篇文章的小伙伴我强烈建议先去看看:
【Unity小技巧】Unity2D TileMap的探究(最简单,最全面的TileMap使用介绍)

先来看看本文实现的最终效果
在这里插入图片描述
源码在文章末尾

柏林噪声

柏林噪声(Perlin noise)是由Ken Perlin于1983年提出的一种随机数生成算法,常用于计算机图形学中的纹理、地形和粒子系统等领域。它产生了一种平滑、连续的随机分布,常用于生成自然风格的纹理和地形。

柏林噪声和直接随机有以下几个区别:

  1. 平滑性:柏林噪声生成的值在空间上变化连续平滑,不会出现剧烈的跳变。而直接随机生成的值可能会出现突然的变化,不够平滑。

  2. 一致性:柏林噪声的生成结果是基于一个固定的种子值,因此每次使用相同的种子值生成的结果都是一致的。而直接随机生成的结果每次都不同。

  3. 纹理性:柏林噪声生成的值可以用来模拟自然纹理,如山脉、云彩等。而直接随机生成的值没有这种纹理性,更加随机。

比如随机0-1可能生成跳动比较大的数据:0,0.8,0.1
而使用柏林噪声生成的数据大概率是:0,0.3,0.5

素材导入

在这里插入图片描述
在这里插入图片描述

Rule Tile配置

Rule Tile的使用我这里就不再解释了,不清楚的可以看我前面发的文章链接,这里就直接贴出配置图了,节省大家时间,配置起来也不难就是要多测试,费点时间
在这里插入图片描述

在这里插入图片描述

效果演示,可以看到无论我们如何绘制地图,都可以做很好的兼容
在这里插入图片描述

生成随机地图

新建脚本MapCreate,先定义两个方法

public class MapCreate : MonoBehaviour
{// 创建地图数据public void GenerateMap(){}// 清除地图数据public void CleanTileMap(){}
}

一直启动才生成地图,太慢了,为了加快我们的调试节奏,可以实现未启动unity生成地图效果,我们需要新建一个Editor文件夹
在这里插入图片描述
书写MapCreateEditor脚本

using UnityEditor;
using UnityEngine;[CustomEditor(typeof(MapCreate))]// 自定义编辑器,目标为我们前面创建的MapCreate
public class MapCreateEditor : Editor
{public override void OnInspectorGUI()// 重写OnInspectorGUI方法{base.DrawDefaultInspector();// 绘制默认的检查器if (GUILayout.Button("创建地图"))// 如果GUILayout的按钮被按下,按钮名为"创建地图"{((MapCreate)target).GenerateMap();// 目标MapGenerator生成地图}if (GUILayout.Button("清除地图"))// 如果GUILayout的按钮被按下,按钮名为"清除地图"{((MapCreate)target).CleanTileMap();// 目标MapGenerator清理地图}}
}

效果
在这里插入图片描述
继续完善我们的MapCreate代码,代码我加了详细的中文注释,这里不过多解释了

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;// 创建地图的类
public class MapCreate : MonoBehaviour
{public Tilemap groundTileMap; // 地图的Tilemap组件public int width; // 地图的宽度public int height; // 地图的高度public int seed; // 生成地图的种子public bool useRandomSeed; // 是否使用随机种子public float lacunarity; // 柏林噪声的频率,决定地形的空隙度[Range(0, 1f)]public float waterProbability; // 水域的概率public TileBase groundTile; // 地面的Tilepublic TileBase waterTile; // 水域的Tileprivate bool[,] mapData; // 地图数据,True表示地面,False表示水域// 创建地图数据public void GenerateMap(){GenerateMapData(); // 生成地图数据GenerateTileMap(); // 生成Tile地图}// 生成地图数据private void GenerateMapData(){// 对于种子的应用if (!useRandomSeed) seed = Time.time.GetHashCode(); // 如果不使用随机种子,则使用当前时间的哈希值作为种子UnityEngine.Random.InitState(seed); // 初始化随机状态float randomOffset = UnityEngine.Random.Range(-10000, 10000); // 随机偏移量mapData = new bool[width, height]; // 初始化地图数据for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 使用柏林噪声生成地图数据float noiseValue = Mathf.PerlinNoise(x * lacunarity + randomOffset, y * lacunarity + randomOffset);mapData[x, y] = noiseValue < waterProbability ? false : true; // 如果噪声值小于水域概率,则该位置为水域,否则为地面}}}// 生成Tile地图private void GenerateTileMap(){CleanTileMap(); // 清除地图数据// 生成地面for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 如果地图数据为True,则该位置为地面,否则为空TileBase tile = mapData[x, y] ? groundTile : waterTile;groundTileMap.SetTile(new Vector3Int(x, y), tile); // 设置Tile}}}// 清除地图数据public void CleanTileMap(){groundTileMap.ClearAllTiles(); // 清除所有的Tile}
}

挂载脚本,配置数据
在这里插入图片描述
运行效果
在这里插入图片描述

问题

这里还有一个问题,如果我们把lacunarity值设置很小,且把水的比例调的比较高或者比较低的时候,你会发现没有按我们要的比例生成效果
在这里插入图片描述
Mathf.PerlinNoise(x, y)函数在Unity中用于生成柏林噪声,它的返回值是在0到1之间的浮点数。这个函数的两个参数通常是在一个连续的范围内变化的,例如时间或者空间的坐标。

前面我们使用了x * lacunarity + randomOffset和y * lacunarity + randomOffset作为输入。lacunarity是一个控制频率的参数,randomOffset是一个随机偏移量。

当lacunarity很小的时候,x * lacunarity和y * lacunarity的值会非常接近,这意味着我们在查询柏林噪声的时候,查询的点非常接近。柏林噪声的特性是,查询的点越接近,返回的值越接近。所以,当lacunarity很小的时候,我们得到的噪声值的范围可能会小于0到1。

如果你想让噪声值的范围更接近0到1,你可以尝试增大lacunarity的值。这样,你查询柏林噪声的点就会更分散,返回的噪声值的范围就会更大。但是,这也会影响到生成的地图的样子,可能会使地图的特征更大或者更小,这取决于你的需求。

另外,我们也可以在得到噪声值之后,对其进行一些数学处理,例如缩放或者偏移,来使其范围更接近0到1。例如,可以使用Mathf.InverseLerp来确保噪声值在0到1之间。

Mathf.InverseLerp 是 Unity 中的一个函数,用于反向插值计算。它接受三个参数:a,b 和 value。

函数的工作原理是这样的:首先,它会找到 value 在 a 和 b 之间的相对位置。然后,它会返回一个介于 0 和 1 之间的值,这个值表示 value 在 a 和 b 之间的相对位置。如果 value 等于 a,则返回 0;如果 value 等于 b,则返回 1。如果 value 在 a 和 b 之间,则返回一个介于 0 和 1 之间的值。

例如,Mathf.InverseLerp(0, 10, 5) 将返回 0.5,因为 5 是 0 和 10 之间的中点。

这个函数在需要将一个值映射到 0 到 1 的范围时非常有用,例如在归一化操作中。

修改MapCreate代码

private float[,] mapData; // 地图数据private void GenerateMapData()
{//。。。mapData = new float[width, height]; // 初始化地图数据float minValue = float.MaxValue;float maxValue = float.MinValue;for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 使用柏林噪声生成地图数据float noiseValue = Mathf.PerlinNoise(x * lacunarity + randomOffset, y * lacunarity + randomOffset);mapData[x, y] = noiseValue;if (noiseValue < minValue) minValue = noiseValue;if (noiseValue > maxValue) maxValue = noiseValue;}}// 平滑到0~1for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){mapData[x, y] = Mathf.InverseLerp(minValue, maxValue, mapData[x, y]);}}
}// 生成Tile地图
private void GenerateTileMap()
{//。。。// 如果地图数据为True,则该位置为地面,否则为水TileBase tile = mapData[x, y] > waterProbability ? groundTile : waterTile;
}

float.MaxValue是C#中浮点数类型(float)可以表示的最大值,大约为3.4E+38。float.MinValue是浮点数类型(float)可以表示的最小负值,大约为-3.4E+38。

运行效果,可以看到,现在的效果就是我们的预期,水域显示占比没有问题了
在这里插入图片描述

扩展问题

我这里的地图瓦片是比较全面的,各个方位形状的都有,所有直接生成出来的地形不会出现什么问题,不过有时候我们的输出可能只包括常见的四方向瓦片,那么生成的地图多多少少会出现一些问题

比如:
在这里插入图片描述消除错误没有意义的瓦片,你可以去寻找他们的一个共性,比如就是瓦片都只有一个邻居,消除的大概思路就是遍历每个瓦片进行判断,如果他只有一个邻居就把它变为水。

参考代码

public void GenerateMap()
{// 地图处理 处理次数for (int i = 0; i < 3; i++){if (!RemoveSeparateTile()) // 如果本次操作什么都没有处理,则不进行循环{break;}}
}//移除孤立的瓷砖    
private bool RemoveSeparateTile()
{bool res = false; // 是否是有效的操作for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){// 是地面且只有一个邻居也是地面if (IsGround(x, y) && GetFourNeighborsGroundCount(x, y) <= 1){groundTileMap.SetTile(new Vector3Int(x, y), tile);// 设置为水res = true;}}}return res;
}// 获取四方向地面邻居的数量
private int GetFourNeighborsGroundCount(int x, int y)
{int count = 0;// topif (IsInMapRange(x, y + 1) && IsGround(x, y + 1)) count += 1;// bottomif (IsInMapRange(x, y - 1) && IsGround(x, y - 1)) count += 1;// leftif (IsInMapRange(x - 1, y) && IsGround(x - 1, y)) count += 1;// rightif (IsInMapRange(x + 1, y) && IsGround(x + 1, y)) count += 1;return count;
}// 是否在地图范围内
public bool IsInMapRange(int x, int y)
{return x >= 0 && x < width && y >= 0 && y < height;
}// 是否是地面
public bool IsGround(int x, int y)
{return mapData[x, y] > waterProbability;
}

这样就可以消除错误或者没有意义的瓦片啦。

添加植被

定义一个类存放植被瓦片和权重

[Serializable]
public class ItemData
{public TileBase tile;public int wegith;
}

逻辑代码

public Tilemap itemTileMap;//植被的Tilemap组件
public List<ItemData> ItemData;//植被列表//生成植被
public void CreateItemData()
{// 植被权重和int weightTotal = 0;for (int i = 0; i < ItemData.Count; i++){weightTotal += ItemData[i].wegith;}//生成植被for (int x = 0; x < width; x++){for (int y = 0; y < height; y++){//只有地面可以生成物品if (IsGround(x, y)){float randValue = UnityEngine.Random.Range(1, weightTotal + 1);float temp = 0;for (int i = 0; i < ItemData.Count; i++){temp += ItemData[i].wegith;if (randValue < temp){// 命中if (ItemData[i].tile) itemTileMap.SetTile(new Vector3Int(x, y), ItemData[i].tile);break;}}}}}
}

挂载脚本,配置权重参数,可以像我一样配置一个为空,及控制无植被的占比权重
在这里插入图片描述

运行效果
在这里插入图片描述

源码

后面整理好了,我会放上来。

参考

【视频】https://www.bilibili.com/video/BV1Js4y117C6?p=1

完结

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

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

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

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

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

相关文章

【学习日记】【FreeRTOS】延时列表的实现

前言 本文在前面文章的基础上实现了延时列表&#xff0c;取消了 TCB 中的延时参数。 本文是对野火 RTOS 教程的笔记&#xff0c;融入了笔者的理解&#xff0c;代码大部分来自野火。 一、如何更高效地查找延时到期的任务 1. 朴素方式 在本文之前&#xff0c;我们使用了一种朴…

图像检索,目标检测map的实现

一、图像检索指标Rank1,map 参考&#xff1a;https://blog.csdn.net/weixin_41427758/article/details/81188164?spm1001.2014.3001.5506 1.Rank1: rank-k&#xff1a;算法返回的排序列表中&#xff0c;前k位为存在检索目标则称为rank-k命中。 常用的为rank1&#xff1a;首…

Dockerfile文件详细

Dockerfile 是一个文本文件&#xff0c;里面包含组装新镜像时用到的基础镜像和各种指令&#xff0c;使用dockerfile 文件来定义镜像&#xff0c;然后运行镜像&#xff0c;启动容器。 dockerfile文件的组成部分 一个dockerfile文件包含以下部分&#xff1a; 基础镜像信息&…

SQL注入之万能用户名

文章目录 分析代码原理实现 分析代码 在安装的cms数据库目录C:\phpStudy\WWW\cms\admin下找到login.action.php文件&#xff0c;查看第20行&#xff0c;发现如下php代码&#xff1a; $user_row $db->getOneRow("select userid from cms_users where username "…

docker 04(docker 应用部署)

一、部署Mysql 需求: 在Docker容器中部署MySQL&#xff0c;并通过外部mysql客户端操作MySQLServer。 二、部署tomcat 三、部署nginx 四、部署redis

PHP 房产网站系统Dreamweaver开发mysql数据库web结构php编程计算机网页项目

一、源码特点 PHP 房产网站系统是一套完善的WEB设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 源码 https://download.csdn.net/download/qq_41221322/88233553 论文 https://download…

RISC-V公测平台发布 · 数据库在RISC-V服务器上的适配评估

前言 上一期讲到YCSB在RISC-V服务器上对MySQL进行性能测试&#xff08;RISC-V公测平台发布 使用YCSB测试SG2042上的MySQL性能&#xff09;&#xff0c;在这一期文章中&#xff0c;我们继续深入讨论RISC-V数据库的应用。本期就继续利用HS-2平台来测试数据库软件在RISC-V服务器…

Unity 之 Input类

文章目录 总述具体介绍 总述 Input 类是 Unity 中用于处理用户输入的重要工具&#xff0c;它允许您获取来自键盘、鼠标、触摸屏和控制器等设备的输入数据。通过 Input 类&#xff0c;您可以轻松地检测按键、鼠标点击、鼠标移动、触摸、控制器按钮等用户输入事件。以下是关于 I…

数学建模-模型详解(2)

微分模型 当谈到微分模型时&#xff0c;通常指的是使用微分方程来描述某个系统的动态行为。微分方程是描述变量之间变化率的数学方程。微分模型可以用于解决各种实际问题&#xff0c;例如物理学、工程学、生物学等领域。 微分模型可以分为两类&#xff1a;常微分方程和偏微分…

《学爸》成爆款背后,马栏山以BOT模式示范“文化+科技”路径

文|智能相对论 作者|范柔丝 今年暑期档的爆款电影&#xff0c;必有《学爸》一席之地。 这部给众多深陷教育旋涡的家长带来深刻思考的电影&#xff0c;就是马栏山视频文创产业园经过3年筹备&#xff0c;首部本土孵化出品的教育现实体裁院线大电影。 据猫眼专业版数据&#x…

初步认识OSPF的大致内容(第三课)

1 路由的分类 直连路由&#xff08;Directly Connected Route&#xff09;是指网络拓扑结构中相邻两个网络设备直接相连的路由&#xff0c;也称为直接路由。如果两个设备属于同一IP网络地址&#xff0c;那么它们就是直连设备。直连路由表是指由计算机系统生成的一种用于路由选择…

Vue3 用父子组件通信实现页面页签功能

一、大概流程 二、用到的Vue3知识 1、组件通信 &#xff08;1&#xff09;父给子 在vue3中父组件给子组件传值用到绑定和props 因为页签的数组要放在父页面中&#xff0c; data(){return {tabs: []}}, 所以顶部栏需要向父页面获取页签数组 先在页签页面中定义props用来接…

谷粒商城环境搭建一:Docker容器部署

Docker容器部署 VMware虚拟机安装 参考&#xff1a;VMware虚拟机安装Linux教程 Docker安装 Linux安装Docker # 1.更新apt包索引 sudo apt-get update# 2.安装以下包以使apt可以通过HTTPS使用存储库&#xff08;repository&#xff09; sudo apt-get install -y apt-transpor…

Linux内核学习(六)—— 中断(基于Linux 2.6内核)

一、中断 中断使得硬件得以发出通知给处理器。中断随时都可以产生&#xff0c;如键盘敲击就会触发中断&#xff0c;通知操作系统有按键按下。 不同设备对应的中断不同&#xff0c;而每个中断都通过一个唯一的数字标识。这些中断值通常被称为中断请求&#xff08;IRQ&#xff…

【观察】戴尔科技:构建企业创新“韧性”,开辟数实融合新格局

过去几年&#xff0c;国家高度重视发展数字经济&#xff0c;将其上升为国家战略。其中&#xff0c;“十四五”规划中&#xff0c;就明确提出要推动数字经济和实体经济的深度融合&#xff0c;以数字经济赋能传统产业转型升级&#xff1b;而2023年年初正式发布的《数字中国建设整…

Coremail参与编制|《信创安全发展蓝皮书——系统安全分册(2023年)》

信创安全发展蓝皮书 近日&#xff0c;Coremail参与编制的《信创安全发展蓝皮书—系统安全分册&#xff08;2023年&#xff09;》重磅发布。 此次信创安全发展蓝皮书由工业和信息化部电子第五研究所联合大数据协同安全技术国家工程研究中心重磅共同发布。 本次蓝皮书涵盖信创系…

多种方法实现 Nginx 隐藏式跳转(隐式URL,即浏览器 URL 跳转后保持不变)

多种方法实现 Nginx 隐藏式跳转(隐式URL,即浏览器 URL 跳转后保持不变)。 一个新项目,后端使用 PHP 实现,前端不做路由,提供一个模板,由后端路由控制。 Route::get(pages/{name}, [\App\Http\Controllers\ResourceController::class, getResourceVersion])

【Python原创设计】基于Python Flask 机器学习的全国+上海气象数据采集预测可视化系统-附下载链接以及详细论文报告,原创项目其他均为抄袭

基于Python Flask 机器学习的全国上海气象数据采集预测可视化系统 一、项目简介二、开发环境三、项目技术四、功能结构五、运行截图六、功能实现七、数据库设计八、源码获取 一、项目简介 在信息科技蓬勃发展的当代&#xff0c;我们推出了一款基于Python Flask的全国上海气象数…

多维时序 | MATLAB实现WOA-CNN鲸鱼算法优化卷积神经网络的数据多变量时间序列预测

多维时序 | MATLAB实现WOA-CNN鲸鱼算法优化卷积神经网络的数据多变量时间序列预测 目录 多维时序 | MATLAB实现WOA-CNN鲸鱼算法优化卷积神经网络的数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 多维时序 | MATLAB实现WOA-CNN鲸鱼算法优化卷积神经…

Centos 解决 XXX不在 sudoers 文件中。此事将被报告。的错误

本来想使用 sudo 拷贝一个文件&#xff0c;结果出现上面的问题&#xff01; 下面是解决方法&#xff1a; 首先登录root&#xff0c;然后执行下面的命令 vim /etc/sudoers 将你需要添加的用户带红色框线的地方&#xff0c;模仿root写一遍&#xff0c;然后保存&#xff01; …