diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs index 84b7e8c..1fafbe0 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityGameObjectPlayableWorldSceneSink.cs @@ -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(StringComparer.Ordinal); _presentedByIndex = new Dictionary(); diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs index 7b1334b..e82a86a 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs @@ -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(); + 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; } diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs index bdc2469..17741f4 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs @@ -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? FeedbackAvailable; public event Action? 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; diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs index 2ef7006..acb92b1 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs @@ -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; } diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index 60d6a5b..a790608 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -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(); + var nativeAssets = hostObject.AddComponent(); + 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(); - 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(); + 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(); 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 { @@ -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 _assets = + new Dictionary(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; diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index 609948c..b70ee8e 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -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": {