using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; namespace Invector.vShooter { [vClassHeader("Shooter Weapon", openClose = false)] public class vShooterWeaponBase : vMonoBehaviour { #region Variables [vEditorToolbar("Weapon Settings")] [Tooltip("The category of the weapon\n Used to the IK offset system. \nExample: HandGun, Pistol, Machine-Gun")] public string weaponCategory = "MyCategory"; [Tooltip("Frequency of shots")] public float shootFrequency; [vEditorToolbar("Ammo")] public bool isInfinityAmmo; [Tooltip("Starting ammo")] [SerializeField, vHideInInspector("isInfinityAmmo", true)] public int ammo; [vEditorToolbar("Layer & Tag")] public List ignoreTags = new List(); public LayerMask hitLayer = 1 << 0; [vEditorToolbar("Projectile")] [Tooltip("Prefab of the projectile")] public GameObject projectile; [Tooltip("Assign the muzzle of your weapon")] public Transform muzzle; [Tooltip("How many projectiles will spawn per shot")] [Range(1, 20)] public int projectilesPerShot = 1; [Range(0, 90)] [Tooltip("how much dispersion the weapon have")] public float dispersion = 0; [Range(0, 1000)] [Tooltip("Velocity of your projectile")] public float velocity = 380; [Tooltip("Use the DropOffStart and DropOffEnd to calc damage by distance using min and max damage")] public bool damageByDistance = true; [Tooltip("Min distance to apply damage, used to evaluate the damage between minDamage and maxDamage")] [UnityEngine.Serialization.FormerlySerializedAs("DropOffStart")] public float minDamageDistance = 8f; [Tooltip("Max distance to apply damage, used to evaluate the damage between minDamage and maxDamage")] [UnityEngine.Serialization.FormerlySerializedAs("DropOffEnd")] public float maxDamageDistance = 50f; [Tooltip("Minimum damage caused by the shot, regardless the distance")] public int minDamage; [Tooltip("Maximum damage caused by the close shot")] public int maxDamage; [vEditorToolbar("Audio & VFX")] [Header("Audio")] public AudioSource source; public AudioClip fireClip; public AudioClip emptyClip; [Header("Effects")] public bool testShootEffect; public Light lightOnShot; [SerializeField] public ParticleSystem[] emittShurykenParticle; protected Transform sender; [HideInInspector] public OnDestroyEvent onDestroy; [System.Serializable] public class OnDestroyEvent : UnityEvent { } [System.Serializable] public class OnInstantiateProjectile : UnityEvent { } [vEditorToolbar("Events")] public UnityEvent onShot, onEmptyClip; public OnInstantiateProjectile onInstantiateProjectile; protected float _nextShootTime; protected float _nextEmptyClipTime; #endregion #region Public Methods /// /// Apply additional velocity to the Shot projectile /// public virtual float velocityMultiplierMod { get; set; } /// /// Apply additional damage to the projectile /// public virtual float damageMultiplierMod { get; set; } /// /// Weapon Name /// public virtual string weaponName { get { var value = gameObject.name.Replace("(Clone)", string.Empty); return value; } } /// /// Shoot to direction of the muzzle forward /// public virtual void Shoot() { Shoot(muzzle.position + muzzle.forward * 100f); } /// /// Shoot to direction of the muzzle forward /// /// Sender to reference of the damage /// Action to check if shoot is sucessful public virtual void Shoot(Transform _sender = null, UnityAction successfulShot = null) { Shoot(muzzle.position + muzzle.forward * 100f, _sender, successfulShot); } /// /// Shoot to direction of the aim Position /// /// Aim position to override direction of the projectile /// ender to reference of the damage /// Action to check if shoot is sucessful public virtual void Shoot(Vector3 aimPosition, Transform _sender = null, UnityAction successfulShot = null) { if (HasAmmo()) { if (!CanDoShot) { return; } UseAmmo(); this.sender = _sender != null ? _sender : transform; HandleShot(aimPosition); if (successfulShot != null) { successfulShot.Invoke(true); } _nextShootTime = Time.time + shootFrequency; _nextEmptyClipTime = _nextShootTime; } else { if (!CanDoEmptyClip) { return; } EmptyClipEffect(); if (successfulShot != null) { successfulShot.Invoke(false); } _nextEmptyClipTime = Time.time + shootFrequency; } } /// /// Check if can shoot by /// public virtual bool CanDoShot { get { bool _canShot = _nextShootTime < Time.time; return _canShot; } } /// /// Check if can do empty clip effect, /// public virtual bool CanDoEmptyClip { get { bool _canShot = _nextEmptyClipTime < Time.time; return _canShot; } } /// /// Use weapon Ammo /// /// count to use public virtual void UseAmmo(int count = 1) { if (ammo <= 0) { return; } ammo -= count; if (ammo <= 0) { ammo = 0; } } /// /// Check if Weapon Has Ammo /// /// public virtual bool HasAmmo() { return isInfinityAmmo || ammo > 0; } #endregion #region Protected Methods protected virtual void OnDestroy() { onDestroy.Invoke(gameObject); } private void OnApplicationQuit() { onDestroy.RemoveAllListeners(); } protected virtual void HandleShot(Vector3 aimPosition) { ShootBullet(aimPosition); ShotEffect(); } protected virtual Vector3 Dispersion(Vector3 aim, float distance, float variance) { aim.Normalize(); Vector3 v3 = Vector3.zero; do { v3 = Random.insideUnitSphere; } while (v3 == aim || v3 == -aim); v3 = Vector3.Cross(aim, v3); v3 = v3 * Random.Range(0f, variance); return aim * distance + v3; } protected virtual void ShootBullet(Vector3 aimPosition) { var dir = aimPosition - muzzle.position; var rotation = Quaternion.LookRotation(dir); GameObject bulletObject = null; var velocityChanged = 0f; if (dispersion > 0 && projectile) { for (int i = 0; i < projectilesPerShot; i++) { var dispersionDir = Dispersion(dir.normalized, maxDamageDistance, dispersion); var spreadRotation = Quaternion.LookRotation(dispersionDir); bulletObject = Instantiate(projectile, muzzle.transform.position, spreadRotation); var pCtrl = bulletObject.GetComponent(); pCtrl.shooterTransform = sender; pCtrl.ignoreTags = ignoreTags; pCtrl.hitLayer = hitLayer; pCtrl.damage.sender = sender; pCtrl.startPosition = bulletObject.transform.position; pCtrl.damageByDistance = damageByDistance; pCtrl.maxDamage = (int)((maxDamage / projectilesPerShot) * damageMultiplier); pCtrl.minDamage = (int)((minDamage / projectilesPerShot) * damageMultiplier); pCtrl.minDamageDistance = minDamageDistance; pCtrl.maxDamageDistance = maxDamageDistance; onInstantiateProjectile.Invoke(pCtrl); velocityChanged = velocity * velocityMultiplier; StartCoroutine(ApplyForceToBullet(bulletObject, dispersionDir, velocityChanged)); pCtrl = CreateProjectileData(aimPosition, velocityChanged, dispersionDir, pCtrl); } } else if (projectilesPerShot > 0 && projectile) { bulletObject = Instantiate(projectile, muzzle.transform.position, rotation); var pCtrl = bulletObject.GetComponent(); pCtrl.shooterTransform = sender; pCtrl.ignoreTags = ignoreTags; pCtrl.hitLayer = hitLayer; pCtrl.damage.sender = sender; pCtrl.startPosition = bulletObject.transform.position; pCtrl.damageByDistance = damageByDistance; pCtrl.maxDamage = (int)((maxDamage / projectilesPerShot) * damageMultiplier); pCtrl.minDamage = (int)((minDamage / projectilesPerShot) * damageMultiplier); pCtrl.minDamageDistance = minDamageDistance; pCtrl.maxDamageDistance = maxDamageDistance; onInstantiateProjectile.Invoke(pCtrl); velocityChanged = velocity * velocityMultiplier; StartCoroutine(ApplyForceToBullet(bulletObject, dir, velocityChanged)); } } protected virtual vProjectileControl CreateProjectileData(Vector3 aimPosition, float velocityChanged, Vector3 dispersionDir, vProjectileControl pCtrl) { pCtrl.instantiateData = new vProjectileInstantiateData { aimPos = aimPosition, dir = dispersionDir, vel = velocityChanged }; return pCtrl; } protected virtual IEnumerator ApplyForceToBullet(GameObject bulletObject, Vector3 direction, float velocityChanged) { yield return new WaitForSeconds(0.01f); try { var _rigidbody = bulletObject.GetComponent(); _rigidbody.mass = _rigidbody.mass / projectilesPerShot;//Change mass per projectiles count. _rigidbody.AddForce((direction.normalized * velocityChanged), ForceMode.VelocityChange); } catch { } } protected virtual float damageMultiplier { get { return 1 + damageMultiplierMod; } } protected virtual float velocityMultiplier { get { return 1 + velocityMultiplierMod; } } #region Effects protected virtual void ShotEffect() { onShot.Invoke(); StopCoroutine(LightOnShoot()); if (source && fireClip) { source.PlayOneShot(fireClip); } StartCoroutine(LightOnShoot(0.037f)); StartEmitters(); } protected virtual void StopSound() { if (source) { source.Stop(); } } protected virtual IEnumerator LightOnShoot(float time = 0) { if (lightOnShot) { lightOnShot.enabled = true; yield return new WaitForSeconds(time); lightOnShot.enabled = false; } } protected virtual void StartEmitters() { if (emittShurykenParticle != null) { foreach (ParticleSystem pe in emittShurykenParticle) { pe.Emit(1); } } } protected virtual void StopEmitters() { if (emittShurykenParticle != null) { foreach (ParticleSystem pe in emittShurykenParticle) { pe.Stop(); } } } protected virtual void EmptyClipEffect() { if (source && emptyClip) { source.PlayOneShot(emptyClip); } onEmptyClip.Invoke(); } #endregion #endregion } }