Projectile is drawn on FireControl.RoundAt and integrates nothing
Deletes the Time.deltaTime gravity/drag/position integration and the range-distance kill from Projectile and the UnityEngine.Random jitter, barrel direction and inherited velocity from ProjectileManager. A round holds a copy of its PendingShot and draws at RoundAt(Zone.Time + Clock.Lead) with a barrel offset decaying over BlendTime; ShotResolved ends a hit or burst at the outcome's BurstPoint, a miss flies to the FireRange clamp. Unity compile not yet run at this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0e89dd1673
commit
aa9288d9de
2 changed files with 70 additions and 57 deletions
|
|
@ -1,59 +1,87 @@
|
|||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using static CultMath.math;
|
||||
|
||||
// Cut 3 (docs/fire-control-cut.md): the raycast, shield branch and SendHit are deleted -- FireControl already
|
||||
// decided this shot's fate before this object was ever spawned (R8). What is left is pure flight-path
|
||||
// presentation: fly, and disappear at Range or ShotId's resolution, whichever comes first.
|
||||
// Cut 6b, 6.2 (12.4(b): Apply's own switch on the frozen Fuse decides this now, not Step): the flak-round
|
||||
// blast resolves in the simulation, through FireControl.Detonate -- a MonoBehaviour calling it would be
|
||||
// exactly the authority Cut 3 spent itself deleting, so the blast-distance/blast-radius fields this object
|
||||
// used to carry for that are gone. This object's job stays fly-and-disappear.
|
||||
// A ballistic round, drawn on FireControl.RoundAt and deciding nothing. The simulation owns the round's whole flight
|
||||
// (origin, direction, speed, fire time, arrival, range: the PendingShot copy taken at spawn), so this object stores no
|
||||
// velocity or position of its own and integrates nothing: each frame it asks RoundAt where the round is at sim time
|
||||
// plus the clock's Lead (the same draw-ahead ships use), and the simulation's ShotResolved ends a round that hit or
|
||||
// burst. A round that missed flies on to the FireRange point RoundAt clamps at, and is done there. The barrel is only
|
||||
// where the round is first seen: its offset from the sim line decays to zero over BlendTime sim seconds. The trail's
|
||||
// fade after the end stays real time (pure presentation, ruling sim-speed-presentation).
|
||||
public class Projectile : MonoBehaviour
|
||||
{
|
||||
public TrailRenderer Trail;
|
||||
public float Gravity;
|
||||
public float Drag = .1f;
|
||||
public Prototype HitEffect;
|
||||
|
||||
// Sim seconds over which the barrel's offset from the sim line decays to nothing.
|
||||
public float BlendTime = .1f;
|
||||
|
||||
private PendingShot _shot;
|
||||
private Zone _zone;
|
||||
private Vector3 _barrelOffset;
|
||||
private bool _alive;
|
||||
private IDisposable _binding;
|
||||
|
||||
// Cut 3: FireControl.Fire's ShotId -- this projectile's handle onto Zone.ShotCommitted/ShotResolved.
|
||||
public int ShotId { get; set; }
|
||||
public Zone Zone { get; set; }
|
||||
|
||||
public Vector3 StartPosition { get; set; }
|
||||
public Vector3 Velocity { get; set; }
|
||||
public Entity SourceEntity { get; set; }
|
||||
public float Range { get; set; }
|
||||
|
||||
private void OnEnable()
|
||||
// Takes the frozen shot, 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)
|
||||
{
|
||||
_zone = zone;
|
||||
_shot = shot;
|
||||
var origin = shot.FireOrigin;
|
||||
_barrelOffset = barrel - new Vector3(origin.x, origin.y, origin.z);
|
||||
_binding?.Dispose();
|
||||
_binding = zone.ShotResolved.Where(o => o.ShotId == shot.ShotId).Subscribe(Resolve);
|
||||
_alive = true;
|
||||
transform.forward = new Vector3(shot.TravelDirection.x, 0, shot.TravelDirection.y);
|
||||
transform.position = barrel;
|
||||
}
|
||||
|
||||
private Vector3 At(CultMath.float2 planar, float time)
|
||||
{
|
||||
var blend = BlendTime > 0 ? saturate(1 - (time - _shot.FireTime) / BlendTime) : 0f;
|
||||
return new Vector3(planar.x, _shot.FireOrigin.y, planar.y) + _barrelOffset * blend;
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
if (SourceEntity == null) return;
|
||||
|
||||
if(_alive)
|
||||
{
|
||||
var t = transform;
|
||||
Velocity -= Vector3.up * (Gravity * Time.deltaTime);
|
||||
Velocity *= max(0, 1 - Drag * Time.deltaTime);
|
||||
t.forward = Velocity.normalized;
|
||||
|
||||
transform.position += Velocity * Time.deltaTime;
|
||||
var distanceTraveled = (transform.position - StartPosition).magnitude;
|
||||
if(distanceTraveled > Range)
|
||||
StartCoroutine(Kill());
|
||||
}
|
||||
if (!_alive) return;
|
||||
var time = _zone.Time + ActionGameManager.Clock.Lead;
|
||||
transform.position = At(FireControl.RoundAt(_shot, time), time);
|
||||
var end = _shot.Speed > .01f ? _shot.FireTime + _shot.FireRange / _shot.Speed : _shot.FireTime;
|
||||
if (time >= end) Finish();
|
||||
}
|
||||
|
||||
IEnumerator Kill()
|
||||
// The simulation's verdict. A round that hit or burst stops where the sim put it, at the outcome's burst point,
|
||||
// else where it arrives on its line; a miss keeps flying.
|
||||
private void Resolve(ShotOutcome outcome)
|
||||
{
|
||||
if (!_alive || (outcome.Result != ShotResult.Hit && outcome.Result != ShotResult.Burst)) return;
|
||||
var planar = outcome.HasBurstPoint ? outcome.BurstPoint : FireControl.RoundAt(_shot, _shot.ArrivalTime);
|
||||
transform.position = At(planar, _shot.ArrivalTime);
|
||||
if (HitEffect != null) HitEffect.Instantiate<Transform>().position = transform.position;
|
||||
Finish();
|
||||
}
|
||||
|
||||
private void Finish()
|
||||
{
|
||||
_alive = false;
|
||||
_binding?.Dispose();
|
||||
_binding = null;
|
||||
StartCoroutine(Fade());
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
_alive = false;
|
||||
_binding?.Dispose();
|
||||
_binding = null;
|
||||
}
|
||||
|
||||
IEnumerator Fade()
|
||||
{
|
||||
var startTime = Time.time;
|
||||
var lifetime = Trail.time;
|
||||
while (Time.time - startTime < lifetime)
|
||||
|
|
|
|||
|
|
@ -1,32 +1,17 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
using UnityEngine;
|
||||
|
||||
public class ProjectileManager : InstantWeaponEffectManager
|
||||
{
|
||||
public Prototype ProjectilePrototype;
|
||||
public bool InheritVelocity;
|
||||
|
||||
// The round is drawn on the line the simulation flies it (FireControl.RoundAt), 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.
|
||||
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;
|
||||
var p = ProjectilePrototype.Instantiate<Projectile>();
|
||||
var hp = item.Hardpoint;
|
||||
var barrel = source.GetBarrel(hp);
|
||||
var angle = weapon.Spread / 2;
|
||||
p.ShotId = shotId;
|
||||
p.SourceEntity = source.Entity;
|
||||
p.Velocity = Quaternion.Euler(
|
||||
Random.Range(-angle, angle),
|
||||
Random.Range(-angle, angle),
|
||||
Random.Range(-angle, angle)) *
|
||||
barrel.forward *
|
||||
weapon.Velocity;
|
||||
p.StartPosition = p.transform.position = barrel.position + p.Velocity * (Random.value * Time.deltaTime);
|
||||
if(InheritVelocity)
|
||||
p.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y);
|
||||
p.Range = weapon.Range;
|
||||
p.Zone = source.Entity.Zone;
|
||||
p.Launch(zone, shot, source.GetBarrel(item.Hardpoint).position);
|
||||
p.Trail.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue