270 lines
9.9 KiB
C#
270 lines
9.9 KiB
C#
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;
|
|
|
|
[Title("슬로우 설정")]
|
|
[SerializeField]
|
|
private bool _isSlowedMoveSpeed;
|
|
|
|
[SerializeField, ShowIf("@_isSlowedMoveSpeed")]
|
|
private float _slowDuration;
|
|
|
|
[SerializeField, ShowIf("@_isSlowedMoveSpeed")]
|
|
private float _moveSpeedCoefficient;
|
|
|
|
[Title("푸쉬 설정")]
|
|
[SerializeField]
|
|
private bool _isPushed;
|
|
|
|
[SerializeField, ShowIf("@_isPushed")]
|
|
private float _pushPower;
|
|
|
|
[Title("효과음 설정")]
|
|
[SerializeField]
|
|
private string _awakeSfxName;
|
|
|
|
[SerializeField]
|
|
private string _attackSfxName;
|
|
|
|
[Title("자동 파괴 옵션")]
|
|
[SerializeField]
|
|
private bool _useAutoDestroy = true;
|
|
|
|
[SerializeField, ShowIf("@_useAutoDestroy")]
|
|
private float _autoDestroyTime = 10f;
|
|
|
|
public float SphereRadius { get; private set; }
|
|
|
|
private float _detectionDistance;
|
|
private Collider[] _hitColliders;
|
|
private bool _isHitCheckRealtime;
|
|
|
|
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;
|
|
|
|
if (!_isHitCheckRealtime) return;
|
|
|
|
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<ParticleSystem>();
|
|
foreach (var element in trailParticles)
|
|
{
|
|
if (!element.gameObject.name.Contains("Trail")) continue;
|
|
|
|
element.transform.SetParent(null);
|
|
Destroy(element.gameObject, 2f);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_attackSfxName))
|
|
{
|
|
AudioManager.Instance.PlaySfx(_attackSfxName);
|
|
}
|
|
var impactParticle = Instantiate(ImpactParticle, transform.position, Quaternion.identity);
|
|
// TODO : HitBox가 레이어로 설정되어있으도, 부모 객체 Player를 계속 가져오는 버그가 있음
|
|
var iDamageable = hitCollider.GetComponentInParent<IDamageable>();
|
|
if (iDamageable != null && iDamageable.CanDamage())
|
|
{
|
|
iDamageable.TakeDamage(_attackDamage);
|
|
OnHitAction?.Invoke();
|
|
|
|
if (_isSlowedMoveSpeed)
|
|
{
|
|
var slowable = hitCollider.GetComponentInParent<ISlowable>();
|
|
slowable?.SlowMoveSpeed(_slowDuration, _moveSpeedCoefficient);
|
|
}
|
|
|
|
if (_isPushed)
|
|
{
|
|
var physicMovable = hitCollider.GetComponentInParent<IPhysicMovable>();
|
|
if (physicMovable != null)
|
|
{
|
|
var pushDirection = hitCollider.transform.position - transform.position;
|
|
physicMovable.SetPush(pushDirection.normalized, _pushPower);
|
|
}
|
|
}
|
|
}
|
|
|
|
Destroy(_projectilePrefab, 3f);
|
|
if (impactParticle)
|
|
{
|
|
Destroy(impactParticle, 3.5f);
|
|
}
|
|
Destroy(gameObject);
|
|
}
|
|
|
|
public void DoAttack()
|
|
{
|
|
_hitColliders = new Collider[8];
|
|
var hitCount = Physics.OverlapSphereNonAlloc(transform.position, SphereRadius, _hitColliders, _targetLayer, QueryTriggerInteraction.Collide);
|
|
|
|
// transform.position = raycastHit.point + raycastHit.normal * _collideOffset;
|
|
var trailParticles = GetComponentsInChildren<ParticleSystem>();
|
|
foreach (var element in trailParticles)
|
|
{
|
|
if (!element.gameObject.name.Contains("Trail")) continue;
|
|
|
|
element.transform.SetParent(null);
|
|
Destroy(element.gameObject, 2f);
|
|
}
|
|
|
|
for (var i = 0; i < hitCount; i++)
|
|
{
|
|
// TODO : HitBox가 레이어로 설정되어있으도, 부모 객체 Player를 계속 가져오는 버그가 있음
|
|
var iDamageable = _hitColliders[i].GetComponentInParent<IDamageable>();
|
|
if (iDamageable == null || !iDamageable.CanDamage()) continue;
|
|
|
|
iDamageable.TakeDamage(_attackDamage);
|
|
OnHitAction?.Invoke();
|
|
|
|
if (_isSlowedMoveSpeed)
|
|
{
|
|
var slowable = _hitColliders[i].GetComponentInParent<ISlowable>();
|
|
slowable?.SlowMoveSpeed(_slowDuration, _moveSpeedCoefficient);
|
|
}
|
|
|
|
if (_isPushed)
|
|
{
|
|
var physicMovable = _hitColliders[i].GetComponentInParent<IPhysicMovable>();
|
|
if (physicMovable != null)
|
|
{
|
|
var pushDirection = _hitColliders[i].transform.position - transform.position;
|
|
physicMovable.SetPush(pushDirection.normalized, _pushPower);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(_attackSfxName))
|
|
{
|
|
AudioManager.Instance.PlaySfx(_attackSfxName);
|
|
}
|
|
var impactParticle = Instantiate(ImpactParticle, transform.position, Quaternion.identity);
|
|
|
|
Destroy(_projectilePrefab, 3f);
|
|
if (impactParticle)
|
|
{
|
|
Destroy(impactParticle, 3.5f);
|
|
}
|
|
Destroy(gameObject);
|
|
}
|
|
|
|
public void Initialize(int attackDamage, LayerMask targetLayer, bool isHitCheckRealtime = true, Action onHitAction = null)
|
|
{
|
|
_attackDamage = attackDamage;
|
|
_targetLayer = targetLayer;
|
|
_isHitCheckRealtime = isHitCheckRealtime;
|
|
OnHitAction = onHitAction;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 파티클이 충돌할 때, 슬로우 효과를 적용할 때 사용
|
|
/// </summary>
|
|
/// <param name="slowDuration">슬로우 효과 지속시간</param>
|
|
/// <param name="moveSpeedCoefficient">값이 1f일 때, 기본 이동속도\n0.3f라면, 70%감소 효과 (기존 속도의 30%)</param>
|
|
public void SetSlowMoveSpeed(float slowDuration, float moveSpeedCoefficient)
|
|
{
|
|
_isSlowedMoveSpeed = true;
|
|
_slowDuration = slowDuration;
|
|
_moveSpeedCoefficient = moveSpeedCoefficient;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 파티클이 충돌할 때, 밀어내는 효과를 적용할 때 사용
|
|
/// </summary>
|
|
/// <param name="pushPower">hit되는 캐릭터의 moveSpeed를 고려해서 적용</param>
|
|
public void SetPush(float pushPower)
|
|
{
|
|
_isPushed = true;
|
|
_pushPower = pushPower;
|
|
}
|
|
|
|
public void AddForce(Vector3 force, ForceMode forceMode) => _rigidbody.AddForce(force, forceMode);
|
|
}
|
|
} |