diff --git a/packages/org.gamecult.eve.unity-scene/README.md b/packages/org.gamecult.eve.unity-scene/README.md index 27f94bd..5ad692f 100644 --- a/packages/org.gamecult.eve.unity-scene/README.md +++ b/packages/org.gamecult.eve.unity-scene/README.md @@ -29,6 +29,12 @@ own which semantic content belongs in a view, while EveUnity owns the native layer mapping and subtracts only those layers from the camera's configured culling mask. +World surfaces may also advertise a presentation-only directional key light +through `keyLightDirection`, `keyLightColor`, and `keyLightIntensity`. +EveUnity lowers those generic values into a transient Unity light and removes +it when the world camera lease ends. The provider owns the lighting design; +the light never becomes physics, visibility, or gameplay authority. + Retained provider feedback is lowered without becoming Unity authority. `EveUnityFeedbackPresenter` emits each new `feedback.event` identity once, and `EveUnityShotReceiptPresenter` does the same for `shot.receipt` trajectories. diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs index 4ca6b15..7b50fad 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldCameraRig.cs @@ -33,6 +33,7 @@ namespace GameCult.Eve.UnityScene private float _previousReflectionIntensity; private Camera? _environmentCamera; private CameraClearFlags _previousCameraClearFlags; + private GameObject? _keyLightObject; public EveUnityPlayableWorldClientHost? Host { @@ -124,6 +125,7 @@ namespace GameCult.Eve.UnityScene var cameraComponent = camera.GetComponent(); ApplyAmbientEnvironment(activeWorld, cameraComponent, skybox, reflection); + ApplyKeyLight(activeWorld); if (cameraComponent != null && _renderPolicySource != null) { var cullingMask = cameraComponent.cullingMask; @@ -217,7 +219,14 @@ namespace GameCult.Eve.UnityScene !IsFinite(world.AmbientLightG) || !IsFinite(world.AmbientLightB) || !IsFinite(world.AmbientLightIntensity) || - !IsFinite(world.ReflectionIntensity)) + !IsFinite(world.ReflectionIntensity) || + !IsFinite(world.KeyLightDirectionX) || + !IsFinite(world.KeyLightDirectionY) || + !IsFinite(world.KeyLightDirectionZ) || + !IsFinite(world.KeyLightColorR) || + !IsFinite(world.KeyLightColorG) || + !IsFinite(world.KeyLightColorB) || + !IsFinite(world.KeyLightIntensity)) return false; var assets = host.NativeAssetProvider; if (!string.IsNullOrWhiteSpace(world.SkyboxAssetRef)) @@ -245,6 +254,54 @@ namespace GameCult.Eve.UnityScene return true; } + private void ApplyKeyLight(EveUnityPlayableWorldProjection world) + { + var direction = new Vector3( + world.KeyLightDirectionX, + world.KeyLightDirectionY, + world.KeyLightDirectionZ); + if (world.KeyLightIntensity <= 0f || direction.sqrMagnitude <= 0.000001f) + { + ReleaseKeyLight(); + return; + } + + if (_keyLightObject == null) + { + _keyLightObject = new GameObject("Eve World Key Light"); + _keyLightObject.transform.SetParent(transform, worldPositionStays: false); + var created = _keyLightObject.AddComponent(); + created.type = LightType.Directional; + created.shadows = LightShadows.None; + } + + var light = _keyLightObject.GetComponent(); + light.color = new Color( + Mathf.Max(0f, world.KeyLightColorR), + Mathf.Max(0f, world.KeyLightColorG), + Mathf.Max(0f, world.KeyLightColorB), + 1f); + light.intensity = Mathf.Max(0f, world.KeyLightIntensity); + var normalized = direction.normalized; + var up = Mathf.Abs(Vector3.Dot(normalized, Vector3.up)) > 0.999f + ? Vector3.forward + : Vector3.up; + _keyLightObject.transform.rotation = Quaternion.LookRotation(normalized, up); + _keyLightObject.SetActive(true); + } + + private void ReleaseKeyLight() + { + if (_keyLightObject == null) + return; + _keyLightObject.SetActive(false); + if (Application.isPlaying) + Destroy(_keyLightObject); + else + DestroyImmediate(_keyLightObject); + _keyLightObject = null; + } + private static bool IsFinite(float value) => !float.IsNaN(value) && !float.IsInfinity(value); private void ApplyAmbientEnvironment( @@ -307,6 +364,7 @@ namespace GameCult.Eve.UnityScene private void RestoreAmbientEnvironment() { + ReleaseKeyLight(); if (!_ownsAmbientEnvironment) return; var environmentChanged = !ReferenceEquals(RenderSettings.skybox, _previousSkybox); RenderSettings.ambientMode = _previousAmbientMode; diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs index acb92b1..5607764 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs @@ -142,7 +142,10 @@ namespace GameCult.Eve.UnityScene worldRoot.GetProp("lookModel"), worldRoot.GetProp("skyboxAssetRef"), worldRoot.GetProp("reflectionAssetRef"), - ParseFloat(worldRoot.GetProp("reflectionIntensity"), 1f)); + ParseFloat(worldRoot.GetProp("reflectionIntensity"), 1f), + ParseVector3(worldRoot.GetProp("keyLightDirection")), + ParseVector3(worldRoot.GetProp("keyLightColor")), + ParseFloat(worldRoot.GetProp("keyLightIntensity"), 0f)); } private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component) @@ -369,7 +372,10 @@ namespace GameCult.Eve.UnityScene string lookModel = "", string skyboxAssetRef = "", string reflectionAssetRef = "", - float reflectionIntensity = 1f) + float reflectionIntensity = 1f, + (float x, float y, float z) keyLightDirection = default, + (float r, float g, float b) keyLightColor = default, + float keyLightIntensity = 0f) { WorldRootId = worldRootId ?? ""; StatePointerId = statePointerId ?? ""; @@ -406,6 +412,13 @@ namespace GameCult.Eve.UnityScene SkyboxAssetRef = skyboxAssetRef ?? ""; ReflectionAssetRef = reflectionAssetRef ?? ""; ReflectionIntensity = reflectionIntensity; + KeyLightDirectionX = keyLightDirection.x; + KeyLightDirectionY = keyLightDirection.y; + KeyLightDirectionZ = keyLightDirection.z; + KeyLightColorR = keyLightColor.r; + KeyLightColorG = keyLightColor.g; + KeyLightColorB = keyLightColor.b; + KeyLightIntensity = keyLightIntensity; } public string WorldRootId { get; } @@ -468,6 +481,20 @@ namespace GameCult.Eve.UnityScene public float ReflectionIntensity { get; } + public float KeyLightDirectionX { get; } + + public float KeyLightDirectionY { get; } + + public float KeyLightDirectionZ { get; } + + public float KeyLightColorR { get; } + + public float KeyLightColorG { get; } + + public float KeyLightColorB { get; } + + public float KeyLightIntensity { 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 41ab39c..0f7d926 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -1159,6 +1159,14 @@ namespace GameCult.Eve.UnityScene.Tests 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 keyLightTransform = hostObject.transform.Find("Eve World Key Light"); + Assert.That(keyLightTransform, Is.Not.Null); + var keyLight = keyLightTransform.GetComponent(); + Assert.That(keyLight.type, Is.EqualTo(LightType.Directional)); + Assert.That(keyLight.intensity, Is.EqualTo(0.75f).Within(0.001f)); + Assert.That(keyLight.color, Is.EqualTo(new Color(1f, 0.95f, 0.9f, 1f))); + Assert.That(Vector3.Dot(keyLightTransform.forward, new Vector3(0.4f, -1f, 0.25f).normalized), + Is.GreaterThan(0.999f)); Assert.That(nativeAssets.LastBinding?.AssetRef, Is.EqualTo("texture.environment.reflection")); Assert.That(nativeAssets.LastBinding?.EntityKind, Is.Empty); @@ -1210,6 +1218,7 @@ namespace GameCult.Eve.UnityScene.Tests Is.EqualTo(10f * (1f - Mathf.Exp(-0.5f))).Within(0.001f)); rig.CameraTransform = null; Assert.That(host.ActiveCameraTransform, Is.Null); + Assert.That(hostObject.transform.Find("Eve World Key Light"), Is.Null); aim.RefreshNow(); Assert.That(aim.ViewDotVisible, Is.False); Assert.That(RenderSettings.ambientMode, Is.EqualTo(ambientMode)); @@ -1895,6 +1904,9 @@ namespace GameCult.Eve.UnityScene.Tests ["skyboxAssetRef"] = skyboxAssetRef, ["reflectionAssetRef"] = reflectionAssetRef, ["reflectionIntensity"] = "1", + ["keyLightDirection"] = "0.4,-1,0.25", + ["keyLightColor"] = "1,0.95,0.9", + ["keyLightIntensity"] = "0.75", ["excludedRenderChannels"] = "map", ["playerEntityId"] = "player-vanguard", ["movementCommand"] = "aetheria.daemon.move_intent", diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index b70ee8e..f19cb5a 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.26", + "version": "0.3.27", "unity": "2021.3", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {