Files

308 lines
8.0 KiB
C#
Raw Permalink Normal View History

2026-06-04 17:37:58 +03:00
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class Weapon : MonoBehaviour
{
public WeaponData data;
public Text ammoText;
public Text weaponNameText;
public Text reloadText;
public Transform firePoint;
public Camera playerCamera;
public WeaponHUD hud;
private int currentAmmo;
private int currentReserve;
private float nextFireTime;
private bool isReloading;
private Vector3 originalLocalPosition;
private Quaternion originalCameraRotation;
private bool initialized;
private AudioSource audioSource;
private Coroutine reloadRoutine;
private Coroutine recoilRoutine;
private SimpleFPSController fpsController;
private void Awake()
{
audioSource = GetComponent<AudioSource>();
originalLocalPosition = transform.localPosition;
}
private void OnEnable()
{
if (Application.isPlaying)
OnSelected();
}
private void Update()
{
if (data == null)
return;
bool wantsToShoot = data.automatic ? Input.GetButton("Fire1") : Input.GetButtonDown("Fire1");
if (wantsToShoot)
TryShoot();
if (Input.GetKeyDown(KeyCode.R))
Reload();
UpdateUI();
}
public void Initialize()
{
if (data == null || initialized)
return;
currentAmmo = Mathf.Max(0, data.magazineSize);
currentReserve = Mathf.Max(0, data.totalReserveAmmo);
originalLocalPosition = transform.localPosition;
if (playerCamera != null)
{
originalCameraRotation = playerCamera.transform.localRotation;
fpsController = playerCamera.GetComponentInParent<SimpleFPSController>();
}
initialized = true;
UpdateUI();
}
public void TryShoot()
{
if (data == null)
return;
// Shooting behavior is data-driven: automatic weapons use held Fire1, semi-auto weapons use one click.
if (isReloading)
return;
if (currentAmmo <= 0)
{
ShowMessage("No ammo. Press R to reload.");
return;
}
if (Time.time < nextFireTime)
return;
if (!CanShoot())
return;
Shoot();
}
public void Shoot()
{
if (data == null || playerCamera == null)
return;
currentAmmo--;
nextFireTime = Time.time + Mathf.Max(0.01f, data.fireRate);
PlaySound(data.shootSound);
SpawnMuzzleFlash();
int pelletCount = Mathf.Max(1, data.pelletsPerShot);
for (int i = 0; i < pelletCount; i++)
FireRay();
ApplyRecoil();
UpdateUI();
}
public void Reload()
{
if (data == null || isReloading)
return;
if (currentAmmo >= data.magazineSize)
{
ShowMessage("Magazine is full.");
return;
}
if (currentReserve <= 0)
{
ShowMessage("No reserve ammo.");
return;
}
reloadRoutine = StartCoroutine(ReloadRoutine());
}
public void UpdateUI()
{
if (data != null && weaponNameText != null)
weaponNameText.text = "Weapon: " + data.weaponName;
if (ammoText != null)
ammoText.text = "Ammo: " + currentAmmo + " / " + currentReserve;
if (reloadText != null)
reloadText.text = isReloading ? "Reloading..." : "Ready";
if (hud != null)
{
hud.SetWeapon(data);
hud.SetAmmo(currentAmmo, currentReserve);
hud.SetReloading(isReloading);
}
}
public void OnSelected()
{
Initialize();
UpdateUI();
}
public void OnDeselected()
{
if (recoilRoutine != null)
{
StopCoroutine(recoilRoutine);
recoilRoutine = null;
}
transform.localPosition = originalLocalPosition;
}
public bool CanShoot()
{
return data != null && !isReloading && currentAmmo > 0 && Time.time >= nextFireTime;
}
public int GetCurrentAmmo()
{
return currentAmmo;
}
public int GetCurrentReserve()
{
return currentReserve;
}
public bool IsReloading()
{
return isReloading;
}
private IEnumerator ReloadRoutine()
{
isReloading = true;
UpdateUI();
PlaySound(data.reloadSound);
yield return new WaitForSeconds(Mathf.Max(0f, data.reloadTime));
int neededAmmo = Mathf.Max(0, data.magazineSize - currentAmmo);
int ammoToLoad = Mathf.Min(neededAmmo, currentReserve);
currentAmmo += ammoToLoad;
currentReserve -= ammoToLoad;
isReloading = false;
reloadRoutine = null;
UpdateUI();
}
private void FireRay()
{
Vector3 origin = playerCamera.transform.position;
// Spread is applied around the camera forward vector, so all weapons use the same aiming source.
Vector2 randomOffset = Random.insideUnitCircle * Mathf.Max(0f, data.spread);
Vector3 direction = playerCamera.transform.forward +
playerCamera.transform.right * randomOffset.x +
playerCamera.transform.up * randomOffset.y;
direction.Normalize();
Debug.DrawRay(origin, direction * data.range, Color.yellow, 1f);
if (Physics.Raycast(origin, direction, out RaycastHit hit, Mathf.Max(0.1f, data.range)))
{
EnemyTarget target = hit.collider.GetComponentInParent<EnemyTarget>();
if (target != null)
target.TakeDamage(data.damage);
SpawnHitEffect(hit);
}
}
private void ApplyRecoil()
{
// The FPS controller owns camera look; the weapon only feeds a temporary visual recoil offset.
if (fpsController != null)
fpsController.AddRecoil(data.recoilForce);
else if (playerCamera != null)
playerCamera.transform.localRotation = originalCameraRotation * Quaternion.Euler(-data.recoilForce, 0f, 0f);
if (recoilRoutine != null)
StopCoroutine(recoilRoutine);
recoilRoutine = StartCoroutine(RecoilRoutine());
}
private IEnumerator RecoilRoutine()
{
Vector3 recoilPosition = originalLocalPosition - Vector3.forward * Mathf.Clamp(data.recoilForce * 0.035f, 0.02f, 0.18f);
float t = 0f;
while (t < 1f)
{
t += Time.deltaTime * 20f;
transform.localPosition = Vector3.Lerp(transform.localPosition, recoilPosition, t);
yield return null;
}
t = 0f;
while (t < 1f)
{
t += Time.deltaTime * 10f;
transform.localPosition = Vector3.Lerp(transform.localPosition, originalLocalPosition, t);
if (fpsController == null && playerCamera != null)
playerCamera.transform.localRotation = Quaternion.Slerp(playerCamera.transform.localRotation, originalCameraRotation, t);
yield return null;
}
transform.localPosition = originalLocalPosition;
recoilRoutine = null;
}
private void SpawnMuzzleFlash()
{
if (data.muzzleFlashPrefab == null || firePoint == null)
return;
GameObject flash = Instantiate(data.muzzleFlashPrefab, firePoint.position, firePoint.rotation, firePoint);
Destroy(flash, 0.08f);
}
private void SpawnHitEffect(RaycastHit hit)
{
if (data.hitEffectPrefab == null)
return;
Quaternion rotation = Quaternion.LookRotation(hit.normal);
GameObject effect = Instantiate(data.hitEffectPrefab, hit.point + hit.normal * 0.01f, rotation);
Destroy(effect, 0.8f);
}
private void PlaySound(AudioClip clip)
{
if (clip != null && audioSource != null)
audioSource.PlayOneShot(clip);
}
private void ShowMessage(string message)
{
if (hud != null)
hud.ShowMessage(message);
}
}