using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class AimTower : Tower { [SerializeField] protected Barrel barrel; [SerializeField] protected EditableArc horizontalArc; [SerializeField] protected EditableArc verticalArc; [Header("Trajectory")] [SerializeField] private float spaceBetweenGhosts = 0.5f; [SerializeField] private int trajectoryBounces = 3; private const float k_trajectory_maxdist = 100f; private List ghosts = new(); [SerializeField] private GameObject ghostPrefab; [SerializeField] private LineRenderer trajectoryLine; [SerializeField] private int trajectoryObjectPoolSize = 25; [SerializeField] private LayerMask wallMask; private int poolIdx = 0; public Vector3 AimDirection => barrel.transform.forward; public Vector2 HorizontalRotationMinMax => horizontalArc.RotationMinMax; public Vector2 VerticalRotationMinMax => verticalArc.RotationMinMax; public float HorizontalRotation => horizontalArc.Value; public float VerticalRotation => verticalArc.Value; public override void Placed() { base.Placed(); horizontalArc.Value.AddListener(UpdateBarrelRotation); verticalArc.Value.AddListener(UpdateBarrelRotation); horizontalArc.Value.AddListener(UpdateTrajectory); verticalArc.Value.AddListener(UpdateTrajectory); horizontalArc.Value.AddListener(SnapVerticalToHorizontal); UpdateTrajectory(); UpdateBarrelRotation(); } public override void TowerSelected(bool selected) { base.TowerSelected(selected); horizontalArc.gameObject.SetActive(selected); verticalArc.gameObject.SetActive(selected); trajectoryLine.gameObject.SetActive(selected); UpdateTrajectory(); } protected override void OnDestroy() { horizontalArc.Value.RemoveListener(UpdateBarrelRotation); verticalArc.Value.RemoveListener(UpdateBarrelRotation); horizontalArc.Value.RemoveListener(SnapVerticalToHorizontal); } private void UpdateBarrelRotation(float unused) => UpdateBarrelRotation(); // Rotate barrel to match rotation private void UpdateBarrelRotation() { barrel.transform.localRotation = Quaternion.Euler(-verticalArc.Value, horizontalArc.Value, 0f); } private void SnapVerticalToHorizontal(float horizontalAngle) { verticalArc.transform.rotation = Quaternion.Euler(verticalArc.transform.rotation.eulerAngles.x, horizontalAngle, verticalArc.transform.rotation.eulerAngles.z); } private void UpdateTrajectory(float unused) => UpdateTrajectory(); private void UpdateTrajectory() { foreach (var ghost in ghosts) { ghost.SetActive(false); } if (!this.selected) 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, wallMask)) break; pointsInTrajectory.Add(hit.point); dir = Vector3.Reflect(dir, hit.normal); origin = hit.point; } trajectoryLine.positionCount = pointsInTrajectory.Count; trajectoryLine.SetPositions(pointsInTrajectory.ToArray()); // Build trajectory for (int i = 0; i < pointsInTrajectory.Count - 1; i++) { Vector3 point1 = pointsInTrajectory[i]; Vector3 point2 = pointsInTrajectory[i + 1]; Vector3 trajDir = (point2 - point1).normalized; float dist = Vector3.Distance(point1, point2); for (float j = 0; j < dist; j += spaceBetweenGhosts) { Vector3 ghostPos = point1 + trajDir * j; // Use object pool or spawn new if (ghosts.Count >= trajectoryObjectPoolSize) { ghosts[poolIdx].transform.position = ghostPos; ghosts[poolIdx].SetActive(true); poolIdx = (poolIdx + 1) % trajectoryObjectPoolSize; } else { var ghost = Instantiate(ghostPrefab); ghost.transform.position = ghostPos; ghost.transform.parent = transform; ghosts.Add(ghost); } } } } }