using UnityEngine; using System.Collections; public class Cannon : MonoBehaviour { [Header("Parts")] public Transform basePart; // Подставка (вращается по Y) public Transform barrel; // Дуло (вращается по X + отдача) [Header("Projectile")] public GameObject projectilePrefab; public float projectileSpeed = 20f; [Header("Reloading")] public float reloadTime = 2f; private bool isReloading = false; [Header("Rotation")] public float rotationSpeed = 60f; // градусов в секунду private float currentLaunchAngle = 10f; public float coreSpeed = 1f; public ParticleSystem[] shotParticles; // частицы из дула public ParticleSystem smokeParticles; [Header("Command")] public PirateCommand assignedCommand; public Transform barrelMuzzle; public Transform[] reloadPoints; public Transform reloadTarget; public bool IsReady => !isReloading; private Quaternion baseInitialRotation; private Quaternion barrelInitialRotation; private Vector3 barrelInitialLocalPosition; private Vector3 barrelInitialLocalScale; // NEW private Coroutine _reloadPoseCo; // NEW private int playerId = 1; private void Awake() { baseInitialRotation = basePart.rotation; barrelInitialRotation = barrel.localRotation; barrelInitialLocalPosition = barrel.localPosition; barrelInitialLocalScale = barrel.localScale; // NEW var player = transform.root.GetComponent(); if (player != null) playerId = player.PlayerId; } public void ShootAtTransform(Transform target, System.Action onImpact) { if (isReloading) return; StartCoroutine(ShootRoutine(target, onImpact)); } private IEnumerator ShootRoutine(Transform target, System.Action onImpact) { // 1. Поворот yield return StartCoroutine(RotateBaseToTarget(target.position)); yield return StartCoroutine(AimBarrelAtTarget(target.position)); // 2. Анимация отдачи + выстрел yield return StartCoroutine(BarrelShootWithProjectile(target.position, onImpact)); // 4. Возврат в исходное положение yield return StartCoroutine(ReturnToDefaultPose()); // 3. Перезарядка yield return StartCoroutine(Reload()); } public void OnReloadComplete() { isReloading = false; // пушку можно стрелять снова } private IEnumerator BarrelShootWithProjectile(Vector3 targetPos, System.Action onImpact) { SoundManager.Instance.PlayShoot(playerId); Vector3 startPos = barrel.localPosition; Vector3 backPos = startPos - new Vector3(0, 0, 0.2f); Vector3 stretchPos = startPos + new Vector3(0, 0, 0.1f); Vector3 startScale = barrel.localScale; Vector3 squeezeScale = new Vector3(startScale.x * 1.8f, startScale.y * 1.8f, startScale.z * 0.5f); Vector3 stretchScale = new Vector3(startScale.x * 0.9f, startScale.y * 0.9f, startScale.z * 1.1f); float t = 0f; while (t < 1f) { barrel.localPosition = Vector3.Lerp(startPos, backPos, t); barrel.localScale = Vector3.Lerp(startScale, squeezeScale, t); t += Time.deltaTime * 10f; yield return null; } if (shotParticles != null && shotParticles.Length > 0) { foreach (var ps in shotParticles) { if (ps == null) continue; ps.transform.position = barrelMuzzle.position; ps.transform.rotation = barrelMuzzle.rotation; ps.Play(); } } if (EffectManager.Instance != null) { Vector3 spawnPos = basePart.position + basePart.right * -1.5f + Vector3.up * 4f; EffectManager.Instance.PlayShotText(spawnPos); } GameObject projectile = Instantiate(projectilePrefab, barrelMuzzle.position, barrelMuzzle.rotation); StartCoroutine(MoveProjectileArc(projectile.transform, targetPos, currentLaunchAngle, coreSpeed, onImpact)); t = 0f; while (t < 1f) { barrel.localPosition = Vector3.Lerp(backPos, stretchPos, t); barrel.localScale = Vector3.Lerp(squeezeScale, stretchScale, t); t += Time.deltaTime * 10f; yield return null; } t = 0f; while (t < 1f) { barrel.localPosition = Vector3.Lerp(stretchPos, startPos, t); barrel.localScale = Vector3.Lerp(stretchScale, startScale, t); t += Time.deltaTime * 10f; yield return null; } } private IEnumerator MoveProjectileArc( Transform proj, Vector3 target, float launchAngleDegrees, float timeScale, System.Action onImpact) { float g = Mathf.Abs(Physics.gravity.y); Vector3 start = proj.position; Vector3 planarStart = new Vector3(start.x, 0, start.z); Vector3 planarTarget = new Vector3(target.x, 0, target.z); float distance = Vector3.Distance(planarStart, planarTarget); float heightDifference = target.y - start.y; float launchAngle = launchAngleDegrees * Mathf.Deg2Rad; float denom = distance * Mathf.Tan(launchAngle) - heightDifference; if (denom <= 0.001f) { proj.position = target; onImpact?.Invoke(); Destroy(proj.gameObject); yield break; } float v2 = Mathf.Pow((1f / Mathf.Cos(launchAngle)) * Mathf.Sqrt( (0.5f * g * distance * distance) / denom), 2f); Vector3 direction = (planarTarget - planarStart).normalized; float v = Mathf.Sqrt(v2); float flightDuration = distance / (v * Mathf.Cos(launchAngle)); float elapsed = 0f; float hitThreshold = 0.2f; while (elapsed < flightDuration) { float t = elapsed; Vector3 moveXZ = direction * v * Mathf.Cos(launchAngle) * t; float moveY = v * Mathf.Sin(launchAngle) * t - 0.5f * g * t * t; proj.position = start + moveXZ + Vector3.up * moveY; if (Vector3.Distance(proj.position, target) <= hitThreshold) { proj.position = target; onImpact?.Invoke(); Destroy(proj.gameObject); yield break; } elapsed += Time.deltaTime * timeScale; yield return null; } proj.position = target; onImpact?.Invoke(); Destroy(proj.gameObject); } private IEnumerator ReturnToDefaultPose() { float t = 0f; float duration = 1f; Quaternion baseStartRot = basePart.rotation; Quaternion barrelStartRot = barrel.localRotation; Vector3 barrelStartPos = barrel.localPosition; Vector3 barrelStartScale = barrel.localScale; // NEW: запоминаем текущий scale while (t < 1f) { basePart.rotation = Quaternion.Slerp(baseStartRot, baseInitialRotation, t); barrel.localRotation = Quaternion.Slerp(barrelStartRot, barrelInitialRotation, t); barrel.localPosition = Vector3.Lerp(barrelStartPos, barrelInitialLocalPosition, t); barrel.localScale = Vector3.Lerp(barrelStartScale, barrelInitialLocalScale, t); // NEW: возвращаем scale t += Time.deltaTime / duration; yield return null; } basePart.rotation = baseInitialRotation; barrel.localRotation = barrelInitialRotation; barrel.localPosition = barrelInitialLocalPosition; barrel.localScale = barrelInitialLocalScale; // NEW: финальная установка scale } private IEnumerator RotateBaseToTarget(Vector3 targetPos) { while (true) { Vector3 flatTarget = new Vector3(targetPos.x, basePart.position.y, targetPos.z); Vector3 dir = (flatTarget - basePart.position).normalized; if (dir.sqrMagnitude < 0.0001f) yield break; Quaternion targetRot = Quaternion.LookRotation(dir); basePart.rotation = Quaternion.RotateTowards( basePart.rotation, targetRot, rotationSpeed * Time.deltaTime ); if (Quaternion.Angle(basePart.rotation, targetRot) < 0.5f) break; yield return null; } } private IEnumerator AimBarrelAtTarget(Vector3 targetPos) { Vector3 dir = targetPos - barrel.position; float distance = new Vector2(dir.x, dir.z).magnitude; float height = dir.y; float g = Mathf.Abs(Physics.gravity.y); float v = projectileSpeed; float v2 = v * v; float underSqrt = v2 * v2 - g * (g * distance * distance + 2 * height * v2); float angle; if (underSqrt >= 0) { float sqrtPart = Mathf.Sqrt(underSqrt); float lowAngle = Mathf.Atan((v2 - sqrtPart) / (g * distance)); float highAngle = Mathf.Atan((v2 + sqrtPart) / (g * distance)); angle = lowAngle * Mathf.Rad2Deg; } else { angle = 45f; } angle = Mathf.Clamp(angle, 10f, 70f); currentLaunchAngle = angle; Quaternion targetRot = Quaternion.Euler(-angle, 0, 0); while (true) { barrel.localRotation = Quaternion.RotateTowards( barrel.localRotation, targetRot, rotationSpeed * Time.deltaTime ); if (Quaternion.Angle(barrel.localRotation, targetRot) < 0.5f) break; yield return null; } } private IEnumerator Reload() { isReloading = true; _reloadPoseCo = StartCoroutine(BarrelReloadPose()); // NEW if (assignedCommand != null) { assignedCommand.AssignPirateToCannon(this); while (isReloading) yield return null; } else { yield return new WaitForSeconds(reloadTime); isReloading = false; } } private IEnumerator BarrelReloadPose() { Vector3 startScale = barrel.localScale; Vector3 reloadScale = new Vector3(startScale.x * 1.2f, startScale.y * 1.2f, startScale.z * 0.6f); Quaternion startRot = barrel.localRotation; Quaternion reloadRot = startRot * Quaternion.Euler(-45f, 0, 0); float t = 0f; float duration = 0.3f; if (smokeParticles != null) { smokeParticles.transform.rotation = Quaternion.identity; var main = smokeParticles.main; main.simulationSpeed = 0.5f; smokeParticles.Play(); } while (t < 1f) { barrel.localScale = Vector3.Lerp(startScale, reloadScale, t); barrel.localRotation = Quaternion.Slerp(startRot, reloadRot, t); t += Time.deltaTime / duration; yield return null; } float pulseSpeed = 10f; float pulseAmount = 0.05f; while (isReloading) { float pulse = 1f + Mathf.Sin(Time.time * pulseSpeed) * pulseAmount; barrel.localScale = reloadScale * pulse; barrel.localRotation = reloadRot; yield return null; } if (smokeParticles != null) smokeParticles.Stop(); t = 0f; while (t < 1f) { barrel.localScale = Vector3.Lerp(reloadScale, startScale, t); barrel.localRotation = Quaternion.Slerp(reloadRot, startRot, t); t += Time.deltaTime / duration; yield return null; } barrel.localScale = startScale; barrel.localRotation = startRot; _reloadPoseCo = null; // NEW } public Transform GetNearestReloadPoint(Vector3 fromPos) { Transform best = null; float bestDist = Mathf.Infinity; foreach (var p in reloadPoints) { float d = Vector3.Distance(fromPos, p.position); if (d < bestDist) { bestDist = d; best = p; } } return best; } /// /// Форс-перевод пушки в состояние "готова". Снимает зависание перезарядки, /// останавливает визуальную позу и возвращает ствол/частицы к дефолту. /// /// true — моментально вернуть позу; false — дать корутине завершиться плавно public void ForceReloadReset(bool instant = true) { // снять зависание ожидания загрузчика isReloading = false; if (instant) { // остановить позу перезарядки, если крутится if (_reloadPoseCo != null) { StopCoroutine(_reloadPoseCo); _reloadPoseCo = null; } if (smokeParticles != null) smokeParticles.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); // вернуть геометрию/позу в исходное состояние basePart.rotation = baseInitialRotation; barrel.localRotation = barrelInitialRotation; barrel.localPosition = barrelInitialLocalPosition; barrel.localScale = barrelInitialLocalScale; } // если instant == false, BarrelReloadPose заметит isReloading=false и плавно откатит позу } }