374 lines
10 KiB
C#
374 lines
10 KiB
C#
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<IDamageable>();
|
|
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<Camera>();
|
|
if (_firingCamera == null)
|
|
{
|
|
_firingCamera = Camera.main;
|
|
}
|
|
}
|
|
|
|
if (_controller == null)
|
|
{
|
|
_controller = GetComponentInParent<FPSController>();
|
|
}
|
|
|
|
if (_weaponSwitcher == null)
|
|
{
|
|
_weaponSwitcher = GetComponentInParent<WeaponSwitcher>();
|
|
}
|
|
|
|
if (_hud == null)
|
|
{
|
|
_hud = FindObjectOfType<HUDController>();
|
|
}
|
|
|
|
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<ParticleSystem>();
|
|
}
|
|
else
|
|
{
|
|
muzzleFlash = GetComponentInChildren<ParticleSystem>(true);
|
|
}
|
|
}
|
|
|
|
if (shootAudioSource == null)
|
|
{
|
|
Transform shootTransform = transform.Find("ShootAudio");
|
|
if (shootTransform != null)
|
|
{
|
|
shootAudioSource = shootTransform.GetComponent<AudioSource>();
|
|
}
|
|
}
|
|
|
|
if (reloadAudioSource == null)
|
|
{
|
|
Transform reloadTransform = transform.Find("ReloadAudio");
|
|
if (reloadTransform != null)
|
|
{
|
|
reloadAudioSource = reloadTransform.GetComponent<AudioSource>();
|
|
}
|
|
}
|
|
|
|
if (shootClip == null)
|
|
{
|
|
shootClip = Resources.Load<AudioClip>($"Audio/{weaponName.ToLowerInvariant()}_fire");
|
|
}
|
|
|
|
if (reloadClip == null)
|
|
{
|
|
reloadClip = Resources.Load<AudioClip>($"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<LineRenderer>();
|
|
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;
|
|
}
|
|
}
|