using System.Collections; using UnityEngine; public class Turret : MonoBehaviour { [SerializeField] Transform turretBase; // horizontal rotation [SerializeField] Transform cannon; // vertical rotation [SerializeField] Transform[] barrels; [SerializeField] float rotationSpeed = 5f; [SerializeField] float turretRange = 10f; [SerializeField] float fireRate = 1f; [SerializeField] int damage = 10; [SerializeField] bool isEnabled = true; [SerializeField] float smoothTime = 0.2f; [SerializeField] float projectileSpeed = 30f; [SerializeField] GameObject hitVFXPrefab; private Quaternion baseStartRot; private Quaternion cannonStartRot; float baseYawVelocity; float cannonPitchVelocity; Transform target; float fireCooldown; void Awake() { baseStartRot = turretBase.localRotation; cannonStartRot = cannon.localRotation; } void Update() { if (!isEnabled) return; FindTarget(); if (target == null) { ResetTurret(); return; } RotateTurret(); HandleShoot(); } void FindTarget() { Collider[] hits = Physics.OverlapSphere(transform.position, turretRange); float closestDist = Mathf.Infinity; Transform closest = null; foreach (var hit in hits) { EnemyHealth enemy = hit.GetComponent(); if (enemy == null) continue; float dist = Vector3.Distance(transform.position, hit.transform.position); if (dist < closestDist) { closestDist = dist; closest = hit.transform; } } target = closest; } void RotateTurret() { Vector3 predicted = PredictTargetPosition(); Vector3 dir = predicted - turretBase.position; // Horizontal rotation Vector3 flatDir = new Vector3(dir.x, 0f, dir.z); float targetYaw = Quaternion.LookRotation(flatDir).eulerAngles.y; float newYaw = Mathf.SmoothDampAngle( turretBase.eulerAngles.y, targetYaw, ref baseYawVelocity, smoothTime ); turretBase.rotation = Quaternion.Euler(0f, newYaw, 0f); // Vertical rotation float targetPitch = Quaternion.LookRotation(dir).eulerAngles.x; float newPitch = Mathf.SmoothDampAngle( cannon.localEulerAngles.x, targetPitch, ref cannonPitchVelocity, smoothTime ); cannon.localRotation = Quaternion.Euler(newPitch, 0f, 0f); } void HandleShoot() { fireCooldown -= Time.deltaTime; if (fireCooldown > 0f) return; fireCooldown = 1f / fireRate; // 1. Play muzzle flashes on all barrels foreach (var barrel in barrels) { var flash = barrel.GetComponentInChildren(); flash?.Play(); StartCoroutine(BarrelRecoil(barrel)); } // 3. Raycast from cannon forward (or predicted aim point) RaycastHit hit; Vector3 fireOrigin = cannon.position; Vector3 predicted = PredictTargetPosition(); Vector3 fireDirection = (predicted - fireOrigin).normalized; if (Physics.Raycast(fireOrigin, fireDirection, out hit, turretRange)) { // 4. Spawn hit VFX if (hitVFXPrefab != null) Instantiate(hitVFXPrefab, hit.point, Quaternion.identity); // 5. Apply damage EnemyHealth enemyHealth = hit.transform.GetComponent(); enemyHealth?.TakeDamage(damage); } } public void Activate() { isEnabled = true; } public void Deactivate() { isEnabled = false; target = null; } Vector3 PredictTargetPosition() { Rigidbody rb = target.GetComponent(); if (rb == null) return target.position; Vector3 targetPos = target.position; Vector3 targetVel = rb.linearVelocity; Vector3 dir = targetPos - cannon.position; float distance = dir.magnitude; float timeToHit = distance / projectileSpeed; return targetPos + targetVel * timeToHit; } void ResetTurret() { // Smoothly return to idle rotation float smooth = rotationSpeed / 5 * Time.deltaTime; turretBase.localRotation = Quaternion.Lerp( turretBase.localRotation, baseStartRot, smooth ); cannon.localRotation = Quaternion.Lerp( cannon.localRotation, cannonStartRot, smooth ); } IEnumerator BarrelRecoil(Transform barrel) { Vector3 startPos = barrel.localPosition; Vector3 recoilPos = startPos + new Vector3(0f, 0f, -0.2f); // adjust recoil distance float t = 0f; float recoilTime = 0.05f; // Move backward while (t < 1f) { t += Time.deltaTime / recoilTime; barrel.localPosition = Vector3.Lerp(startPos, recoilPos, t); yield return null; } // Move forward t = 0f; while (t < 1f) { t += Time.deltaTime / recoilTime; barrel.localPosition = Vector3.Lerp(recoilPos, startPos, t); yield return null; } } }