using UnityEngine; using System.Collections; /// /// 발사된 화살 발사체 (파티클 프리팹에 부착됨) /// SphereCast 기반 정밀 충돌 감지 (기존 100% 유지) /// [NEW] 속성 데미지(DoT) 시스템 추가 /// 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(); } 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"); } } /// /// [MODIFIED] 초기화 — 속성 정보 포함 (7개 파라미터) /// PlayerAttack.OnShootArrow()에서 호출됩니다. /// 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(); if (rb != null) { rb.useGravity = false; rb.isKinematic = false; rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; rb.velocity = shootDirection * speed; } Destroy(gameObject, 5f); } /// /// [하위 호환성] 방향 없는 3-파라미터 버전 /// 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; } /// /// SphereCast 기반 정밀 충돌 (기존 로직 100% 유지) /// 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); } } /// /// [MODIFIED] 충돌 처리 — 속성 데미지 적용 추가 /// 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(); if (monster == null) monster = hitCollider.GetComponent(); 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); } } /// /// [NEW] 속성 효과 적용 /// MonsterClass에 ApplyStatusEffect 메서드가 있어야 합니다. /// 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; } } /// /// 백업 충돌 감지 (OnTriggerEnter) — 기존 로직 유지 /// 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); } } /// /// Gizmo 디버그 시각화 — 기존 로직 유지 + 속성 색상 추가 /// 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); } } }