ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Unity战争迷雾系统:三脚本实现RTS游戏视野与探索机制

2026/8/9 13:55:00 拓冰建站 浏览量
Unity战争迷雾系统:三脚本实现RTS游戏视野与探索机制 1. 项目概述与核心价值做RTS即时战略游戏战争迷雾系统绝对是绕不开的核心机制。它不仅仅是地图上的一层黑色遮罩更是游戏策略深度、玩家决策和战场信息博弈的灵魂。想象一下你指挥的部队在一片未知的黑暗中探索随着单位的移动地图被逐渐点亮但一旦离开视野又会重新被迷雾笼罩只有友方建筑或单位存在的区域才会被永久揭示。这种对“未知”和“已知”的动态管理直接决定了游戏的战术节奏和玩家的心理博弈。市面上很多教程要么过于复杂引入了网格计算、高度图、甚至GPU计算让初学者望而却步要么过于简陋效果生硬无法满足商业级需求。我折腾过不少方案也踩过很多坑最终沉淀下来的这套“三脚本”方案是我认为在效果、性能和易用性上达到最佳平衡点的实践。它的核心目标就一个用最少的代码实现一套视觉效果专业、运行效率可靠、且易于集成到任何Unity RTS项目中的战争迷雾系统。整个系统完全基于Unity内置的渲染管线无论是Built-in还是URP稍作调整即可适配不依赖任何特殊插件。核心就是三个C#脚本分别处理视野提供者如单位、战争迷雾的渲染、以及地图探索状态的记录。你从GitHub上拿到源码拖入场景配置好参数基本上半小时内就能看到效果。这对于独立开发者或者需要快速验证玩法的团队来说效率提升是巨大的。接下来我会带你彻底拆解这三个脚本的每一行关键代码不仅告诉你怎么做更会深入解释为什么这么做以及在实际项目中可能遇到的“坑”和应对技巧。无论你是刚接触Unity的RTS爱好者还是正在为项目寻找轻量级迷雾方案的开发者这篇文章都能给你一份可以直接“抄作业”的完整蓝图。2. 系统核心设计与思路拆解在动手写代码之前我们必须把设计思路理清楚。一个完整的战争迷雾系统需要解决以下几个核心问题谁来看 (Vision Providers)哪些游戏对象单位、建筑能够提供视野驱散周围的迷雾。怎么看 (Vision Logic)视野的范围圆形、扇形、是否会被障碍物树林、山脉阻挡是即时揭示还是渐变揭示怎么看 (Vision Logic)视野的范围圆形、扇形、是否会被障碍物树林、山脉阻挡是即时揭示还是渐变揭示如何渲染 (Fog Rendering)如何将“可见”、“已探索但不可见”、“未探索”这三种状态用视觉方式通常是颜色和透明度流畅地表现出来。状态如何持久化 (Exploration Memory)被探索过的区域即使再次被迷雾覆盖也应该与完全未知的黑色区域有所区别通常显示为半透明的灰色这需要系统能记住整个地图的探索历史。2.1 极简架构三脚本分工为了高效解决上述问题我们摒弃了复杂的类继承和消息系统采用了最直观的“数据-逻辑-渲染”分离架构FogOfWarVision.cs(视野提供者脚本)这是一个MonoBehaviour需要挂载到每一个可以提供视野的游戏对象上比如士兵、侦察车、瞭望塔。它的职责很简单告诉系统“我在这里我的视野半径是X”。它不关心渲染只负责在每帧或按需更新自己的位置信息到一个中央数据管理器。FogOfWarRenderer.cs(迷雾渲染器脚本)这是系统的视觉核心通常挂在一个覆盖全地图的Quad或Plane上。它持有一张或两张与地图分辨率对应的Render Texture作为“迷雾纹理”。它的工作流程是收集所有FogOfWarVision提供者的位置和半径。在一个独立的摄像机我们称之为“迷雾摄像机”下将这些视野区域渲染到一张临时纹理上通常用Shader画圆。将这张临时纹理与上一帧的“当前视野纹理”进行混合实现视野的平滑过渡例如从不可见到完全可见的淡入效果。将“当前视野纹理”与“已探索纹理”结合生成最终用于屏幕显示的“迷雾纹理”。通过一个自定义Shader使用这张最终的“迷雾纹理”来调制地图物体的颜色和透明度实现迷雾视觉效果。FogOfWarExploration.cs(探索记录器脚本)这个脚本管理一张与地图同分辨率的“已探索纹理”Exploration Texture。每当有新的视野区域被揭示它就会将这些区域“烙印”到这张纹理上。这张纹理是持久化的即使提供视野的单位离开这些区域在“已探索纹理”中仍然是“已点亮”状态。渲染器会结合“当前视野”和“已探索”状态决定最终某个地图位置是“明亮”当前可见、“暗淡”已探索但不可见还是“黑暗”未探索。为什么选择Render Texture而不是实时计算这是性能权衡的关键。在CPU端实时计算每个像素是否在某个单位的视野内复杂度是O(单位数*像素数)在大型地图上不可行。而利用GPU进行并行渲染将视野区域画到纹理上再由Shader进行像素级混合效率要高几个数量级。Render Texture正是连接CPU逻辑和GPU渲染的桥梁。2.2 关键技术选型与考量1. 渲染纹理Render Texture的分辨率分辨率直接影响视觉效果和性能。分辨率太低迷雾边缘会有明显的锯齿太高则会浪费显存和带宽。一个实用的经验公式是迷雾纹理分辨率 地图世界尺寸单位米 * 像素每米Pixels Per Meter。通常PPI设为2-4就能在大多数RTS视角下获得平滑的边缘。例如一个100x100米的地图用PPI2就需要一张200x200的Render Texture。你可以根据游戏摄像机的俯角和高动态调整。2. 视野的混合与过渡如果视野纹理只是每帧完全重绘视野的开启和关闭会非常生硬。我们引入“混合Blending”的概念。在FogOfWarRenderer中我们维护两张纹理_currentVisionTexture当前帧计算的理想视野和_blendedVisionTexture实际用于渲染的、平滑过渡的视野。每一帧我们并不直接用新的覆盖旧的而是用类似_blendedVisionTexture Lerp(_blendedVisionTexture, _currentVisionTexture, blendSpeed * Time.deltaTime)的方式进行插值。blendSpeed参数控制过渡的快慢值越大视野变化越迅速。这带来了迷雾淡入淡出的柔和效果。3. 障碍物遮挡Line of Sight, LOS这是实现战术深度的关键。我们的方案采用一种轻量级的射线检测法。在FogOfWarVision中除了提供位置和半径我们还可以在渲染视野纹理时不是简单地画一个实心圆而是通过Shader或CPU端采样模拟视线阻挡。一种高效的做法是在生成视野纹理的Pass中对每个像素计算其到视野中心的方向和距离然后发射一条射线通过Physics.Raycast或自定义的2D网格查询如果射线被标记为“障碍物”Obstacle的碰撞体阻挡则该像素不被点亮。这种方法比传统的“阴影投射”Shadow Casting算法更易实现和集成到现有的物理系统中。4. 已探索区域的记录FogOfWarExploration管理的“已探索纹理”本质上是一张单通道如R通道的Render Texture初始值为0黑色代表未探索。当某个区域被视野揭示时我们通过Graphics.Blit操作以“Max”混合模式将当前视野纹理“绘制”到探索纹理上。因为使用的是Max混合探索纹理上对应像素的值只会增加从0到1而不会减少实现了“永久点亮”的效果。在最终渲染时Shader读取“当前视野纹理”和“已探索纹理”的值通过一组阈值判断如果当前视野值 0.5则完全可见如果当前视野值 0.5 但已探索值 0.5则显示为半透明的已探索状态否则显示为完全不透明的未探索状态。3. 核心脚本解析与实操要点现在我们深入到每一个脚本的内部看看它们具体是如何工作的以及有哪些必须注意的配置细节。3.1 FogOfWarVision.cs视野的源头这个脚本是数据的生产者。它需要被添加到场景中每一个能提供视野的GameObject上。using UnityEngine; public class FogOfWarVision : MonoBehaviour { [Header(Vision Settings)] public float visionRadius 10f; // 视野半径 public LayerMask obstacleMask; // 阻挡视野的层如“Obstacles” public bool isActive true; // 是否激活视野 // 提供一个属性方便FogOfWarRenderer获取数据 public VisionData GetVisionData() { if (!isActive) return null; return new VisionData { position transform.position, radius visionRadius, obstacleMask obstacleMask }; } } // 一个简单的数据结构用于传递视野信息 public struct VisionData { public Vector3 position; public float radius; public LayerMask obstacleMask; }关键点解析与实操心得obstacleMask的配置这是实现视线阻挡的关键。你需要在Unity的Layer设置中创建一个名为“Obstacle”或类似的层并将所有会阻挡视野的物体如树木、岩石、墙壁的Layer设置为这一层。然后在FogOfWarVision的Inspector面板中将Obstacle Mask设置为这个层。这样在计算视野时系统就能知道哪些碰撞体需要被检测。性能考量静态视野提供者对于像建筑这类不会移动的视野提供者你不需要每帧都更新它的位置。可以在FogOfWarVision中增加一个bool isStatic选项。如果是静态的只在Start()或位置改变时通知渲染器一次即可避免不必要的每帧数据传递。视野形状扩展当前是圆形视野。如果你想实现扇形视野比如单位有朝向只能看到前方180度可以在VisionData结构中加入direction和angle字段并在渲染视野纹理的Shader中做相应的片元剔除计算。3.2 FogOfWarRenderer.cs视觉效果的引擎这是最复杂的脚本负责所有渲染相关的逻辑。我们将其拆解为几个关键方法。using UnityEngine; using System.Collections.Generic; public class FogOfWarRenderer : MonoBehaviour { [Header(Render Textures)] public int textureResolution 256; // 迷雾纹理分辨率 private RenderTexture _currentVisionTexture; // 当前帧计算的原始视野 private RenderTexture _blendedVisionTexture; // 平滑混合后的视野 private RenderTexture _fogTexture; // 最终输出的迷雾纹理 [Header(Rendering)] public Material visionRenderMaterial; // 用于将视野数据画到纹理的材质 public Material fogCompositeMaterial; // 用于合成最终迷雾的材质 public Camera fogCamera; // 一个正交摄像机用于渲染视野到纹理 public float blendSpeed 5f; // 视野混合速度 [Header(Fog Settings)] public Color unexploredColor Color.black; // 未探索区域颜色 public Color exploredColor new Color(0.2f, 0.2f, 0.2f, 0.8f); // 已探索但不可见区域颜色 // 当前可见区域则显示地图原色 private ListFogOfWarVision _visionProviders new ListFogOfWarVision(); private FogOfWarExploration _explorationManager; void Start() { InitializeRenderTextures(); SetupFogCamera(); _explorationManager FindObjectOfTypeFogOfWarExploration(); // 将自身材质设置为使用生成的_fogTexture GetComponentRenderer().material.SetTexture(_FogTex, _fogTexture); } void Update() { UpdateVisionProvidersList(); // 动态查找所有视野提供者可优化为注册制 RenderVisionToTexture(); // 将视野渲染到_currentVisionTexture BlendVisionTextures(); // 混合_current和_blended实现平滑过渡 CompositeFinalFogTexture(); // 结合_blended和已探索纹理生成_fogTexture } void InitializeRenderTextures() { // 创建三张Render Texture格式为ARGB32关闭抗锯齿以获得清晰边缘 RenderTextureDescriptor desc new RenderTextureDescriptor(textureResolution, textureResolution, RenderTextureFormat.ARGB32, 0); desc.autoGenerateMips false; desc.useMipMap false; _currentVisionTexture new RenderTexture(desc); _blendedVisionTexture new RenderTexture(desc); _fogTexture new RenderTexture(desc); // 初始清空纹理 ClearTexture(_currentVisionTexture, Color.clear); ClearTexture(_blendedVisionTexture, Color.clear); ClearTexture(_fogTexture, unexploredColor); } // ... 其他方法RenderVisionToTexture, BlendVisionTextures等将在下文展开 }关键点解析与实操心得fogCamera的设置你需要创建一个新的Camera将其设置为正交投影Orthographic。这个摄像机的Size应该等于你地图世界尺寸的一半如果地图中心在(0,0)。例如100x100的地图Camera的Size应设为50。将这个摄像机的Culling Mask设置为Nothing因为我们不需要它渲染任何场景物体它只用于Graphics.Blit操作。将其Target Texture设置为_currentVisionTexture。它的作用仅仅是提供一个从世界坐标到纹理UV坐标的变换矩阵。visionRenderMaterial的Shader这个材质使用一个自定义Shader它的输入是所有视野提供者的位置和半径数组。在Shader中对于纹理上的每一个像素即地图上的每一个点计算其到每个视野中心的距离。如果距离小于视野半径则输出白色表示可见否则输出黑色表示不可见。如果有多个视野重叠取最大值即只要在一个视野内就可见。这就是将逻辑视野“画”到纹理上的过程。纹理混合的艺术BlendVisionTextures方法是实现柔和过渡的核心。我们使用Graphics.Blit配合一个简单的混合Shader。这个Shader接收两张纹理_PrevTex和_CurrTex和一个混合系数_Blend然后执行lerp(_PrevTex, _CurrTex, _Blend)。在Update中我们以blendSpeed * Time.deltaTime作为混合系数进行Blit操作。blendSpeed值通常设置在3到10之间值太小过渡太慢显得迟钝值太大则失去平滑感。与探索纹理的合成CompositeFinalFogTexture方法将平滑后的视野纹理_blendedVisionTexture和探索管理器提供的已探索纹理_explorationManager.exploredTexture作为输入通过fogCompositeMaterial输出最终的_fogTexture。这个合成Shader的逻辑决定了三种状态的视觉表现。3.3 FogOfWarExploration.cs地图的记忆这个脚本相对独立它只负责一件事记录哪些地方被探索过。using UnityEngine; public class FogOfWarExploration : MonoBehaviour { public RenderTexture exploredTexture; // 已探索纹理 public Material explorationUpdateMaterial; // 用于更新探索纹理的材质 void Start() { InitializeExploredTexture(); } void InitializeExploredTexture() { // 创建一张单通道R8的纹理来记录探索状态初始为0黑色 RenderTextureDescriptor desc new RenderTextureDescriptor( FogOfWarRenderer.Instance.textureResolution, // 应与迷雾渲染器分辨率一致 FogOfWarRenderer.Instance.textureResolution, RenderTextureFormat.R8, // 单通道节省内存 0 ); exploredTexture new RenderTexture(desc); ClearTexture(exploredTexture, Color.black); // 初始状态全未探索 } // 每帧由FogOfWarRenderer调用将当前视野“烙印”到探索纹理上 public void UpdateExploration(RenderTexture currentVisionTexture) { if (explorationUpdateMaterial null || currentVisionTexture null) return; // 使用一个特殊的材质进行Blit混合模式为Max确保探索值只增不减 Graphics.Blit(currentVisionTexture, exploredTexture, explorationUpdateMaterial); } }关键点解析与实操心得纹理格式RenderTextureFormat.R8探索状态只需要一个通道从0到1来表示“探索程度”使用R8格式比ARGB32节省75%的显存。这在大型地图高分辨率下非常有用。explorationUpdateMaterial的Shader这个材质使用的Shader极其简单。它的片段着色器Fragment Shader大概是这样fixed4 frag(v2f i) : SV_Target { float existing tex2D(_ExploredTex, i.uv).r; float newVision tex2D(_VisionTex, i.uv).r; return max(existing, newVision); }。max操作是关键它保证了探索状态是单向的、累积的。探索纹理的持久化在真正的游戏项目中你需要在地图切换或游戏保存时将exploredTexture的数据通过RenderTexture.GetPixels序列化到硬盘。在加载时再重新创建纹理并填充数据。否则每次重启游戏地图都是全新的。4. 完整集成与场景配置实战理解了每个脚本的原理后让我们一步步在Unity场景中搭建起整个系统。4.1 场景搭建步骤创建地图基底创建一个Plane或Quad命名为“Map”。这代表你的游戏地图。为其赋予一个清晰的地面材质。创建迷雾覆盖层复制上一步的“Map”重命名为“FogOverlay”。将其放置在比地图略高的位置例如Y轴高0.1个单位以防止Z-fighting。这个对象将用来显示战争迷雾。设置迷雾渲染器将FogOfWarRenderer脚本挂载到“FogOverlay”对象上。创建一个新的Camera命名为“FogCamera”。将其设置为正交投影Projection: Orthographic。调整其Size使其视口能完整覆盖你的地图范围。将其Position的Z轴设置为-10左右使其看向地图。在Target Texture栏先留空脚本会动态设置。将“FogCamera”拖拽到FogOfWarRenderer脚本的fogCamera字段。创建两个新的Material分别用于visionRenderMaterial和fogCompositeMaterial并为其分配对应的Shader源码包中会提供。在FogOfWarRenderer的Inspector中设置textureResolution如256blendSpeed如5以及unexploredColor和exploredColor。设置探索管理器在场景中创建一个空GameObject命名为“FogManager”将FogOfWarExploration脚本挂载上去。为其explorationUpdateMaterial字段分配对应的材质。创建视野提供者为你场景中的单位或建筑预制体添加FogOfWarVision脚本。设置合理的visionRadius如5或10并配置好obstacleMask。配置迷雾Shader最关键的一步是给“FogOverlay”对象的材质赋值。这个材质应该使用一个自定义的“FogOfWar”Shader。这个Shader的主要功能是采样_FogTex由FogOfWarRenderer每帧更新根据其RGB值或单个强度值来决定如何混合地图颜色和迷雾颜色。通常这个Shader是透明的并且关闭深度写入ZWrite Off使用混合模式Blend SrcAlpha OneMinusSrcAlpha。4.2 核心Shader代码片段解析这里给出最终合成迷雾的Shader的关键片段帮助你理解其工作原理Shader Custom/FogOfWar { Properties { _MainTex (Map Texture, 2D) white {} // 实际的地图纹理 _FogTex (Fog Texture, 2D) black {} // 由FogOfWarRenderer传入的动态纹理 _UnexploredColor (Unexplored Color, Color) (0,0,0,1) _ExploredColor (Explored Color, Color) (0.2,0.2,0.2,0.8) } SubShader { Tags { QueueTransparent RenderTypeTransparent } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; sampler2D _MainTex; sampler2D _FogTex; fixed4 _UnexploredColor; fixed4 _ExploredColor; v2f vert (appdata v) { ... } // 标准顶点变换 fixed4 frag (v2f i) : SV_Target { // 1. 采样地图颜色 fixed4 mapColor tex2D(_MainTex, i.uv); // 2. 采样迷雾纹理 fixed4 fogData tex2D(_FogTex, i.uv); // 假设fogData.r存储当前视野强度fogData.g存储已探索强度 float currentVision fogData.r; float explored fogData.g; fixed4 finalColor mapColor; float finalAlpha 1.0; // 3. 根据迷雾状态决定最终颜色和透明度 if (currentVision 0.5) { // 当前可见完全显示地图无迷雾 finalAlpha 0.0; } else if (explored 0.5) { // 已探索但不可见显示地图但叠加一层半透明的探索迷雾 finalColor lerp(mapColor, _ExploredColor, _ExploredColor.a); finalAlpha _ExploredColor.a; } else { // 未探索完全被黑色迷雾覆盖 finalColor _UnexploredColor; finalAlpha 1.0; } return fixed4(finalColor.rgb, finalAlpha); } ENDCG } } }这个Shader的逻辑清晰地区分了三种状态并通过Alpha混合来实现迷雾的叠加效果。4.3 性能优化技巧视野提供者管理不要在FogOfWarRenderer的Update里每帧用FindObjectsOfTypeFogOfWarVision。改为让FogOfWarVision在OnEnable()和OnDisable()时向一个静态管理器如FogOfWarRenderer.Instance注册和注销自己。这能显著降低CPU开销。纹理分辨率动态调整可以根据游戏摄像机的缩放级别动态降低或提高textureResolution。当镜头拉远看到整个地图时可以使用较低的分辨率当镜头拉近关注局部细节时再切换到高分辨率。这能平衡视觉效果和性能。按需更新如果视野提供者都是静态的如固定建筑或者移动单位在短时间内位置没变化可以不用每帧更新视野纹理。可以设置一个阈值当所有视野提供者的累计移动距离超过一定值或经过一定时间后再触发一次完整的视野计算和纹理更新。Command Buffer对于更高级的优化可以考虑使用Command Buffer来组织整个迷雾渲染过程减少渲染状态的切换。5. 常见问题排查与实战心得即使按照步骤操作你也可能会遇到一些棘手的问题。这里我整理了开发过程中最常见的几个“坑”及其解决方案。5.1 迷雾边缘锯齿严重现象战争迷雾的边缘看起来像素化有明显的“楼梯”状锯齿。原因迷雾纹理Render Texture分辨率太低或者用于渲染视野的Shader中没有进行平滑处理。解决方案提高FogOfWarRenderer中的textureResolution。尝试加倍观察效果。在渲染视野的ShadervisionRenderMaterial所用的Shader中对视野边缘进行平滑Smoothstep。不要用简单的if(distance radius)改用smoothstep(radius - feather, radius feather, distance)其中feather是一个很小的值如0.5个纹理像素对应的世界单位这会在边缘产生一个平滑的过渡带。确保迷雾纹理的过滤模式Filter Mode设置为Bilinear或Trilinear而不是Point。5.2 迷雾不随摄像机移动/缩放现象当游戏摄像机移动或缩放时覆盖在地图上的迷雾层位置错乱没有正确贴合地图。原因负责渲染迷雾的“FogOverlay”Quad对象其UV与游戏世界坐标的映射关系没有动态更新。“FogCamera”的视口范围可能也没有随主摄像机变化。解决方案确保“FogOverlay”对象始终覆盖整个屏幕。一个常用的技巧是将“FogOverlay”设为摄像机或一个固定于摄像机的空物体的子物体并使其Scale足够大如100, 100, 1这样它就会一直填满屏幕。在FogOfWarRenderer中需要根据主摄像机的位置和视口大小动态计算并设置fogCamera的orthographicSize和transform.position使其与主摄像机看到的世界范围同步。这通常需要一些坐标转换计算。5.3 单位进入障碍物后视野异常现象单位走进树林障碍物后本应被阻挡的视野却依然能驱散树林另一侧的迷雾。原因视线阻挡LOS检测逻辑有误。可能是在渲染视野纹理时只检测了从视野中心到目标点的直线但没有考虑目标点本身是否在障碍物“内部”。解决方案在视野渲染Shader中进行更精确的检测。一种改进方法是不仅检测“中心到像素”的射线还要考虑像素点本身的“可通行性”。我们可以准备一张与迷雾纹理同分辨率的“障碍物纹理”Obstacle Texture在游戏初始化时将障碍物的位置“烘焙”到这张纹理上白色代表障碍物。在视野Shader中采样这个像素点的障碍物值如果大于某个阈值比如0.5则直接视为不可见无论射线检测结果如何。5.4 移动平台性能开销大现象在手机或低性能设备上游戏帧率明显下降。原因每帧进行多次Graphics.Blit操作和全屏的Shader绘制对移动端的GPU带宽和填充率是挑战。解决方案降低分辨率这是最有效的方法。将textureResolution降到128甚至64在移动设备的小屏幕上锯齿感不会太明显。减少更新频率将视野纹理的更新从每帧改为每2-3帧一次。对于节奏不是极快的RTS游戏玩家通常感知不到细微的延迟。简化Shader检查最终迷雾合成的Shader移除不必要的复杂计算。确保没有使用discard操作并尽量减少纹理采样次数。考虑备用方案对于极度追求性能的场景可以考虑回退到基于网格Grid的CPU端战争迷雾虽然效果粗糙但CPU开销可控。5.5 已探索区域在重新加载游戏后丢失现象游戏存档再读档后之前探索过的地图又变成了全黑。原因RenderTexture是GPU资源其内容不会自动序列化。FogOfWarExploration脚本中的exploredTexture数据没有保存到磁盘。解决方案在游戏保存时你需要将exploredTexture的像素数据读取出来转换成字节数组或Base64字符串然后和你其他的游戏数据如单位位置、玩家资源一起保存。代码示例如下public byte[] SaveExplorationData() { Texture2D tex new Texture2D(exploredTexture.width, exploredTexture.height, TextureFormat.R8, false); RenderTexture.active exploredTexture; tex.ReadPixels(new Rect(0, 0, exploredTexture.width, exploredTexture.height), 0, 0); tex.Apply(); RenderTexture.active null; byte[] bytes tex.EncodeToPNG(); // 或者使用更紧凑的自定义格式 Destroy(tex); return bytes; }在加载时你需要反向操作创建新的RenderTexture将保存的字节数据加载到Texture2D然后通过Graphics.Blit将其绘制到RenderTexture上。这套“三脚本”战争迷雾系统经过多个项目的实战检验在效果和性能之间取得了很好的平衡。它最大的优势是清晰的分层架构和高度的可定制性。你可以很容易地修改Shader来改变迷雾的视觉效果例如从纯色迷雾改为噪波扰动迷雾或者扩展FogOfWarVision来支持更复杂的视野逻辑如科技升级增加视野范围。我提供的GitHub源码包含了完整的项目示例所有脚本和Shader都已就绪。你只需要导入Unity工程打开示例场景就能立刻看到运行效果。强烈建议你在理解本文原理的基础上再去阅读和运行源码遇到任何问题也可以随时参考源码中的实现细节。希望这套方案能帮你扫清RTS开发路上的一个关键障碍让你能更专注于游戏玩法本身的打磨。