425 lines
13 KiB
C#
425 lines
13 KiB
C#
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<Player>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ôîðñ-ïåðåâîä ïóøêè â ñîñòîÿíèå "ãîòîâà". Ñíèìàåò çàâèñàíèå ïåðåçàðÿäêè,
|
|
/// îñòàíàâëèâàåò âèçóàëüíóþ ïîçó è âîçâðàùàåò ñòâîë/÷àñòèöû ê äåôîëòó.
|
|
/// </summary>
|
|
/// <param name="instant">true — ìîìåíòàëüíî âåðíóòü ïîçó; false — äàòü êîðóòèíå çàâåðøèòüñÿ ïëàâíî</param>
|
|
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 è ïëàâíî îòêàòèò ïîçó
|
|
}
|
|
}
|