using System; 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; private float _detectionDistance; public Action OnHitAction; private void OnDrawGizmosSelected() { var radius = _sphereCollider ? _sphereCollider.radius : _colliderRadius; var direction = _rigidbody ? _rigidbody.linearVelocity.normalized : transform.forward; Gizmos.color = Color.red; Gizmos.DrawWireSphere(transform.position, radius); Gizmos.DrawLine(transform.position, transform.position + direction * _detectionDistance); } private void Start() { 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 radius = _sphereCollider ? _sphereCollider.radius : _colliderRadius; var direction = _rigidbody.linearVelocity.normalized; _detectionDistance = _rigidbody.linearVelocity.magnitude * Time.deltaTime; if (!Physics.SphereCast(transform.position, radius, direction, out var raycastHit, _detectionDistance, _targetLayer, QueryTriggerInteraction.Collide)) return; transform.position = raycastHit.point + raycastHit.normal * _collideOffset; var trailParticles = GetComponentsInChildren(); foreach (var element in trailParticles) { if (!element.gameObject.name.Contains("Trail")) continue; element.transform.SetParent(null); Destroy(element.gameObject, 2f); } GameObject impactParticle = null; // TODO : HitBox가 레이어로 설정되어있으도, 부모 객체 Player를 계속 가져오는 버그가 있음 var hitTransform = raycastHit.transform; var iDamageable = hitTransform.GetComponentInParent(); if (iDamageable != null && iDamageable.CanDamage()) { impactParticle = Instantiate(ImpactParticle, transform.position, Quaternion.FromToRotation(Vector3.up, raycastHit.normal)); 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); } }