219 lines
8.1 KiB
C#
219 lines
8.1 KiB
C#
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<Animator>();
|
||
}
|
||
|
||
if (audioSource == null)
|
||
{
|
||
audioSource = GetComponent<AudioSource>();
|
||
}
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
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<PlayerGun>();
|
||
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<PlayerMovement>()?.SnapToAim(attackCooldown);
|
||
}
|
||
}
|
||
|
||
if (audioSource != null && punchSound != null)
|
||
{
|
||
audioSource.PlayOneShot(punchSound);
|
||
}
|
||
|
||
// Запуск анимации удара
|
||
if (animator != null && !string.IsNullOrEmpty(attackTrigger))
|
||
{
|
||
animator.SetTrigger(attackTrigger);
|
||
}
|
||
|
||
Debug.Log("Игрок наносит рукопашный удар!");
|
||
|
||
HashSet<MutantHealth> damagedEnemies = new HashSet<MutantHealth>();
|
||
|
||
// 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<MutantHealth>();
|
||
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<MutantHealth>();
|
||
if (mutantHealth != null && !damagedEnemies.Contains(mutantHealth))
|
||
{
|
||
damagedEnemies.Add(mutantHealth);
|
||
ApplyDamage(mutantHealth, hit.ClosestPoint(attackCenter));
|
||
}
|
||
}
|
||
|
||
// 4. Метод В: Прямая проверка расстояния в секторе обзора (если мутант в упор бьет игрока)
|
||
if (damagedEnemies.Count == 0)
|
||
{
|
||
var allMutants = FindObjectsByType<MutantHealth>(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($"<color=green>Удар попал по мутанту: {target.name}! Нанесено {attackDamage} урона.</color>");
|
||
|
||
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<BlindMutantAI>();
|
||
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);
|
||
}
|
||
}
|