using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; [RequireComponent(typeof(ProjectileSpawner))] public class ProjectileTower : Tower { [SerializeField, Range(0.01f, 20f)] private float attackSecondsDelay = 1f; [SerializeField] private ProjectilePattern[] projectileSequence; [SerializeField] private Barrel barrel; [SerializeField] private LineRenderer trajectory; [SerializeField] private int trajectoryBounces = 2; private const float k_trajectory_maxdist = 100f; private ProjectileSpawner projectileSpawner; public Vector3 AimDirection => transform.TransformVector(transform.InverseTransformVector(horizontalArc.ToKnobVector) + transform.InverseTransformVector(verticalArc.ToKnobVector)); protected override void Awake() { base.Awake(); projectileSpawner = GetComponent(); Assert.IsNotNull(projectileSpawner); horizontalArc.Value.AddListener(UpdateBarrelRotation); verticalArc.Value.AddListener(UpdateBarrelRotation); horizontalArc.Value.AddListener(UpdateTrajectory); verticalArc.Value.AddListener(UpdateTrajectory); UpdateBarrelRotation(); UpdateTrajectory(); StartCoroutine(AttackLoop()); } private void UpdateTrajectory(float unused) => UpdateTrajectory(); private void UpdateTrajectory() { if (trajectory == null) return; Vector3 origin = barrel.Tip.position; Vector3 dir = barrel.transform.forward; List pointsInTrajectory = new(); pointsInTrajectory.Add(origin); for (int i = 0; i < trajectoryBounces; i++) { Debug.DrawRay(origin, dir.normalized * k_trajectory_maxdist, Color.red, 5f); RaycastHit hit; if (!Physics.Raycast(origin, dir, out hit, k_trajectory_maxdist)) break; pointsInTrajectory.Add(hit.point); dir = Vector3.Reflect(dir, hit.normal); origin = hit.point; } trajectory.positionCount = pointsInTrajectory.Count; trajectory.SetPositions(pointsInTrajectory.ToArray()); } protected override void OnDestroy() { horizontalArc.Value.RemoveListener(UpdateBarrelRotation); verticalArc.Value.RemoveListener(UpdateBarrelRotation); } private IEnumerator AttackLoop() { do { yield return new WaitForSeconds(attackSecondsDelay); UpdateBarrelRotation(); Debug.DrawRay(transform.position, horizontalArc.ToKnobVector, Color.red, attackSecondsDelay); Debug.DrawRay(transform.position, verticalArc.ToKnobVector, Color.green, attackSecondsDelay); Debug.DrawRay(transform.position, AimDirection, Color.yellow, attackSecondsDelay); projectileSpawner.RunBulletSequence(transform.position, transform.up, AimDirection, projectileSequence); } while (true); } private void UpdateBarrelRotation(float unused) => UpdateBarrelRotation(); // Rotate barrel to match rotation private void UpdateBarrelRotation() { barrel.transform.localRotation = Quaternion.Euler(verticalArc.Value, horizontalArc.Value, 0f); } }