using Fusion; using UnityEngine; namespace Projectiles { /// /// A projectile that mimics a spray behaviour. To achieve this effect, the projectile can slow down, rise up or down, /// or change its diameter over time. When fired with higher dispersion, this projectile can represent the spray-like /// behavior of various weapons such as flamethrowers, acid guns, healing sprays, icing guns, and similar devices. /// public class SprayProjectile : KinematicProjectile { // PRIVATE MEMBERS [SerializeField] private AnimationCurve _damageOverLifetime; [SerializeField] private AnimationCurve _radiusOverLifetime; [SerializeField] private EHitType _hitType = EHitType.Projectile; [SerializeField] private LayerMask _hitMask; [SerializeField] private float _gravity = 20f; [SerializeField] private float _drag = 1f; // KinematicProjectile INTERFACE public override void OnFixedUpdate(ref KinematicData data) { var runner = Context.Runner; var previousPosition = GetMovePosition(ref data, runner.Tick - 1, runner.DeltaTime); var nextPosition = GetMovePosition(ref data, runner.Tick, runner.DeltaTime); var direction = nextPosition - previousPosition; float distance = direction.magnitude; if (distance <= 0f) return; // Normalize direction /= distance; if (_length > 0f) { float elapsedDistanceSqr = (previousPosition - data.Position).sqrMagnitude; float projectileLength = elapsedDistanceSqr > _length * _length ? _length : Mathf.Sqrt(elapsedDistanceSqr); previousPosition -= direction * projectileLength; distance += projectileLength; } int elapsedTicks = runner.Tick - data.FireTick; float lifetimeProgress = elapsedTicks / (float)_lifetimeTicks; float radius = _radiusOverLifetime.Evaluate(lifetimeProgress); if (ProjectileUtility.CircleCast(runner, Context.Owner, previousPosition, direction, distance, radius, 5, _hitMask, out LagCompensatedHit hit) == true) { float damage = _damageOverLifetime.Evaluate(lifetimeProgress); HitUtility.ProcessHit(Context.Owner, direction, hit, damage, _hitType); data.ImpactPosition = hit.Point; data.ImpactNormal = (hit.Normal - direction) * 0.5f; data.IsFinished = true; SpawnImpact(data.ImpactPosition, data.ImpactNormal); } base.OnFixedUpdate(ref data); } protected override Vector3 GetRenderPosition(ref KinematicData data, ref KinematicData fromData, float alpha) { var runner = Context.Runner; float renderTime = Context.Owner == runner.LocalPlayer ? runner.LocalRenderTime : runner.RemoteRenderTime; return GetMovePosition(ref data, renderTime / runner.DeltaTime, runner.DeltaTime); } public override void Render(ref KinematicData data, ref KinematicData dataFrom, float alpha) { base.Render(ref data, ref dataFrom, alpha); // Only fire velocity is used for rotating projectile visuals as including also move velocity // (data.InheritedVelocity) would cause particles to rotate to one side when firing while strafing transform.rotation = Quaternion.LookRotation(data.Velocity); } // PRIVATE METHODS private Vector3 GetMovePosition(ref KinematicData data, float currentTick, float deltaTime) { float time = (currentTick - data.FireTick) * deltaTime; if (time <= 0f) return data.Position; var velocity = data.Velocity + data.Spray.InheritedVelocity; return data.Position + velocity * time - velocity * _drag * time * time * 0.5f + new Vector3(0f, -_gravity, 0f) * time * time * 0.5f; } } }