using System; using UnityEngine; [RequireComponent(typeof(SphereCollider))] public class CaptureZone : MonoBehaviour { public enum Owner { Neutral, Player, Enemy } public enum CaptureState { Neutral, CapturingByPlayer, CapturedByPlayer, CapturingByEnemy, CapturedByEnemy, Contested } [Header("Identity")] public string zoneName = "Zone"; [Header("Capture")] public Owner currentOwner = Owner.Neutral; [Range(0f, 1f)] public float captureProgress = 0.5f; public float captureTime = 5f; public int playersInside; public int enemiesInside; public int pointsPerSecond = 5; [Header("Visuals")] public Renderer zoneRenderer; public Color neutralColor = new Color(0.55f, 0.55f, 0.55f, 0.45f); public Color playerColor = new Color(0.1f, 0.35f, 1f, 0.55f); public Color enemyColor = new Color(1f, 0.12f, 0.08f, 0.55f); public Color contestedColor = new Color(1f, 0.82f, 0.05f, 0.65f); public event Action onCapturedByPlayer; public event Action onCapturedByEnemy; private Owner lastNotifiedOwner; private Vector3 baseScale; private float pulseTimer; private float scoreTimer; private void Awake() { SphereCollider trigger = GetComponent(); trigger.isTrigger = true; lastNotifiedOwner = currentOwner; baseScale = transform.localScale; ApplyOwnerProgress(currentOwner); UpdateVisual(); } private void Update() { UpdateCaptureProgress(); TickScoreIncome(); UpdateVisual(); UpdateCapturePulse(); } private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { playersInside++; } else if (other.CompareTag("Enemy")) { enemiesInside++; } } private void OnTriggerExit(Collider other) { if (other.CompareTag("Player")) { playersInside = Mathf.Max(0, playersInside - 1); } else if (other.CompareTag("Enemy")) { enemiesInside = Mathf.Max(0, enemiesInside - 1); } } public float GetProgress() { return captureProgress; } public Owner GetOwner() { return currentOwner; } public string GetStateLabel() { switch (GetCaptureState()) { case CaptureState.CapturingByPlayer: return "Capturing by Player"; case CaptureState.CapturedByPlayer: return "Player"; case CaptureState.CapturingByEnemy: return "Capturing by Enemy"; case CaptureState.CapturedByEnemy: return "Enemy"; case CaptureState.Contested: return "Contested"; default: return "Neutral"; } } public CaptureState GetCaptureState() { bool hasPlayer = playersInside > 0; bool hasEnemy = enemiesInside > 0; if (hasPlayer && hasEnemy) { return CaptureState.Contested; } if (hasPlayer && currentOwner != Owner.Player) { return CaptureState.CapturingByPlayer; } if (hasEnemy && currentOwner != Owner.Enemy) { return CaptureState.CapturingByEnemy; } if (currentOwner == Owner.Player) { return CaptureState.CapturedByPlayer; } if (currentOwner == Owner.Enemy) { return CaptureState.CapturedByEnemy; } return CaptureState.Neutral; } public void ForceSetOwner(Owner owner) { SetOwner(owner, true); } private void UpdateCaptureProgress() { bool hasPlayer = playersInside > 0; bool hasEnemy = enemiesInside > 0; // Спорная зона замораживает прогресс, чтобы игрок видел механику contest. if (hasPlayer == hasEnemy) { return; } float step = Time.deltaTime / Mathf.Max(0.1f, captureTime); if (hasPlayer) { captureProgress = Mathf.MoveTowards(captureProgress, 1f, step); if (Mathf.Approximately(captureProgress, 1f)) { SetOwner(Owner.Player, false); } } else if (hasEnemy) { captureProgress = Mathf.MoveTowards(captureProgress, 0f, step); if (Mathf.Approximately(captureProgress, 0f)) { SetOwner(Owner.Enemy, false); } } } private void TickScoreIncome() { if (currentOwner == Owner.Neutral || ResourceManager.Instance == null) { scoreTimer = 0f; return; } scoreTimer += Time.deltaTime; if (scoreTimer < 1f) { return; } int ticks = Mathf.FloorToInt(scoreTimer); scoreTimer -= ticks; int amount = pointsPerSecond * ticks; if (currentOwner == Owner.Player) { ResourceManager.Instance.AddPlayerPoints(amount); } else if (currentOwner == Owner.Enemy) { ResourceManager.Instance.AddEnemyPoints(amount); } } private void SetOwner(Owner owner, bool forceNotify) { if (currentOwner == owner && !forceNotify) { return; } Owner previousOwner = currentOwner; currentOwner = owner; ApplyOwnerProgress(owner); pulseTimer = 0.25f; if (previousOwner == owner && !forceNotify) { return; } if (lastNotifiedOwner != currentOwner || forceNotify) { lastNotifiedOwner = currentOwner; if (currentOwner == Owner.Player) { onCapturedByPlayer?.Invoke(this); } else if (currentOwner == Owner.Enemy) { onCapturedByEnemy?.Invoke(this); } } } private void ApplyOwnerProgress(Owner owner) { if (owner == Owner.Player) { captureProgress = 1f; } else if (owner == Owner.Enemy) { captureProgress = 0f; } else if (Mathf.Approximately(captureProgress, 0f) || Mathf.Approximately(captureProgress, 1f)) { captureProgress = 0.5f; } } private void UpdateVisual() { if (zoneRenderer == null) { return; } Color color = GetVisualColor(); zoneRenderer.material.color = color; } private Color GetVisualColor() { CaptureState state = GetCaptureState(); if (state == CaptureState.Contested || state == CaptureState.CapturingByPlayer || state == CaptureState.CapturingByEnemy) { float pulse = Mathf.Lerp(0.75f, 1f, Mathf.PingPong(Time.time * 3f, 1f)); return contestedColor * pulse; } if (currentOwner == Owner.Player) { return playerColor; } if (currentOwner == Owner.Enemy) { return enemyColor; } return neutralColor; } private void UpdateCapturePulse() { if (pulseTimer <= 0f) { transform.localScale = baseScale; return; } pulseTimer -= Time.deltaTime; float t = Mathf.Clamp01(pulseTimer / 0.25f); float scale = 1f + Mathf.Sin(t * Mathf.PI) * 0.12f; transform.localScale = baseScale * scale; } }