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(); 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(); } 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(); 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); } }