304 lines
9.6 KiB
C#
304 lines
9.6 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
|
|
/// <summary>
|
|
/// 발사된 화살 발사체 (파티클 프리팹에 부착됨)
|
|
/// SphereCast 기반 정밀 충돌 감지 (기존 100% 유지)
|
|
/// [NEW] 속성 데미지(DoT) 시스템 추가
|
|
/// </summary>
|
|
public class PlayerArrow : MonoBehaviour
|
|
{
|
|
[Header("--- 정밀 피격 판정 설정 ---")]
|
|
[SerializeField] private float raycastDistance = 0.8f;
|
|
[SerializeField] private float raycastRadius = 0.08f;
|
|
[SerializeField] private LayerMask hitLayers;
|
|
[SerializeField] private Transform arrowTip;
|
|
|
|
[Header("--- 디버그 시각화 ---")]
|
|
[SerializeField] private bool showDebugRay = true;
|
|
|
|
// 화살 스탯
|
|
private float damage;
|
|
private float speed;
|
|
private float range;
|
|
private Vector3 startPos;
|
|
private Vector3 shootDirection;
|
|
private bool isFired = false;
|
|
private bool hasHit = false;
|
|
|
|
// [NEW] 속성 데미지 시스템
|
|
private ArrowElementType elementType = ArrowElementType.None;
|
|
private float elementDamage = 0f;
|
|
private float elementDuration = 0f;
|
|
|
|
// Raycast 추적용
|
|
private Vector3 previousPosition;
|
|
private Rigidbody rb;
|
|
|
|
private void Awake()
|
|
{
|
|
rb = GetComponent<Rigidbody>();
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (arrowTip == null)
|
|
{
|
|
Transform tip = transform.Find("ArrowTip");
|
|
if (tip == null) tip = transform.Find("Tip");
|
|
arrowTip = tip ?? transform;
|
|
}
|
|
|
|
if (hitLayers.value == 0)
|
|
{
|
|
hitLayers = LayerMask.GetMask("Enemy", "EnemyHitBox", "Wall", "Ground", "Default");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [MODIFIED] 초기화 — 속성 정보 포함 (7개 파라미터)
|
|
/// PlayerAttack.OnShootArrow()에서 호출됩니다.
|
|
/// </summary>
|
|
public void Initialize(
|
|
float dmg,
|
|
float arrowSpeed,
|
|
float maxRange,
|
|
Vector3 direction,
|
|
ArrowElementType element,
|
|
float elemDmg,
|
|
float elemDur)
|
|
{
|
|
this.damage = dmg;
|
|
this.speed = arrowSpeed;
|
|
this.range = maxRange;
|
|
this.shootDirection = direction.normalized;
|
|
this.startPos = transform.position;
|
|
this.previousPosition = transform.position;
|
|
this.isFired = true;
|
|
|
|
// [NEW] 속성 정보 저장
|
|
this.elementType = element;
|
|
this.elementDamage = elemDmg;
|
|
this.elementDuration = elemDur;
|
|
|
|
// 발사 방향으로 회전
|
|
if (shootDirection != Vector3.zero)
|
|
{
|
|
transform.rotation = Quaternion.LookRotation(shootDirection);
|
|
}
|
|
|
|
// Rigidbody 설정
|
|
if (rb == null) rb = GetComponent<Rigidbody>();
|
|
|
|
if (rb != null)
|
|
{
|
|
rb.useGravity = false;
|
|
rb.isKinematic = false;
|
|
rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
|
|
rb.velocity = shootDirection * speed;
|
|
}
|
|
|
|
Destroy(gameObject, 5f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// [하위 호환성] 방향 없는 3-파라미터 버전
|
|
/// </summary>
|
|
public void Initialize(float dmg, float arrowSpeed, float maxRange)
|
|
{
|
|
Initialize(dmg, arrowSpeed, maxRange, transform.forward,
|
|
ArrowElementType.None, 0f, 0f);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!isFired || hasHit) return;
|
|
|
|
// 사거리 체크
|
|
float traveledDistance = Vector3.Distance(startPos, transform.position);
|
|
if (traveledDistance >= range)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
// 정밀 충돌 감지
|
|
CheckPrecisionCollision();
|
|
|
|
previousPosition = transform.position;
|
|
}
|
|
|
|
/// <summary>
|
|
/// SphereCast 기반 정밀 충돌 (기존 로직 100% 유지)
|
|
/// </summary>
|
|
private void CheckPrecisionCollision()
|
|
{
|
|
Vector3 tipPosition = arrowTip != null ? arrowTip.position : transform.position;
|
|
|
|
Vector3 direction = rb != null && rb.velocity.magnitude > 0.1f
|
|
? rb.velocity.normalized
|
|
: transform.forward;
|
|
|
|
float frameDistance = Vector3.Distance(previousPosition, transform.position);
|
|
float checkDistance = Mathf.Max(frameDistance, raycastDistance);
|
|
|
|
RaycastHit hit;
|
|
bool didHit = Physics.SphereCast(
|
|
tipPosition,
|
|
raycastRadius,
|
|
direction,
|
|
out hit,
|
|
checkDistance,
|
|
hitLayers
|
|
);
|
|
|
|
if (showDebugRay)
|
|
{
|
|
Debug.DrawRay(tipPosition, direction * checkDistance, didHit ? Color.red : Color.green, 0.1f);
|
|
}
|
|
|
|
if (didHit)
|
|
{
|
|
HandleHit(hit.collider, hit.point);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [MODIFIED] 충돌 처리 — 속성 데미지 적용 추가
|
|
/// </summary>
|
|
private void HandleHit(Collider hitCollider, Vector3 hitPoint)
|
|
{
|
|
if (hasHit) return;
|
|
hasHit = true;
|
|
|
|
// 적 감지 (Tag 또는 Layer)
|
|
bool isEnemy = hitCollider.CompareTag("Enemy");
|
|
|
|
// EnemyHitBox 레이어 체크 (레이어가 존재하는 경우에만)
|
|
int enemyHitBoxLayerIndex = LayerMask.NameToLayer("EnemyHitBox");
|
|
if (!isEnemy && enemyHitBoxLayerIndex != -1)
|
|
{
|
|
isEnemy = hitCollider.gameObject.layer == enemyHitBoxLayerIndex;
|
|
}
|
|
|
|
if (isEnemy)
|
|
{
|
|
// MonsterClass를 찾아 데미지 적용
|
|
MonsterClass monster = hitCollider.GetComponentInParent<MonsterClass>();
|
|
if (monster == null) monster = hitCollider.GetComponent<MonsterClass>();
|
|
|
|
if (monster != null)
|
|
{
|
|
// 1. 기본 데미지 적용
|
|
monster.TakeDamage(damage);
|
|
Debug.Log($"적 명중! 기본데미지: {damage}");
|
|
|
|
// 2. [NEW] 속성 효과 적용
|
|
ApplyElementEffect(monster);
|
|
}
|
|
|
|
Destroy(gameObject, 0.05f);
|
|
}
|
|
else if (hitCollider.CompareTag("Wall") || hitCollider.CompareTag("Ground"))
|
|
{
|
|
Debug.Log($"벽/바닥 충돌! 위치: {hitPoint}");
|
|
Destroy(gameObject, 0.05f);
|
|
}
|
|
else
|
|
{
|
|
// 기타 충돌 (Unknown)
|
|
Destroy(gameObject, 0.05f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// [NEW] 속성 효과 적용
|
|
/// MonsterClass에 ApplyStatusEffect 메서드가 있어야 합니다.
|
|
/// </summary>
|
|
private void ApplyElementEffect(MonsterClass monster)
|
|
{
|
|
if (elementType == ArrowElementType.None || elementDamage <= 0f) return;
|
|
|
|
// MonsterClass에 ApplyStatusEffect가 있는지 확인
|
|
switch (elementType)
|
|
{
|
|
case ArrowElementType.Fire:
|
|
monster.ApplyStatusEffect(StatusEffectType.Burn, elementDamage, elementDuration);
|
|
Debug.Log($"화염 효과! {elementDamage} 데미지 x {elementDuration}초");
|
|
break;
|
|
|
|
case ArrowElementType.Ice:
|
|
monster.ApplyStatusEffect(StatusEffectType.Slow, elementDamage, elementDuration);
|
|
Debug.Log($"빙결 효과! 슬로우 {elementDuration}초");
|
|
break;
|
|
|
|
case ArrowElementType.Poison:
|
|
monster.ApplyStatusEffect(StatusEffectType.Poison, elementDamage, elementDuration);
|
|
Debug.Log($"독 효과! {elementDamage} 데미지 x {elementDuration}초");
|
|
break;
|
|
|
|
case ArrowElementType.Lightning:
|
|
monster.ApplyStatusEffect(StatusEffectType.Shock, elementDamage, elementDuration);
|
|
Debug.Log($"감전 효과! {elementDamage} 범위 데미지");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 백업 충돌 감지 (OnTriggerEnter) — 기존 로직 유지
|
|
/// </summary>
|
|
private void OnTriggerEnter(Collider other)
|
|
{
|
|
if (hasHit) return;
|
|
|
|
bool isEnemy = other.CompareTag("Enemy");
|
|
int enemyHitBoxLayerIndex = LayerMask.NameToLayer("EnemyHitBox");
|
|
if (!isEnemy && enemyHitBoxLayerIndex != -1)
|
|
{
|
|
isEnemy = other.gameObject.layer == enemyHitBoxLayerIndex;
|
|
}
|
|
|
|
if (isEnemy)
|
|
{
|
|
HandleHit(other, other.ClosestPoint(transform.position));
|
|
}
|
|
else if (other.CompareTag("Wall") || other.CompareTag("Ground"))
|
|
{
|
|
hasHit = true;
|
|
Destroy(gameObject, 0.05f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gizmo 디버그 시각화 — 기존 로직 유지 + 속성 색상 추가
|
|
/// </summary>
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (!Application.isPlaying || !isFired || !showDebugRay) return;
|
|
|
|
Vector3 tipPosition = arrowTip != null ? arrowTip.position : transform.position;
|
|
Vector3 direction = rb != null && rb.velocity.magnitude > 0.1f
|
|
? rb.velocity.normalized
|
|
: transform.forward;
|
|
|
|
Gizmos.color = hasHit ? Color.red : Color.green;
|
|
Gizmos.DrawWireSphere(tipPosition, raycastRadius);
|
|
Gizmos.DrawWireSphere(tipPosition + direction * raycastDistance, raycastRadius);
|
|
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawLine(tipPosition, tipPosition + direction * raycastDistance);
|
|
|
|
// [NEW] 속성별 색상 표시
|
|
switch (elementType)
|
|
{
|
|
case ArrowElementType.Fire: Gizmos.color = Color.red; break;
|
|
case ArrowElementType.Ice: Gizmos.color = Color.cyan; break;
|
|
case ArrowElementType.Poison: Gizmos.color = Color.green; break;
|
|
case ArrowElementType.Lightning: Gizmos.color = Color.yellow; break;
|
|
}
|
|
if (elementType != ArrowElementType.None)
|
|
{
|
|
Gizmos.DrawWireSphere(transform.position, 0.5f);
|
|
}
|
|
}
|
|
} |