主题
SCE 场景内置节点、地形、天气与光照运行时控制
SCE 场景会在底层 SceneGraph 中创建一组固定用途的内置节点,例如地形根节点、LightGroup、DirectionalLight 和 PointLights。WasiCore 提供了一组 Scene 扩展方法,用于在运行时安全获取这些内置节点,并通过控制器操作地形、天气、雾效、战争迷雾、光照和 LightGroup 预设。
这组 API 只适用于 SCE 场景加载流程创建的 Scene。普通 SceneGraph 可以由项目脚本自由创建,不保证存在这些固定命名节点,因此高层入口放在 Scene 上,而不是放在 SceneGraph 上。
使用边界
- 仅客户端可用。相关类型和控制器位于
#if CLIENTAPI 表面。 - 仅运行时视觉修改。不会同步服务器,不会自动影响其他玩家,也不会写回地图资源。
- 依赖 SCE 场景内置节点。场景尚未加载完成、
Game.LocalScene.IsEngineFallback == true或当前不是 SCE 场景时,应认为控制器可能返回null。 - 需要跨端或持久化时,把“天气/光照意图”记录到项目自己的玩法数据中,再在客户端按意图应用视觉参数。
编辑器中的“氛围”文件是什么
地形编辑器里看到的氛围(atmosphere),对应运行时的 LightGroup 环境节点。项目地图的氛围资源通常位于地图根目录的 atmosphere/ 文件夹,例如:
text
<project-map>/atmosphere/default.lightgroup
<project-map>/atmosphere/夜晚.lightgroup.lightgroup 只是资源后缀;文件内容是可以直接阅读的 XML,通常包含 Zone 组件(环境光、雾、环境贴图、Bloom 等)以及一个或多个 Light 节点(方向光/点光源)。因此,AI 接到“把地图氛围改成更暗、更暖、加雾”等请求时,应先在目标项目的 atmosphere/ 中定位对应 .lightgroup 文件,再修改 XML 中的相关 <attribute>,而不是去改地形高度或凭空创建一个 LightGroup 节点。保留原有 id、origin、组件结构和资源路径;name.json 只是氛围名称映射,不是光照参数本体。
这是持久化地图编辑,与下面的运行时 API 有明确区别:
| 需求 | 正确入口 | 是否写回地图 |
|---|---|---|
| 永久修改编辑器地图的氛围、雾、环境光、太阳光 | 修改 atmosphere/*.lightgroup XML | 是 |
| 当前客户端临时变暗、变色或渐变换预设 | SceneLightingController | 否 |
| 运行时修改地表材质/权重或地形派生数据 | SceneTerrainController | 否(仅当前客户端) |
修改 .lightgroup 后应重新加载地图或重新启动客户端验证;运行时控制器修改则只在当前客户端生效。不要把项目的 .lightgroup 误写成示例中的引擎预设路径 LightGroup/Daytime.xml,也不要为了“修复后缀”把文件重命名为 .xml。
入口
常用入口在 GameGraph.NodeSystem 命名空间下:
csharp
#if CLIENT
using GameGraph.NodeSystem;
var scene = Game.LocalScene;
if (scene.IsEngineFallback)
{
return;
}
var weather = scene.TryGetWeatherController();
var lighting = scene.TryGetLightingController();
#endif如果只需要节点本身,也可以直接读取内置节点:
csharp
#if CLIENT
var terrainNode = Game.LocalScene.TryGetTerrainNode();
var terrain = Game.LocalScene.TryGetTerrainController();
var weatherNode = Game.LocalScene.TryGetWeatherNode();
var lightingNode = Game.LocalScene.TryGetLightingNode();
var lightGroup = Game.LocalScene.GetBuiltinNode(SceneBuiltinNodeKind.LightGroup);
#endifTryGetTerrainController()、TryGetWeatherController() 和 TryGetLightingController() 在找不到对应入口节点时返回 null。建议在地图加载完成之后调用,例如等待目标 Scene.OnLoadEnd,不要用 GameStart 或固定延迟推断 SCE 场景已经可用。
地形运行时操作
SceneTerrainController 面向当前 SCE 场景内置地形。常用能力包括:
- 读取当前客户端地形材质:
GetMaterials()、MaterialCount、GetMaterial(...)、TryGetMaterial(...)。 - 读取和替换地形材质贴图槽:
GetMaterialTexture(...)、SetMaterialTexture(...)。 - 读取和设置普通 tile 地表权重:
WorldPositionToTextureWeightPoint(...)、TryGetTextureWeights(...)、GetTextureWeights(...)、SetTextureWeights(...)。 - 查询采样点主导贴图标签:
TryGetTextureLabel(...)、GetTextureLabel(...)。 - 刷新地形 dirty mesh:
RefreshDirtyTerrain()。 - 读取和设置 Landscape patch / weight map:
LandscapePatchCount、TryGetLandscapePatchBlend(...)、SetLandscapePatchBlend(...)、LandscapeWeightMapSize、TryGetLandscapeWeightMapPixel(...)、SetLandscapeWeightMapPixel(...)、PaintLandscapeWeights(...)。
示例:替换当前客户端地形材质贴图。
csharp
#if CLIENT
using EngineInterface.Urho3DInterface.Graphics;
using GameGraph.NodeSystem;
using ResourceTexture = GameCore.ResourceType.Texture;
var terrain = Game.LocalScene.TryGetTerrainController();
terrain?.SetMaterialTexture(
materialIndex: 0,
unit: TextureUnit.Diffuse,
texturePath: (ResourceTexture)"res/terrain/grass.png");
#endif离散瓦片地表编辑的加载前配置
在场景数据中设置 GameDataScene.EnableRuntimeTerrainTextureEditing = true,客户端才会为该场景保留纹理权重和地形重建数据。默认值为 false,未开启的普通场景继续在加载收尾释放这些数据。此项只用于离散瓦片地表纹理索引和权重编辑,不启用高度、拓扑、道路或装饰物编辑,也不改变 Landscape 的权重图接口。
该属性必须在场景加载前配置;运行中把它改为 true 不会恢复已释放的数据。保留数据会增加客户端内存占用,随场景实例销毁释放;显式缓存场景时会延长其生命周期,清理缓存后才能释放。同一地形资产被不同场景复用时,加载会检查编辑开关,避免使用不匹配的缓存。
更新编辑器后,也可在场景编辑器右侧的场景属性面板中开启“允许运行时修改地表纹理”。该选项仅在离散瓦片场景显示,修改后自动写入场景数据;保存项目并重新进入调试或重新加载运行时场景后生效。
使用前由项目在初始化时检查 ClientEnvironment.Runtime.Supports(ClientRuntimeFeature.RuntimeTerrainTextureEditing),最低客户端能力版本为 5。旧客户端不会因这个字段而获得运行时编辑能力。材质贴图槽替换不需要开启此属性。
对于数据编辑器维护的场景,直接在对应 editor/data/.../GameDataScene/*.json 的场景节点中配置该字段,再让编辑器生成代码;不要回写 ScopeData.*.Data 或手改 DataGenerated。示例字段:
json
"EnableRuntimeTerrainTextureEditing": true示例:场景加载完成后,调整指定世界坐标处的地表权重。批量编辑时逐点使用 refresh: false,全部写完后调用一次 RefreshDirtyTerrain()。
csharp
#if CLIENT
using GameGraph.MaterialSystem;
using GameGraph.NodeSystem;
var terrain = Game.LocalScene.TryGetTerrainController();
if (terrain?.GetTextureWeights(position) is { } weights)
{
terrain.SetTextureWeights(position, weights with
{
Weight1 = 1.0f,
Weight2 = 0.0f
});
}
#endif材质替换和地形权重操作用于当前运行时地形状态,不等同于编辑器地形资产保存流程。需要服务端权威判定、持久化地图编辑或项目同步时,应在项目自己的数据/编辑器流程中记录意图,而不是只依赖客户端本地地形表现。
区域混合、动画与恢复示例
map_templates/code_sample/src/RuntimeTerrainTextureSample/ 提供可运行的完整示例,并自带 scene/runtime_terrain_texture_sample/。在示例项目选择 RuntimeTerrainTextureSample 模式后,可比较纯纹理替换、25%/50%/75% 混合、三纹理渐变、柔和圆形笔刷和扩散动画,并恢复原貌。独立场景定义在加载前开启编辑缓存,不影响其他示例场景。
混合和恢复需要遵循以下规则:
- 完整保存状态: 为编辑区域逐点保存整个
TerrainTextureWeights,包括TextureStyle、三个纹理索引、两个基础权重及ExtendedWeights。不要只保存Weight1/Weight2。 - 按实际资源解释索引: 纹理风格、槽位及混合组来自当前地图加载的资源目录。示例使用内置自然风光组 1、风格 0、槽位 2/3/7,并在启动时验证;这些编号不是通用规则。
- 两套权重保持一致:
ExtendedWeights.XYZ按混合组顺序表示权重,W表示组编号;基础纹理索引及基础权重也应表达同一结果。只改基础权重不保证所有渲染路径都显示预期混合。示例的EncodeGroup展示完整编码。 - 核实扩展组编号支持: 当前 SDK 会将扩展向量的各分量限制到
[0,1],示例因此使用组 1。使用更大编号的组前,应核实 SDK 的写入与回读支持,不能直接把W改成更大编号。 - 从快照计算动画: 每帧根据初始状态与本帧笔刷覆盖率计算,避免累积涂刷导致边缘漂移。不同风格或无法解释的混合状态应保留或显式处理,不能假设任意纹理都能互混。
- 整批刷新与释放: 逐点写入用
refresh: false,随后统一RefreshDirtyTerrain();恢复时同样批量回写快照。场景切换时停止动画,结束时释放 UI 与快照引用。
验收应同时包含权重读回和画面观察:检查混合比例、柔和边缘、动画连续性、恢复与重新进入后的初始状态。后台窗口可能暂缓三维渲染;自动截图应等待前台画面稳定,不应只凭 HUD 更新推断地表已经更新。已有用户目视确认时,无需为重复取图延长验收。
天气与雾效
SceneWeatherController 面向 SCE 场景内置天气、TerrainData 雾效和战争迷雾。常用能力包括:
- 读取和设置 TerrainData 运行时状态:
HasTerrainData、TerrainDataEnabled、WeatherEffectEnabled、TerrainFogColor。 - 读取和写入 Zone 雾效快照:
GetFogSettings()、ApplyFogSettings(...)。 - 将天气颜色/亮度应用到相关灯光:
ApplyLightWeatherValues(...)、SetLightWeatherParamBlend(...)。 - 修改 cloud shadow:
SetCloudShadowColor(...)、RefreshCloudShadow()。 - 创建和调整当前客户端战争迷雾:
OpenFogOfWar(...)、FogOfWarOpen、FogOfWarEnabled、FogOfWarSize、GetFogOfWarSettings()、ApplyFogOfWarSettings(...)、SetFogOfWarWeight(...)、AddFogOfWarWeight(...)、MixFogOfWarWeight(...)、RefreshFogOfWar(...)、SwapFogOfWarLayer()。
示例:临时切换地形天气和雾色。
csharp
#if CLIENT
using System.Drawing;
using GameGraph.NodeSystem;
var weather = Game.LocalScene.TryGetWeatherController();
if (weather is null || !weather.HasTerrainData)
{
return;
}
weather.TerrainDataEnabled = true;
weather.WeatherEffectEnabled = true;
weather.TerrainFogColor = Color.FromArgb(255, 80, 100, 120);
weather.ApplyLightWeatherValues(Color.FromArgb(255, 120, 150, 190), brightness: 0.75f);
weather.SetLightWeatherParamBlend(1.0f);
#endif示例:打开战争迷雾并调整显示参数。
csharp
#if CLIENT
using System.Drawing;
using GameGraph.NodeSystem;
var weather = Game.LocalScene.TryGetWeatherController();
if (weather?.OpenFogOfWar(true) == true)
{
weather.ApplyFogOfWarSettings(new SceneFogOfWarSettings(
Blend: 0.65f,
OpenSpeed: 4.0f,
Brightness: 0.85f,
FogColor: Color.FromArgb(255, 40, 48, 60),
RangeColor: Color.FromArgb(255, 100, 120, 140)));
weather.SetFogOfWarWeight(x: 8, y: 12, weight: 0.0f);
weather.RefreshFogOfWar(force: true);
}
#endif战争迷雾这里是 TerrainData 的客户端视觉层,不等同于 SceneVisionSystem 的玩法视野同步。需要“玩家是否看见单位、是否可攻击、是否同步对象”等玩法判定时,仍应使用场景视野系统或项目自己的服务端逻辑。
光照与环境参数
SceneLightingController 面向 SCE 场景内置 LightGroup、主光、点光源集合和 Zone 环境参数。常用能力包括:
- 获取入口节点和组件:
LightGroupNode、DirectionalLightNode、PointLightsNode、MainLight、Lights、PointLights、Zone。 - 读取和写入光源快照:
GetMainLightSettings()、ApplyMainLightSettings(...)、CaptureLightSettings(...)、ApplyLightSettings(...)。 - 读取和写入 Zone 雾效:
GetFogSettings()、ApplyFogSettings(...)。 - 读取和写入环境光照/后处理:
GetEnvironmentSettings()、ApplyEnvironmentSettings(...)。 - 加载或渐变切换 LightGroup XML 预设:
LoadPreset(...)、StartPresetFade(...)、IsPresetFading、ResetPresetFade()。
示例:调整主光亮度。
csharp
#if CLIENT
using GameGraph.NodeSystem;
var lighting = Game.LocalScene.TryGetLightingController();
if (lighting?.GetMainLightSettings() is { } mainLight)
{
lighting.ApplyMainLightSettings(mainLight with
{
Brightness = 1.25f,
ShadowIntensity = 0.7f
});
}
#endif示例:调整 Bloom 与暗角。
csharp
#if CLIENT
using GameGraph.NodeSystem;
var lighting = Game.LocalScene.TryGetLightingController();
if (lighting?.GetEnvironmentSettings() is { } environment)
{
lighting.ApplyEnvironmentSettings(environment with
{
BloomWeight = 0.4f,
VignetteEnabled = true,
VignetteIntensity = 0.25f
});
}
#endif示例:渐变切换 LightGroup 预设。
csharp
#if CLIENT
using GameGraph.NodeSystem;
var lighting = Game.LocalScene.TryGetLightingController();
lighting?.StartPresetFade("LightGroup/Daytime.xml", fadeInTime: 1.5f);
#endifLoadPreset(...) 会直接把 LightGroup 节点加载为指定 XML;StartPresetFade(...) 会启动当前客户端的 LightGroup 渐变状态。二者都不会修改地图资源。
组件级 API
如果项目代码已经持有具体组件,也可以直接使用组件级 API:
LightComponent:Type、Color、Brightness、Range、SpecularIntensity、Fov、PerVertex、ShadowIntensity、ShadowResolution、ShadowMaxExtrusion、UseWeatherValues、WeatherColor、WeatherBrightness、SetWeatherParamBlend(...)。Zone:环境光、顶点环境光、深度雾、高度雾、环境贴图强度、环境立方体贴图角度、角色 SH、Bloom、Vignette 等参数。
组件级 API 不负责寻找 SCE 内置节点。需要操作 SCE 场景整体天气或光照时,优先从 Scene.TryGetWeatherController() / Scene.TryGetLightingController() 进入。
常见问题
为什么不是 SceneGraph.TryGetWeatherNode()?
因为普通 SceneGraph 可以是项目脚本创建的任意图,不必然有 SCE 固定命名节点。SCE 场景对应的 Scene 才表达“这是由 SCE 场景加载流程创建的运行时场景”,所以高层 API 放在 Scene 扩展方法上。
为什么控制器返回 null?
常见原因是代码运行得太早、当前是 Engine Fallback Scene、地图不是 SCE 场景、当前运行端没有客户端图形对象,或地图资源本身缺少对应内置节点。依赖地图内容的逻辑应等待场景加载完成,并始终保留 null 兜底。
能不能从服务端改全体玩家天气?
这组 API 本身不做同步。服务端可以同步“天气状态”这类玩法数据给客户端,客户端收到后再调用这些 API 应用视觉效果。
能不能保存回编辑器地图?
不能。这组 API 是运行时控制,不写回地图配置、LightGroup XML 或地形资源。