Prefer provider-authored shot effects

This commit is contained in:
Meta Vens 2026-07-15 04:18:13 +01:00
commit e5a43cffcd
4 changed files with 117 additions and 15 deletions

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#nullable enable
@ -20,6 +21,7 @@ namespace GameCult.Eve.UnityScene
private Material? _material;
public int ActiveTrajectoryCount => _active.Count;
public int ActiveFallbackTrajectoryCount => _active.Count(trajectory => trajectory.Line != null);
public void Bind(EveUnityPlayableWorldClientHost host, IEveUnityGameObjectAssetProvider? assetProvider = null)
{
@ -43,19 +45,10 @@ namespace GameCult.Eve.UnityScene
if (receipt == null) throw new ArgumentNullException(nameof(receipt));
var shot = new GameObject("Eve shot " + receipt.ShotId);
shot.transform.SetParent(transform, false);
var line = shot.AddComponent<LineRenderer>();
line.useWorldSpace = true;
line.positionCount = 2;
var intensityScale = Mathf.Clamp(Mathf.Sqrt((float)receipt.PresentationIntensity), 0.5f, 4f);
line.startWidth = Math.Max(0.01f, width * intensityScale);
line.endWidth = Math.Max(0.01f, width * 0.5f * intensityScale);
line.material = SharedMaterial();
line.startColor = line.endColor = receipt.Hit ? hitColor : missColor;
var origin = Vector(receipt.Origin);
line.SetPosition(0, origin);
var endpoint = Vector(receipt.Endpoint);
var effectPrefab = ResolveEffectPrefab(receipt);
LineRenderer? line = null;
if (effectPrefab != null)
{
var effect = Instantiate(effectPrefab, shot.transform);
@ -71,10 +64,23 @@ namespace GameCult.Eve.UnityScene
lightning.StartAnimation();
}
}
else
{
line = shot.AddComponent<LineRenderer>();
line.useWorldSpace = true;
line.positionCount = 2;
var intensityScale = Mathf.Clamp(Mathf.Sqrt((float)receipt.PresentationIntensity), 0.5f, 4f);
line.startWidth = Math.Max(0.01f, width * intensityScale);
line.endWidth = Math.Max(0.01f, width * 0.5f * intensityScale);
line.material = SharedMaterial();
line.startColor = line.endColor = receipt.Hit ? hitColor : missColor;
line.SetPosition(0, origin);
}
var impactPrefab = ResolveRolePrefab("effect.impact." + receipt.ImpactKind);
var travels = string.Equals(receipt.PresentationKind, "bolt", StringComparison.Ordinal) ||
string.Equals(receipt.PresentationKind, "guided", StringComparison.Ordinal);
line.SetPosition(1, travels ? origin : endpoint);
if (line != null)
line.SetPosition(1, travels ? origin : endpoint);
_active.Add(new ActiveTrajectory(
shot,
line,
@ -93,7 +99,7 @@ namespace GameCult.Eve.UnityScene
{
var trajectory = _active[index];
var progress = Mathf.Clamp01((now - trajectory.StartedAt) / trajectory.Duration);
if (trajectory.Travels)
if (trajectory.Travels && trajectory.Line != null)
trajectory.Line.SetPosition(1, Vector3.Lerp(trajectory.Origin, trajectory.Endpoint, progress));
if (progress >= 1 && !trajectory.ImpactPresented)
{
@ -146,7 +152,7 @@ namespace GameCult.Eve.UnityScene
private sealed class ActiveTrajectory
{
public ActiveTrajectory(GameObject root, LineRenderer line, Vector3 origin, Vector3 endpoint, float startedAt, float duration, bool travels, GameObject? impactPrefab)
public ActiveTrajectory(GameObject root, LineRenderer? line, Vector3 origin, Vector3 endpoint, float startedAt, float duration, bool travels, GameObject? impactPrefab)
{
Root = root;
Line = line;
@ -159,7 +165,7 @@ namespace GameCult.Eve.UnityScene
}
public GameObject Root { get; }
public LineRenderer Line { get; }
public LineRenderer? Line { get; }
public Vector3 Origin { get; }
public Vector3 Endpoint { get; }
public float StartedAt { get; }

View file

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
#nullable enable
namespace GameCult.Eve.UnityScene.Tests
{
public sealed class EveUnityShotTrajectoryRendererTests
{
[Test]
public void ProviderShotEffectReplacesGenericFallbackLine()
{
var root = new GameObject("shot-renderer-provider-test");
var prefab = new GameObject("provider-bolt");
try
{
var host = root.AddComponent<EveUnityPlayableWorldClientHost>();
var renderer = root.AddComponent<EveUnityShotTrajectoryRenderer>();
renderer.Bind(host, new BoltProvider(prefab));
renderer.Present(Shot("provider-shot"));
Assert.That(renderer.ActiveTrajectoryCount, Is.EqualTo(1));
Assert.That(renderer.ActiveFallbackTrajectoryCount, Is.Zero);
Assert.That(root.GetComponentsInChildren<LineRenderer>(), Is.Empty);
Assert.That(root.transform.GetChild(0).Find("provider-bolt(Clone)"), Is.Not.Null);
}
finally
{
UnityEngine.Object.DestroyImmediate(root);
UnityEngine.Object.DestroyImmediate(prefab);
}
}
[Test]
public void MissingProviderShotEffectUsesGenericFallbackLine()
{
var root = new GameObject("shot-renderer-fallback-test");
try
{
var host = root.AddComponent<EveUnityPlayableWorldClientHost>();
var renderer = root.AddComponent<EveUnityShotTrajectoryRenderer>();
renderer.Bind(host, new BoltProvider(null));
renderer.Present(Shot("fallback-shot"));
Assert.That(renderer.ActiveTrajectoryCount, Is.EqualTo(1));
Assert.That(renderer.ActiveFallbackTrajectoryCount, Is.EqualTo(1));
Assert.That(root.GetComponentsInChildren<LineRenderer>(), Has.Length.EqualTo(1));
}
finally
{
UnityEngine.Object.DestroyImmediate(root);
}
}
private static EveUnityShotReceipt Shot(string id) =>
new EveUnityShotReceipt(new EveUnitySceneNode(id, "shot.receipt", "scene-node",
new Dictionary<string, string>(StringComparer.Ordinal)
{
["shotId"] = id,
["origin"] = "0,0",
["endpoint"] = "10,0",
["presentationDuration"] = "0.2",
["presentationKind"] = "bolt",
["impactKind"] = "armor",
["presentationIntensity"] = "85",
["hit"] = "true"
}, 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 BoltProvider : IEveUnityGameObjectAssetProvider
{
private readonly GameObject? _prefab;
public BoltProvider(GameObject? prefab) { _prefab = prefab; }
public GameObject? ResolvePrefab(EveUnityPlayableWorldAssetBinding asset) =>
asset.EntityKind == "effect.shot.bolt" ? _prefab : null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1d80f8e29164e23ad367feefb67c49e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,7 +1,7 @@
{
"name": "org.gamecult.eve.unity-scene",
"displayName": "Eve Unity Scene Lowering",
"version": "0.3.19",
"version": "0.3.20",
"unity": "2021.3",
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
"dependencies": {