当前位置: 首页 > news >正文

[Godot] C#基于噪声的简单TileMap地图生成

地图生成的方式有很多种,这里给大家讲一下我最近实现的基于噪声的TileMap地图生成

噪声生成

首先,我们需要生成一个噪声,为了可复现,我们设置了种子,以及噪声的频率,我为了方便调整用了一个变量,大家根据需要去写吧,具体函数如下

private float[,] GenerateNoise()        //噪声图生成{var noise = new FastNoiseLite(){NoiseType = FastNoiseLite.NoiseTypeEnum.Perlin,Frequency = noiseValue,Seed = Seed,FractalType = FastNoiseLite.FractalTypeEnum.Ridged};float[,] noiseMap = new float[mapX, mapY];for (int x = 0; x < mapX; x++)for (int y = 0; y < mapY; y++){float n = (noise.GetNoise2D(x, y) + 1f) / 2f;noiseMap[x, y] = n;}return noiseMap;}

使用该函数,我们将获得一个float类型的二维数组,方便我们接下来可以去生成不同的地块,我们声明一个变量用于存储他

private float[,] noise;

地块生成

接下来,我们使用一个TileMapLayer去渲染地块,我这里因为需要,还写了一个bool类型的二维数组作为占用网格,来存储墙

private bool[,] tileOccupy;

为了生成不同的地块,我添加了一个权重表,至于如何加载,大家就根据需要去写吧,我这里就不专门讲了

private List<(int index, float weight)> weightTable = new();

最重要的,不要忘记声明地图的长和宽的变量

[Export] private int mapX;       //长
[Export] private int mapY;       //宽

生成代码

private async Task GenerateTile(){//这里我的TileMap也是程序化生成的,大家根据需要去修改if (tileMaps.GetChildCount() == 0){tileMapLayer = new TileMapLayer();tileMapLayer.TileSet = tilesSet;tileMaps.AddChild(tileMapLayer);}tileOccupy = new bool[mapX, mapY];        //墙占用数组for (int i = 0; i < mapX; i++){for (int j = 0; j < mapY; j++){//这里我是根据自定义的地块权重表来得到不同的地块int tileIndex = GetWeightIndex(noise[i, j], weightTable);tileMapLayer.SetCell(new Vector2I(i, j), tileIndex, Vector2I.Zero);//这里是我自定义的地块数据,来设置是否墙tileOccupy[i, j] = packDatas[tileIndex].isWall;}//一列一列更新,看起来很顺滑await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);}}

通过权重获取索引

private int GetWeightIndex(float n, List<(int index, float weight)> weights){foreach (var w in weights)if (n <= w.weight)return w.index;return weights[^1].index;}

初始化生成

我们写了一个Init函数用来初始化,当然你可以根据需要写在Ready函数中,因为我需要传入一些自定义的数据,所以专门写了一个初始化函数

Init函数

public async Task Init(){Seed = seed;noise = GenerateNoise();rng = new Random(Seed);//这里是关于地块数据加载的,里面还有权重表加载,大家根据需要去写吧LoadTilePacksData();await GenerateMap();}

变量的声明我就不一一列举了,接下来我们可以看看效果

生成效果

ps:我只有两种颜色的地块,看起来有很多颜色是因为我开启了碰撞体显示,用来看墙的生成状况,这个图使用的噪声频率是0.008

打通所有路

接下来,我们需要将所有的路都打通,这里我使用的是DFS来获取所有岛,得到主岛(地块最多的岛)和其他岛,刚开始我是直接获取所有岛的集合,但是经过思考我发现我只需要获得岛的外圈即可连接两个最近点,函数如下

DFS获取主岛和外岛

private (List<Vector2I> mainLand, List<List<Vector2I>> otherLands) DFS_Land(){int maxSize = 0;var mainLand = new List<Vector2I>();var otherLands = new List<List<Vector2I>>();var visited = new bool[mapX, mapY];var dirs = new (int x, int y)[] { (0, 1), (0, -1), (1, 0), (-1, 0) };for (int x = 0; x < mapX; x++){for (int y = 0; y < mapY; y++){if (tileOccupy[x, y] || visited[x, y])continue;var isLand = new List<Vector2I>();var q = new Queue<Vector2I>();q.Enqueue(new Vector2I(x, y));visited[x, y] = true;int size = 0;//队列搜索while (q.Count > 0){var vec = q.Dequeue();size++;bool isEdge = false;foreach (var dir in dirs){var nx = vec.X + dir.x;var ny = vec.Y + dir.y;//判断墙和边界if (nx < 0 || ny < 0 || nx >= mapX || ny >= mapY || tileOccupy[nx, ny]){isEdge = true;continue;}//未访问,入队if (!visited[nx, ny]){visited[nx, ny] = true;q.Enqueue(new Vector2I(nx, ny));}}if (isEdge)isLand.Add(vec);}if (size > maxSize){if (mainLand.Count > 0)otherLands.Add(mainLand);maxSize = size;mainLand = isLand;}elseotherLands.Add(isLand);}}return (mainLand, otherLands);}

获取两岛之间最近的两点

这里我声明了一个没有墙的地块权重表

private void ConnectPoint(Vector2I a, Vector2I b, List<(int index, float weight)> weights){int ax = a.X, ay = a.Y;int bx = b.X, by = b.Y;while (ax != bx || ay != by){int width = rng.Next(1, 3);bool dir = rng.NextDouble() < 0.5f;//随机偏移int ox = rng.Next(-1, 2);int oy = rng.Next(-1, 2);var vec = new Vector2I(ax + ox, ay + oy);DrawRoadTile(vec, width, weights);if (dir)ax += ax < bx ? 1 : -1;elseay += ay < by ? 1 : -1;}}

连接并绘制路线

这里我加了一点随机偏移,不然直路看起来太奇怪了,以及路的宽度的大小,大家根据需要去调整

private void ConnectPoint(Vector2I a, Vector2I b, List<(int index, float weight)> weights){int ax = a.X, ay = a.Y;int bx = b.X, by = b.Y;while (ax != bx || ay != by){int width = rng.Next(1, 3);bool dir = rng.NextDouble() < 0.5f;//随机偏移int ox = rng.Next(-1, 2);int oy = rng.Next(-1, 2);var vec = new Vector2I(ax + ox, ay + oy);DrawRoadTile(vec, width, weights);if (dir)ax += ax < bx ? 1 : -1;elseay += ay < by ? 1 : -1;}}private void DrawRoadTile(Vector2I point, int width, List<(int index, float weight)> weights){for (int x = -width; x < width; x++){for (int y = -width; y < width; y++){int px = x + point.X; int py = y + point.Y;if (px < 0 || py < 0 || px >= mapX || py >= mapY)continue;tileOccupy[px, py] = false;var tileID = GetWeightIndex(noise[px, py], weights);tileMapLayer.SetCell(new Vector2I(px, py), tileID, Vector2I.Zero);}}}

调用函数

private async Task ThroughRoad(){var isLands = DFS_Land();//这里我把我的道路地块取出var weights = weightTable.Where(w => !packDatas[w.index].isWall).ToList();//生成所有岛连接线foreach (var other in isLands.otherLands){var twoPoint = FindClosePoint(isLands.mainLand, other);ConnectPoint(twoPoint.a, twoPoint.b, weights);await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);}}

我们将其加入初始化函数内

public async Task Init(){...await ThroughRoad();}

最终生成效果

结语

以上是我简单实现的过程,大家可以根据需要自行修改,感谢大家观看

http://www.dtcms.com/a/609419.html

相关文章:

  • Linux(4)—— 基础开发工具
  • 广州好的网站设计公司如何建单位网站
  • 无损改造+智能防控,安科瑞ASCB3为古建筑与历史街区量身打造安全用电解决方案
  • 解决“Move to iOS 卡在准备中”的 9 种有效方法
  • iOS IPA 上传工具全面解析,从 Transporter 到开心上架(Appuploader)命令行的高效上架实践
  • iOS性能调优的系统化实践,从架构分层到多工具协同的全流程优化指南(开发者深度版)
  • GitHub爆火开源项目——RustScan深度拆解
  • iOS和安卓应用上架全指南:从备案到审核发布
  • 海外购物网站排名云商网站建设
  • 解决 Node.js 18+ 构建错误:digital envelope routines::unsupported 完全指南
  • 索尼PSP游戏资源下载 推荐中文汉化ios格式合集分享开源掌机模拟器都支持
  • 【赵渝强老师】OceanBase的连接与路由管理
  • 教育网站建设情况报告长安高端装备网站设计公司
  • Unity游戏打包加密方案解析
  • 考研论文引用格式 AI 校验实操:工具合集 + 技术原理
  • Linux:安装 ActiveMQ 从部署到实践
  • 思政部网站建设总结汕头cms模板建站
  • 云原生与 AI 驱动下的数据工程新图景——解读 DZone 数据工程趋势报告【附报告下载】
  • Linux——解压缩各类文件
  • 基于STM32的多功能旅行箱_329
  • 探索 Java 中的新 HTTP 客户端
  • Swagger技术
  • 100多台物理GPU服务器,每台服务器上有8张GPU卡,组网
  • 英文营销网站 知乎旅游网站建设导航栏
  • 网站服务器管理系统企业网站托管方案
  • vllm缓存使用基础调优实验
  • IGM焊接机器人节气设备
  • 企业网站案例公司德州企业网站建设
  • 从图片到PPT:用Python实现多图片格式(PNG/JPG/SVG)到幻灯片的批量转换
  • 鸿蒙应用构建体系深度解析:ABC、HAP、HAR、HSP与APP的技术全貌