Projectile: a round is told of its shot by its manager's one subscription, and first seen where its barrel was when the sim fired it inside the step

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
soul 2026-10-22 23:49:52 +02:00
commit de9e1ba2f9
3 changed files with 39 additions and 15 deletions

View file

@ -159,7 +159,7 @@ namespace Aetheria.EditorTools
MaxRange = 1e6f, FireRange = 1e6f, CommitTime = 1e5f, ArrivalTime = 2e5f
};
var projectile = new GameObject("Wiring Round").AddComponent<Projectile>();
projectile.Launch(zone, shot, new Vector3(0, 0, 0));
projectile.Launch(null, zone, shot, new Vector3(0, 0, 0));
yield return null;
zone.Update(.5f);

View file

@ -24,19 +24,16 @@ public class Projectile : MonoBehaviour
private CultMath.float3 _barrel;
private ShotOutcome _known;
private bool _alive;
private CompositeDisposable _binding;
private ProjectileManager _owner;
// Takes the shot as the sim holds it, the zone whose sim time it is drawn at, and where the barrel is as this round is first seen.
public void Launch(Zone zone, PendingShot shot, Vector3 barrel)
public void Launch(ProjectileManager owner, Zone zone, PendingShot shot, Vector3 barrel)
{
_owner = owner;
_zone = zone;
_shot = shot;
_barrel = new CultMath.float3(barrel.x, barrel.y, barrel.z);
_known = null;
_binding?.Dispose();
_binding = new CompositeDisposable(
zone.ShotCommitted.Where(o => o.ShotId == shot.ShotId).Subscribe(Committed),
zone.ShotResolved.Where(o => o.ShotId == shot.ShotId).Subscribe(Resolve));
_alive = true;
transform.forward = new Vector3(shot.TravelDirection.x, 0, shot.TravelDirection.y);
Draw();
@ -56,14 +53,14 @@ public class Projectile : MonoBehaviour
}
// The sim's commit: the record it holds now (a contact burst's arrival is shortened here) and what it decided.
private void Committed(ShotOutcome outcome)
internal void Committed(ShotOutcome outcome)
{
if (_zone.TryGetShot(outcome.ShotId, out var record)) _shot = record;
_known = outcome;
}
// The simulation's verdict. A round that hit or burst is drawn at its end and stops; a miss keeps flying.
private void Resolve(ShotOutcome outcome)
internal void Resolve(ShotOutcome outcome)
{
if (!_alive) return;
_known = outcome;
@ -78,16 +75,14 @@ public class Projectile : MonoBehaviour
private void Finish()
{
_alive = false;
_binding?.Dispose();
_binding = null;
_owner?.Forget(_shot.ShotId);
StartCoroutine(Fade());
}
void OnDisable()
{
_alive = false;
_binding?.Dispose();
_binding = null;
_owner?.Forget(_shot.ShotId);
}
IEnumerator Fade()

View file

@ -1,17 +1,46 @@
using UnityEngine;
using System.Collections.Generic;
using UniRx;
using UnityEngine;
public class ProjectileManager : InstantWeaponEffectManager
{
public Prototype ProjectilePrototype;
// The rounds this manager has in the air by shot id, told of the sim's commit and verdict by one subscription per zone: a
// gun that fires hundreds of rounds in a step costs the dictionary an entry per round and nothing else (no subscription,
// closure or disposable per round).
private readonly Dictionary<int, Projectile> _live = new Dictionary<int, Projectile>();
private readonly CompositeDisposable _listening = new CompositeDisposable();
private Zone _zone;
// The round is drawn by DrawAhead from what the simulation publishes, so there is nothing to aim, jitter
// or inherit here: spread is priced by FireControl, and the sim's round inherits nothing from its shooter.
// A round the sim fired inside the step (rounds per step) is first seen where its shooter's barrel was at that moment:
// the barrel as it is now, carried back along the shooter's motion for the time since.
public override void Fire(InstantWeapon weapon, EquippedItem item, EntityInstance source, EntityInstance target, int shotId)
{
var zone = source.Entity.Zone;
if (!zone.TryGetShot(shotId, out var shot)) return;
Listen(zone);
var age = (float) (zone.ExactTime - shot.FireTime);
var velocity = source.Entity.Velocity;
var p = ProjectilePrototype.Instantiate<Projectile>();
p.Launch(zone, shot, source.GetBarrel(item.Hardpoint).position);
_live[shotId] = p;
p.Launch(this, zone, shot, source.GetBarrel(item.Hardpoint).position - new Vector3(velocity.x, 0, velocity.y) * age);
p.Trail.Clear();
}
internal void Forget(int shotId) => _live.Remove(shotId);
private void Listen(Zone zone)
{
if (_zone == zone) return;
_listening.Clear();
_live.Clear();
_zone = zone;
zone.ShotCommitted.Subscribe(outcome => { if (_live.TryGetValue(outcome.ShotId, out var p)) p.Committed(outcome); }).AddTo(_listening);
zone.ShotResolved.Subscribe(outcome => { if (_live.TryGetValue(outcome.ShotId, out var p)) p.Resolve(outcome); }).AddTo(_listening);
}
void OnDestroy() => _listening.Dispose();
}