【Unity】TankBattle联机坦克大战(二)关卡生成算法
更新日期2026年8月6日。项目源码获取源码。索引关卡生成算法关卡生成种子一、关卡中的地块分类二、关卡的大小三、关卡生成算法的流程1.初始化算法数据2.随机撒种子3.计算地块的目标格子数4.种子并行生长5.边缘优化6.生成玩家老巢关卡生成算法在序章中有一个夸张说法我们的TankBattle联机坦克大战拥有多达2147483647个的超多关卡int型的上限事实上只是因为关卡生成算法采用了一个int型作为种子就像我的世界那样提供一个相同的生成种子将生成一个完全一样的世界。关卡生成种子那么在此之前必须通过关卡种子来定义一个随机器/// summary/// 关卡地图的随机器LevelSeed为关卡种子比如第一关是1第二关是2....../// /summarypublicSystem.RandomMapRandom{get;privateset;}newSystem.Random(LevelSeed);注意在任何设备、任何环境下通过这个种子生成出来的关卡都必须是一模一样的。一、关卡中的地块分类关卡中的地块共有7个类型地块类型地块编号地块功能地块图像空白0坦克和子弹可穿过砖墙1坦克不可穿过子弹可销毁钢板2坦克不可穿过子弹可销毁需玩家坦克达到3星森林3坦克和子弹可穿过但会被遮挡视野海水4坦克不可穿过子弹可穿过雪地5坦克和子弹可穿过但坦克会打滑玩家老巢6坦克不可穿过子弹可销毁定义地块类型BlockType如下/// summary/// 地块类型/// /summarypublicenumBlockType{/// summary/// 空白/// /summaryNone0,/// summary/// 砖墙/// /summaryBrickWall1,/// summary/// 钢板/// /summarySteelPlate2,/// summary/// 森林/// /summaryForest3,/// summary/// 海水/// /summarySeawater4,/// summary/// 雪地/// /summarySnowfield5,/// summary/// 玩家老巢/// /summaryLair6}二、关卡的大小关卡的大小地块数量为13*13保持跟原版一致地块的索引为左下角0,0右上角12,12/// summary/// 地图行数/// /summarypublicstaticreadonlyintRows13;/// summary/// 地图列数/// /summarypublicstaticreadonlyintCols13;基于此我们先创建一个可以代表整个关卡地图的二维数组//初始化空白地图BlockType[,]mapnewBlockType[Rows,Cols];for(intr0;rRows;r){for(intc0;cCols;c){map[r,c]BlockType.None;}}三、关卡生成算法的流程我们的关卡生成算法流程如下1.初始化算法数据2.随机撒种子3.计算地块的目标格子数4.种子并行生长5.边缘优化6.生成玩家老巢1.初始化算法数据定义关卡生成算法所需要用到的数据和字段/// summary/// 聚集因子控制地形成片程度40~80之间值越大越聚集/// /summaryprivateconstintClusterFactor65;/// summary/// 地块的权重数值越大该地块越多/// /summaryprivatereadonlyDictionaryBlockType,intBlockWeightsnewDictionaryBlockType,int();/// summary/// 生成地图的例外区域玩家出生点、敌人出生点、玩家老巢等/// /summaryprivatereadonlyHashSet(introw,intcol)ExceptionZonesnewHashSet(int,int)();/// summary/// 所有地块的种子数量/// /summaryprivateDictionaryBlockType,Queue(introw,intcol)_blockSeedsnewDictionaryBlockType,Queue(introw,intcol)();/// summary/// 所有地块的目标格子数/// /summaryprivateDictionaryBlockType,int_blockTargetCountnewDictionaryBlockType,int();/// summary/// 所有地块的当前格子数/// /summaryprivateDictionaryBlockType,int_blockCurrentCountnewDictionaryBlockType,int();/// summary/// 邻居地块缓存队列/// /summaryprivateList(introw,intcol)_neighborCachenewList(introw,intcol)();定义各个地块的权重砖墙最多钢板和森林次之//地块的权重数值越大该地块越多BlockWeights.Add(BlockType.BrickWall,40);BlockWeights.Add(BlockType.SteelPlate,15);BlockWeights.Add(BlockType.Forest,15);BlockWeights.Add(BlockType.Seawater,10);BlockWeights.Add(BlockType.Snowfield,5);定义例外区域玩家老巢、玩家出生点、敌人出生点为例外区域不会在这些区域生成地块//玩家老巢及周边保护区域ExceptionZones.Add((5,0));ExceptionZones.Add((5,1));ExceptionZones.Add((6,0));ExceptionZones.Add((6,1));ExceptionZones.Add((7,0));ExceptionZones.Add((7,1));//玩家出生点ExceptionZones.Add((4,0));ExceptionZones.Add((8,0));//敌人出生点ExceptionZones.Add((0,12));ExceptionZones.Add((6,12));ExceptionZones.Add((12,12));然后在每一轮关卡生成前初始化相关数据//重置地图数据_blockSeeds[BlockType.BrickWall].Clear();_blockSeeds[BlockType.SteelPlate].Clear();_blockSeeds[BlockType.Forest].Clear();_blockSeeds[BlockType.Seawater].Clear();_blockSeeds[BlockType.Snowfield].Clear();_blockTargetCount[BlockType.BrickWall]0;_blockTargetCount[BlockType.SteelPlate]0;_blockTargetCount[BlockType.Forest]0;_blockTargetCount[BlockType.Seawater]0;_blockTargetCount[BlockType.Snowfield]0;_blockCurrentCount[BlockType.BrickWall]0;_blockCurrentCount[BlockType.SteelPlate]0;_blockCurrentCount[BlockType.Forest]0;_blockCurrentCount[BlockType.Seawater]0;_blockCurrentCount[BlockType.Snowfield]0;_neighborCache.Clear();2.随机撒种子先根据权重值在地图上随机撒种子权重值越高种子越多/// summary/// 随机撒种子/// /summary/// param namemap地图/param/// param nameblockType种子地块类型/paramprivatevoidSeeding(BlockType[,]map,BlockTypeblockType){//根据权重决定种子数量至少1颗intnumSeedsMath.Max(1,BlockWeights[blockType]/5);//播种所有种子for(intm0;mnumSeeds;m){//每一个种子尝试播种200次for(intattempt0;attempt200;attempt){//使用关卡种子随机器MapRandomintrMapRandom.Next(0,Rows);intcMapRandom.Next(0,Cols);//排除非空地块if(map[r,c]!BlockType.None)continue;//排除例外区域if(ExceptionZones.Contains((r,c)))continue;//排除距离例外区域太近的距离小于等于1格if(IsTooCloseOfExceptionZones(r,c))continue;//种下种子map[r,c]blockType;_blockSeeds[blockType].Enqueue((r,c));break;}}}将5种类型的地块种子都撒下//随机撒种子Seeding(map,BlockType.BrickWall);Seeding(map,BlockType.SteelPlate);Seeding(map,BlockType.Forest);Seeding(map,BlockType.Seawater);Seeding(map,BlockType.Snowfield);撒下种子后大概就是如下这样3.计算地块的目标格子数在种子生长前我们先计算所有地块的最终数目以控制种子生长上限/// summary/// 计算地块的目标格子数/// /summary/// param nameblockType种子地块类型/paramprivatevoidCalculateTargetCount(BlockTypeblockType){//总的可生成地块数量预留10个空白inttotalBlockRows*Cols-ExceptionZones.Count-10;//填充比例doublefillRatio0.55;//总的地块权重intsumWeightsBlockWeights.Values.Sum();//计算地块的目标格子数保底4个inttarget(int)(totalBlock*(BlockWeights[blockType]/(double)sumWeights)*fillRatio);_blockTargetCount[blockType]Math.Max(4,target);}计算5种类型的地块最终数目//计算地块的目标格子数CalculateTargetCount(BlockType.BrickWall);CalculateTargetCount(BlockType.SteelPlate);CalculateTargetCount(BlockType.Forest);CalculateTargetCount(BlockType.Seawater);CalculateTargetCount(BlockType.Snowfield);4.种子并行生长最核心的环节就是让所有种子并行生长向邻居地块生长/// summary/// 地块种子生长/// /summary/// param namemap地图/param/// param nameblockType种子地块类型/param/// returns是否存在任意一个地块生长成功/returnsprivateboolSeedGrowth(BlockType[,]map,BlockTypeblockType){boolanyGrowthfalse;//如果已生长到目标数量则不再生长if(_blockCurrentCount[blockType]_blockTargetCount[blockType])returnanyGrowth;//如果种子数量为零则不再生长if(_blockSeeds[blockType].Count0)returnanyGrowth;//取出一个种子(introw,intcol)seed_blockSeeds[blockType].Dequeue();//获取其邻居地块_neighborCache.Clear();_neighborCache.Add((seed.row-1,seed.col-1));_neighborCache.Add((seed.row-1,seed.col));_neighborCache.Add((seed.row-1,seed.col1));_neighborCache.Add((seed.row,seed.col-1));_neighborCache.Add((seed.row,seed.col1));_neighborCache.Add((seed.row1,seed.col-1));_neighborCache.Add((seed.row1,seed.col));_neighborCache.Add((seed.row1,seed.col1));//向邻居地块生长for(inti0;i_neighborCache.Count;i){//如果已生长到目标数量则不再生长if(_blockCurrentCount[blockType]_blockTargetCount[blockType])break;intr_neighborCache[i].row;intc_neighborCache[i].col;//生长位置为有效区域if(r0rRowsc0cCols){//排除非空地块if(map[r,c]!BlockType.None)continue;//排除例外区域if(ExceptionZones.Contains((r,c)))continue;//以ClusterFactor的概率扩散if(MapRandom.IsTriggerProbability(ClusterFactor)){//生长成功map[r,c]blockType;_blockCurrentCount[blockType];_blockSeeds[blockType].Enqueue((r,c));anyGrowthtrue;}}}returnanyGrowth;}所有种子共生长2000次这个值不固定只是我根据情况微调的结果//地块种子并行生长生长2000次_blockCurrentCount[BlockType.BrickWall]_blockSeeds[BlockType.BrickWall].Count;_blockCurrentCount[BlockType.SteelPlate]_blockSeeds[BlockType.SteelPlate].Count;_blockCurrentCount[BlockType.Forest]_blockSeeds[BlockType.Forest].Count;_blockCurrentCount[BlockType.Seawater]_blockSeeds[BlockType.Seawater].Count;_blockCurrentCount[BlockType.Snowfield]_blockSeeds[BlockType.Snowfield].Count;intnumGrowth2000;for(inti0;inumGrowth;i){boolbrickWallGrowthSeedGrowth(map,BlockType.BrickWall);boolsteelPlateGrowthSeedGrowth(map,BlockType.SteelPlate);boolforestGrowthSeedGrowth(map,BlockType.Forest);boolseawaterGrowthSeedGrowth(map,BlockType.Seawater);boolsnowfieldGrowthSeedGrowth(map,BlockType.Snowfield);//如果没有任何生长发生则提前结束if(!brickWallGrowth!steelPlateGrowth!forestGrowth!seawaterGrowth!snowfieldGrowth){break;}}为了让各个地块区域砖墙区、海水区等看起来更连贯、更成片而不是抛玉米般分散一地所以各个类型的地块种子是并行生长的你生长一次我生长一次由聚集因子决定了生长的成功率一次完整的生长后地图变成了如下这样看起来比纯粹的随机每一个地块随机一下自己的类型更有可玩性。5.边缘优化生长完成后补充一个边缘优化算法旨在将边缘的海水和钢板转为空白40%几率转化提高可玩性//边缘优化边缘的海水和钢板转为空白提高可玩性for(intr0;rRows;r){if(map[r,0]BlockType.Seawater||map[r,0]BlockType.SteelPlate){if(MapRandom.IsTriggerProbability(40))map[r,0]BlockType.None;}if(map[r,Cols-1]BlockType.Seawater||map[r,Cols-1]BlockType.SteelPlate){if(MapRandom.IsTriggerProbability(40))map[r,Cols-1]BlockType.None;}}for(intc0;cCols;c){if(map[0,c]BlockType.Seawater||map[0,c]BlockType.SteelPlate){if(MapRandom.IsTriggerProbability(40))map[0,c]BlockType.None;}if(map[Rows-1,c]BlockType.Seawater||map[Rows-1,c]BlockType.SteelPlate){if(MapRandom.IsTriggerProbability(40))map[Rows-1,c]BlockType.None;}}因为边缘是敌方坦克的主要移动区域所以其应该更通畅。6.生成玩家老巢最后生成玩家的老巢区域就大功告成了//生成玩家老巢及保护地块map[5,0]BlockType.BrickWall;map[5,1]BlockType.BrickWall;map[6,0]BlockType.Lair;map[6,1]BlockType.BrickWall;map[7,0]BlockType.BrickWall;map[7,1]BlockType.BrickWall;当然我们会发现问题所在玩家老巢的保护地块应该都是半砖不过这与关卡生成算法无关其涉及到地块的真实创建与分片后续再来处理它吧。

相关新闻

[Freebuff] 一款Free Token的编码工具,更好的Vibe Coding

[Freebuff] 一款Free Token的编码工具,更好的Vibe Coding

前言 Freebuff 是一款基于命令行界面的编码工具,无需订阅、API 密钥或令牌费用即可使用。可以选择以下模型: DeepSeek V4 Pro 和 FlashMiniMax M3GPT-5.6 Luna 一、多智能体架构 Freebuff 最大的技术特色不是"又换了个大模型套壳"&#xff…

2026/10/11 7:14:13 阅读更多 →
制药企业2026年智能化改造项目备案数据汇总

制药企业2026年智能化改造项目备案数据汇总

2026年上半年,吉林省鑫辉药业有限公司在吉林省投资项目在线审批监管平台完成了多个项目的备案与审批。以下为项目数据汇总:一、智能化改扩建工程(2026年2月备案)项目利用原有土地进行改扩建,分两期建设。一期改扩建约1…

2026/10/12 10:22:48 阅读更多 →
提示模板:大语言模型应用开发中的工程化实践与LangChain实现

提示模板:大语言模型应用开发中的工程化实践与LangChain实现

1. 项目概述:为什么我们需要提示模板?如果你已经开始接触大语言模型的应用开发,无论是用 LangChain 还是直接调用 OpenAI 的 API,大概率都写过类似这样的代码:prompt f"""请根据以下用户输入&#xff…

2026/10/12 19:08:19 阅读更多 →

最新新闻

MCP与A2A:构建可工程化落地的多Agent系统

MCP与A2A:构建可工程化落地的多Agent系统

1. 这不是概念炒作,是工程落地的分水岭“多 Agent、MCP、A2A——最该补的一堂课”,这标题乍看像培训广告,实则戳中了当前智能系统开发里最真实、最普遍、也最容易被忽视的断层。我带过七支不同行业的AI工程团队,从金融风控后台到工…

2026/10/15 12:20:55 阅读更多 →
CPD级联部分解码器:显著目标检测的速度与精度平衡实践

CPD级联部分解码器:显著目标检测的速度与精度平衡实践

1. 为什么CPD值得单独拿出来聊显著目标检测这个方向,做过的人都知道,模型精度和推理速度几乎是一对天敌。早期的方法要么精度勉强能看但跑一张图要几百毫秒甚至上秒级,要么速度快但边缘糊成一团、小目标直接漏检。CVPR 2019那篇Cascaded Part…

2026/10/15 12:20:55 阅读更多 →
SpringBoot3+Vue3全栈进销存系统开发实战

SpringBoot3+Vue3全栈进销存系统开发实战

1. 项目概述:全栈进销存系统开发指南这套教程面向需要完成毕业设计或希望掌握全栈开发技能的初学者,通过SpringBoot3Vue3技术栈实现一个完整的进销存管理系统。我在实际企业级项目开发中发现,这类系统涵盖了前后端分离架构中最典型的CRUD操作…

2026/10/15 12:20:55 阅读更多 →
SSM+Vue旅游网站开发实战与毕业设计指南

SSM+Vue旅游网站开发实战与毕业设计指南

1. 项目背景与核心价值 去年指导过一位计算机专业学生的毕业设计,他选择了旅游资源网站作为课题。这个选题看似普通,但实际开发中涉及的技术栈组合和业务逻辑复杂度,完全能够支撑起一个合格的本科毕业设计。SSM(SpringSpringMVCMy…

2026/10/15 12:20:55 阅读更多 →
SpringBoot3+Vue3企业级进销存系统开发实战

SpringBoot3+Vue3企业级进销存系统开发实战

1. 项目概述:企业级进销存系统开发实战这套教程是我在指导了37名计算机专业毕业生后提炼出的实战方案,专门针对需要完成毕业设计又缺乏商业项目经验的学生群体。不同于市面上简单的CRUD示例,我们采用SpringBoot3Vue3技术栈实现了一个具备完整…

2026/10/15 12:20:55 阅读更多 →
Django 后端实战指南_3:插入 RBAC 测试数据

Django 后端实战指南_3:插入 RBAC 测试数据

阶段 3:插入 RBAC 测试数据 合辑:Django 系统阶段开发手册 | 阶段:3 / 18 | 定位:以自定义管理命令初始化 RBAC 测试数据,并多渠道验证四张表中的数据关系 | 建议学时:3–…

2026/10/15 12:19:54 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →