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

Unity RTS/TD游戏:从网格数据到动态建造的实战解析

1. 网格系统:RTS/TD游戏的建造基石

在RTS(即时战略)和TD(塔防)游戏中,网格系统就像现实世界中的建筑工地测量仪。想象一下你要在一片凹凸不平的荒地上建造城堡,首先得用石灰粉画出整齐的方格线,标记出哪些区域适合打地基。Unity中的网格系统就是数字世界的"石灰线",它把复杂的三维地形转化为规整的二维坐标系。

我做过一个中世纪题材的TD项目,当时用这个结构体存储每个格子信息:

[System.Serializable] public struct GridCell { public Vector2Int coordinate; // 网格坐标(X,Z) public float height; // 地形高度 public float slopeAngle; // 坡度角度 public bool isOccupied; // 是否被建筑占据 public Building building; // 关联的建筑对象 }

初始化网格时有个坑要注意:地形尺寸与网格密度的平衡。在《帝国时代》这类游戏中,1个单位通常代表1米,如果单元格设为2x2米会显得建筑摆放太稀疏,0.5x0.5米又会导致性能问题。我的经验公式是:

  • 小型建筑(箭塔等):占2x2格子
  • 中型建筑(兵营等):占3x3格子
  • 大型建筑(主城等):占4x4格子

实测发现,将TerrainData的heightmap分辨率设为129x129,配合2米单元格大小,既能保证建造精度又不会过度消耗内存。初始化代码关键部分如下:

void GenerateGrid() { TerrainData terrainData = terrain.terrainData; gridSize = new Vector2Int( Mathf.CeilToInt(terrainData.size.x / cellSize), Mathf.CeilToInt(terrainData.size.z / cellSize) ); gridCells = new GridCell[gridSize.x, gridSize.y]; for (int x = 0; x < gridSize.x; x++) { for (int y = 0; y < gridSize.y; y++) { Vector3 worldPos = new Vector3( x * cellSize + cellSize * 0.5f, 0, y * cellSize + cellSize * 0.5f ); // 获取地形高度和坡度 Vector2 normalizedPos = new Vector2( worldPos.x / terrainData.size.x, worldPos.z / terrainData.size.z ); gridCells[x, y].height = terrainData.GetInterpolatedHeight(normalizedPos.x, normalizedPos.y); gridCells[x, y].slopeAngle = terrainData.GetSteepness(normalizedPos.x, normalizedPos.y); } } }

2. 动态建造的三大核心交互

2.1 鼠标悬停检测:像磁铁吸附一样自然

在《星际争霸2》中,建筑会像被磁铁吸住一样自动对齐到网格,这背后是射线检测与网格坐标转换的魔法。我推荐采用分层检测策略:

  1. 第一层:地形碰撞检测

    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100f, terrainLayerMask)) { Vector2Int gridPos = WorldToGridPosition(hit.point); }
  2. 第二层:UI遮挡判断

    if (EventSystem.current.IsPointerOverGameObject()) { return; // 鼠标在UI上时不处理建造 }
  3. 第三层:建筑碰撞检测

    Collider[] colliders = Physics.OverlapBox(hit.point, buildingSize, Quaternion.identity, buildingLayerMask); bool canPlace = colliders.Length == 0;

提示:WorldToGridPosition方法需要处理地形高度差,建议用Mathf.RoundToInt代替直接强制转换,避免浮点误差导致的坐标偏移。

2.2 放置预览:给玩家明确的视觉反馈

好的预览系统应该像汽车倒车雷达,用颜色和形状告诉玩家"这里能不能停"。我常用的方案是:

  • 可放置状态:半透明绿色模型+网格线
  • 不可放置状态:红色闪烁模型+阻挡区域高亮
  • 特殊状态:黄色表示需要满足前置条件(如附近要有主城)

Shader代码实现轮廓光效果:

// 在片段着色器中添加 float rim = 1 - saturate(dot(normalize(i.worldNormal), normalize(_WorldSpaceCameraPos - i.worldPos))); float3 rimColor = _CanBuild ? float3(0,1,0) : float3(1,0,0); o.Emission = rimColor * pow(rim, _RimPower) * _RimIntensity;

2.3 即时验证:建造条件的多重校验

在《魔兽争霸3》中,即使金钱足够,在斜坡上造建筑也会被拒绝。我们需要实现类似的智能验证:

public bool ValidateBuildingPlacement(Vector2Int gridPos, BuildingType type) { BuildingData data = buildingDataDict[type]; // 检查是否超出地图边界 if (gridPos.x < 0 || gridPos.x + data.Width > gridSize.x || gridPos.y < 0 || gridPos.y + data.Height > gridSize.y) { return false; } // 检查地形坡度 float maxSlope = 0; for (int x = gridPos.x; x < gridPos.x + data.Width; x++) { for (int y = gridPos.y; y < gridPos.y + data.Height; y++) { maxSlope = Mathf.Max(maxSlope, gridCells[x, y].slopeAngle); if (gridCells[x, y].isOccupied) return false; } } return maxSlope <= data.MaxAllowedSlope; }

3. 高级建造效果实现技巧

3.1 动态网格绘制:Shader魔法

传统Gizmos绘制在移动端性能堪忧,我用Projector+Shader方案实现了《文明6》风格的动态网格:

// 网格线Shader核心算法 float2 uv = i.uv * _GridSize; float2 grid = abs(frac(uv - 0.5) - 0.5) / fwidth(uv); float line = min(grid.x, grid.y); float4 color = saturate(1 - line) * _GridColor; // 添加抗锯齿 float2 derivative = fwidth(uv); float2 pixelUV = uv * _MainTex_TexelSize.zw; float pixelSize = length(derivative) * 0.707; // 对角线系数 color.a = smoothstep(0.5 - pixelSize, 0.5 + pixelSize, color.a);

注意:Projector要设置Orthographic(正交投影),Near/Far Clip Plane根据地形高度差调整,避免穿帮。

3.2 建造动画:从零到完整的生长过程

没有美术资源?用Shader动画照样能做出惊艳效果。这是我自研的建筑生长Shader控制参数:

  • _Progress (0-1):控制建造进度
  • _DissolveHeight:溶解边缘高度
  • _GlowIntensity:建造时的发光强度
// 在建造协程中动态调整参数 IEnumerator BuildingProgress(Material mat, float duration) { float timer = 0; while (timer < duration) { timer += Time.deltaTime; float progress = timer / duration; mat.SetFloat("_Progress", progress); mat.SetFloat("_GlowIntensity", Mathf.PingPong(progress * 2, 1)); yield return null; } }

3.3 多单位格处理:非对称建筑对齐

当建筑占用偶数个格子时(如2x4),直接取中心点会导致视觉偏差。解决方案是引入对齐偏移量:

public Vector3 GetSnappedPosition(Vector3 rawPos, Vector2Int buildingSize) { Vector2Int gridPos = WorldToGridPosition(rawPos); Vector3 center = GridToWorldPosition(gridPos); // 处理偶数尺寸偏移 if (buildingSize.x % 2 == 0) center.x -= cellSize * 0.5f; if (buildingSize.y % 2 == 0) center.z -= cellSize * 0.5f; // 保持y轴与地形贴合 center.y = GetTerrainHeightAt(center); return center; }

4. 性能优化实战经验

4.1 网格数据查询优化

在500x500的网格中,频繁调用GetCell()会导致CPU瓶颈。我采用分层存储策略:

  • 基础层:原始高度图数据
  • 缓存层:最近访问的区块(类似CPU缓存)
  • 标记层:用BitArray存储占用状态
private Dictionary<Vector2Int, GridChunk> cachedChunks = new Dictionary<Vector2Int, GridChunk>(); public GridCell GetCell(Vector2Int gridPos) { Vector2Int chunkPos = new Vector2Int( gridPos.x / CHUNK_SIZE, gridPos.y / CHUNK_SIZE ); if (!cachedChunks.TryGetValue(chunkPos, out GridChunk chunk)) { chunk = LoadChunkFromDisk(chunkPos); cachedChunks[chunkPos] = chunk; if (cachedChunks.Count > MAX_CACHED_CHUNKS) { // LRU缓存淘汰 } } return chunk.GetLocalCell(gridPos.x % CHUNK_SIZE, gridPos.y % CHUNK_SIZE); }

4.2 批量绘制优化

用Graphics.DrawMeshInstanced批量绘制网格指示器,比单独GameObject性能提升10倍:

MaterialPropertyBlock props = new MaterialPropertyBlock(); Matrix4x4[] matrices = new Matrix4x4[validCells.Count]; Color[] colors = new Color[validCells.Count]; for (int i = 0; i < validCells.Count; i++) { matrices[i] = Matrix4x4.TRS( GridToWorldPosition(validCells[i]), Quaternion.identity, Vector3.one * cellSize ); colors[i] = GetCellColor(validCells[i]); } props.SetVectorArray("_Color", colors); Graphics.DrawMeshInstanced(gridMesh, 0, gridMaterial, matrices, matrices.Length, props);

4.3 异步加载策略

大型地图采用分帧加载避免卡顿:

IEnumerator LoadGridDataAsync() { int cellsPerFrame = Mathf.Max(gridSize.x * gridSize.y / 10, 100); int processed = 0; for (int x = 0; x < gridSize.x; x++) { for (int y = 0; y < gridSize.y; y++) { gridCells[x, y] = CalculateCellData(x, y); processed++; if (processed % cellsPerFrame == 0) { yield return null; // 每处理一定数量单元格就暂停一帧 } } } }

在最近的一个塔防项目中,这些优化使建造系统的CPU耗时从8ms降到了1.2ms,内存占用减少了65%。关键是要根据实际游戏规模选择合适的技术方案——小型地图可以用全内存存储,大型开放世界则需要更智能的流式加载。

http://www.jsqmd.com/news/673553/

相关文章:

  • Circle部署与优化指南:如何将项目管理应用部署到生产环境
  • 如何在5分钟内开始使用LCM:大型概念模型快速入门教程
  • 告别盲目调试:用串口打印和LED灯,5分钟可视化你的Ra-01S LoRa通信状态
  • 别再傻傻重装软件了!Win7/Win10系统报错‘api-ms-win-crt-runtime-l1-1-0.dll丢失’的终极修复指南
  • Dify金融合规配置全栈解析(含GDPR+《生成式AI服务管理暂行办法》双标对齐)
  • Unity RTS/TD游戏:从网格数据到动态建造的实战架构
  • 【MimiClaw 嵌入式 AI Agent 实战】ESP32-S3 从零搭建多端互联智能体:26天36篇开发记录的全方位踩坑与经验总结
  • kubectl-debug性能优化:如何配置资源限制和启动参数
  • 为什么92%的Java团队卡在Loom响应式配置最后一公里?这份内部调试日志级配置清单请收好
  • 告别客户端混乱!用Mountain Duck把OneDrive、Google Drive都变成电脑本地硬盘(保姆级配置)
  • xrdp终极指南:免费实现Windows到Linux的完美远程桌面连接
  • 打造家庭KTV新体验:3个步骤用UltraStar Deluxe开启免费卡拉OK之旅
  • 面试官:详细聊聊Spring的拓展功能!
  • 天猫茅台抢票时间策略:Tmall_Tickets如何精准把握抢购时机
  • 终极大麦网抢票指南:告别手速烦恼,三分钟搞定演唱会门票
  • C# 14原生AOT部署Dify客户端:从“Hello World”到生产就绪的72小时极速落地路径(含Docker multi-stage构建+符号调试逆向指南)
  • PowerCat在企业环境中的应用:合规使用的最佳实践指南
  • Circle最佳实践:10个提升团队协作效率的技巧与策略
  • Rust 并发同步之屏障(Barrier):让多线程步调一致
  • Qwen3-Reranker-8B模型安全指南:防御对抗攻击
  • xalpha 性能调优与缓存策略:处理大规模数据的终极方案
  • Speechless:免费Chrome插件,一键完整备份微博记忆的终极方案
  • 大厂Java面试:谈谈你对redis的理解?
  • Prisma Client Go查询构建器详解:10个高效数据库操作技巧
  • 别再只用EEMD了!CEEMDAN在MATLAB里这么用,信号分解又快又准
  • 打工人效率神器!OpenClaw 部署与办公自动化教程
  • 游戏天气系统动态变化与视觉效果
  • 别只看容量!深入聊聊STM32F103C6T6与C8T6那些容易被忽略的细节差异
  • CefSharp 中加载超长 HTML 的解决方案
  • 如何用Serverless Components构建完整无服务器应用?5个实用模板快速上手