Guest User

Undercooked - Gun Controller Script

a guest
Jan 5th, 2022
90
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 27.28 KB | None | 0 0
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4.  
  5. // Author: Darcy Matheson
  6. // Purpose: A base class for the guns used in the game, allowing all kinds of configuration which can convert guns between archetypes.
  7.  
  8. public class GunController : MonoBehaviour
  9. {
  10.     #region Variables
  11.  
  12.     #region Internal
  13.  
  14.     public bool isRecoiling { get; private set; }
  15.     public bool isReloading { get; private set; }
  16.  
  17.     public enum WeaponType
  18.     {
  19.         MicrowaveGun,
  20.         ASaltRifle,
  21.         PepperShotgun
  22.     }
  23.    
  24.     [HideInInspector]
  25.     public bool isCurrentlyEquipped;
  26.  
  27.     private bool inputAcknowledged;
  28.     [HideInInspector]
  29.     public int currentAmmoInMagazine;
  30.     [HideInInspector]
  31.     public int currentAmmoInReserves;
  32.  
  33.     private float shotTimeInterval;
  34.     private float shotIntervalTimer;
  35.  
  36.     private float recoilTimer;
  37.     private Vector3 startingPosition;
  38.     private Vector3 targetPosition;
  39.  
  40.     private float reloadTimer;
  41.  
  42.     private float baseShotVolume;
  43.  
  44.     #endregion
  45.  
  46.     #region Parameters
  47.  
  48.     #region Configuration
  49.     [Header("Configuration")]
  50.  
  51.     [Tooltip("Which weapon type this script is on.")]
  52.     public WeaponType weaponType;
  53.  
  54.     [Tooltip("The layers which bullets interact with, should include enemies and all layers which can block shots.")]
  55.     public LayerMask bulletInteractionLayers;
  56.     #endregion
  57.  
  58.     #region General Stats
  59.     [Header("General Stats")]
  60.  
  61.     [Tooltip("The time it takes to stow and draw the weapon. Added together with the weapon we are switch to, this is the total time taken to switch."), Range(0.1f, 1f)]
  62.     public float swapTime;
  63.  
  64.     [Tooltip("The rate at which the weapon rotates towards a grapple point while using the hookshot."), Range(1, 25)]
  65.     public int grappleRotationSpeed;
  66.  
  67.     [Tooltip("Whether or not the shots from this weapon should pierce through enemies or not.")]
  68.     public bool piercingBullets;
  69.  
  70.     [Tooltip("Only applies if above is true, the maximum number of enemies that can be damaged at a time."), Range(2, 10)]
  71.     public int maximumPenetrationCount;
  72.  
  73.     [Tooltip("The base damage dealt by each bullet of this weapon."), Range(1, 1000)]
  74.     public int baseDamagePerBullet;
  75.     [HideInInspector]
  76.     public int scaledDamagePerBullet { get; private set; }
  77.  
  78.     [Tooltip("The damage multiplier applied when dealing maximum damage with this weapon (at closer range)."), Range(1f, 2f)]
  79.     public float damageMultiplier;
  80.  
  81.     [Tooltip("The furthest angle the bullets from this weapon can stray from the forward vector."), Range(0f, 45f)]
  82.     public float bulletSpreadAngle;
  83.  
  84.     [Tooltip("The maximum amount of ultimate charge awarded per hit with this weapon (0 - 100, in percent)."), Range(0f, 1f)]
  85.     public float ultimateChargePerHit;
  86.     #endregion
  87.  
  88.     #region Upgrades
  89.     [Header("Upgrades")]
  90.  
  91.     [Tooltip("How much more damage each sequential level of the weapon deals (multiplies the damage at the previous level)."), Range(1f, 2f)]
  92.     public float damageMultiplierPerLevel;
  93.  
  94.     [Tooltip("The starting cost of upgrading this weapon."), Range(1, 500)]
  95.     public int weaponUpgradeCostBase;
  96.  
  97.     [Tooltip("How much more expensive each sequential upgrade of the weapon costs (multiplies the damage at the previous level)."), Range(1f, 2f)]
  98.     public float weaponUpgradeCostMultiplierPerLevel;
  99.  
  100.     [HideInInspector]
  101.     public int gunUpgradeLevel { get; private set; }
  102.     #endregion
  103.  
  104.     #region Range and Fire Rate
  105.     [Header("Range and Fire Rate")]
  106.  
  107.     [Tooltip("Whether or not the weapon will fire each consecutive shot automatically.")]
  108.     public bool fullAutoFiring;
  109.  
  110.     [Tooltip("How many bullets are fired from the weapon for each round."), Range(1, 10)]
  111.     public int shotsPerRound;
  112.  
  113.     [Tooltip("How many rounds are fired from this weapon per minute, its maximum fire rate."), Range(60, 1000)]
  114.     public int roundsPerMinute;
  115.  
  116.     [Tooltip("The maximum distance in world units that this weapon is able to deal damage (should be greater than dropoff range)."), Range(0, 250)]
  117.     public int maxRange;
  118.  
  119.     [Tooltip("The range at which damage starts to fall off from the maximum."), Range(0, 100)]
  120.     public int dropoffRange;
  121.     #endregion
  122.  
  123.     #region Ammo
  124.     [Header("Ammo")]
  125.  
  126.     [Tooltip("How much ammo the magazine can hold."), Range(1, 100)]
  127.     public int magazineSize;
  128.  
  129.     [Tooltip("How much ammo this weapon can hold in reserves, -1 sets the reserves to ininite."), Range(-1, 1000)]
  130.     public int ammoTotalReserves;
  131.  
  132.     [Tooltip("How much ammo is consumed for every round that is fired from this weapon, should usually be 1."), Range(1, 10)]
  133.     public int ammoPerRound;
  134.  
  135.     [Tooltip("The cost for buying a bullet for this weapon at an upgrade station."), Range(0f, 10f)]
  136.     public float costPerBullet;
  137.     #endregion
  138.  
  139.     #region Reloading
  140.     [Header("Reloading")]
  141.  
  142.     [Tooltip("How long it takes to reload this weapon (in seconds)."), Range(0.1f, 5f)]
  143.     public float reloadDuration;
  144.  
  145.     [Tooltip("How many rotations the gun completes while reloading."), Range(1, 10)]
  146.     public int reloadRotations;
  147.  
  148.     [Tooltip("Controls the direction of the we rotation when reloading.")]
  149.     public bool rotateBackwards;
  150.     #endregion
  151.  
  152.     #region Firing Recoil
  153.     [Header("Firing Recoil")]
  154.  
  155.     [Tooltip("The maximum distance the weapon pulls back to while recoiling."), Range(0f, 1f)]
  156.     public float weaponRecoilDistance;
  157.  
  158.     [Tooltip("How long it takes to pull back all the way while recoiling from a resting position (in seconds)."), Range(0f, 1f)]
  159.     public float recoilEffectTime;
  160.  
  161.     [Tooltip("How long it takes to recover from a fully recoiled position (in seconds)."), Range(0, 3f)]
  162.     public float recoilRecoveryTime;
  163.  
  164.     [Tooltip("How intensely the camera is able to recoil while firing this weapon (measured in mouse delta per frame).")]
  165.     public Vector2 cameraRecoil;
  166.     #endregion
  167.  
  168.     #region Sounds
  169.  
  170.     #region Sound Clips
  171.     [Header("Sound Clips")]
  172.  
  173.     [Tooltip("The sound that plays at the end of a reload.")]
  174.     public AudioClip reloadSound;
  175.  
  176.     [Tooltip("The various sounds that can play per bullet shot. If the gun is looping a sound instead, only the first one will be used.")]
  177.     public AudioClip[] firingSounds;
  178.     #endregion
  179.  
  180.     #region Sound Looping
  181.     [Header("Sound Looping")]
  182.  
  183.     [Tooltip("Whether the sound should be looped or played per shot.")]
  184.     public bool loopFiringSound;
  185.  
  186.     [Tooltip("Only applies if box above is checked. Affects how fast the volume of looping weapons drops off when stopping firing."), Range(0f, 10f)]
  187.     public float loopingVolumeDropoffRate;
  188.     #endregion
  189.  
  190.     #region Sound Pitching
  191.     [Header("Sound Pitching")]
  192.  
  193.     [Tooltip("Whether to change the pitch of the weapon based on the ammo remaining in the magazine.")]
  194.     public bool pitchAmmoDepletion;
  195.  
  196.     [Tooltip("Only applies if box above is checked. As ammo runs out in the magazine, pitch the shot sound up according to this curve.")]
  197.     public AnimationCurve ammoDepletionSoundCurve;
  198.  
  199.     [Tooltip("Only applies if box above is checked. Pitch ranges between 1 and this number as ammo runs out."), Range(0f, 1f)]
  200.     public float maximumPitch;
  201.     #endregion
  202.  
  203.     #endregion
  204.  
  205.     #region Visual Effects
  206.     [Header("Visual Effects")]
  207.  
  208.     public Transform[] shotOrigins;
  209.     public ParticleSystem beamEffect;
  210.     public GameObject shotEffectPrefab;
  211.  
  212.     #endregion
  213.  
  214.     #endregion
  215.  
  216.     #region Components
  217.     [Header("Components")]
  218.  
  219.     public TrailRenderer reloadSmokeTrail;
  220.  
  221.     public Transform recoilHolder;
  222.     public Transform weaponContainerTransform;
  223.     public Transform weaponModelTransform;
  224.     public Transform weaponFirePoint;
  225.  
  226.     public Transform damageNumberParentTransform;
  227.     public GameObject damageNumberPrefab;
  228.    
  229.     public CameraController cameraController;
  230.  
  231.     public AudioSource shootingAudioSource;
  232.     public AudioSource alternateAudioSource;
  233.  
  234.     private Transform mainCameraTransform;
  235.  
  236.     private Movement playerMovement;
  237.     private PlayerStats playerStats;
  238.     #endregion
  239.  
  240.     #endregion
  241.  
  242.     // Start is called before the first frame update
  243.     void Start()
  244.     {
  245.         #region Initialisation
  246.  
  247.         // Start with gun full of ammo
  248.         currentAmmoInMagazine = magazineSize;
  249.         currentAmmoInReserves = ammoTotalReserves;
  250.  
  251.         // Calculate shot interval from rounds per minute
  252.         shotTimeInterval = (float)(60f / roundsPerMinute);
  253.  
  254.         // Get camera
  255.         mainCameraTransform = Camera.main.transform;
  256.  
  257.         // Get player scripts
  258.         playerMovement = GameObject.FindGameObjectWithTag("Player").GetComponent<Movement>();
  259.         playerStats = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerStats>();
  260.  
  261.         // Save the original volume
  262.         baseShotVolume = shootingAudioSource.volume;
  263.  
  264.         // Determine the starting damage per shot
  265.         scaledDamagePerBullet = baseDamagePerBullet;
  266.         gunUpgradeLevel = 1;
  267.  
  268.         #endregion
  269.     }
  270.  
  271.     // Update is called once per frame
  272.     void Update()
  273.     {
  274.         if (isCurrentlyEquipped)
  275.         {
  276.             #region Player Inputs
  277.  
  278.             #region Firing and Reloading
  279.  
  280.             if (isCurrentlyEquipped && WeaponCoordinator.switchingWeapons == false && Movement.isGrappling == false && PlayerStats.gamePaused == false)
  281.             {
  282.                 if (isReloading == false && currentAmmoInMagazine == 0 && (currentAmmoInReserves > 0 || ammoTotalReserves == -1))
  283.                 {
  284.                     // Ensure rotation is reset
  285.                     if (weaponModelTransform.localRotation == Quaternion.identity)
  286.                     {
  287.                         // No ammo, auto reload
  288.                         isReloading = true;
  289.                         reloadTimer = reloadDuration;
  290.                     }
  291.                 }
  292.                 else if (Input.GetKey(KeyCode.R) && isReloading == false && currentAmmoInMagazine < magazineSize && (currentAmmoInReserves > 0 || ammoTotalReserves == -1))
  293.                 {
  294.                     // Ensure rotation is reset
  295.                     if (weaponModelTransform.localRotation == Quaternion.identity)
  296.                     {
  297.                         // Some ammo, manual reload
  298.                         isReloading = true;
  299.                         reloadTimer = reloadDuration;
  300.                     }
  301.                 }
  302.                 else if (Input.GetMouseButton(0) && currentAmmoInMagazine > 0 && shotIntervalTimer <= 0f && isReloading == false)
  303.                 {
  304.                     // Some ammo, shooting
  305.                     if (fullAutoFiring)
  306.                     {
  307.                         // Firing full auto
  308.                         shotIntervalTimer = shotTimeInterval;
  309.                     }
  310.                     else if (fullAutoFiring == false && inputAcknowledged == false)
  311.                     {
  312.                         // Firing manually
  313.                         shotIntervalTimer = shotTimeInterval;
  314.                         inputAcknowledged = true;
  315.                     }
  316.  
  317.                     // Shooting / Sprinting iterruption
  318.                     if (Input.GetMouseButtonDown(0) && Movement.isSprinting)
  319.                     {
  320.                         // Stop sprinting
  321.                         playerMovement.InterruptSprint();
  322.                     }
  323.                     else if (Input.GetMouseButton(0) && Movement.isSprinting)
  324.                     {
  325.                         // Stop shooting
  326.                         shotIntervalTimer = -1f;
  327.                     }
  328.                 }
  329.                 else if (Input.GetMouseButtonUp(0) && inputAcknowledged && fullAutoFiring == false)
  330.                 {
  331.                     // The shoot button has been released, allow another manual shot
  332.                     inputAcknowledged = false;
  333.                 }
  334.             }
  335.  
  336.             #endregion
  337.  
  338.             #endregion
  339.  
  340.             #region Looped Sounds
  341.  
  342.             // If the sound should loop
  343.             if (loopFiringSound && Input.GetMouseButton(0) && isReloading == false && Movement.isGrappling == false && PlayerStats.gamePaused == false && Movement.isSprinting == false)
  344.             {
  345.                 // Pitch up as magazine begins to end
  346.                 shootingAudioSource.pitch = pitchAmmoDepletion ? ((ammoDepletionSoundCurve.Evaluate(1f - ((float)currentAmmoInMagazine / (float)magazineSize)) * maximumPitch) + 1f) : 1f;
  347.  
  348.                 // If the sound isn't playing currently
  349.                 if (shootingAudioSource.isPlaying == false)
  350.                 {
  351.                     shootingAudioSource.clip = firingSounds[0];
  352.                     shootingAudioSource.Play();
  353.                 }
  354.             }
  355.             else if (loopFiringSound && (isReloading || Input.GetMouseButton(0) == false || Movement.isGrappling || PlayerStats.gamePaused || Movement.isSprinting))
  356.             {
  357.                 // If we stop firing for some reason
  358.                 if (shootingAudioSource.isPlaying)
  359.                 {
  360.                     // Lower volume over time
  361.                     shootingAudioSource.volume -= loopingVolumeDropoffRate * Time.unscaledDeltaTime;
  362.  
  363.                     // If gun is silent, stop playing
  364.                     if (shootingAudioSource.volume <= 0f)
  365.                     {
  366.                         shootingAudioSource.volume = 0f;
  367.                         shootingAudioSource.Stop();
  368.                     }
  369.                 }
  370.             }
  371.  
  372.             #endregion
  373.  
  374.             #region Looping Visual Effects
  375.  
  376.             if (weaponType == WeaponType.MicrowaveGun)
  377.             {
  378.                 bool playEffect = (recoilTimer > 0f && isReloading == false && Input.GetMouseButton(0));
  379.  
  380.                 if (playEffect)
  381.                 {
  382.                     // Start the effect
  383.                     if (beamEffect.IsAlive() == false)
  384.                     {
  385.                         beamEffect.Play(true);
  386.                     }
  387.                 }
  388.                 else
  389.                 {
  390.                     // Stop the effect
  391.                     beamEffect.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
  392.                 }
  393.             }
  394.  
  395.             #endregion
  396.  
  397.             #region Reloading
  398.  
  399.             if (isReloading)
  400.             {
  401.                 // Play reload sound at the start of the reload
  402.                 if (reloadTimer == reloadDuration)
  403.                 {
  404.                     alternateAudioSource.PlayOneShot(reloadSound);
  405.                 }
  406.  
  407.                 // Decrement timer
  408.                 reloadTimer -= Time.deltaTime;
  409.            
  410.                 // Smoke effect on
  411.                 reloadSmokeTrail.emitting = true;
  412.  
  413.                 // Calculate reload progress
  414.                 float progress = Mathf.Clamp01(1f - (reloadTimer / reloadDuration));
  415.  
  416.                 // Evaluate progress on a curve
  417.                 float totalAnglularRotation = 360f * (float)reloadRotations;
  418.                 float newRotation = (ReloadSpinCurve(progress) * totalAnglularRotation);
  419.                 Quaternion spinRotation = Quaternion.Euler(Vector3.right * newRotation * (rotateBackwards ? -1f : 1f));
  420.                 weaponContainerTransform.localRotation = spinRotation;
  421.  
  422.                 // When reload has ended
  423.                 if (reloadTimer <= 0f)
  424.                 {
  425.                     isReloading = false;
  426.  
  427.                     // The maximum amount of extra ammo that can be put in the magazine out of reserves
  428.                     if (ammoTotalReserves == -1)
  429.                     {
  430.                         // Infinite reserves
  431.                         currentAmmoInMagazine = magazineSize;
  432.                     }
  433.                     else
  434.                     {
  435.                         // Limited ammo in reserves
  436.                         int missingAmmo = Mathf.Clamp(magazineSize - currentAmmoInMagazine, 0, currentAmmoInReserves);
  437.                         currentAmmoInReserves -= missingAmmo;
  438.                         currentAmmoInMagazine += missingAmmo;
  439.                     }
  440.  
  441.                     // Snap rotation to finished position
  442.                     weaponContainerTransform.localRotation = Quaternion.identity;
  443.                
  444.                     // Smoke effect off
  445.                     reloadSmokeTrail.emitting = false;
  446.                 }
  447.             }
  448.  
  449.             #endregion
  450.  
  451.             #region Weapon Recoil
  452.  
  453.             if (recoilTimer > recoilRecoveryTime)
  454.             {
  455.                 // Recoil backwards
  456.                 float recoilProgress = Mathf.Clamp01((recoilTimer - recoilRecoveryTime) / recoilEffectTime);
  457.                 Vector3 recoilPosition = startingPosition + (RecoilCurve(1f - recoilProgress) * (targetPosition - startingPosition));
  458.                 recoilHolder.localPosition = recoilPosition;
  459.  
  460.                 recoilTimer -= Time.deltaTime;
  461.             }
  462.             else if (recoilTimer > 0f)
  463.             {
  464.                 // Recover from recoil
  465.                 float recoveryProgress = Mathf.Clamp01(recoilTimer / recoilRecoveryTime);
  466.                 Vector3 recoveryPosition = startingPosition + (RecoveryCurve(recoveryProgress) * (targetPosition - startingPosition));
  467.                 recoilHolder.localPosition = recoveryPosition;
  468.  
  469.                 recoilTimer -= Time.deltaTime;
  470.             }
  471.             else if (recoilTimer <= 0f)
  472.             {
  473.                 // Reset position
  474.                 recoilHolder.localPosition = startingPosition;
  475.                 isRecoiling = false;
  476.             }
  477.  
  478.             #endregion
  479.  
  480.             #region Update UI
  481.  
  482.             if (isCurrentlyEquipped)
  483.             {
  484.                 // Reloading indicator
  485.                 float reloadProgress = Mathf.Clamp01(reloadTimer / reloadDuration);
  486.                 UserInterfaceHUD.reloadProgress = reloadProgress;
  487.  
  488.                 // Ammo display
  489.                 UserInterfaceHUD.ammoInMagazine = currentAmmoInMagazine;
  490.                 UserInterfaceHUD.ammoInReserves = currentAmmoInReserves;
  491.                 UserInterfaceHUD.equippedWeapon = (int)weaponType;
  492.             }
  493.  
  494.             #endregion
  495.         }
  496.     }
  497.  
  498.     // FixedUpdate is called once every physics iteration
  499.     void FixedUpdate()
  500.     {
  501.         #region Shooting
  502.  
  503.         if (shotIntervalTimer > 0f)
  504.         {
  505.             // Fire weapon immediately
  506.             if (shotIntervalTimer == shotTimeInterval)
  507.             {
  508.                 ShootWeapon();
  509.             }
  510.  
  511.             // Decrement timer
  512.             shotIntervalTimer -= Time.fixedDeltaTime;
  513.         }
  514.  
  515.         #endregion
  516.     }
  517.  
  518.     // Fires a single round from the weapon
  519.     void ShootWeapon()
  520.     {
  521.         // Ensure volume is at the intended level
  522.         if (loopFiringSound)
  523.         {
  524.             shootingAudioSource.volume = baseShotVolume;
  525.         }
  526.  
  527.         // Subtract ammo per round
  528.         currentAmmoInMagazine -= ammoPerRound;
  529.         currentAmmoInMagazine = Mathf.Clamp(currentAmmoInMagazine, 0, magazineSize);
  530.  
  531.         #region Raycast Shots
  532.  
  533.         // For each bullet in the round
  534.         for (int bulletNumber = 0; bulletNumber < shotsPerRound; bulletNumber++)
  535.         {
  536.             // Calculate raycast direction
  537.             Vector3 bulletDirection = mainCameraTransform.forward;
  538.  
  539.             if (bulletSpreadAngle > 0f)
  540.             {
  541.                 float spreadInstance = Random.Range(0f, bulletSpreadAngle);
  542.                 Vector3 unrotatedDirection = Quaternion.AngleAxis(spreadInstance, mainCameraTransform.up).normalized * mainCameraTransform.forward;
  543.  
  544.                 float rotationInstance = Random.Range(0f, 360f);
  545.                 bulletDirection = Quaternion.AngleAxis(rotationInstance, mainCameraTransform.forward).normalized * unrotatedDirection;
  546.                 bulletDirection.Normalize();
  547.             }
  548.  
  549.             #region Individual Visual Effects
  550.  
  551.             // Weapons that shoot one bullet at a time, can be compared to whether the sound loops or not
  552.             if (weaponType != WeaponType.MicrowaveGun)
  553.             {
  554.                 // Shoots one bullet effect from each barrel, as specified in exposed shotOrigins Transform array
  555.                 for (int i = 0; i < shotOrigins.Length; i++)
  556.                 {
  557.                     // Alternates rendering shots from each barrel
  558.                     if ((i + bulletNumber) % 2 == 0)
  559.                     {
  560.                         Quaternion launchRotation = Quaternion.LookRotation(bulletDirection, Vector3.Cross(bulletDirection, mainCameraTransform.right)) * Quaternion.Euler(0f, 90f, 0f);
  561.                         launchRotation = launchRotation * Quaternion.Euler(-2f, 0f, 0f) * Quaternion.Euler(0f, -1f, 0f);
  562.                         Instantiate<GameObject>(shotEffectPrefab, shotOrigins[i].position, launchRotation, weaponModelTransform);
  563.                     }
  564.                 }
  565.             }
  566.  
  567.             #endregion
  568.  
  569.             // Fire from camera
  570.             // Get hit info (RaycastAll for piercing ammo)
  571.             if (piercingBullets)
  572.             {
  573.                 RaycastHit[] hits;
  574.                 hits = Physics.RaycastAll(mainCameraTransform.position, bulletDirection, maxRange, bulletInteractionLayers);
  575.  
  576.                 // Sort by distance
  577.                 System.Array.Sort(hits, (RaycastHit x, RaycastHit y) => x.distance.CompareTo(y.distance));
  578.  
  579.                 // Compare from shortest to furthest
  580.                 for (int i = 0; i < Mathf.Min(hits.Length, maximumPenetrationCount); i++)
  581.                 {
  582.                     if (hits[i].transform != null && hits[i].collider.tag == "Enemy")
  583.                     {
  584.                         // We hit an enemy, proceed with damage calculation
  585.                         int damage = CalculateDamageDropoff(scaledDamagePerBullet, hits[i].distance);
  586.  
  587.                         // Deal damage to enemy script and give player ultimate charge
  588.                         if (hits[i].collider.gameObject.GetComponent<Enemy>())
  589.                         {
  590.                             hits[i].collider.gameObject.GetComponent<Enemy>().TakeDamage(Mathf.CeilToInt(damage * ((damage == scaledDamagePerBullet) ? damageMultiplier : 1f)), Mathf.CeilToInt(scaledDamagePerBullet * ((damage == scaledDamagePerBullet) ? damageMultiplier : 1f)), hits[i].point, false);
  591.                             playerStats.AddUltimateCharge(ultimateChargePerHit * ((float)damage / (float)scaledDamagePerBullet) * ((damage == scaledDamagePerBullet) ? damageMultiplier : 1f));
  592.                         }
  593.                     }
  594.                     else
  595.                     {
  596.                         break;
  597.                     }
  598.                 }
  599.             }
  600.             else
  601.             {
  602.                 RaycastHit hit;
  603.                 Physics.Raycast(mainCameraTransform.position, bulletDirection, out hit, maxRange, bulletInteractionLayers);
  604.  
  605.                 if (hit.transform != null && hit.collider.tag == "Enemy")
  606.                 {
  607.                     // We hit an enemy, proceed with damage calculation
  608.                     int damage = CalculateDamageDropoff(scaledDamagePerBullet, hit.distance);
  609.  
  610.                     // Deal damage to enemy script
  611.                     if (hit.collider.gameObject.GetComponent<Enemy>())
  612.                     {
  613.                         hit.collider.gameObject.GetComponent<Enemy>().TakeDamage(Mathf.CeilToInt(damage * ((damage == scaledDamagePerBullet) ? damageMultiplier : 1f)), Mathf.CeilToInt(scaledDamagePerBullet * ((damage == scaledDamagePerBullet) ? damageMultiplier : 1f)), hit.point, false);
  614.                         playerStats.AddUltimateCharge(ultimateChargePerHit * ((float)damage / (float)scaledDamagePerBullet) * ((damage == scaledDamagePerBullet) ? damageMultiplier : 1f));
  615.                     }
  616.                 }
  617.             }
  618.         }
  619.  
  620.         #endregion
  621.  
  622.         AddRecoil();
  623.  
  624.         // Play shooting sound per shot
  625.         if (loopFiringSound == false && firingSounds.Length > 0)
  626.         {
  627.             int randomSoundIndex = Random.Range(0, firingSounds.Length);
  628.             shootingAudioSource.pitch = pitchAmmoDepletion ? ((ammoDepletionSoundCurve.Evaluate(1f - ((float)currentAmmoInMagazine / (float)magazineSize)) * maximumPitch) + 1f) : 1f;
  629.             shootingAudioSource.PlayOneShot(firingSounds[randomSoundIndex]);
  630.         }
  631.     }
  632.  
  633.     // Recoils the gun and camera
  634.     void AddRecoil()
  635.     {
  636.         // Set positions for lerp
  637.         startingPosition = Vector3.zero;
  638.         targetPosition = Vector3.back * weaponRecoilDistance;
  639.  
  640.         // Evaluate how far through current recoil animation based on time
  641.         float currentProgress = 0f; // 0 is resting position, 1 is fully recoiled
  642.         currentProgress = (recoilHolder.localPosition.z - startingPosition.z) / (-weaponRecoilDistance);
  643.  
  644.         // Set timer for effect
  645.         recoilTimer = (recoilEffectTime * (1f - currentProgress)) + recoilRecoveryTime;
  646.  
  647.         // Add camera recoil
  648.         cameraController.AddRecoil(cameraRecoil.x, cameraRecoil.y);
  649.         isRecoiling = true;
  650.     }
  651.  
  652.     // Calculate the damage of a bullet after accounting for damage dropoff
  653.     int CalculateDamageDropoff(float baseDamage, float range)
  654.     {
  655.         int result = 0;
  656.  
  657.         if (range <= dropoffRange)
  658.         {
  659.             result = Mathf.CeilToInt(baseDamage);
  660.         }
  661.         else if (range < maxRange)
  662.         {
  663.             // Damage dropoff
  664.             result = Mathf.CeilToInt(baseDamage - (baseDamage * Mathf.Sqrt(range - dropoffRange) / Mathf.Sqrt(maxRange - dropoffRange)));
  665.         }
  666.         else
  667.         {
  668.             // Shot is out of range (deal no damage)
  669.             result = 0;
  670.         }
  671.  
  672.         return result;
  673.     }
  674.  
  675.     // Upgrade the weapon
  676.     public void UpgradeWeapon()
  677.     {
  678.         ulong upgradeCost = (ulong)CalculateWeaponUpgradeCost();
  679.  
  680.         if (upgradeCost <= playerStats.currentFibre)
  681.         {
  682.             scaledDamagePerBullet = CalculateUpgradeDamage();
  683.  
  684.             gunUpgradeLevel += 1;
  685.             playerStats.currentFibre -= upgradeCost;
  686.         }
  687.     }
  688.  
  689.     // Calculates the weapon's damage based on its current level
  690.     public int CalculateUpgradeDamage()
  691.     {
  692.         float newDamagePerBullet = baseDamagePerBullet;
  693.         for (int i = 0; i < gunUpgradeLevel; i++)
  694.         {
  695.             newDamagePerBullet *= damageMultiplierPerLevel;
  696.         }
  697.  
  698.         return Mathf.CeilToInt(newDamagePerBullet);
  699.     }
  700.  
  701.     // Calculates the cost of the upgrade
  702.     public int CalculateWeaponUpgradeCost()
  703.     {
  704.         float scaledCost = weaponUpgradeCostBase;
  705.         for (int i = 0; i < gunUpgradeLevel - 1; i++)
  706.         {
  707.             scaledCost *= weaponUpgradeCostMultiplierPerLevel;
  708.         }
  709.  
  710.         return Mathf.CeilToInt(scaledCost);
  711.     }
  712.  
  713.     // The curve used for the spin easing
  714.     float ReloadSpinCurve(float progress)
  715.     {
  716.         float result = 0f;
  717.  
  718.         result = (Mathf.Cos(Mathf.PI * (progress + 1f)) + 1f) / 2f;
  719.         result = Mathf.Clamp01(result);
  720.  
  721.         return result;
  722.     }
  723.  
  724.     // The curve used for pulling the weapon back when recoiling
  725.     float RecoilCurve(float progress)
  726.     {
  727.         float result = 0f;
  728.         result = 1f - ((progress - 1f) * (progress - 1f));
  729.  
  730.         return result;
  731.     }
  732.  
  733.     // The curve used for easing the weapon back a resting position
  734.     float RecoveryCurve(float progress)
  735.     {
  736.         float result = 0f;
  737.         result = (Mathf.Cos((Mathf.PI * progress) - Mathf.PI) + 1f) / 2f;
  738.  
  739.         return result;
  740.     }
  741. }
  742.  
Advertisement
Add Comment
Please, Sign In to add comment