Lower provider-owned native environments

This commit is contained in:
Meta Vens 2026-07-15 06:34:39 +01:00
commit 2945512e2a
6 changed files with 231 additions and 29 deletions

View file

@ -184,7 +184,11 @@ namespace GameCult.Eve.UnityScene
{
foreach (var instance in _instances.Values)
if (instance != null)
{
instance.transform.SetParent(null, false);
instance.SetActive(false);
DestroyInstance(instance);
}
_instances.Clear();
_presentedById = new Dictionary<string, EveUnityPresentedEntityHandle>(StringComparer.Ordinal);
_presentedByIndex = new Dictionary<int, EveUnityPresentedEntityHandle>();

View file

@ -24,6 +24,13 @@ namespace GameCult.Eve.UnityScene
private bool _ownsAmbientEnvironment;
private AmbientMode _previousAmbientMode;
private Color _previousAmbientLight;
private float _previousAmbientIntensity;
private Material? _previousSkybox;
private DefaultReflectionMode _previousReflectionMode;
private Texture? _previousCustomReflection;
private float _previousReflectionIntensity;
private Camera? _environmentCamera;
private CameraClearFlags _previousCameraClearFlags;
public EveUnityPlayableWorldClientHost? Host
{
@ -31,7 +38,7 @@ namespace GameCult.Eve.UnityScene
set
{
if (ReferenceEquals(host, value)) return;
ReleaseCamera();
ReleaseRig();
host = value;
}
}
@ -39,7 +46,12 @@ namespace GameCult.Eve.UnityScene
public Transform? CameraTransform
{
get => cameraTransform;
set => cameraTransform = value;
set
{
if (ReferenceEquals(cameraTransform, value)) return;
ReleaseRig();
cameraTransform = value;
}
}
public IEveUnityCameraRenderPolicySource? RenderPolicySource
@ -71,11 +83,6 @@ namespace GameCult.Eve.UnityScene
RestoreAmbientEnvironment();
return false;
}
if (activeWorld.CameraRig == "planar.top-down-follow.v1")
ApplyAmbientEnvironment(activeWorld);
else
RestoreAmbientEnvironment();
if (!string.IsNullOrWhiteSpace(activeWorld.CameraRig) &&
activeWorld.CameraRig != "planar.top-down-follow.v1" &&
activeWorld.CameraRig != "arpg.orbital-follow.v1" &&
@ -95,10 +102,20 @@ namespace GameCult.Eve.UnityScene
ReleaseCamera();
return false;
}
resolvedHost.ClaimWorldCamera(this, camera);
if (!TryResolveEnvironment(resolvedHost, activeWorld, out var skybox, out var reflection))
{
ReleaseRig();
return false;
}
if (!resolvedHost.TryClaimWorldCamera(this, camera))
{
RestoreAmbientEnvironment();
return false;
}
_cameraHost = resolvedHost;
var cameraComponent = camera.GetComponent<Camera>();
ApplyAmbientEnvironment(activeWorld, cameraComponent, skybox, reflection);
if (cameraComponent != null && _renderPolicySource != null)
{
var cullingMask = cameraComponent.cullingMask;
@ -180,20 +197,93 @@ namespace GameCult.Eve.UnityScene
world.CameraNearClipPlane > 0f &&
world.CameraFarClipPlane > world.CameraNearClipPlane;
private void ApplyAmbientEnvironment(EveUnityPlayableWorldProjection world)
private static bool TryResolveEnvironment(
EveUnityPlayableWorldClientHost host,
EveUnityPlayableWorldProjection world,
out Material? skybox,
out Cubemap? reflection)
{
skybox = null;
reflection = null;
var assets = host.NativeAssetProvider;
if (!string.IsNullOrWhiteSpace(world.SkyboxAssetRef))
{
skybox = assets?.ResolveAsset(
new EveUnityPlayableWorldAssetBinding(
world.SkyboxAssetRef,
"environment.skybox",
"provider-asset-ref"),
typeof(Material)) as Material;
if (skybox == null || skybox.shader == null || !skybox.shader.isSupported)
return false;
}
if (!string.IsNullOrWhiteSpace(world.ReflectionAssetRef))
{
reflection = assets?.ResolveAsset(
new EveUnityPlayableWorldAssetBinding(
world.ReflectionAssetRef,
"environment.reflection",
"provider-asset-ref"),
typeof(Cubemap)) as Cubemap;
if (reflection == null)
return false;
}
return true;
}
private void ApplyAmbientEnvironment(
EveUnityPlayableWorldProjection world,
Camera? camera,
Material? skybox,
Cubemap? reflection)
{
if (!_ownsAmbientEnvironment)
{
_previousAmbientMode = RenderSettings.ambientMode;
_previousAmbientLight = RenderSettings.ambientLight;
_previousAmbientIntensity = RenderSettings.ambientIntensity;
_previousSkybox = RenderSettings.skybox;
_previousReflectionMode = RenderSettings.defaultReflectionMode;
_previousCustomReflection = RenderSettings.customReflectionTexture;
_previousReflectionIntensity = RenderSettings.reflectionIntensity;
_environmentCamera = camera;
if (camera != null)
_previousCameraClearFlags = camera.clearFlags;
_ownsAmbientEnvironment = true;
}
RenderSettings.ambientMode = AmbientMode.Flat;
RenderSettings.ambientLight = new Color(
Mathf.Max(0f, world.AmbientLightR * world.AmbientLightIntensity),
Mathf.Max(0f, world.AmbientLightG * world.AmbientLightIntensity),
Mathf.Max(0f, world.AmbientLightB * world.AmbientLightIntensity),
1f);
if (skybox != null)
{
RenderSettings.skybox = skybox;
RenderSettings.ambientMode = AmbientMode.Skybox;
RenderSettings.ambientIntensity = Mathf.Max(0f, world.AmbientLightIntensity);
if (camera != null)
camera.clearFlags = CameraClearFlags.Skybox;
}
else
{
RenderSettings.skybox = _previousSkybox;
RenderSettings.ambientMode = AmbientMode.Flat;
RenderSettings.ambientIntensity = _previousAmbientIntensity;
RenderSettings.ambientLight = new Color(
Mathf.Max(0f, world.AmbientLightR * world.AmbientLightIntensity),
Mathf.Max(0f, world.AmbientLightG * world.AmbientLightIntensity),
Mathf.Max(0f, world.AmbientLightB * world.AmbientLightIntensity),
1f);
if (_environmentCamera != null)
_environmentCamera.clearFlags = _previousCameraClearFlags;
}
if (reflection != null)
{
RenderSettings.defaultReflectionMode = DefaultReflectionMode.Custom;
RenderSettings.customReflectionTexture = reflection;
RenderSettings.reflectionIntensity = Mathf.Max(0f, world.ReflectionIntensity);
}
else
{
RenderSettings.defaultReflectionMode = _previousReflectionMode;
RenderSettings.customReflectionTexture = _previousCustomReflection;
RenderSettings.reflectionIntensity = _previousReflectionIntensity;
}
}
private void RestoreAmbientEnvironment()
@ -201,6 +291,16 @@ namespace GameCult.Eve.UnityScene
if (!_ownsAmbientEnvironment) return;
RenderSettings.ambientMode = _previousAmbientMode;
RenderSettings.ambientLight = _previousAmbientLight;
RenderSettings.ambientIntensity = _previousAmbientIntensity;
RenderSettings.skybox = _previousSkybox;
RenderSettings.defaultReflectionMode = _previousReflectionMode;
RenderSettings.customReflectionTexture = _previousCustomReflection;
RenderSettings.reflectionIntensity = _previousReflectionIntensity;
if (_environmentCamera != null)
_environmentCamera.clearFlags = _previousCameraClearFlags;
_environmentCamera = null;
_previousSkybox = null;
_previousCustomReflection = null;
_ownsAmbientEnvironment = false;
}

View file

@ -40,6 +40,8 @@ namespace GameCult.Eve.UnityScene
public long ActiveVersion => Runtime?.ActiveVersion ?? 0;
public int ConnectionEpoch { get; private set; }
public Transform? ActiveCameraTransform { get; private set; }
public IEveUnityNativeAssetProvider? NativeAssetProvider =>
Runtime?.GameObjectAssetProvider as IEveUnityNativeAssetProvider;
public event Action<EveUnityFeedbackEvent>? FeedbackAvailable;
public event Action<EveUnityShotReceipt>? ShotAvailable;
@ -113,10 +115,15 @@ namespace GameCult.Eve.UnityScene
return presentation;
}
internal void ClaimWorldCamera(object owner, Transform camera)
internal bool TryClaimWorldCamera(object owner, Transform camera)
{
_cameraOwner = owner ?? throw new ArgumentNullException(nameof(owner));
ActiveCameraTransform = camera != null ? camera : throw new ArgumentNullException(nameof(camera));
if (owner == null) throw new ArgumentNullException(nameof(owner));
if (camera == null) throw new ArgumentNullException(nameof(camera));
if (_cameraOwner != null && !ReferenceEquals(_cameraOwner, owner))
return false;
_cameraOwner = owner;
ActiveCameraTransform = camera;
return true;
}
internal void ReleaseWorldCamera(object owner)
@ -190,6 +197,8 @@ namespace GameCult.Eve.UnityScene
public void Disconnect()
{
if (_cameraOwner is EveUnityPlayableWorldCameraRig rig)
rig.ReleaseRig();
if (Runtime != null)
{
Runtime.FeedbackAvailable -= OnFeedbackAvailable;

View file

@ -139,7 +139,10 @@ namespace GameCult.Eve.UnityScene
ParseFloat(worldRoot.GetProp("cameraFarClipPlane"), 0f),
worldRoot.GetProp("lookCommand"),
ParseFloat(worldRoot.GetProp("lookSensitivityRadians"), 0f),
worldRoot.GetProp("lookModel"));
worldRoot.GetProp("lookModel"),
worldRoot.GetProp("skyboxAssetRef"),
worldRoot.GetProp("reflectionAssetRef"),
ParseFloat(worldRoot.GetProp("reflectionIntensity"), 1f));
}
private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component)
@ -363,7 +366,10 @@ namespace GameCult.Eve.UnityScene
float cameraFarClipPlane = 0f,
string lookCommand = "",
float lookSensitivityRadians = 0f,
string lookModel = "")
string lookModel = "",
string skyboxAssetRef = "",
string reflectionAssetRef = "",
float reflectionIntensity = 1f)
{
WorldRootId = worldRootId ?? "";
StatePointerId = statePointerId ?? "";
@ -397,6 +403,9 @@ namespace GameCult.Eve.UnityScene
LookCommand = lookCommand ?? "";
LookSensitivityRadians = lookSensitivityRadians;
LookModel = lookModel ?? "";
SkyboxAssetRef = skyboxAssetRef ?? "";
ReflectionAssetRef = reflectionAssetRef ?? "";
ReflectionIntensity = reflectionIntensity;
}
public string WorldRootId { get; }
@ -453,6 +462,12 @@ namespace GameCult.Eve.UnityScene
public string LookModel { get; }
public string SkyboxAssetRef { get; }
public string ReflectionAssetRef { get; }
public float ReflectionIntensity { get; }
public string FocusCommand { get; }
public string TargetCommand { get; }

View file

@ -7,6 +7,7 @@ using GameCult.Eve.Surface;
using GameCult.Mesh;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.Rendering;
#nullable enable
@ -410,7 +411,10 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(projection.PlayableWorld.AmbientLightR, Is.EqualTo(0.2f));
Assert.That(projection.PlayableWorld.AmbientLightG, Is.EqualTo(0.2f));
Assert.That(projection.PlayableWorld.AmbientLightB, Is.EqualTo(0.2f));
Assert.That(projection.PlayableWorld.AmbientLightIntensity, Is.EqualTo(1f));
Assert.That(projection.PlayableWorld.AmbientLightIntensity, Is.EqualTo(1.46f));
Assert.That(projection.PlayableWorld.SkyboxAssetRef, Is.Empty);
Assert.That(projection.PlayableWorld.ReflectionAssetRef, Is.Empty);
Assert.That(projection.PlayableWorld.ReflectionIntensity, Is.EqualTo(1f));
Assert.That(projection.PlayableWorld.ExcludedRenderChannels, Is.EqualTo(new[] { "map" }));
Assert.That(projection.PlayableWorld.PlayerEntityId, Is.EqualTo("player-vanguard"));
Assert.That(projection.PlayableWorld.MovementCommand, Is.EqualTo("aetheria.daemon.move_intent"));
@ -1075,16 +1079,33 @@ namespace GameCult.Eve.UnityScene.Tests
var rootObject = new GameObject("generic-eve-world-root");
var hostObject = new GameObject("generic-eve-client");
var cameraObject = new GameObject("generic-eve-camera");
var competingCameraObject = new GameObject("competing-eve-camera");
hostObject.SetActive(false);
var ambientMode = RenderSettings.ambientMode;
var ambientLight = RenderSettings.ambientLight;
var ambientIntensity = RenderSettings.ambientIntensity;
var previousSkybox = RenderSettings.skybox;
var previousReflectionMode = RenderSettings.defaultReflectionMode;
var previousCustomReflection = RenderSettings.customReflectionTexture;
var previousReflectionIntensity = RenderSettings.reflectionIntensity;
Material? skyboxMaterial = null;
Cubemap? reflectionCubemap = null;
try
{
var provider = hostObject.AddComponent<FakePlayableWorldProviderComponent>();
var nativeAssets = hostObject.AddComponent<FixedNativeAssetProviderComponent>();
var skyboxShader = Shader.Find("Skybox/Procedural");
Assert.That(skyboxShader, Is.Not.Null);
skyboxMaterial = new Material(skyboxShader!);
reflectionCubemap = new Cubemap(4, TextureFormat.RGBA32, false);
nativeAssets.Set("material.environment.skybox", skyboxMaterial);
nativeAssets.Set("texture.environment.reflection", reflectionCubemap);
provider.Set(
new EveUnitySceneProviderSurfaceDocument(
PlayableArpgDocument(),
PlayableArpgDocument(
skyboxAssetRef: "material.environment.skybox",
reflectionAssetRef: "texture.environment.reflection"),
Advertisement("aetheria.daemon.game"),
"cultmesh://aetheria/eve/surfaces/aetheria.daemon.game",
1),
@ -1094,7 +1115,7 @@ namespace GameCult.Eve.UnityScene.Tests
"aetheria"));
var host = hostObject.AddComponent<EveUnityPlayableWorldClientHost>();
host.Configure(rootObject.transform, provider, provider, provider, provider);
host.Configure(rootObject.transform, provider, provider, provider, provider, nativeAssets);
host.Connect();
hostObject.SetActive(true);
@ -1126,8 +1147,19 @@ namespace GameCult.Eve.UnityScene.Tests
var viewport = camera.WorldToViewportPoint(player.transform.position);
Assert.That(viewport.x, Is.EqualTo(0.9f).Within(0.001f));
Assert.That(viewport.y, Is.EqualTo(0.55f).Within(0.001f));
Assert.That(RenderSettings.ambientMode, Is.EqualTo(UnityEngine.Rendering.AmbientMode.Flat));
Assert.That(RenderSettings.ambientLight.r, Is.EqualTo(0.2f).Within(0.001f));
Assert.That(RenderSettings.ambientMode, Is.EqualTo(UnityEngine.Rendering.AmbientMode.Skybox));
Assert.That(RenderSettings.ambientIntensity, Is.EqualTo(1.46f).Within(0.001f));
Assert.That(RenderSettings.skybox, Is.SameAs(skyboxMaterial));
Assert.That(RenderSettings.defaultReflectionMode, Is.EqualTo(DefaultReflectionMode.Custom));
Assert.That(RenderSettings.customReflectionTexture, Is.SameAs(reflectionCubemap));
Assert.That(RenderSettings.reflectionIntensity, Is.EqualTo(1f).Within(0.001f));
Assert.That(camera.clearFlags, Is.EqualTo(CameraClearFlags.Skybox));
var competingRig = competingCameraObject.AddComponent<EveUnityPlayableWorldCameraRig>();
competingRig.Host = host;
competingRig.CameraTransform = competingCameraObject.transform;
Assert.That(competingRig.ApplyRig(0f), Is.False);
Assert.That(host.ActiveCameraTransform, Is.SameAs(cameraObject.transform));
var aim = hostObject.GetComponent<EveUnityAimPresentationRenderer>();
Assert.That(aim, Is.Not.Null);
@ -1157,17 +1189,34 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(
cameraObject.transform.position.x - beforeFollow.x,
Is.EqualTo(10f * (1f - Mathf.Exp(-0.5f))).Within(0.001f));
rig.ReleaseRig();
rig.CameraTransform = null;
Assert.That(host.ActiveCameraTransform, Is.Null);
aim.RefreshNow();
Assert.That(aim.ViewDotVisible, Is.False);
Assert.That(RenderSettings.ambientMode, Is.EqualTo(ambientMode));
Assert.That(RenderSettings.ambientLight, Is.EqualTo(ambientLight));
Assert.That(RenderSettings.ambientIntensity, Is.EqualTo(ambientIntensity));
Assert.That(RenderSettings.skybox, Is.SameAs(previousSkybox));
Assert.That(RenderSettings.defaultReflectionMode, Is.EqualTo(previousReflectionMode));
Assert.That(RenderSettings.customReflectionTexture, Is.SameAs(previousCustomReflection));
Assert.That(RenderSettings.reflectionIntensity, Is.EqualTo(previousReflectionIntensity));
nativeAssets.Clear();
nativeAssets.Set("some.other.asset", skyboxMaterial);
Assert.That(rig.ApplyRig(0f), Is.False);
Assert.That(host.ActiveCameraTransform, Is.Null);
}
finally
{
RenderSettings.ambientMode = ambientMode;
RenderSettings.ambientLight = ambientLight;
RenderSettings.ambientIntensity = ambientIntensity;
RenderSettings.skybox = previousSkybox;
RenderSettings.defaultReflectionMode = previousReflectionMode;
RenderSettings.customReflectionTexture = previousCustomReflection;
RenderSettings.reflectionIntensity = previousReflectionIntensity;
if (skyboxMaterial != null) UnityEngine.Object.DestroyImmediate(skyboxMaterial);
if (reflectionCubemap != null) UnityEngine.Object.DestroyImmediate(reflectionCubemap);
UnityEngine.Object.DestroyImmediate(competingCameraObject);
UnityEngine.Object.DestroyImmediate(cameraObject);
UnityEngine.Object.DestroyImmediate(hostObject);
UnityEngine.Object.DestroyImmediate(rootObject);
@ -1680,7 +1729,9 @@ namespace GameCult.Eve.UnityScene.Tests
private static EveSurfaceDocument PlayableArpgDocument(
bool includeRaider = true,
string playerPosition = "0,0,0")
string playerPosition = "0,0,0",
string skyboxAssetRef = "",
string reflectionAssetRef = "")
{
var playableChildren = new List<EveSurfaceComponent>
{
@ -1812,7 +1863,10 @@ namespace GameCult.Eve.UnityScene.Tests
["cameraNearClipPlane"] = "0.3",
["cameraFarClipPlane"] = "4096",
["ambientLightColor"] = "0.2,0.2,0.2",
["ambientLightIntensity"] = "1",
["ambientLightIntensity"] = "1.46",
["skyboxAssetRef"] = skyboxAssetRef,
["reflectionAssetRef"] = reflectionAssetRef,
["reflectionIntensity"] = "1",
["excludedRenderChannels"] = "map",
["playerEntityId"] = "player-vanguard",
["movementCommand"] = "aetheria.daemon.move_intent",
@ -2378,6 +2432,26 @@ namespace GameCult.Eve.UnityScene.Tests
public GameObject? ResolvePrefab(EveUnityPlayableWorldAssetBinding asset) => _prefab;
}
private sealed class FixedNativeAssetProviderComponent : MonoBehaviour, IEveUnityNativeAssetProvider
{
private readonly Dictionary<string, UnityEngine.Object> _assets =
new Dictionary<string, UnityEngine.Object>(StringComparer.Ordinal);
public void Set(string assetRef, UnityEngine.Object asset)
{
_assets[assetRef ?? ""] = asset;
}
public void Clear() => _assets.Clear();
public GameObject? ResolvePrefab(EveUnityPlayableWorldAssetBinding asset) => null;
public UnityEngine.Object? ResolveAsset(EveUnityPlayableWorldAssetBinding asset, Type assetType) =>
_assets.TryGetValue(asset?.AssetRef ?? "", out var value) && assetType.IsInstanceOfType(value)
? value
: null;
}
private sealed class ObservingGameObjectAssetProvider : IEveUnityGameObjectAssetProvider
{
private readonly Action _onResolve;

View file

@ -1,7 +1,7 @@
{
"name": "org.gamecult.eve.unity-scene",
"displayName": "Eve Unity Scene Lowering",
"version": "0.3.25",
"version": "0.3.26",
"unity": "2021.3",
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
"dependencies": {