news 2026/4/20 22:51:19

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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%。关键是要根据实际游戏规模选择合适的技术方案——小型地图可以用全内存存储,大型开放世界则需要更智能的流式加载。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 22:51:18

Qwen3-Reranker-8B模型安全指南:防御对抗攻击

Qwen3-Reranker-8B模型安全指南&#xff1a;防御对抗攻击 1. 引言 在AI模型广泛应用的今天&#xff0c;模型安全问题日益凸显。特别是像Qwen3-Reranker-8B这样的重排序模型&#xff0c;在处理敏感信息检索任务时&#xff0c;面临着各种对抗攻击的威胁。想象一下&#xff0c;如…

作者头像 李华
网站建设 2026/4/20 22:50:35

如何用May协程库5分钟构建高性能并发服务器

如何用May协程库5分钟构建高性能并发服务器 【免费下载链接】may rust stackful coroutine library 项目地址: https://gitcode.com/gh_mirrors/ma/may May是一个轻量级的Rust栈式协程库&#xff0c;能帮助开发者轻松构建高性能的并发服务器。本文将带你快速上手&#x…

作者头像 李华
网站建设 2026/4/20 22:50:29

07华夏之光永存:黄大年茶思屋榜文解法「第10期第7题」存算分离架构核心瓶颈:分布式索引性能双路径工程解法

华夏之光永存&#xff1a;黄大年茶思屋榜文解法「第10期第7题」 存算分离架构核心瓶颈&#xff1a;分布式索引性能双路径工程解法 一、摘要 本题为该领域顶级技术难题&#xff0c;本文采用工程化可复现逻辑&#xff0c;提供两条标准化解题路径&#xff0c;全程符合工程师技术认…

作者头像 李华
网站建设 2026/4/20 22:47:53

BetterGI:让原神日常变轻松的5大智能助手功能全解析

BetterGI&#xff1a;让原神日常变轻松的5大智能助手功能全解析 【免费下载链接】better-genshin-impact &#x1f4e6;BetterGI 更好的原神 - 自动拾取 | 自动剧情 | 全自动钓鱼(AI) | 全自动七圣召唤 | 自动伐木 | 自动刷本 | 自动采集/挖矿/锄地 | 一条龙 | 全连音游 | 自动…

作者头像 李华
网站建设 2026/4/20 22:37:07

原神成就管理终极指南:YaeAchievement免费工具完整使用教程

原神成就管理终极指南&#xff1a;YaeAchievement免费工具完整使用教程 【免费下载链接】YaeAchievement 更快、更准的原神数据导出工具 项目地址: https://gitcode.com/gh_mirrors/ya/YaeAchievement 你是否还在为《原神》中数百个成就的记录和管理感到头疼&#xff1f…

作者头像 李华