Harden generic authoritative aim lifecycle
This commit is contained in:
parent
6d31f92fa3
commit
58d87c628a
9 changed files with 160 additions and 17 deletions
|
|
@ -52,10 +52,15 @@ namespace GameCult.Eve.UnityScene
|
|||
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);
|
||||
var camera = _host?.ActiveCameraTransform;
|
||||
if (camera == null)
|
||||
{
|
||||
SetVisible(false);
|
||||
return;
|
||||
}
|
||||
var right = camera.right;
|
||||
var up = camera.up;
|
||||
var center = ViewDotPosition - camera.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);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ namespace GameCult.Eve.UnityScene
|
|||
public sealed class EveUnityGameObjectPlayableWorldSceneSink :
|
||||
IEveUnityPlayableWorldSceneSink,
|
||||
IEveUnityEntityGenerationSink,
|
||||
IEveUnityPresentedEntityRegistry
|
||||
IEveUnityPresentedEntityRegistry,
|
||||
IDisposable
|
||||
{
|
||||
private readonly Transform _root;
|
||||
private readonly IEveUnityGameObjectAssetProvider _assetProvider;
|
||||
|
|
@ -179,6 +180,17 @@ namespace GameCult.Eve.UnityScene
|
|||
DestroyInstance(instance);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var instance in _instances.Values)
|
||||
if (instance != null)
|
||||
DestroyInstance(instance);
|
||||
_instances.Clear();
|
||||
_presentedById = new Dictionary<string, EveUnityPresentedEntityHandle>(StringComparer.Ordinal);
|
||||
_presentedByIndex = new Dictionary<int, EveUnityPresentedEntityHandle>();
|
||||
CurrentGeneration = null;
|
||||
}
|
||||
|
||||
private GameObject InstantiateEntity(
|
||||
EveUnityPlayableWorldEntity entity,
|
||||
EveUnityPlayableWorldAssetBinding asset,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace GameCult.Eve.UnityScene
|
|||
[SerializeField] private float yawDegrees = 35f;
|
||||
[SerializeField] private float followDamping = 12f;
|
||||
private IEveUnityCameraRenderPolicySource? _renderPolicySource;
|
||||
private EveUnityPlayableWorldClientHost? _cameraHost;
|
||||
private bool _ownsAmbientEnvironment;
|
||||
private AmbientMode _previousAmbientMode;
|
||||
private Color _previousAmbientLight;
|
||||
|
|
@ -27,7 +28,12 @@ namespace GameCult.Eve.UnityScene
|
|||
public EveUnityPlayableWorldClientHost? Host
|
||||
{
|
||||
get => host;
|
||||
set => host = value;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(host, value)) return;
|
||||
ReleaseCamera();
|
||||
host = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Transform? CameraTransform
|
||||
|
|
@ -42,7 +48,11 @@ namespace GameCult.Eve.UnityScene
|
|||
set => _renderPolicySource = value;
|
||||
}
|
||||
|
||||
public void ReleaseRig() => RestoreAmbientEnvironment();
|
||||
public void ReleaseRig()
|
||||
{
|
||||
ReleaseCamera();
|
||||
RestoreAmbientEnvironment();
|
||||
}
|
||||
|
||||
public bool ApplyRig(float deltaTime)
|
||||
{
|
||||
|
|
@ -50,12 +60,14 @@ namespace GameCult.Eve.UnityScene
|
|||
var activeWorld = resolvedHost?.ActiveWorld;
|
||||
if (resolvedHost == null || activeWorld == null)
|
||||
{
|
||||
ReleaseCamera();
|
||||
RestoreAmbientEnvironment();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (activeWorld.CameraRig == "planar.top-down-follow.v1" && !HasValidPlanarContract(activeWorld))
|
||||
{
|
||||
ReleaseCamera();
|
||||
RestoreAmbientEnvironment();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -69,6 +81,7 @@ namespace GameCult.Eve.UnityScene
|
|||
activeWorld.CameraRig != "arpg.orbital-follow.v1" &&
|
||||
activeWorld.CameraRig != "third-person-orbit")
|
||||
{
|
||||
ReleaseCamera();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +91,12 @@ namespace GameCult.Eve.UnityScene
|
|||
: activeWorld.CameraTargetEntityId;
|
||||
var player = FindEntity(resolvedHost, targetEntityId);
|
||||
if (player == null)
|
||||
{
|
||||
ReleaseCamera();
|
||||
return false;
|
||||
}
|
||||
resolvedHost.ClaimWorldCamera(this, camera);
|
||||
_cameraHost = resolvedHost;
|
||||
|
||||
var cameraComponent = camera.GetComponent<Camera>();
|
||||
if (cameraComponent != null && _renderPolicySource != null)
|
||||
|
|
@ -186,6 +204,12 @@ namespace GameCult.Eve.UnityScene
|
|||
_ownsAmbientEnvironment = false;
|
||||
}
|
||||
|
||||
private void ReleaseCamera()
|
||||
{
|
||||
_cameraHost?.ReleaseWorldCamera(this);
|
||||
_cameraHost = null;
|
||||
}
|
||||
|
||||
private static Bounds? CalculateVisualBounds(Transform player, int cullingMask)
|
||||
{
|
||||
Bounds? bounds = null;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace GameCult.Eve.UnityScene
|
|||
[SerializeField] private bool renderShotTrajectories = true;
|
||||
|
||||
private float _nextRefreshAt;
|
||||
private object? _cameraOwner;
|
||||
|
||||
public EveUnityPlayableWorldRuntime? Runtime { get; private set; }
|
||||
|
||||
|
|
@ -37,6 +38,8 @@ namespace GameCult.Eve.UnityScene
|
|||
(providerSurfaceDocuments as IEveUnityInputCapabilitySource)?.CurrentInputCapability;
|
||||
|
||||
public long ActiveVersion => Runtime?.ActiveVersion ?? 0;
|
||||
public int ConnectionEpoch { get; private set; }
|
||||
public Transform? ActiveCameraTransform { get; private set; }
|
||||
|
||||
public event Action<EveUnityFeedbackEvent>? FeedbackAvailable;
|
||||
public event Action<EveUnityShotReceipt>? ShotAvailable;
|
||||
|
|
@ -105,10 +108,24 @@ namespace GameCult.Eve.UnityScene
|
|||
thermal.Bind(this, Runtime.GameObjectAssetProvider as IEveUnityNativeAssetProvider);
|
||||
|
||||
var presentation = Runtime.Connect();
|
||||
ConnectionEpoch++;
|
||||
_nextRefreshAt = Time.unscaledTime + Math.Max(0.01f, refreshIntervalSeconds);
|
||||
return presentation;
|
||||
}
|
||||
|
||||
internal void ClaimWorldCamera(object owner, Transform camera)
|
||||
{
|
||||
_cameraOwner = owner ?? throw new ArgumentNullException(nameof(owner));
|
||||
ActiveCameraTransform = camera != null ? camera : throw new ArgumentNullException(nameof(camera));
|
||||
}
|
||||
|
||||
internal void ReleaseWorldCamera(object owner)
|
||||
{
|
||||
if (!ReferenceEquals(_cameraOwner, owner)) return;
|
||||
_cameraOwner = null;
|
||||
ActiveCameraTransform = null;
|
||||
}
|
||||
|
||||
public EveUnityPlayableWorldPresentation Refresh()
|
||||
{
|
||||
RefreshProviderSources();
|
||||
|
|
@ -180,6 +197,8 @@ namespace GameCult.Eve.UnityScene
|
|||
}
|
||||
Runtime?.Dispose();
|
||||
Runtime = null;
|
||||
_cameraOwner = null;
|
||||
ActiveCameraTransform = null;
|
||||
}
|
||||
|
||||
private void OnFeedbackAvailable(EveUnityFeedbackEvent value) => FeedbackAvailable?.Invoke(value);
|
||||
|
|
|
|||
|
|
@ -30,11 +30,18 @@ namespace GameCult.Eve.UnityScene
|
|||
private bool _wasMoving;
|
||||
private float _lookYawRadians;
|
||||
private bool _hasLookYaw;
|
||||
private float _pendingLookHorizontal;
|
||||
private string _lookOwnerKey = "";
|
||||
|
||||
public EveUnityPlayableWorldClientHost? Host
|
||||
{
|
||||
get => host;
|
||||
set => host = value;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(host, value)) return;
|
||||
host = value;
|
||||
ResetLookState();
|
||||
}
|
||||
}
|
||||
|
||||
public Transform? CameraTransform
|
||||
|
|
@ -104,14 +111,17 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
var resolvedHost = ResolveHost();
|
||||
var world = resolvedHost?.ActiveWorld;
|
||||
if (world == null || string.IsNullOrWhiteSpace(world.LookCommand) ||
|
||||
if (world == null || !string.Equals(world.LookModel, "planar-yaw.v1", StringComparison.Ordinal) ||
|
||||
string.IsNullOrWhiteSpace(world.LookCommand) ||
|
||||
Mathf.Abs(horizontalDelta) <= 0.0001f)
|
||||
return null;
|
||||
|
||||
if (!_hasLookYaw)
|
||||
var ownerKey = $"{resolvedHost!.ConnectionEpoch}\n{world.WorldRootId}\n{world.PlayerEntityId}";
|
||||
if (!_hasLookYaw || !string.Equals(_lookOwnerKey, ownerKey, StringComparison.Ordinal))
|
||||
{
|
||||
_lookYawRadians = ResolveControlledYawRadians(resolvedHost!, world.PlayerEntityId);
|
||||
_hasLookYaw = true;
|
||||
_lookOwnerKey = ownerKey;
|
||||
}
|
||||
var sensitivity = Mathf.Abs(world.LookSensitivityRadians) > 0.000001f
|
||||
? world.LookSensitivityRadians
|
||||
|
|
@ -126,6 +136,15 @@ namespace GameCult.Eve.UnityScene
|
|||
issuedAt);
|
||||
}
|
||||
|
||||
public void QueueLookInput(float horizontalDelta) => _pendingLookHorizontal += horizontalDelta;
|
||||
|
||||
public EveSurfaceCommandRequest? SubmitPendingLookInput(DateTimeOffset? issuedAt = null)
|
||||
{
|
||||
var pending = _pendingLookHorizontal;
|
||||
_pendingLookHorizontal = 0f;
|
||||
return SubmitLookInput(pending, issuedAt);
|
||||
}
|
||||
|
||||
public static string ResolvePrimaryActionId(EveInputCapabilityDocument? capability)
|
||||
{
|
||||
if (capability == null)
|
||||
|
|
@ -148,13 +167,17 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
private void Update()
|
||||
{
|
||||
if (!driveInUpdate || Time.unscaledTime < _nextCommandAt)
|
||||
if (!driveInUpdate)
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(lookHorizontalAxis))
|
||||
QueueLookInput(Input.GetAxisRaw(lookHorizontalAxis));
|
||||
if (Time.unscaledTime < _nextCommandAt)
|
||||
return;
|
||||
|
||||
_nextCommandAt = Time.unscaledTime + Math.Max(0.01f, commandIntervalSeconds);
|
||||
SubmitMoveVectorInput(Input.GetAxisRaw(horizontalAxis), Input.GetAxisRaw(verticalAxis));
|
||||
if (!string.IsNullOrWhiteSpace(lookHorizontalAxis))
|
||||
SubmitLookInput(Input.GetAxisRaw(lookHorizontalAxis));
|
||||
SubmitPendingLookInput();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(actionButton) && Input.GetButtonDown(actionButton))
|
||||
SubmitPrimaryAction();
|
||||
|
|
@ -176,6 +199,14 @@ namespace GameCult.Eve.UnityScene
|
|||
return marker.transform.eulerAngles.y * Mathf.Deg2Rad;
|
||||
return 0f;
|
||||
}
|
||||
|
||||
private void ResetLookState()
|
||||
{
|
||||
_lookYawRadians = 0f;
|
||||
_hasLookYaw = false;
|
||||
_pendingLookHorizontal = 0f;
|
||||
_lookOwnerKey = "";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct EveUnityPlayableWorldLookDirection
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace GameCult.Eve.UnityScene
|
|||
private readonly IEveUnityEntitySoaViewDocumentSource? _entityViews;
|
||||
private readonly EveUnityEntitySoaPresenter _entityPresenter;
|
||||
private readonly IEveUnityPresentedEntityRegistry? _presentedEntities;
|
||||
private IDisposable? _ownedSceneSink;
|
||||
private bool _entityViewsConnected;
|
||||
private bool _assetManifestConnected;
|
||||
|
||||
|
|
@ -106,6 +107,7 @@ namespace GameCult.Eve.UnityScene
|
|||
new EveUnityAssetRefResolver(),
|
||||
assetManifests);
|
||||
runtime.GameObjectAssetProvider = liveAssetProvider;
|
||||
runtime._ownedSceneSink = sceneSink;
|
||||
return runtime;
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +200,8 @@ namespace GameCult.Eve.UnityScene
|
|||
Disconnect();
|
||||
_client.Dispose();
|
||||
_assetManifestSource?.Dispose();
|
||||
_ownedSceneSink?.Dispose();
|
||||
_ownedSceneSink = null;
|
||||
}
|
||||
|
||||
private void EnsureAssetManifestConnected()
|
||||
|
|
|
|||
|
|
@ -138,7 +138,8 @@ namespace GameCult.Eve.UnityScene
|
|||
ParseFloat(worldRoot.GetProp("cameraNearClipPlane"), 0f),
|
||||
ParseFloat(worldRoot.GetProp("cameraFarClipPlane"), 0f),
|
||||
worldRoot.GetProp("lookCommand"),
|
||||
ParseFloat(worldRoot.GetProp("lookSensitivityRadians"), 0f));
|
||||
ParseFloat(worldRoot.GetProp("lookSensitivityRadians"), 0f),
|
||||
worldRoot.GetProp("lookModel"));
|
||||
}
|
||||
|
||||
private static EveUnityPlayableWorldEntity BuildPlayableEntity(EveSurfaceComponent component)
|
||||
|
|
@ -361,7 +362,8 @@ namespace GameCult.Eve.UnityScene
|
|||
float cameraNearClipPlane = 0f,
|
||||
float cameraFarClipPlane = 0f,
|
||||
string lookCommand = "",
|
||||
float lookSensitivityRadians = 0f)
|
||||
float lookSensitivityRadians = 0f,
|
||||
string lookModel = "")
|
||||
{
|
||||
WorldRootId = worldRootId ?? "";
|
||||
StatePointerId = statePointerId ?? "";
|
||||
|
|
@ -394,6 +396,7 @@ namespace GameCult.Eve.UnityScene
|
|||
CameraFarClipPlane = cameraFarClipPlane;
|
||||
LookCommand = lookCommand ?? "";
|
||||
LookSensitivityRadians = lookSensitivityRadians;
|
||||
LookModel = lookModel ?? "";
|
||||
}
|
||||
|
||||
public string WorldRootId { get; }
|
||||
|
|
@ -448,6 +451,8 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
public float LookSensitivityRadians { get; }
|
||||
|
||||
public string LookModel { get; }
|
||||
|
||||
public string FocusCommand { get; }
|
||||
|
||||
public string TargetCommand { get; }
|
||||
|
|
|
|||
|
|
@ -993,13 +993,28 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
var controlledYaw = rootObject.GetComponentsInChildren<EveUnityPlayableWorldEntityMarker>(true)
|
||||
.Single(marker => marker.EntityId == "player-vanguard")
|
||||
.transform.eulerAngles.y * Mathf.Deg2Rad;
|
||||
var look = driver.SubmitLookInput(1000f);
|
||||
driver.QueueLookInput(250f);
|
||||
driver.QueueLookInput(250f);
|
||||
driver.QueueLookInput(500f);
|
||||
var look = driver.SubmitPendingLookInput();
|
||||
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));
|
||||
|
||||
host.Connect();
|
||||
var rebasedYaw = rootObject.GetComponentsInChildren<EveUnityPlayableWorldEntityMarker>(true)
|
||||
.Single(marker => marker.EntityId == "player-vanguard")
|
||||
.transform.eulerAngles.y * Mathf.Deg2Rad;
|
||||
var rebasedLook = driver.SubmitLookInput(1000f);
|
||||
Assert.That(rebasedLook, Is.Not.Null);
|
||||
Assert.That(provider.Submitted.Count, Is.EqualTo(3));
|
||||
Assert.That(rebasedLook!.Payload.GetDouble("directionX", 0),
|
||||
Is.EqualTo(Mathf.Sin(rebasedYaw - 1f)).Within(0.0001f));
|
||||
Assert.That(rebasedLook.Payload.GetDouble("directionZ", 0),
|
||||
Is.EqualTo(Mathf.Cos(rebasedYaw - 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));
|
||||
|
|
@ -1100,6 +1115,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
largeVisual.layer = 14;
|
||||
|
||||
Assert.That(rig.ApplyRig(0f), Is.True);
|
||||
Assert.That(host.ActiveCameraTransform, Is.SameAs(cameraObject.transform));
|
||||
Assert.That(camera.cullingMask & (1 << 14), Is.Zero);
|
||||
Assert.That(camera.fieldOfView, Is.EqualTo(60f).Within(0.001f));
|
||||
Assert.That(camera.orthographic, Is.False);
|
||||
|
|
@ -1113,6 +1129,16 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(RenderSettings.ambientMode, Is.EqualTo(UnityEngine.Rendering.AmbientMode.Flat));
|
||||
Assert.That(RenderSettings.ambientLight.r, Is.EqualTo(0.2f).Within(0.001f));
|
||||
|
||||
var aim = hostObject.GetComponent<EveUnityAimPresentationRenderer>();
|
||||
Assert.That(aim, Is.Not.Null);
|
||||
var playerPosition = player.transform.position;
|
||||
var playerRotation = player.transform.rotation;
|
||||
aim.RefreshNow();
|
||||
Assert.That(aim.ViewDotVisible, Is.True);
|
||||
Assert.That(Vector3.Distance(playerPosition, aim.ViewDotPosition), Is.EqualTo(50f).Within(0.001f));
|
||||
Assert.That(player.transform.position, Is.EqualTo(playerPosition));
|
||||
Assert.That(player.transform.rotation, Is.EqualTo(playerRotation));
|
||||
|
||||
var selectedTarget = rootObject.GetComponentsInChildren<EveUnityPlayableWorldEntityMarker>()
|
||||
.Single(marker => marker.EntityId == "raider-scout");
|
||||
Assert.That(EveUnityCombatPresentation.Find(host.ActiveProjection)?.SelectedTargetEntityId,
|
||||
|
|
@ -1132,6 +1158,9 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
cameraObject.transform.position.x - beforeFollow.x,
|
||||
Is.EqualTo(10f * (1f - Mathf.Exp(-0.5f))).Within(0.001f));
|
||||
rig.ReleaseRig();
|
||||
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));
|
||||
}
|
||||
|
|
@ -1710,6 +1739,19 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"",
|
||||
""));
|
||||
|
||||
playableChildren.Add(new EveSurfaceComponent(
|
||||
"aetheria.daemon.game.aim",
|
||||
"aim.presentation",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["controlledEntityId"] = "player-vanguard",
|
||||
["convergenceTargetEntityId"] = "",
|
||||
["viewDotRole"] = "pilot.view-direction",
|
||||
["minimumConvergenceDistance"] = "50",
|
||||
["viewDotRadius"] = "0.8"
|
||||
},
|
||||
Array.Empty<EveSurfaceComponent>()));
|
||||
|
||||
playableChildren.Add(new EveSurfaceComponent(
|
||||
"aetheria.daemon.game.combat",
|
||||
"combat.presentation",
|
||||
|
|
@ -1775,6 +1817,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
["playerEntityId"] = "player-vanguard",
|
||||
["movementCommand"] = "aetheria.daemon.move_intent",
|
||||
["lookCommand"] = "aetheria.daemon.look_intent",
|
||||
["lookModel"] = "planar-yaw.v1",
|
||||
["lookSensitivityRadians"] = "-0.001",
|
||||
["focusCommand"] = "aetheria.daemon.focus",
|
||||
["targetCommand"] = "aetheria.daemon.target",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.3.24",
|
||||
"version": "0.3.25",
|
||||
"unity": "2021.3",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
|
|
|
|||
Reference in a new issue