Skip to content

SCE 场景内置节点、地形、天气与光照运行时控制 ​

SCE 场景会在底层 SceneGraph 中创建一组固定用途的内置节点,例如地形根节点、LightGroup、DirectionalLight 和 PointLights。WasiCore 提供了一组 Scene 扩展方法,用于在运行时安全获取这些内置节点,并通过控制器操作地形、天气、雾效、战争迷雾、光照和 LightGroup 预设。

这组 API 只适用于 SCE 场景加载流程创建的 Scene。普通 SceneGraph 可以由项目脚本自由创建,不保证存在这些固定命名节点,因此高层入口放在 Scene 上,而不是放在 SceneGraph 上。

使用边界

  • 仅客户端可用。相关类型和控制器位于 #if CLIENT API 表面。
  • 仅运行时视觉修改。不会同步服务器,不会自动影响其他玩家,也不会写回地图资源。
  • 依赖 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);
#endif

TryGetTerrainController()、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);
#endif

LoadPreset(...) 会直接把 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 或地形资源。

相关文档 ​