468 lines
17 KiB
C#
468 lines
17 KiB
C#
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 (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. Выстрел (ЛКМ)
|
||
bool shootInput = isAutomatic
|
||
? Mouse.current.leftButton.isPressed
|
||
: Mouse.current.leftButton.wasPressedThisFrame;
|
||
|
||
if (shootInput)
|
||
{
|
||
TryShoot();
|
||
}
|
||
|
||
// 5. Перезарядка (Клавиша R)
|
||
if (Keyboard.current.rKey.wasPressedThisFrame)
|
||
{
|
||
TryStartReload();
|
||
}
|
||
}
|
||
|
||
/// <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()
|
||
{
|
||
// Звук выстрела
|
||
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);
|
||
}
|
||
}
|