using System; using BlueWater.Audios; using BlueWater.Interfaces; using Sirenix.OdinInspector; using UnityEngine; using UnityEngine.Serialization; namespace BlueWater { public class ProjectileController : MonoBehaviour { [Title("컴포넌트")] [SerializeField, Required] private Rigidbody _rigidbody; [SerializeField] private SphereCollider _sphereCollider; [field: Title("파티클 설정")] [FormerlySerializedAs("_projectileParticle")] [SerializeField] private GameObject _projectilePrefab; [SerializeField] private GameObject _muzzleParticle; [field: SerializeField] public GameObject ImpactParticle { get; private set; } [Title("충돌체 설정")] [SerializeField, Tooltip("Sphere Collider가 없는 경우, 기본 충돌 크기(반지름)"), ShowIf("@!_sphereCollider")] private float _colliderRadius = 1f; // [SerializeField, Range(0f, 1f), Tooltip("타겟보다 해당 값만큼 떨어진 위치에서 충돌")] // private float _collideOffset; [SerializeField] private int _attackDamage; [SerializeField] private LayerMask _targetLayer; [SerializeField] private bool _useAutoDestroy = true; [SerializeField, ShowIf("@_useAutoDestroy")] private float _autoDestroyTime = 10f; [SerializeField] private string _awakeSfxName; public float SphereRadius { get; private set; } private float _detectionDistance; private Collider[] _hitColliders; public Action OnHitAction; private void OnDrawGizmosSelected() { if (SphereRadius == 0f) return; var direction = _rigidbody ? _rigidbody.linearVelocity.normalized : transform.forward; Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, SphereRadius); Gizmos.DrawLine(transform.position, transform.position + direction * _detectionDistance); } private void Start() { if (!string.IsNullOrEmpty(_awakeSfxName)) { AudioManager.Instance.PlaySfx(_awakeSfxName); } SphereRadius = _sphereCollider ? _sphereCollider.radius : _colliderRadius; _hitColliders = new Collider[1]; if (_useAutoDestroy) { Destroy(gameObject, _autoDestroyTime); } if (_projectilePrefab) { _projectilePrefab = Instantiate(_projectilePrefab, transform.position, transform.rotation, transform); } if (_muzzleParticle) { _muzzleParticle = Instantiate(_muzzleParticle, transform.position, transform.rotation, transform); Destroy(_muzzleParticle, 1.5f); } } private void FixedUpdate() { if (_rigidbody.linearVelocity.magnitude != 0) { transform.rotation = Quaternion.LookRotation(_rigidbody.linearVelocity); } //var direction = _rigidbody.linearVelocity.normalized; _detectionDistance = _rigidbody.linearVelocity.magnitude * Time.deltaTime; var hitCount = Physics.OverlapSphereNonAlloc(transform.position, SphereRadius, _hitColliders, _targetLayer, QueryTriggerInteraction.Collide); if (hitCount == 0) return; // transform.position = raycastHit.point + raycastHit.normal * _collideOffset; var hitCollider = _hitColliders[0]; var trailParticles = GetComponentsInChildren(); foreach (var element in trailParticles) { if (!element.gameObject.name.Contains("Trail")) continue; element.transform.SetParent(null); Destroy(element.gameObject, 2f); } var impactParticle = Instantiate(ImpactParticle, transform.position, Quaternion.identity); // TODO : HitBox가 레이어로 설정되어있으도, 부모 객체 Player를 계속 가져오는 버그가 있음 var iDamageable = hitCollider.GetComponentInParent(); if (iDamageable != null && iDamageable.CanDamage()) { iDamageable.TakeDamage(_attackDamage); OnHitAction?.Invoke(); } Destroy(_projectilePrefab, 3f); if (impactParticle) { Destroy(impactParticle, 3.5f); } Destroy(gameObject); } public void Initialize(int attackDamage, LayerMask targetLayer, Action onHitAction = null) { _attackDamage = attackDamage; _targetLayer = targetLayer; OnHitAction = onHitAction; } public void AddForce(Vector3 force, ForceMode forceMode) => _rigidbody.AddForce(force, forceMode); } }