using System.Collections; using UnityEngine; public class Gun : MonoBehaviour { [Header("Stats")] public string weaponName = "Pistol"; public bool automatic; public float damage = 20f; public float range = 90f; public float fireRate = 3f; public float spread = 0.008f; public int projectilesPerShot = 1; public int magazineSize = 12; public int reserveAmmo = 48; public float reloadTime = 1.1f; public float recoilPitch = 1.6f; public float recoilYaw = 0.45f; public Color tracerColor = new Color(1f, 0.85f, 0.3f); [Header("References")] public Transform muzzlePoint; public ParticleSystem muzzleFlash; public AudioSource shootAudioSource; public AudioSource reloadAudioSource; public AudioClip shootClip; public AudioClip reloadClip; [Header("Audio")] public Vector2 shootPitchRange = new Vector2(0.98f, 1.02f); public Vector2 reloadPitchRange = new Vector2(0.98f, 1.02f); public float shootVolume = 1f; public float reloadVolume = 1f; public int AmmoInMagazine { get; private set; } public int ReserveAmmo => reserveAmmo; public bool IsReloading { get; private set; } private static Material _sharedTracerMaterial; private Camera _firingCamera; private FPSController _controller; private HUDController _hud; private WeaponSwitcher _weaponSwitcher; private float _nextFireTime; private Coroutine _reloadRoutine; private bool _initialized; public void Initialize(Camera firingCamera, FPSController controller, HUDController hud, WeaponSwitcher weaponSwitcher) { _firingCamera = firingCamera; _controller = controller; _hud = hud; _weaponSwitcher = weaponSwitcher; AmmoInMagazine = magazineSize; _initialized = true; } public void BindOwnership(HUDController hud, WeaponSwitcher weaponSwitcher) { _hud = hud; _weaponSwitcher = weaponSwitcher; UpdateHud(); } private void OnEnable() { EnsureInitialized(); UpdateHud(); } private void OnDisable() { CancelReload(); muzzleFlash?.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } private void Update() { EnsureInitialized(); if (_firingCamera == null) { return; } if (Input.GetKeyDown(KeyCode.R)) { TryReload(); } bool firePressed = automatic ? Input.GetButton("Fire1") : Input.GetButtonDown("Fire1"); if (firePressed) { TryFire(); } } public void SetEquipCooldown(float cooldown) { _nextFireTime = Mathf.Max(_nextFireTime, Time.time + cooldown); } public bool AddReserveAmmo(int amount) { if (amount <= 0) { return false; } reserveAmmo += amount; UpdateHud(); return true; } public void CancelReload() { if (_reloadRoutine != null) { StopCoroutine(_reloadRoutine); _reloadRoutine = null; } IsReloading = false; if (reloadAudioSource != null) { reloadAudioSource.Stop(); } UpdateHud(); } private void TryFire() { if (IsReloading || Time.time < _nextFireTime) { return; } if (AmmoInMagazine <= 0) { TryReload(); return; } AmmoInMagazine--; _nextFireTime = Time.time + (1f / fireRate); _controller?.ApplyRecoil(recoilPitch, recoilYaw); PlayShootEffects(); Vector3 tracerStart = muzzlePoint != null ? muzzlePoint.position : _firingCamera.transform.position; Vector3 lastEndPoint = tracerStart + _firingCamera.transform.forward * range; for (int i = 0; i < Mathf.Max(1, projectilesPerShot); i++) { Vector3 viewportPoint = new Vector3( 0.5f + Random.Range(-spread, spread), 0.5f + Random.Range(-spread, spread), 0f); Ray ray = _firingCamera.ViewportPointToRay(viewportPoint); Vector3 endPoint = ray.origin + ray.direction * range; if (Physics.Raycast(ray, out RaycastHit hit, range, ~0, QueryTriggerInteraction.Ignore)) { endPoint = hit.point; IDamageable damageable = hit.collider.GetComponentInParent(); damageable?.TakeDamage(damage, hit.point, hit.normal); } lastEndPoint = endPoint; if (projectilesPerShot == 1 || i < 2 || i == projectilesPerShot - 1) { ShowTracer(tracerStart, endPoint, tracerColor); } } if (projectilesPerShot > 1) { ShowTracer(tracerStart, lastEndPoint, tracerColor); } UpdateHud(); } private void TryReload() { if (IsReloading || AmmoInMagazine >= magazineSize || reserveAmmo <= 0) { return; } _reloadRoutine = StartCoroutine(ReloadRoutine()); } private IEnumerator ReloadRoutine() { IsReloading = true; UpdateHud(); PlayReloadSound(); yield return new WaitForSeconds(reloadTime); int ammoNeeded = magazineSize - AmmoInMagazine; int ammoToLoad = Mathf.Min(ammoNeeded, reserveAmmo); AmmoInMagazine += ammoToLoad; reserveAmmo -= ammoToLoad; IsReloading = false; _reloadRoutine = null; UpdateHud(); } private void UpdateHud() { if (_hud == null || _weaponSwitcher == null || _weaponSwitcher.CurrentGun != this) { return; } _hud.SetWeapon(weaponName); _hud.SetAmmo(AmmoInMagazine, reserveAmmo); _hud.SetCenterMessage(IsReloading ? "Reloading..." : string.Empty); } private void EnsureInitialized() { if (_firingCamera == null) { _firingCamera = GetComponentInParent(); if (_firingCamera == null) { _firingCamera = Camera.main; } } if (_controller == null) { _controller = GetComponentInParent(); } if (_weaponSwitcher == null) { _weaponSwitcher = GetComponentInParent(); } if (_hud == null) { _hud = FindObjectOfType(); } if (muzzlePoint == null) { Transform foundMuzzle = transform.Find("Muzzle"); if (foundMuzzle != null) { muzzlePoint = foundMuzzle; } } if (muzzleFlash == null) { Transform flashTransform = muzzlePoint != null ? muzzlePoint.Find("MuzzleFlash") : transform.Find("Muzzle/MuzzleFlash"); if (flashTransform != null) { muzzleFlash = flashTransform.GetComponent(); } else { muzzleFlash = GetComponentInChildren(true); } } if (shootAudioSource == null) { Transform shootTransform = transform.Find("ShootAudio"); if (shootTransform != null) { shootAudioSource = shootTransform.GetComponent(); } } if (reloadAudioSource == null) { Transform reloadTransform = transform.Find("ReloadAudio"); if (reloadTransform != null) { reloadAudioSource = reloadTransform.GetComponent(); } } if (shootClip == null) { shootClip = Resources.Load($"Audio/{weaponName.ToLowerInvariant()}_fire"); } if (reloadClip == null) { reloadClip = Resources.Load($"Audio/{weaponName.ToLowerInvariant()}_reload"); } if (AmmoInMagazine <= 0) { AmmoInMagazine = magazineSize; } _initialized = _firingCamera != null; } private void PlayShootEffects() { if (muzzleFlash != null) { muzzleFlash.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); muzzleFlash.Play(); } PlayClip(shootAudioSource, shootClip, shootPitchRange, shootVolume, false); } private void PlayReloadSound() { PlayClip(reloadAudioSource, reloadClip, reloadPitchRange, reloadVolume, true); } private static void PlayClip(AudioSource source, AudioClip clip, Vector2 pitchRange, float volume, bool replaceClip) { if (source == null || clip == null) { return; } source.pitch = Random.Range(pitchRange.x, pitchRange.y); source.volume = volume; if (replaceClip) { source.Stop(); source.clip = clip; source.Play(); return; } source.PlayOneShot(clip, volume); } private void ShowTracer(Vector3 start, Vector3 end, Color color) { GameObject tracer = new GameObject($"{weaponName}_Tracer"); LineRenderer renderer = tracer.AddComponent(); renderer.material = GetTracerMaterial(); renderer.positionCount = 2; renderer.SetPosition(0, start); renderer.SetPosition(1, end); renderer.startWidth = 0.032f; renderer.endWidth = 0.012f; renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; renderer.receiveShadows = false; renderer.startColor = color; renderer.endColor = new Color(color.r, color.g, color.b, 0.1f); Destroy(tracer, 0.06f); } private static Material GetTracerMaterial() { if (_sharedTracerMaterial == null) { _sharedTracerMaterial = new Material(Shader.Find("Sprites/Default")); } return _sharedTracerMaterial; } }