using System; using Fusion; using UnityEngine; namespace Projectiles { /// /// Weapon component that fires a laser beam that damages targets on hit. /// [DefaultExecutionOrder(15)] public class WeaponBeam : WeaponComponent { // PRIVATE MEMBERS [SerializeField] private float _damage = 10f; [SerializeField] private EHitType _hitType = EHitType.Projectile; [SerializeField] private LayerMask _hitMask; [SerializeField] private float _maxDistance = 50f; [SerializeField] private float _beamRadius = 0.2f; [SerializeField, Tooltip("Number of raycast rays fired. First is always in center, other are spread around in the radius distance.")] private int _raycastAmount = 5; [SerializeField] private WeaponTrigger _weaponTrigger; [Header("Beam Visuals")] [SerializeField] private GameObject _beamStart; [SerializeField] private GameObject _beamEnd; [SerializeField] private LineRenderer _beam; [SerializeField] private float _beamEndOffset = 0.5f; [SerializeField] private bool _updateBeamMaterial; [SerializeField] private float _textureScale = 3f; [SerializeField] private float _textureScrollSpeed = -8f; [Header("Camera Effect")] [SerializeField] private ShakeSetup _cameraShakePosition; [SerializeField] private ShakeSetup _cameraShakeRotation; [Networked] private float _beamDistance { get; set; } // WeaponComponent INTERFACE public override void Fire() { var hit = ProcessBeamHit(); if (hit.Distance > 0f) { HitUtility.ProcessHit(Object.InputAuthority, FireTransform.forward, hit, _damage, _hitType); } } // NetworkBehaviour INTERFACE public override void FixedUpdateNetwork() { // Update beam distance only when trigger is firing if (_weaponTrigger.IsBusy == true) { ProcessBeamHit(); } else { _beamDistance = -1f; } } private void LateUpdate() { if (Object == null || Object.IsValid == false) return; // Beam needs to be updated after camera pivot change // - after PlayerAgent.LateUpdate UpdateBeam(); if (_beamDistance > 0f && HasInputAuthority == true) { var cameraShake = Context.Camera.ShakeEffect; cameraShake.Play(_cameraShakePosition, EShakeForce.ReplaceSame); cameraShake.Play(_cameraShakeRotation, EShakeForce.ReplaceSame); } } // PRIVATE MEMBERS private LagCompensatedHit ProcessBeamHit() { _beamDistance = _maxDistance; if (ProjectileUtility.CircleCast(Runner, Object.InputAuthority, FireTransform.position, FireTransform.forward, _maxDistance, _beamRadius, _raycastAmount, _hitMask, out LagCompensatedHit hit) == true) { _beamDistance = hit.Distance; return hit; } return default; } private void UpdateBeam() { bool beamActive = _beamDistance > 0f; _beamStart.SetActiveSafe(beamActive); _beamEnd.SetActiveSafe(beamActive); _beam.gameObject.SetActiveSafe(beamActive); if (beamActive == false) return; var startPosition = _beamStart.transform.position; var targetPosition = FireTransform.position + FireTransform.forward * _beamDistance; var visualDirection = targetPosition - startPosition; float visualDistance = visualDirection.magnitude; visualDirection /= visualDistance; // Normalize if (_beamEndOffset > 0f) { // Adjust target position visualDistance = visualDistance > _beamEndOffset ? visualDistance - _beamEndOffset : 0f; targetPosition = startPosition + visualDirection * visualDistance; } _beamEnd.transform.SetPositionAndRotation(targetPosition, Quaternion.LookRotation(-visualDirection)); _beam.SetPosition(0, startPosition); _beam.SetPosition(1, targetPosition); if (_updateBeamMaterial == true) { var beamMaterial = _beam.material; beamMaterial.mainTextureScale = new Vector2(visualDistance / _textureScale, 1f); beamMaterial.mainTextureOffset += new Vector2(Time.deltaTime * _textureScrollSpeed, 0f); } } } }