From 3075f71ece0f395aecb770a890ec1b1728e4573c Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 15 Jul 2026 05:55:53 +0100 Subject: [PATCH] Lower authoritative pilot aim in generic worlds --- .../Runtime/EveUnityAimPresentation.cs | 49 +++++++ .../Runtime/EveUnityAimPresentation.cs.meta | 2 + .../EveUnityAimPresentationRenderer.cs | 136 ++++++++++++++++++ .../EveUnityAimPresentationRenderer.cs.meta | 2 + .../EveUnityPlayableWorldClientHost.cs | 13 ++ .../EveUnityPlayableWorldInputDriver.cs | 57 ++++++++ .../EveUnityPlayableWorldLiveClient.cs | 10 ++ .../Runtime/EveUnityPlayableWorldRuntime.cs | 10 ++ .../Runtime/EveUnitySceneClientSession.cs | 20 +++ .../EveUnitySceneProviderConnection.cs | 10 ++ .../Runtime/EveUnitySceneSurfaceLowerer.cs | 14 +- .../Editor/EveUnityAimPresentationTests.cs | 57 ++++++++ .../EveUnityAimPresentationTests.cs.meta | 2 + .../EveUnitySceneSurfaceLowererTests.cs | 24 ++++ .../org.gamecult.eve.unity-scene/package.json | 2 +- 15 files changed, 405 insertions(+), 3 deletions(-) create mode 100644 packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs create mode 100644 packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs.meta create mode 100644 packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs create mode 100644 packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs.meta create mode 100644 packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs create mode 100644 packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs.meta diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs new file mode 100644 index 0000000..fbe8720 --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs @@ -0,0 +1,49 @@ +using System; +using System.Globalization; + +#nullable enable + +namespace GameCult.Eve.UnityScene +{ + public sealed class EveUnityAimPresentation + { + public EveUnityAimPresentation(EveUnitySceneNode node) + { + if (node == null) throw new ArgumentNullException(nameof(node)); + ControlledEntityId = Get(node, "controlledEntityId"); + ConvergenceTargetEntityId = Get(node, "convergenceTargetEntityId"); + ViewDotRole = Get(node, "viewDotRole"); + MinimumConvergenceDistance = Math.Max(0.01f, Number(node, "minimumConvergenceDistance", 50f)); + ViewDotRadius = Math.Max(0.01f, Number(node, "viewDotRadius", 0.8f)); + } + + public string ControlledEntityId { get; } + public string ConvergenceTargetEntityId { get; } + public string ViewDotRole { get; } + public float MinimumConvergenceDistance { get; } + public float ViewDotRadius { get; } + + public static EveUnityAimPresentation? Find(EveUnitySceneProjection? projection) => + projection == null ? null : Find(projection.Root); + + private static EveUnityAimPresentation? Find(EveUnitySceneNode node) + { + if (string.Equals(node.ComponentKind, "aim.presentation", StringComparison.Ordinal)) + return new EveUnityAimPresentation(node); + foreach (var child in node.Children) + { + var found = Find(child); + if (found != null) return found; + } + return null; + } + + private static string Get(EveUnitySceneNode node, string key) => + node.Props.TryGetValue(key, out var value) ? value ?? "" : ""; + + private static float Number(EveUnitySceneNode node, string key, float fallback) => + float.TryParse(Get(node, key), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) + ? value + : fallback; + } +} diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs.meta b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs.meta new file mode 100644 index 0000000..8a50d5a --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentation.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f859926d39664cefa5aa96ed3c2d8903 diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs new file mode 100644 index 0000000..25b8ce8 --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs @@ -0,0 +1,136 @@ +using System; +using UnityEngine; + +#nullable enable + +namespace GameCult.Eve.UnityScene +{ + public sealed class EveUnityAimPresentationRenderer : MonoBehaviour + { + [SerializeField] private Color viewDotColor = new Color(0.35f, 0.95f, 1f, 0.95f); + [SerializeField] private float width = 0.1f; + + private EveUnityPlayableWorldClientHost? _host; + private LineRenderer? _ring; + private LineRenderer? _horizontal; + private LineRenderer? _vertical; + private Material? _material; + + public EveUnityAimPresentation? Current { get; private set; } + public bool ViewDotVisible => _ring != null && _ring.enabled; + public Vector3 ViewDotPosition { get; private set; } + + public void Bind(EveUnityPlayableWorldClientHost host) + { + _host = host != null ? host : throw new ArgumentNullException(nameof(host)); + } + + public void RefreshNow() + { + Current = EveUnityAimPresentation.Find(_host?.ActiveProjection); + if (Current == null || string.IsNullOrWhiteSpace(Current.ControlledEntityId)) + { + SetVisible(false); + return; + } + + var controlled = FindMarker(Current.ControlledEntityId); + if (controlled == null) + { + SetVisible(false); + return; + } + + var distance = Current.MinimumConvergenceDistance; + var target = FindMarker(Current.ConvergenceTargetEntityId); + if (target != null) + distance = Mathf.Max(distance, Vector3.Distance(controlled.transform.position, target.transform.position)); + var forward = controlled.transform.forward; + forward.y = 0f; + if (forward.sqrMagnitude <= 0.0001f) forward = Vector3.forward; + forward.Normalize(); + ViewDotPosition = controlled.transform.position + forward * distance; + + EnsureVisuals(); + var camera = Camera.main; + var right = camera == null ? Vector3.right : camera.transform.right; + var up = camera == null ? Vector3.up : camera.transform.up; + var center = ViewDotPosition + (camera == null ? Vector3.zero : -camera.transform.forward * 0.1f); + Ring(_ring!, center, right, up, Current.ViewDotRadius); + Segment(_horizontal!, center, right, Current.ViewDotRadius * 1.6f); + Segment(_vertical!, center, up, Current.ViewDotRadius * 1.6f); + SetVisible(true); + } + + private void LateUpdate() => RefreshNow(); + + private EveUnityPlayableWorldEntityMarker? FindMarker(string entityId) + { + if (_host == null || string.IsNullOrWhiteSpace(entityId)) return null; + foreach (var marker in _host.SceneRoot.GetComponentsInChildren(true)) + if (string.Equals(marker.EntityId, entityId, StringComparison.Ordinal)) return marker; + return null; + } + + private void EnsureVisuals() + { + if (_ring != null) return; + var root = new GameObject("Eve aim presentation"); + root.transform.SetParent(transform, false); + _ring = Line(root.transform, "View direction ring"); + _horizontal = Line(root.transform, "View direction horizontal"); + _vertical = Line(root.transform, "View direction vertical"); + } + + private LineRenderer Line(Transform parent, string name) + { + var child = new GameObject(name); + child.transform.SetParent(parent, false); + var line = child.AddComponent(); + line.useWorldSpace = true; + line.material = SharedMaterial(); + line.startColor = line.endColor = viewDotColor; + line.startWidth = line.endWidth = Mathf.Max(0.01f, width); + line.numCapVertices = 2; + return line; + } + + private Material SharedMaterial() + { + if (_material != null) return _material; + var shader = Shader.Find("Sprites/Default") ?? Shader.Find("Universal Render Pipeline/Unlit"); + if (shader == null) throw new InvalidOperationException("EveUnity requires an unlit shader for aim presentation."); + return _material = new Material(shader) { name = "EveUnity aim presentation" }; + } + + private static void Ring(LineRenderer line, Vector3 center, Vector3 right, Vector3 up, float radius) + { + const int segments = 33; + line.positionCount = segments; + for (var index = 0; index < segments; index++) + { + var angle = index / (float)(segments - 1) * Mathf.PI * 2f; + line.SetPosition(index, center + right * (Mathf.Cos(angle) * radius) + up * (Mathf.Sin(angle) * radius)); + } + } + + private static void Segment(LineRenderer line, Vector3 center, Vector3 axis, float radius) + { + line.positionCount = 2; + line.SetPosition(0, center - axis * radius); + line.SetPosition(1, center + axis * radius); + } + + private void SetVisible(bool visible) + { + if (_ring != null) _ring.enabled = visible; + if (_horizontal != null) _horizontal.enabled = visible; + if (_vertical != null) _vertical.enabled = visible; + } + + private void OnDestroy() + { + if (_material != null) Destroy(_material); + } + } +} diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs.meta b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs.meta new file mode 100644 index 0000000..66058c7 --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityAimPresentationRenderer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 97c7b809756a4350a6e61c67bba69b82 diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs index 1323c80..56ed0ef 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldClientHost.cs @@ -95,6 +95,9 @@ namespace GameCult.Eve.UnityScene var combatPresentation = GetComponent(); if (combatPresentation == null) combatPresentation = gameObject.AddComponent(); combatPresentation.Bind(this); + var aimPresentation = GetComponent(); + if (aimPresentation == null) aimPresentation = gameObject.AddComponent(); + aimPresentation.Bind(this); var thermal = GetComponent(); var thermalHud = GetComponent(); if (thermalHud == null) thermalHud = gameObject.AddComponent(); @@ -135,6 +138,16 @@ namespace GameCult.Eve.UnityScene return RequireRuntime().SubmitMoveVectorIntent(entityId, directionX, directionY, scalarValue, issuedAt); } + public EveSurfaceCommandRequest SubmitLookDirectionIntent( + string entityId, + float directionX, + float directionY, + float directionZ, + DateTimeOffset? issuedAt = null) + { + return RequireRuntime().SubmitLookDirectionIntent(entityId, directionX, directionY, directionZ, issuedAt); + } + public EveSurfaceCommandRequest SubmitFocusIntent( string entityId, DateTimeOffset? issuedAt = null) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs index c321815..ffd3f75 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldInputDriver.cs @@ -20,12 +20,16 @@ namespace GameCult.Eve.UnityScene [SerializeField] private string horizontalAxis = "Horizontal"; [SerializeField] private string verticalAxis = "Vertical"; [SerializeField] private string actionButton = "Fire1"; + [SerializeField] private string lookHorizontalAxis = "Mouse X"; + [SerializeField] private float defaultLookSensitivityRadians = 0.001f; [SerializeField] private float deadZone = 0.01f; [SerializeField] private float commandIntervalSeconds = 0.05f; [SerializeField] private bool submitZeroOnRelease = true; private float _nextCommandAt; private bool _wasMoving; + private float _lookYawRadians; + private bool _hasLookYaw; public EveUnityPlayableWorldClientHost? Host { @@ -94,6 +98,34 @@ namespace GameCult.Eve.UnityScene resolvedActionId); } + public EveSurfaceCommandRequest? SubmitLookInput( + float horizontalDelta, + DateTimeOffset? issuedAt = null) + { + var resolvedHost = ResolveHost(); + var world = resolvedHost?.ActiveWorld; + if (world == null || string.IsNullOrWhiteSpace(world.LookCommand) || + Mathf.Abs(horizontalDelta) <= 0.0001f) + return null; + + if (!_hasLookYaw) + { + _lookYawRadians = ResolveControlledYawRadians(resolvedHost!, world.PlayerEntityId); + _hasLookYaw = true; + } + var sensitivity = Mathf.Abs(world.LookSensitivityRadians) > 0.000001f + ? world.LookSensitivityRadians + : defaultLookSensitivityRadians; + _lookYawRadians += horizontalDelta * sensitivity; + var direction = EveUnityPlayableWorldLookDirection.FromPlanarYaw(_lookYawRadians); + return resolvedHost!.SubmitLookDirectionIntent( + world.PlayerEntityId, + direction.DirectionX, + 0f, + direction.DirectionZ, + issuedAt); + } + public static string ResolvePrimaryActionId(EveInputCapabilityDocument? capability) { if (capability == null) @@ -121,6 +153,8 @@ namespace GameCult.Eve.UnityScene _nextCommandAt = Time.unscaledTime + Math.Max(0.01f, commandIntervalSeconds); SubmitMoveVectorInput(Input.GetAxisRaw(horizontalAxis), Input.GetAxisRaw(verticalAxis)); + if (!string.IsNullOrWhiteSpace(lookHorizontalAxis)) + SubmitLookInput(Input.GetAxisRaw(lookHorizontalAxis)); if (!string.IsNullOrWhiteSpace(actionButton) && Input.GetButtonDown(actionButton)) SubmitPrimaryAction(); @@ -134,6 +168,29 @@ namespace GameCult.Eve.UnityScene host = GetComponent(); return host; } + + private static float ResolveControlledYawRadians(EveUnityPlayableWorldClientHost host, string entityId) + { + foreach (var marker in host.SceneRoot.GetComponentsInChildren(true)) + if (string.Equals(marker.EntityId, entityId, StringComparison.Ordinal)) + return marker.transform.eulerAngles.y * Mathf.Deg2Rad; + return 0f; + } + } + + public readonly struct EveUnityPlayableWorldLookDirection + { + public EveUnityPlayableWorldLookDirection(float directionX, float directionZ) + { + DirectionX = directionX; + DirectionZ = directionZ; + } + + public float DirectionX { get; } + public float DirectionZ { get; } + + public static EveUnityPlayableWorldLookDirection FromPlanarYaw(float yawRadians) => + new EveUnityPlayableWorldLookDirection(Mathf.Sin(yawRadians), Mathf.Cos(yawRadians)); } public readonly struct EveUnityPlayableWorldMoveVector diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs index bf44ee2..adbb76e 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldLiveClient.cs @@ -88,6 +88,16 @@ namespace GameCult.Eve.UnityScene return _connection.SubmitMoveVectorIntent(entityId, directionX, directionY, scalarValue, issuedAt); } + public EveSurfaceCommandRequest SubmitLookDirectionIntent( + string entityId, + float directionX, + float directionY, + float directionZ, + DateTimeOffset? issuedAt = null) + { + return _connection.SubmitLookDirectionIntent(entityId, directionX, directionY, directionZ, issuedAt); + } + public EveSurfaceCommandRequest SubmitFocusIntent( string entityId, DateTimeOffset? issuedAt = null) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs index a5d7e5c..c81e6b6 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnityPlayableWorldRuntime.cs @@ -143,6 +143,16 @@ namespace GameCult.Eve.UnityScene return _client.SubmitMoveVectorIntent(entityId, directionX, directionY, scalarValue, issuedAt); } + public EveSurfaceCommandRequest SubmitLookDirectionIntent( + string entityId, + float directionX, + float directionY, + float directionZ, + DateTimeOffset? issuedAt = null) + { + return _client.SubmitLookDirectionIntent(entityId, directionX, directionY, directionZ, issuedAt); + } + public EveSurfaceCommandRequest SubmitFocusIntent( string entityId, DateTimeOffset? issuedAt = null) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs index e82cf56..473670d 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneClientSession.cs @@ -83,6 +83,26 @@ namespace GameCult.Eve.UnityScene issuedAt); } + public EveSurfaceCommandRequest CreateLookDirectionIntent( + string entityId, + float directionX, + float directionY, + float directionZ, + DateTimeOffset? issuedAt = null) + { + var playableWorld = RequirePlayableWorld(); + return CreatePlayableWorldIntent( + playableWorld.LookCommand, + new Dictionary(StringComparer.Ordinal) + { + ["entityId"] = entityId ?? "", + ["directionX"] = FormatFloat(directionX), + ["directionY"] = FormatFloat(directionY), + ["directionZ"] = FormatFloat(directionZ) + }, + issuedAt); + } + public EveSurfaceCommandRequest CreateFocusIntent( string entityId, DateTimeOffset? issuedAt = null) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs index e21292c..3d4f18c 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneProviderConnection.cs @@ -221,6 +221,16 @@ namespace GameCult.Eve.UnityScene return Submit(_session.CreateMoveVectorIntent(entityId, directionX, directionY, scalarValue, issuedAt)); } + public EveSurfaceCommandRequest SubmitLookDirectionIntent( + string entityId, + float directionX, + float directionY, + float directionZ, + DateTimeOffset? issuedAt = null) + { + return Submit(_session.CreateLookDirectionIntent(entityId, directionX, directionY, directionZ, issuedAt)); + } + public EveSurfaceCommandRequest SubmitFocusIntent( string entityId, DateTimeOffset? issuedAt = null) diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs index b8a33a0..1b67179 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/EveUnitySceneSurfaceLowerer.cs @@ -136,7 +136,9 @@ namespace GameCult.Eve.UnityScene ParseVector3(worldRoot.GetProp("ambientLightColor")), ParseFloat(worldRoot.GetProp("ambientLightIntensity"), 1f), ParseFloat(worldRoot.GetProp("cameraNearClipPlane"), 0f), - ParseFloat(worldRoot.GetProp("cameraFarClipPlane"), 0f)); + ParseFloat(worldRoot.GetProp("cameraFarClipPlane"), 0f), + worldRoot.GetProp("lookCommand"), + ParseFloat(worldRoot.GetProp("lookSensitivityRadians"), 0f)); } private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component) @@ -357,7 +359,9 @@ namespace GameCult.Eve.UnityScene (float r, float g, float b) ambientLightColor = default, float ambientLightIntensity = 1f, float cameraNearClipPlane = 0f, - float cameraFarClipPlane = 0f) + float cameraFarClipPlane = 0f, + string lookCommand = "", + float lookSensitivityRadians = 0f) { WorldRootId = worldRootId ?? ""; StatePointerId = statePointerId ?? ""; @@ -388,6 +392,8 @@ namespace GameCult.Eve.UnityScene AmbientLightIntensity = ambientLightIntensity; CameraNearClipPlane = cameraNearClipPlane; CameraFarClipPlane = cameraFarClipPlane; + LookCommand = lookCommand ?? ""; + LookSensitivityRadians = lookSensitivityRadians; } public string WorldRootId { get; } @@ -438,6 +444,10 @@ namespace GameCult.Eve.UnityScene public string MovementCommand { get; } + public string LookCommand { get; } + + public float LookSensitivityRadians { get; } + public string FocusCommand { get; } public string TargetCommand { get; } diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs new file mode 100644 index 0000000..fbff51e --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; + +#nullable enable + +namespace GameCult.Eve.UnityScene.Tests +{ + public sealed class EveUnityAimPresentationTests + { + [Test] + public void ProjectionPreservesProviderAimConvergenceSemantic() + { + var projection = new EveUnitySceneProjection( + "provider", "surface", "world", "commands", "receipt", "provider", null, + Node("root", "surface", Node("aim", "aim.presentation", new Dictionary + { + ["controlledEntityId"] = "run.zone.0.entity.4", + ["convergenceTargetEntityId"] = "run.zone.0.entity.9", + ["viewDotRole"] = "aim.marker.view-direction", + ["minimumConvergenceDistance"] = "50", + ["viewDotRadius"] = "0.8" + }))); + + var aim = EveUnityAimPresentation.Find(projection); + + Assert.That(aim, Is.Not.Null); + Assert.That(aim!.ControlledEntityId, Is.EqualTo("run.zone.0.entity.4")); + Assert.That(aim.ConvergenceTargetEntityId, Is.EqualTo("run.zone.0.entity.9")); + Assert.That(aim.MinimumConvergenceDistance, Is.EqualTo(50f)); + Assert.That(aim.ViewDotRadius, Is.EqualTo(0.8f)); + } + + [Test] + public void PlanarYawProducesPortableUnitLookDirection() + { + var forward = EveUnityPlayableWorldLookDirection.FromPlanarYaw(0f); + var right = EveUnityPlayableWorldLookDirection.FromPlanarYaw(Mathf.PI * 0.5f); + + Assert.That(forward.DirectionX, Is.EqualTo(0f).Within(0.0001f)); + Assert.That(forward.DirectionZ, Is.EqualTo(1f).Within(0.0001f)); + Assert.That(right.DirectionX, Is.EqualTo(1f).Within(0.0001f)); + Assert.That(right.DirectionZ, Is.EqualTo(0f).Within(0.0001f)); + } + + private static EveUnitySceneNode Node(string id, string kind, params EveUnitySceneNode[] children) => + Node(id, kind, Empty(), children); + + private static EveUnitySceneNode Node(string id, string kind, IReadOnlyDictionary props, params EveUnitySceneNode[] children) => + new EveUnitySceneNode(id, kind, "scene-node", props, Empty(), Empty(), 0, 0, + Array.Empty(), null, children); + + private static IReadOnlyDictionary Empty() => + new Dictionary(StringComparer.Ordinal); + } +} diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs.meta b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs.meta new file mode 100644 index 0000000..c2b439c --- /dev/null +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityAimPresentationTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2bcc030e48554a49accc4efc105c36b4 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 c1f7051..3ebb32b 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -512,6 +512,18 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(moveVectorIntent.Payload.GetString("directionX"), Is.EqualTo("0.25")); Assert.That(moveVectorIntent.Payload.GetString("directionY"), Is.EqualTo("0.75")); Assert.That(moveVectorIntent.Payload.GetString("scalarValue"), Is.EqualTo("0.8")); + + var lookIntent = session.CreateLookDirectionIntent( + "player-vanguard", + 0.6f, + 0f, + 0.8f, + DateTimeOffset.Parse("2026-07-09T00:00:02Z")); + Assert.That(lookIntent.Payload.GetString("commandId"), Is.EqualTo("aetheria.daemon.look_intent")); + Assert.That(lookIntent.Payload.GetString("entityId"), Is.EqualTo("player-vanguard")); + Assert.That(lookIntent.Payload.GetString("directionX"), Is.EqualTo("0.6")); + Assert.That(lookIntent.Payload.GetString("directionY"), Is.EqualTo("0")); + Assert.That(lookIntent.Payload.GetString("directionZ"), Is.EqualTo("0.8")); } [Test] @@ -978,6 +990,16 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(request.Payload.GetDouble("directionY", 0), Is.EqualTo(0f).Within(0.0001f)); Assert.That(request.Payload.GetDouble("scalarValue", 0), Is.EqualTo(1f).Within(0.0001f)); + var controlledYaw = rootObject.GetComponentsInChildren(true) + .Single(marker => marker.EntityId == "player-vanguard") + .transform.eulerAngles.y * Mathf.Deg2Rad; + var look = driver.SubmitLookInput(1000f); + Assert.That(look, Is.Not.Null); + Assert.That(provider.Submitted.Count, Is.EqualTo(2)); + Assert.That(look!.Payload.GetString("commandId"), Is.EqualTo("aetheria.daemon.look_intent")); + Assert.That(look.Payload.GetDouble("directionX", 0), Is.EqualTo(Mathf.Sin(controlledYaw - 1f)).Within(0.0001f)); + Assert.That(look.Payload.GetDouble("directionZ", 0), Is.EqualTo(Mathf.Cos(controlledYaw - 1f)).Within(0.0001f)); + var movement = EveUnityPlayableWorldMoveVector.FromCameraRelativeInput(1f, 1f, null); Assert.That(movement.HasInput, Is.True); Assert.That(movement.ScalarValue, Is.EqualTo(1f).Within(0.0001f)); @@ -1752,6 +1774,8 @@ namespace GameCult.Eve.UnityScene.Tests ["excludedRenderChannels"] = "map", ["playerEntityId"] = "player-vanguard", ["movementCommand"] = "aetheria.daemon.move_intent", + ["lookCommand"] = "aetheria.daemon.look_intent", + ["lookSensitivityRadians"] = "-0.001", ["focusCommand"] = "aetheria.daemon.focus", ["targetCommand"] = "aetheria.daemon.target", ["actionCommand"] = "aetheria.daemon.use_equipment" diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index d6f6274..895bac3 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.23", + "version": "0.3.24", "unity": "2021.3", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {