Kursach_StealthAction/Assets/Scripts/PlayerGun.cs

492 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerGun : MonoBehaviour
{
[Header("Параметры стрельбы")]
public float damage = 35f; // Урон за одно попадание
public float fireRate = 0.25f; // Минимальная задержка между выстрелами (сек)
public float maxRange = 60f; // Максимальная дистанция поражения
public float noiseRadius = 30f; // Радиус шума выстрела (слепые мутанты услышат)
public bool isAutomatic = false; // Одиночный или автоматический огонь
[Header("Боезапас")]
public int currentAmmo = 7; // Патронов в магазине сейчас
public int magSize = 7; // Вместимость одного магазина
public int reserveAmmo = 21; // Патронов в резерве
public int maxReserveAmmo = 56; // Максимум патронов в резерве
public float reloadDuration = 1.6f; // Длительность перезарядки (сек)
[Header("Ссылки на точки и визуал")]
public Transform muzzlePoint; // Точка вылета пули / вспышки
public GameObject hitEffectPrefab; // Префаб искр / пыли при попадании
public Light muzzleFlashLight; // Свет вспышки (мигает при выстреле)
public Transform gunModelTransform; // Модель оружия (для анимации отдачи)
[Header("Анимация и эффекты оружия (Nokobot M1911)")]
public Animator gunAnimator; // Аниматор самого пистолета (затвор M1911)
public Animator playerAnimator; // Аниматор персонажа (стойка, выстрел, перезарядка)
public GameObject muzzleFlashPrefab; // Эффект дульной вспышки (Nokobot)
public GameObject casingPrefab; // Префаб гильзы (45ACP)
public Transform casingExitPoint; // Точка выброса гильзы
[Header("Экипировка оружия")]
[Tooltip("Находится ли оружие сейчас в руках")]
public bool isGunEquipped = false; // По умолчанию убрано в кобуру (в руках кулаки)
public bool hasGun = true; // Есть ли у игрока огнестрел
public AudioClip equipSound; // Звук доставания оружия
public AudioClip holsterSound; // Звук убирания оружия
[Header("Аудио")]
public AudioSource audioSource;
public AudioClip shootSound;
public AudioClip reloadSound;
public AudioClip emptyClickSound;
[Header("Состояние")]
private bool isReloading = false;
private float nextFireTime = 0f;
private Camera mainCamera;
private Vector3 originalGunLocalPos;
// Свойства для UI и других скриптов
public int CurrentAmmo => currentAmmo;
public int ReserveAmmo => reserveAmmo;
public int MagSize => magSize;
public bool IsReloading => isReloading;
public bool IsEquipped => isGunEquipped;
public bool HasGun => hasGun;
void Awake()
{
if (playerAnimator == null)
{
playerAnimator = GetComponentInChildren<Animator>();
}
}
void Start()
{
mainCamera = Camera.main;
if (playerAnimator == null)
{
playerAnimator = GetComponentInChildren<Animator>();
}
if (audioSource == null)
{
audioSource = GetComponent<AudioSource>();
if (audioSource == null)
{
audioSource = gameObject.AddComponent<AudioSource>();
audioSource.playOnAwake = false;
audioSource.spatialBlend = 0.3f;
}
}
if (gunModelTransform != null)
{
originalGunLocalPos = gunModelTransform.localPosition;
}
if (muzzleFlashLight != null)
{
muzzleFlashLight.enabled = false;
}
// Применяем начальную видимость оружия (убрано или в руках)
UpdateGunVisibility();
}
void Update()
{
if (Time.timeScale == 0f) return;
if (mainCamera == null) mainCamera = Camera.main;
HandleInput();
}
private void HandleInput()
{
if (Keyboard.current == null || Mouse.current == null) return;
// 1. Клавиша [1] - Убрать огнестрел (перейти на кулаки / рукопашный бой)
if (Keyboard.current.digit1Key.wasPressedThisFrame)
{
SetEquipped(false);
}
// 2. Клавиша [2] - Достать огнестрел (если уже в руках — убрать как переключатель)
if (Keyboard.current.digit2Key.wasPressedThisFrame)
{
if (hasGun)
{
SetEquipped(!isGunEquipped);
}
else
{
Debug.Log("[Оружие] Огнестрельное оружие еще не найдено!");
}
}
// 3. Клавиша [X] - Быстро убрать / достать оружие (Toggle)
if (Keyboard.current.xKey.wasPressedThisFrame)
{
if (hasGun)
{
ToggleEquip();
}
}
// Если оружие не в руках (убрано), огонь и перезарядка блокируются!
// (ЛКМ в этом случае используется скриптом PlayerAttack для рукопашного удара)
if (!isGunEquipped) return;
// 4. Прицеливание (ПКМ) при оружии в руках
if (Mouse.current.rightButton.isPressed)
{
FaceAimTarget(0.15f);
}
// 5. Выстрел (ЛКМ)
bool shootInput = isAutomatic
? Mouse.current.leftButton.isPressed
: Mouse.current.leftButton.wasPressedThisFrame;
if (shootInput)
{
FaceAimTarget(0.6f);
TryShoot();
}
// 6. Перезарядка (Клавиша R)
if (Keyboard.current.rKey.wasPressedThisFrame)
{
TryStartReload();
}
}
/// <summary>
/// Активирует быстрый и плавный поворот персонажа к направлению прицела камеры (перекрестия)
/// и активирует стрейф-режим перемещения через PlayerMovement.
/// </summary>
public void FaceAimTarget(float duration = 0.5f)
{
PlayerMovement movement = GetComponent<PlayerMovement>();
if (movement != null)
{
movement.SnapToAim(duration);
}
}
/// <summary>
/// Переключение взятия/убирания оружия
/// </summary>
public void ToggleEquip()
{
SetEquipped(!isGunEquipped);
}
/// <summary>
/// Взять огнестрел в руки или убрать в кобуру
/// </summary>
public void SetEquipped(bool equip)
{
if (!hasGun && equip)
{
Debug.Log("[Оружие] Огнестрел недоступен!");
return;
}
if (isGunEquipped == equip) return;
// Если шла перезарядка и мы убираем оружие — прерываем перезарядку
if (!equip && isReloading)
{
StopAllCoroutines();
isReloading = false;
if (muzzleFlashLight != null) muzzleFlashLight.enabled = false;
if (gunModelTransform != null) gunModelTransform.localPosition = originalGunLocalPos;
}
isGunEquipped = equip;
UpdateGunVisibility();
// Проигрываем звук экипировки/убирания, если назначен
if (audioSource != null)
{
if (isGunEquipped && equipSound != null)
audioSource.PlayOneShot(equipSound);
else if (!isGunEquipped && holsterSound != null)
audioSource.PlayOneShot(holsterSound);
}
Debug.Log(isGunEquipped
? "<color=green>[Оружие] Огнестрел взят в руки! (ЛКМ - огонь, R - перезарядка, [1]/[X] - убрать)</color>"
: "<color=cyan>[Оружие] Огнестрел убран в кобуру. В руках кулаки (ЛКМ / F - рукопашный бой, [2] - достать оружие)</color>");
}
/// <summary>
/// Включение/выключение визуала модели оружия
/// </summary>
public void UpdateGunVisibility()
{
if (gunModelTransform != null)
{
gunModelTransform.gameObject.SetActive(isGunEquipped);
}
if (playerAnimator != null)
{
playerAnimator.SetBool("HasGun", isGunEquipped);
if (playerAnimator.layerCount > 1)
{
playerAnimator.SetLayerWeight(1, isGunEquipped ? 1f : 0f);
}
}
}
public void TryShoot()
{
if (!isGunEquipped) return;
if (isReloading) return;
if (Time.time < nextFireTime) return;
// Если магазин пуст
if (currentAmmo <= 0)
{
PlayEmptySound();
nextFireTime = Time.time + 0.3f;
// Автоматически предложить перезарядку, если есть патроны в запасе
if (reserveAmmo > 0)
{
TryStartReload();
}
return;
}
// Производим выстрел
nextFireTime = Time.time + fireRate;
currentAmmo--;
ExecuteShot();
}
private void ExecuteShot()
{
// Поворачиваем персонажа лицом к прицелу перед вычислением траектории выстрела
FaceAimTarget(0.6f);
// Звук выстрела
if (audioSource != null && shootSound != null)
{
audioSource.PlayOneShot(shootSound);
}
// Вспышка и отдача
StartCoroutine(MuzzleFlashRoutine());
StartCoroutine(RecoilRoutine());
// Анимация затвора пистолета M1911 (Nokobot)
if (gunAnimator != null)
{
gunAnimator.SetTrigger("Fire");
}
// Анимация персонажа (отдача при выстреле)
if (playerAnimator != null)
{
playerAnimator.SetTrigger("Shoot");
}
// Спавн визуальной дульной вспышки
if (muzzleFlashPrefab != null && muzzlePoint != null)
{
GameObject flash = Instantiate(muzzleFlashPrefab, muzzlePoint.position, muzzlePoint.rotation);
Destroy(flash, 0.4f);
}
// Выброс стреляной гильзы 45ACP
if (casingPrefab != null && casingExitPoint != null)
{
GameObject casing = Instantiate(casingPrefab, casingExitPoint.position, casingExitPoint.rotation);
Rigidbody rb = casing.GetComponent<Rigidbody>();
if (rb != null)
{
Vector3 ejectDir = casingExitPoint.right * 1.5f + casingExitPoint.up * 1.2f + Random.insideUnitSphere * 0.3f;
rb.AddForce(ejectDir, ForceMode.Impulse);
rb.AddTorque(Random.insideUnitSphere * 15f, ForceMode.Impulse);
}
Destroy(casing, 3f);
}
// Луч выстрела из камеры вперед через перекрестие
Vector3 rayOrigin = mainCamera != null ? mainCamera.transform.position : transform.position + Vector3.up * 1.5f;
Vector3 rayDir = mainCamera != null ? mainCamera.transform.forward : transform.forward;
Vector3 hitPoint = rayOrigin + rayDir * maxRange;
if (Physics.Raycast(rayOrigin, rayDir, out RaycastHit hit, maxRange))
{
hitPoint = hit.point;
// Нанесение урона врагу
MutantHealth mutant = hit.collider.GetComponentInParent<MutantHealth>();
if (mutant != null)
{
mutant.TakeDamage(damage, transform);
Debug.Log($"<color=orange>[Оружие] Попадание по {mutant.name}! Нанесено {damage} урона.</color>");
}
// Эффект попадания
if (hitEffectPrefab != null)
{
Instantiate(hitEffectPrefab, hit.point, Quaternion.LookRotation(hit.normal));
}
}
// Отрисовка трассера выстрела
DrawTracer(muzzlePoint != null ? muzzlePoint.position : transform.position + transform.forward * 0.5f + Vector3.up * 1.2f, hitPoint);
// Шум выстрела привлекает всех ближайших мутантов!
AlertMutants(noiseRadius);
// Также порождаем эхо-волну (если на игроке есть EchoTrigger / визуализатор звука)
TriggerEchoWave();
}
private void AlertMutants(float radius)
{
Collider[] colliders = Physics.OverlapSphere(transform.position, radius);
foreach (Collider col in colliders)
{
BlindMutantAI mutant = col.GetComponentInParent<BlindMutantAI>();
if (mutant != null)
{
mutant.HearSound(transform.position);
}
}
}
private void TriggerEchoWave()
{
EchoTrigger echo = GetComponent<EchoTrigger>();
if (echo != null)
{
// Вызов генерации волны эхолокации при громком выстреле
echo.TriggerExternalWave(transform.position, noiseRadius, 2.5f);
}
}
public void TryStartReload()
{
if (!isGunEquipped) return; // Нельзя перезаряжать убранное оружие
if (isReloading) return;
if (currentAmmo >= magSize) return; // Магазин уже полон
if (reserveAmmo <= 0)
{
Debug.Log("[Оружие] Нет запасных патронов!");
return;
}
StartCoroutine(ReloadRoutine());
}
private IEnumerator ReloadRoutine()
{
isReloading = true;
Debug.Log("[Оружие] Перезарядка...");
if (audioSource != null && reloadSound != null)
{
audioSource.PlayOneShot(reloadSound);
}
// Анимация перезарядки персонажа
if (playerAnimator != null)
{
playerAnimator.SetTrigger("Reload");
}
yield return new WaitForSeconds(reloadDuration);
int needed = magSize - currentAmmo;
int toLoad = Mathf.Min(needed, reserveAmmo);
currentAmmo += toLoad;
reserveAmmo -= toLoad;
isReloading = false;
Debug.Log($"[Оружие] Перезарядка завершена! Патроны: {currentAmmo}/{reserveAmmo}");
}
public bool AddAmmo(int amount)
{
if (amount <= 0) return false;
if (reserveAmmo >= maxReserveAmmo) return false;
reserveAmmo = Mathf.Min(reserveAmmo + amount, maxReserveAmmo);
Debug.Log($"<color=cyan>[Патроны] Подобрано {amount} патронов. Всего в запасе: {reserveAmmo}</color>");
return true;
}
private void PlayEmptySound()
{
if (audioSource != null && emptyClickSound != null)
{
audioSource.PlayOneShot(emptyClickSound);
}
else
{
Debug.Log("[Оружие] *Сухой щелчок* Магазин пуст!");
}
}
private IEnumerator MuzzleFlashRoutine()
{
if (muzzleFlashLight != null)
{
muzzleFlashLight.enabled = true;
yield return new WaitForSeconds(0.04f);
muzzleFlashLight.enabled = false;
}
}
private IEnumerator RecoilRoutine()
{
if (gunModelTransform != null)
{
gunModelTransform.localPosition = originalGunLocalPos - Vector3.forward * 0.08f + Vector3.up * 0.02f;
float elapsed = 0f;
float duration = 0.12f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
gunModelTransform.localPosition = Vector3.Lerp(gunModelTransform.localPosition, originalGunLocalPos, elapsed / duration);
yield return null;
}
gunModelTransform.localPosition = originalGunLocalPos;
}
}
private void DrawTracer(Vector3 start, Vector3 end)
{
GameObject tracer = new GameObject("BulletTracer");
LineRenderer lr = tracer.AddComponent<LineRenderer>();
lr.startWidth = 0.035f;
lr.endWidth = 0.01f;
lr.positionCount = 2;
lr.SetPosition(0, start);
lr.SetPosition(1, end);
Material mat = new Material(Shader.Find("Sprites/Default"));
mat.color = new Color(1f, 0.9f, 0.4f, 0.9f);
lr.material = mat;
Destroy(tracer, 0.06f);
}
}