ZeroVR/ZeroPacientVR/Assets/HurricaneVR/Framework/Scripts/Weapons/HVRRayCastGun.cs

542 lines
15 KiB
C#

using System;
using System.Collections;
using HurricaneVR.Framework.Components;
using HurricaneVR.Framework.Core;
using HurricaneVR.Framework.Core.Grabbers;
using HurricaneVR.Framework.Core.Sockets;
using HurricaneVR.Framework.Core.Utils;
using HurricaneVR.Framework.Shared;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace HurricaneVR.Framework.Weapons
{
[RequireComponent(typeof(HVRGrabbable))]
public class HVRRayCastGun : HVRDamageProvider
{
[Tooltip("If this grabbable is held, the StabilizedRecoilForce is used when shooting.")]
public HVRGrabbable StabilizerGrabbable;
public HVRGrabbable Grabbable { get; private set; }
[Header("Settings")]
[Tooltip("Time between shots")]
public float Cooldown;
[Tooltip("Physics layers for the ray cast")]
public LayerMask HitMask;
public float MuzzleFlashTime = .2f;
[Tooltip("Flexible bullet range per gun type")]
public float BarrelRange = 10;
[Tooltip("Does this gun require ammo inserted to shoot")]
public bool RequiresAmmo = true;
[Tooltip("Is chambering required to shoot")]
public bool RequiresChamberedBullet = true;
public FireType FireType = FireType.Single;
[Tooltip("Speed of the bullet trail prefab")]
public float BulletTrailSpeed = 40f;
[Tooltip("How fast to kick the magazine out of the gun")]
public float AmmoEjectVelocity = 1f;
[Tooltip("How long until we destroy the muzzle smoke object")]
public float MuzzleSmokeTime = 1.5f;
[Tooltip("Should the gun automatically chamber the next round after firing")]
public bool ChambersAfterFiring = true;
[Tooltip("If true will use damage, force, range, from the ammo")]
public bool UseAmmoProperties;
[Tooltip("If not using ammo properties, range of the bullet")]
public float NoAmmoRange = 40f;
[Header("Objects")]
[Tooltip("Optional Direction to eject Ammo - use the z axis")]
public Transform AmmoEjectDirection; //forward
[Tooltip("Socket for taking in ammo / magazines")]
public HVRSocket AmmoSocket;
[Tooltip("Component that handls gun sfx")]
public HVRGunSounds GunSounds;
[Tooltip("Muzzle flash object")]
public GameObject MuzzleFlashObject;
[Tooltip("Recoil settings component")]
public HVRRecoil RecoilComponent;
[Tooltip("Where the bullet should come from, z forward direction")]
public Transform BulletOrigin;
[Tooltip("Muzzle smoke object")]
public GameObject MuzzleSmoke;
[FormerlySerializedAs("BulletPrefab")]
[Header("Prefabs")]
public GameObject BulletTrailPrefab;
public UnityEvent Fired = new UnityEvent();
public GunHitEvent Hit = new GunHitEvent();
private float _timer;
public bool IsBulletChambered { get; set; }
public HVRAmmo Ammo { get; set; }
public float BulletRange
{
get
{
if (UseAmmoProperties && Ammo)
return Ammo.MaxRange + BarrelRange;
return BarrelRange + NoAmmoRange;
}
}
public HVRDamageProvider DamageProvider
{
get
{
if (UseAmmoProperties && Ammo)
return Ammo;
return this;
}
}
public bool OutOfAmmo
{
get
{
if (RequiresChamberedBullet && IsBulletChambered)
return false;
if (!RequiresAmmo)
return false;
if (!Ammo || Ammo.IsEmpty)
return true;
return false;
}
}
private bool _isFiring;
private Coroutine _automaticRoutine;
protected override void Start()
{
base.Start();
Grabbable = GetComponent<HVRGrabbable>();
Grabbable.Activated.AddListener(OnGrabbableActivated);
Grabbable.Deactivated.AddListener(OnGrabbableDeactivated);
Grabbable.Grabbed.AddListener(OnGrabbed);
Grabbable.Released.AddListener(OnReleased);
if (StabilizerGrabbable)
{
StabilizerGrabbable.Grabbed.AddListener(OnStabilizerGrabbed);
StabilizerGrabbable.Released.AddListener(OnStabilizerReleased);
}
if (AmmoSocket)
{
AmmoSocket.Grabbed.AddListener(OnAmmoGrabbed);
}
if (!RecoilComponent)
{
RecoilComponent = GetComponent<HVRRecoil>();
}
if (!GunSounds)
{
GunSounds = GetComponent<HVRGunSounds>();
}
}
private void OnReleased(HVRGrabberBase arg0, HVRGrabbable arg1)
{
if (RecoilComponent)
{
RecoilComponent.HandRigidBody = null;
}
}
private void OnGrabbed(HVRGrabberBase arg0, HVRGrabbable arg1)
{
if (arg0 is HVRHandGrabber hand && RecoilComponent)
{
RecoilComponent.HandRigidBody = hand.Rigidbody;
}
}
private void OnStabilizerReleased(HVRGrabberBase grabber, HVRGrabbable arg1)
{
Grabbable.ForceTwoHandSettings = false;
if (RecoilComponent)
{
RecoilComponent.TwoHanded = false;
}
}
private void OnStabilizerGrabbed(HVRGrabberBase grabber, HVRGrabbable arg1)
{
if (Grabbable.PrimaryGrabber && Grabbable.PrimaryGrabber.IsHandGrabber)
{
Grabbable.ForceTwoHandSettings = true;
if (RecoilComponent)
{
RecoilComponent.TwoHanded = true;
}
}
}
public virtual void ChamberRound()
{
if (IsBulletChambered)
return;
if (Ammo && Ammo.HasAmmo)
{
Ammo.RemoveBullet();
IsBulletChambered = true;
}
}
protected virtual void OnAmmoGrabbed(HVRGrabberBase grabber, HVRGrabbable grabbable)
{
var ammo = grabbable.GetComponent<HVRAmmo>();
if (!ammo)
{
Debug.Log($"{grabbable.name} is missing the ammo component.");
return;
}
Ammo = ammo;
for (var i = 0; i < Grabbable.Colliders.Count; i++)
{
var ourCollider = Grabbable.Colliders[i];
for (var j = 0; j < grabbable.Colliders.Count; j++)
{
var ammoCollider = grabbable.Colliders[j];
Physics.IgnoreCollision(ourCollider, ammoCollider, true);
}
}
}
private void OnGrabbableActivated(HVRGrabberBase arg0, HVRGrabbable arg1)
{
if (!CanFire())
{
OnOutofAmmo();
return;
}
if (FireType == FireType.Single)
{
if (_timer < Cooldown)
return;
_timer = 0f;
OnFired();
AfterFired();
return;
}
if (FireType == FireType.Automatic)
{
_isFiring = true;
if (_automaticRoutine != null)
StopCoroutine(_automaticRoutine);
_automaticRoutine = StartCoroutine(Automatic());
return;
}
if (FireType == FireType.ThreeRoundBurst)
{
if (_timer < Cooldown)
return;
_timer = 0f;
}
}
protected virtual void OnGrabbableDeactivated(HVRGrabberBase arg0, HVRGrabbable arg1)
{
_isFiring = false;
}
private IEnumerator Automatic()
{
try
{
var elapsed = Cooldown + 1f;
while (_isFiring && CanFire() && Grabbable.IsBeingHeld)
{
if (elapsed > Cooldown)
{
elapsed = 0f;
OnFired();
AfterFired();
}
elapsed += Time.deltaTime;
yield return null;
}
if (!CanFire())
{
OnOutofAmmo();
}
}
finally
{
_isFiring = false;
}
}
protected virtual void OnOutofAmmo()
{
if (GunSounds)
{
GunSounds.PlayOutOfAmmo();
return;
}
}
protected virtual void Update()
{
_timer += Time.deltaTime;
//Debug.DrawLine(BulletOrigin.position, BulletOrigin.position + BulletOrigin.forward * Range);
}
public virtual void ReleaseAmmo()
{
if (!AmmoSocket || !AmmoSocket.IsGrabbing)
{
return;
}
var releasedAmmo = Ammo;
Ammo = null;
var ammoGrabbable = AmmoSocket.GrabbedTarget;
AmmoSocket.ForceRelease();
if (ammoGrabbable)
{
var direction = -AmmoSocket.transform.up;
if (AmmoEjectDirection)
direction = AmmoEjectDirection.forward;
if (ammoGrabbable.Rigidbody)
ammoGrabbable.Rigidbody.velocity = direction.normalized * AmmoEjectVelocity;
var hand = Grabbable.PrimaryGrabber as HVRHandGrabber;
if (hand)
{
hand.HandPhysics.IgnoreCollision(ammoGrabbable.Colliders, true);
}
if (!releasedAmmo.HasAmmo && releasedAmmo.DestroyIfEmpty)
{
releasedAmmo.StartDestroy();
}
else
{
StartCoroutine(RenablePhysics(ammoGrabbable, hand));
}
}
}
private IEnumerator RenablePhysics(HVRGrabbable grabbable, HVRHandGrabber hand)
{
yield return new WaitForSeconds(2.50f);
for (var i = 0; i < Grabbable.Colliders.Count; i++)
{
var ourCollider = Grabbable.Colliders[i];
for (var j = 0; j < grabbable.Colliders.Count; j++)
{
var ammoCollider = grabbable.Colliders[j];
Physics.IgnoreCollision(ourCollider, ammoCollider, false);
}
}
if (hand)
{
hand.HandPhysics.IgnoreCollision(grabbable.Colliders, false);
}
}
protected virtual void Recoil()
{
if (RecoilComponent)
{
RecoilComponent.Recoil();
return;
}
}
protected virtual bool CanFire()
{
if (RequiresChamberedBullet)
{
return IsBulletChambered;
}
return !RequiresAmmo || Ammo && Ammo.HasAmmo;
}
protected virtual void OnFired()
{
IsBulletChambered = false;
if (GunSounds)
{
GunSounds.PlayGunFire();
}
Recoil();
FireBullets(BulletOrigin.forward);
if (MuzzleFlashObject)
StartCoroutine(MuzzleFlash());
Smoke();
Fired.Invoke();
}
protected virtual void FireBullets(Vector3 direction)
{
FireBullet(direction);
}
protected virtual void FireBullet(Vector3 direction)
{
Vector3 hitLocation;
if (Physics.Raycast(BulletOrigin.position, direction, out var hit, BulletRange, HitMask, QueryTriggerInteraction.Ignore))
{
OnHit(hit, BulletOrigin.forward);
hitLocation = hit.point;
}
else
{
hitLocation = BulletOrigin.position + direction * BulletRange;
}
if (BulletTrailSpeed > 0f)
{
if (BulletTrailPrefab)
StartCoroutine(FireBullet(BulletOrigin.position, hitLocation));
}
}
protected virtual void AfterFired()
{
IsBulletChambered = false;
if (RequiresChamberedBullet)
{
if (ChambersAfterFiring)
{
ChamberRound();
}
}
else if (RequiresAmmo && Ammo)
{
Ammo.RemoveBullet();
}
}
private IEnumerator MuzzleFlash()
{
MuzzleFlashObject.SetActive(false);/// ADDED to cancel longer fx like smoke to allow flame fx to fire again.
MuzzleFlashObject.SetActive(true);
var elapsed = 0f;
while (elapsed < MuzzleFlashTime)
{
elapsed += Time.deltaTime;
yield return null;
}
MuzzleFlashObject.SetActive(false);
}
protected virtual void Smoke()
{
if (MuzzleSmoke)
{
var muzzleSmoke = Instantiate(MuzzleSmoke, MuzzleSmoke.transform.position, MuzzleSmoke.transform.rotation);
muzzleSmoke.SetActive(true);
Destroy(muzzleSmoke, MuzzleSmokeTime);
}
}
private IEnumerator FireBullet(Vector3 start, Vector3 destination)
{
var direction = destination - start;
var bullet = Instantiate(BulletTrailPrefab, BulletOrigin.position, Quaternion.identity);
bullet.transform.rotation = Quaternion.FromToRotation(bullet.transform.forward, direction);
if (!bullet.activeSelf)
bullet.SetActive(true);
var elapsed = 0f;
var distance = Vector3.Distance(start, destination);
var velocity = BulletTrailSpeed * Time.deltaTime;
var time = distance / BulletTrailSpeed;
while (elapsed < time)
{
bullet.transform.position += velocity * direction.normalized;
elapsed += Time.deltaTime;
yield return null;
}
Destroy(bullet);
}
protected virtual void OnHit(RaycastHit hit, Vector3 direction)
{
var damageHandler = hit.collider.GetComponent<HVRDamageHandlerBase>();
if (damageHandler)
{
damageHandler.HandleDamageProvider(DamageProvider, hit.point, direction);
}
Hit.Invoke(damageHandler);
}
public virtual void EjectBullet()
{
}
public virtual void EjectCasing()
{
}
}
public class GunHitEvent : UnityEvent<HVRDamageHandlerBase>
{
}
public enum FireType
{
Single,
ThreeRoundBurst,
Automatic
}
}