using System.Collections.Generic; using UnityEngine; using UnityEngine.InputSystem; public class PlayerAttack : MonoBehaviour { [Header("Параметры атаки")] public float attackDamage = 25f; public float attackCooldown = 0.8f; public float attackRange = 2.8f; // Достаточная дистанция, превышающая атаку мутанта (1.8м) public float attackRadius = 1.4f; // Радиус зоны поражения удара public float attackNoiseRadius = 10f; [Header("Эффекты (опционально)")] public GameObject hitEffectPrefab; [Header("Аудио")] public AudioSource audioSource; public AudioClip punchSound; public AudioClip hitSound; [Header("Управление")] public bool allowLeftClick = true; // Разрешить ЛКМ для удара кулаком, когда огнестрел убран [Header("Анимация")] public Animator animator; public string attackTrigger = "Attack"; private float nextAttackTime = 0f; private Camera mainCamera; void Start() { mainCamera = Camera.main; if (animator == null) { animator = GetComponentInChildren(); } if (audioSource == null) { audioSource = GetComponent(); } } void Update() { if (Time.timeScale == 0f) return; bool attackPressed = false; // 1. Быстрый рукопашный удар на клавишу F (доступен всегда) if (Keyboard.current != null && Keyboard.current.fKey.wasPressedThisFrame) { attackPressed = true; } // 2. Рукопашный удар на ЛКМ (когда огнестрел убран в кобуру или отсутствует) else if (allowLeftClick && Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame) { PlayerGun gun = GetComponent(); if (gun == null || !gun.IsEquipped) { attackPressed = true; } } if (attackPressed && Time.time >= nextAttackTime) { PerformAttack(); } } private void PerformAttack() { nextAttackTime = Time.time + attackCooldown; if (mainCamera == null) mainCamera = Camera.main; // 1. Определяем направление атаки по прицелу камеры Vector3 attackDir = transform.forward; if (mainCamera != null) { Vector3 camFwd = mainCamera.transform.forward; camFwd.y = 0f; if (camFwd.sqrMagnitude > 0.001f) { attackDir = camFwd.normalized; // Быстро и плавно поворачиваем персонажа лицом к направлению атаки (прицелу) GetComponent()?.SnapToAim(attackCooldown); } } if (audioSource != null && punchSound != null) { audioSource.PlayOneShot(punchSound); } // Запуск анимации удара if (animator != null && !string.IsNullOrEmpty(attackTrigger)) { animator.SetTrigger(attackTrigger); } Debug.Log("Игрок наносит рукопашный удар!"); HashSet damagedEnemies = new HashSet(); // 2. Метод А: Проверка по лучу прицела (SphereCast из камеры) if (mainCamera != null) { Ray aimRay = new Ray(mainCamera.transform.position, mainCamera.transform.forward); float maxCamDist = 12f; // дистанция луча от камеры (камера позади игрока на ~4м) RaycastHit[] aimHits = Physics.SphereCastAll(aimRay, 0.4f, maxCamDist); foreach (var hit in aimHits) { if (hit.collider.gameObject == gameObject) continue; MutantHealth target = hit.collider.GetComponentInParent(); if (target != null && !damagedEnemies.Contains(target)) { // Проверяем, что мутант находится в пределах досягаемости игрока float distFromPlayer = Vector3.Distance(transform.position, target.transform.position); if (distFromPlayer <= attackRange + 0.5f) { damagedEnemies.Add(target); ApplyDamage(target, hit.point); } } } } // 3. Метод Б: Зона поражения перед персонажем (OverlapSphere) Vector3 attackCenter = transform.position + attackDir * (attackRange * 0.5f) + Vector3.up * 1.0f; Collider[] hits = Physics.OverlapSphere(attackCenter, attackRadius); foreach (Collider hit in hits) { if (hit.gameObject == gameObject) continue; MutantHealth mutantHealth = hit.GetComponentInParent(); if (mutantHealth != null && !damagedEnemies.Contains(mutantHealth)) { damagedEnemies.Add(mutantHealth); ApplyDamage(mutantHealth, hit.ClosestPoint(attackCenter)); } } // 4. Метод В: Прямая проверка расстояния в секторе обзора (если мутант в упор бьет игрока) if (damagedEnemies.Count == 0) { var allMutants = FindObjectsByType(FindObjectsInactive.Exclude); foreach (var mutant in allMutants) { if (mutant == null || mutant.IsDead) continue; Vector3 toMutant = mutant.transform.position - transform.position; float dist = toMutant.magnitude; toMutant.y = 0f; // Если мутант в радиусе ближнего боя и перед игроком (угол менее 80 градусов) if (dist <= attackRange && Vector3.Dot(attackDir, toMutant.normalized) > 0.15f) { damagedEnemies.Add(mutant); ApplyDamage(mutant, mutant.transform.position + Vector3.up); } } } if (damagedEnemies.Count == 0) { Debug.Log("Удар промахнулся (врагов в зоне досягаемости нет)."); } // Шум от рукопашного боя привлекает ближайших мутантов MakeAttackNoise(); } private void ApplyDamage(MutantHealth target, Vector3 hitPoint) { target.TakeDamage(attackDamage, transform); Debug.Log($"Удар попал по мутанту: {target.name}! Нанесено {attackDamage} урона."); if (audioSource != null && hitSound != null) { audioSource.PlayOneShot(hitSound); } if (hitEffectPrefab != null) { Instantiate(hitEffectPrefab, hitPoint, Quaternion.identity); } } private void MakeAttackNoise() { Collider[] colliders = Physics.OverlapSphere(transform.position, attackNoiseRadius); foreach (Collider col in colliders) { BlindMutantAI mutant = col.GetComponentInParent(); if (mutant != null) { mutant.HearSound(transform.position); } } } private void OnDrawGizmosSelected() { Vector3 dir = transform.forward; if (mainCamera != null) { Vector3 camFwd = mainCamera.transform.forward; camFwd.y = 0f; if (camFwd.sqrMagnitude > 0.001f) dir = camFwd.normalized; } Vector3 attackCenter = transform.position + dir * (attackRange * 0.5f) + Vector3.up * 1.0f; Gizmos.color = Color.red; Gizmos.DrawWireSphere(attackCenter, attackRadius); } }