using System; using Fusion; using UnityEngine; namespace Projectiles { /// /// Weapon component that is responsible for all fire effects /// - camera shake, weapon knockback, fire sound, fire particles. /// public class WeaponFireEffects : WeaponComponent { // PRIVATE MEMBERS [Header("Muzzle")] [SerializeField] private GameObject _fireParticle; [SerializeField] private float _fireParticleReturnTime = 1f; [Header("Sound")] [SerializeField] private Transform _fireAudioEffectsRoot; [SerializeField] private AudioSetup _fireSound; [Header("Camera")] [SerializeField] private ShakeSetup _cameraShakePosition; [SerializeField] private ShakeSetup _cameraShakeRotation; [Header("Kickback")] [SerializeField] private Transform _kickbackTransform; [SerializeField] private Kickback _positionKickback = new(0.06f, 60f, 20f); [SerializeField] private Kickback _rotationKickback = new(5f, 30f, 20f); private Vector3 _kickbackInitialPosition; private Quaternion _kickbackInitialRotation; private AudioEffect[] _fireAudioEffects; // WeaponComponent INTERFACE public override void FireRender() { _positionKickback.HasFired(); _rotationKickback.HasFired(); if (_fireParticle != null) { var fireParticle = Context.ObjectCache.Get(_fireParticle); Context.ObjectCache.ReturnDeferred(fireParticle, _fireParticleReturnTime); // When using multipeer, disable renderers for other clients. Can be omitted otherwise. if (Runner.Config.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { Runner.AddVisibilityNodes(fireParticle.gameObject); } if (fireParticle.gameObject.layer != Weapon.gameObject.layer) { fireParticle.SetLayer(Weapon.gameObject.layer, true); } fireParticle.transform.SetParent(BarrelTransform, false); } _fireAudioEffects.PlaySound(_fireSound, EForceBehaviour.ForceAny); if (HasInputAuthority == true) { var cameraShake = Context.Camera.ShakeEffect; cameraShake.Play(_cameraShakePosition, EShakeForce.ReplaceSame); cameraShake.Play(_cameraShakeRotation, EShakeForce.ReplaceSame); } } // NetworkBehaviour INTERFACE public override void Render() { UpdateKickback(); } // MONOBEHAVIOUR protected void Awake() { if (_fireAudioEffectsRoot != null) { _fireAudioEffects = _fireAudioEffectsRoot.GetComponentsInChildren(true); } if (_kickbackTransform != null) { _kickbackInitialPosition = _kickbackTransform.localPosition; _kickbackInitialRotation = _kickbackTransform.localRotation; } } // PRIVATE METHODS private void UpdateKickback() { if (_kickbackTransform == null) return; var weaponTransform = Weapon.transform; _positionKickback.UpdateKickback(); _kickbackTransform.localPosition = _kickbackInitialPosition + new Vector3(0f, 0f, -_positionKickback.Current); _rotationKickback.UpdateKickback(0.1f); _kickbackTransform.localRotation = _kickbackInitialRotation; _kickbackTransform.RotateAround(weaponTransform.position, weaponTransform.right, -_rotationKickback.Current); } // HELPERS [Serializable] private class Kickback { // PUBLIC MEMBERS public float Current => _current; // PRIVATE MEMBERS [SerializeField] private float _fireKickback = 0.06f; [SerializeField] private float _speed = 60f; [SerializeField] private float _returnSpeed = 20f; private float _current; private float _target; // CONSTRUCTOR public Kickback(float fireKickback, float speed, float returnSpeed) { _fireKickback = fireKickback; _speed = speed; _returnSpeed = returnSpeed; } // PUBLIC METHODS public void HasFired() { if (_speed <= 0f) return; _target += _fireKickback; } public void UpdateKickback(float zeroThreshold = 0.001f) { if (_speed <= 0f) return; if (_target > 0f) { _target = Mathf.Lerp(_target, 0f, Time.deltaTime * _returnSpeed); if (_target < zeroThreshold) { // Stop lerping _target = 0f; } } _current = Mathf.Lerp(_current, _target, Time.deltaTime * _speed); if (_current < zeroThreshold) { _current = 0f; } } } } }