Render provider feedback effects
This commit is contained in:
parent
0d97dcf56c
commit
290b384703
5 changed files with 165 additions and 0 deletions
|
|
@ -0,0 +1,98 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using UnityEngine;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace GameCult.Eve.UnityScene
|
||||
{
|
||||
public sealed class EveUnityFeedbackEffectRenderer : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private float fallbackLifetimeSeconds = 5f;
|
||||
|
||||
private readonly List<ActiveEffect> _active = new List<ActiveEffect>();
|
||||
private EveUnityPlayableWorldClientHost? _host;
|
||||
private IEveUnityGameObjectAssetProvider? _assetProvider;
|
||||
|
||||
public int ActiveEffectCount => _active.Count;
|
||||
|
||||
public void Bind(EveUnityPlayableWorldClientHost host, IEveUnityGameObjectAssetProvider? assetProvider = null)
|
||||
{
|
||||
if (host == null) throw new ArgumentNullException(nameof(host));
|
||||
if (ReferenceEquals(_host, host) && ReferenceEquals(_assetProvider, assetProvider)) return;
|
||||
Unbind();
|
||||
_host = host;
|
||||
_assetProvider = assetProvider;
|
||||
_host.FeedbackAvailable += Present;
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
if (_host != null) _host.FeedbackAvailable -= Present;
|
||||
_host = null;
|
||||
_assetProvider = null;
|
||||
}
|
||||
|
||||
public void Present(EveUnityFeedbackEvent feedback)
|
||||
{
|
||||
if (feedback == null) throw new ArgumentNullException(nameof(feedback));
|
||||
if (string.IsNullOrWhiteSpace(feedback.Kind)) return;
|
||||
var prefab = ResolveRolePrefab("effect.feedback." + feedback.Kind);
|
||||
if (prefab == null) return;
|
||||
var effect = Instantiate(prefab, transform);
|
||||
effect.name = "Eve feedback " + feedback.Kind + " " + feedback.EventId;
|
||||
if (TryVector(feedback.Position, out var position)) effect.transform.position = position;
|
||||
_active.Add(new ActiveEffect(effect, Time.unscaledTime + Math.Max(0.01f, fallbackLifetimeSeconds)));
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var now = Time.unscaledTime;
|
||||
for (var index = _active.Count - 1; index >= 0; index--)
|
||||
{
|
||||
var effect = _active[index];
|
||||
if (effect.Root != null && now < effect.DestroyAt) continue;
|
||||
if (effect.Root != null) Destroy(effect.Root);
|
||||
_active.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
private GameObject? ResolveRolePrefab(string role) => _assetProvider?.ResolvePrefab(
|
||||
new EveUnityPlayableWorldAssetBinding("", role, "provider-asset-ref"));
|
||||
|
||||
private static bool TryVector(string value, out Vector3 vector)
|
||||
{
|
||||
vector = Vector3.zero;
|
||||
var parts = (value ?? "").Split(',');
|
||||
if (parts.Length < 2 ||
|
||||
!float.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var x) ||
|
||||
!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var z))
|
||||
return false;
|
||||
var y = 0f;
|
||||
if (parts.Length >= 3)
|
||||
{
|
||||
if (!float.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out y) ||
|
||||
!float.TryParse(parts[2], NumberStyles.Float, CultureInfo.InvariantCulture, out z))
|
||||
return false;
|
||||
}
|
||||
vector = new Vector3(x, y, z);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Unbind();
|
||||
foreach (var effect in _active)
|
||||
if (effect.Root != null) Destroy(effect.Root);
|
||||
_active.Clear();
|
||||
}
|
||||
|
||||
private readonly struct ActiveEffect
|
||||
{
|
||||
public ActiveEffect(GameObject root, float destroyAt) { Root = root; DestroyAt = destroyAt; }
|
||||
public GameObject Root { get; }
|
||||
public float DestroyAt { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f0b10c28d7386e54c8e7ffe269a9bfce
|
||||
|
|
@ -85,6 +85,9 @@ namespace GameCult.Eve.UnityScene
|
|||
if (trajectories == null) trajectories = gameObject.AddComponent<EveUnityShotTrajectoryRenderer>();
|
||||
trajectories.Bind(this, Runtime.GameObjectAssetProvider);
|
||||
}
|
||||
var feedbackEffects = GetComponent<EveUnityFeedbackEffectRenderer>();
|
||||
if (feedbackEffects == null) feedbackEffects = gameObject.AddComponent<EveUnityFeedbackEffectRenderer>();
|
||||
feedbackEffects.Bind(this, Runtime.GameObjectAssetProvider);
|
||||
|
||||
return Runtime.Connect();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace GameCult.Eve.UnityScene.Tests
|
||||
{
|
||||
public sealed class EveUnityFeedbackEffectRendererTests
|
||||
{
|
||||
[Test]
|
||||
public void RendererResolvesSemanticFeedbackRoleAtProviderPosition()
|
||||
{
|
||||
var root = new GameObject("feedback-renderer-test");
|
||||
var prefab = new GameObject("destruction-prefab");
|
||||
try
|
||||
{
|
||||
var host = root.AddComponent<EveUnityPlayableWorldClientHost>();
|
||||
var renderer = root.AddComponent<EveUnityFeedbackEffectRenderer>();
|
||||
var provider = new CapturingProvider(prefab);
|
||||
renderer.Bind(host, provider);
|
||||
renderer.Present(Event("destroyed-1", "entity.destroyed", "4,2,7"));
|
||||
|
||||
Assert.That(provider.LastRole, Is.EqualTo("effect.feedback.entity.destroyed"));
|
||||
Assert.That(renderer.ActiveEffectCount, Is.EqualTo(1));
|
||||
Assert.That(root.transform.childCount, Is.EqualTo(1));
|
||||
Assert.That(root.transform.GetChild(0).position, Is.EqualTo(new Vector3(4, 2, 7)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(root);
|
||||
UnityEngine.Object.DestroyImmediate(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
private static EveUnityFeedbackEvent Event(string id, string kind, string position) =>
|
||||
new EveUnityFeedbackEvent(new EveUnitySceneNode(id, "feedback.event", "scene-node",
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["eventId"] = id, ["eventKind"] = kind, ["position"] = position
|
||||
}, Empty(), Empty(), 0, 0, Array.Empty<EveUnitySceneEmbeddedDocumentSlot>(), null,
|
||||
Array.Empty<EveUnitySceneNode>()));
|
||||
|
||||
private static IReadOnlyDictionary<string, string> Empty() =>
|
||||
new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
|
||||
private sealed class CapturingProvider : IEveUnityGameObjectAssetProvider
|
||||
{
|
||||
private readonly GameObject _prefab;
|
||||
public CapturingProvider(GameObject prefab) { _prefab = prefab; }
|
||||
public string LastRole { get; private set; } = "";
|
||||
public GameObject? ResolvePrefab(EveUnityPlayableWorldAssetBinding asset)
|
||||
{
|
||||
LastRole = asset.EntityKind;
|
||||
return _prefab;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a5f577609431418418367103f778ed67
|
||||
Reference in a new issue