2026-03-22 03:31:16 +00:00
|
|
|
using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
|
// HealthAltar — 체력 회복 제단
|
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
|
|
|
|
// 아이템과 달리 "구조물"이므로 플레이어가 통과하면 안 돼요.
|
|
|
|
|
//
|
|
|
|
|
// [핵심 원리]
|
|
|
|
|
// 제단은 Item 레이어에 있으면 Player와 물리 충돌이 꺼져있어서 통과돼요.
|
|
|
|
|
// (Physics → Layer Collision Matrix에서 Item ↔ Player 충돌이 꺼짐)
|
|
|
|
|
//
|
|
|
|
|
// 해결: Awake에서 자동으로 레이어를 분리해요
|
|
|
|
|
// ① 메인 오브젝트 → Environment 레이어 (Player와 충돌 ON = 통과 불가)
|
|
|
|
|
// ② 자식 "AltarTrigger" → Item 레이어 + isTrigger (OverlapSphere 감지용)
|
|
|
|
|
//
|
|
|
|
|
// 이렇게 하면 플레이어가 제단을 뚫고 지나갈 수 없으면서도
|
|
|
|
|
// F키 상호작용(Physics.OverlapSphere + itemLayer)으로 감지는 정상 작동해요.
|
|
|
|
|
// ══════════════════════════════════════════════════════════════
|
2026-02-02 08:30:23 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
public class HealthAltar : MonoBehaviour // 클래스를 선언할거에요 -> MonoBehaviour를 상속받는 HealthAltar를
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
[Header("--- 회복 설정 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 회복 설정 --- 을
|
|
|
|
|
[SerializeField] private float healAmount = 50f; // 변수를 선언할거에요 -> 회복량인 healAmount를
|
|
|
|
|
[SerializeField] private float interactRange = 3.5f; // 변수를 선언할거에요 -> 상호작용 거리인 interactRange를
|
2026-02-02 08:30:23 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
[Header("--- 시각 효과 ---")] // 인스펙터 창에 제목을 표시할거에요 -> --- 시각 효과 --- 를
|
|
|
|
|
[SerializeField] private ParticleSystem healEffect; // 변수를 선언할거에요 -> 회복 이펙트인 healEffect를
|
2026-02-02 08:30:23 +00:00
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
[Header("--- 충돌 설정 ---")]
|
|
|
|
|
[Tooltip("체크하면 Awake에서 레이어와 Collider를 자동 보정해요\n" +
|
|
|
|
|
"메인 → Environment 레이어 (물리 충돌, 통과 불가)\n" +
|
|
|
|
|
"자식 → Item 레이어 + 트리거 (상호작용 감지용)")]
|
|
|
|
|
[SerializeField] private bool autoFixCollider = true; // 변수를 선언할거에요 -> 콜라이더 자동 보정 여부를
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 초기화 — 레이어 + 콜라이더 자동 보정
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
private void Awake() // 함수를 실행할거에요 -> 게임 시작 시 최초 1회
|
|
|
|
|
{
|
|
|
|
|
if (!autoFixCollider) return; // 중단할거에요 -> 자동 보정 꺼져있으면
|
|
|
|
|
|
|
|
|
|
// ── 원래 레이어를 기억 (자식 트리거에 사용) ──────────────
|
|
|
|
|
int originalLayer = gameObject.layer; // 저장할거에요 -> 현재 레이어를 (Item 레이어일 것)
|
|
|
|
|
|
|
|
|
|
// ── 메인 오브젝트를 Environment 레이어로 변경 ─────────
|
|
|
|
|
// Item ↔ Player는 Physics Matrix에서 충돌이 꺼져있어서 통과돼요
|
|
|
|
|
// Environment ↔ Player는 충돌이 켜져있어서 막아줘요
|
|
|
|
|
int envLayer = LayerMask.NameToLayer("Environment"); // 가져올거에요 -> Environment 레이어 번호를
|
|
|
|
|
if (envLayer != -1) // 조건이 맞으면 실행할거에요 -> Environment 레이어가 존재하면
|
|
|
|
|
{
|
|
|
|
|
gameObject.layer = envLayer; // 변경할거에요 -> 메인 오브젝트를 Environment 레이어로
|
|
|
|
|
Debug.Log($"[HealthAltar] 레이어 변경: {LayerMask.LayerToName(originalLayer)} → Environment (통과 방지)"); // 로그를 찍을거에요
|
|
|
|
|
}
|
|
|
|
|
else // Environment 레이어가 없으면
|
|
|
|
|
{
|
|
|
|
|
// Default 레이어로 폴백 (Default ↔ Player도 충돌 켜져있음)
|
|
|
|
|
gameObject.layer = 0; // 변경할거에요 -> Default 레이어로 (0번)
|
|
|
|
|
Debug.LogWarning("[HealthAltar] Environment 레이어를 찾을 수 없어서 Default로 설정했어요!"); // 경고를 찍을거에요
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 메인 Collider를 물리 충돌용으로 확인 ─────────────
|
|
|
|
|
// isTrigger = false여야 물리 충돌이 작동해요
|
|
|
|
|
Collider mainCol = GetComponent<Collider>(); // 가져올거에요 -> 이 오브젝트의 메인 콜라이더를
|
|
|
|
|
if (mainCol != null) // 조건이 맞으면 실행할거에요 -> 콜라이더가 있으면
|
|
|
|
|
{
|
|
|
|
|
if (mainCol.isTrigger) // 조건이 맞으면 실행할거에요 -> 트리거로 설정돼있으면
|
|
|
|
|
{
|
|
|
|
|
mainCol.isTrigger = false; // 끌거에요 -> 트리거를 (물리 충돌 활성화 = 통과 불가)
|
|
|
|
|
Debug.Log("[HealthAltar] 메인 Collider를 isTrigger=false로 변경 (통과 방지)"); // 로그를 찍을거에요
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else // 콜라이더가 아예 없으면
|
|
|
|
|
{
|
|
|
|
|
mainCol = gameObject.AddComponent<BoxCollider>(); // 추가할거에요 -> BoxCollider를
|
|
|
|
|
mainCol.isTrigger = false; // 끌거에요 -> 트리거를 (물리 충돌 = 통과 불가)
|
|
|
|
|
Debug.LogWarning("[HealthAltar] Collider가 없어서 BoxCollider를 자동 추가했어요!"); // 경고를 찍을거에요
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 상호작용 감지용 트리거 자식 오브젝트 생성 ─────────
|
|
|
|
|
// PlayerInteraction의 OverlapSphere는 itemLayer만 감지하므로
|
|
|
|
|
// 트리거 자식은 반드시 원래(Item) 레이어를 유지해야 해요!
|
|
|
|
|
EnsureInteractionTrigger(mainCol, originalLayer); // 실행할거에요 -> 트리거 자식 생성/확인을
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 상호작용 감지용 트리거 자식 오브젝트를 생성하거나 이미 있으면 무시해요.
|
|
|
|
|
/// 핵심: 자식은 원래 레이어(Item)를 유지해야 OverlapSphere(itemLayer)에 걸려요!
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void EnsureInteractionTrigger(Collider mainCol, int itemLayer) // 함수를 선언할거에요 -> 트리거 자식을 확인/생성하는
|
|
|
|
|
{
|
|
|
|
|
// 이미 자식 중에 "AltarTrigger"가 있으면 중복 생성 방지
|
|
|
|
|
Transform existingTrigger = transform.Find("AltarTrigger"); // 찾을거에요 -> 기존 트리거 자식을
|
|
|
|
|
if (existingTrigger != null) return; // 중단할거에요 -> 이미 있으면 (중복 방지)
|
|
|
|
|
|
|
|
|
|
// 새 자식 오브젝트 생성
|
|
|
|
|
GameObject triggerObj = new GameObject("AltarTrigger"); // 생성할거에요 -> 트리거 전용 자식 오브젝트를
|
|
|
|
|
triggerObj.transform.SetParent(transform); // 붙일거에요 -> 이 오브젝트의 자식으로
|
|
|
|
|
triggerObj.transform.localPosition = Vector3.zero; // 설정할거에요 -> 위치를 부모와 동일하게
|
|
|
|
|
triggerObj.transform.localRotation = Quaternion.identity; // 설정할거에요 -> 회전을 부모와 동일하게
|
|
|
|
|
triggerObj.transform.localScale = Vector3.one; // 설정할거에요 -> 스케일을 1로
|
|
|
|
|
|
|
|
|
|
// ⭐ 핵심: 자식은 원래 레이어(Item)를 유지!
|
|
|
|
|
// 부모는 Environment로 바꿨지만, 자식은 Item이어야
|
|
|
|
|
// PlayerInteraction의 OverlapSphere(itemLayer)에서 감지돼요
|
|
|
|
|
triggerObj.layer = itemLayer; // 설정할거에요 -> 원래(Item) 레이어로
|
|
|
|
|
|
|
|
|
|
// 트리거 콜라이더 추가 (메인 콜라이더 크기 복사)
|
|
|
|
|
if (mainCol is BoxCollider box) // 조건이 맞으면 실행할거에요 -> 메인이 BoxCollider면
|
|
|
|
|
{
|
|
|
|
|
BoxCollider triggerBox = triggerObj.AddComponent<BoxCollider>(); // 추가할거에요 -> BoxCollider를
|
|
|
|
|
triggerBox.center = box.center; // 복사할거에요 -> 중심점을
|
|
|
|
|
triggerBox.size = box.size * 1.1f; // 설정할거에요 -> 약간 크게 (상호작용 여유)
|
|
|
|
|
triggerBox.isTrigger = true; // 켤거에요 -> 트리거로 (감지 전용, 물리 충돌 X)
|
|
|
|
|
}
|
|
|
|
|
else if (mainCol is SphereCollider sphere) // 조건이 맞으면 실행할거에요 -> SphereCollider면
|
|
|
|
|
{
|
|
|
|
|
SphereCollider triggerSphere = triggerObj.AddComponent<SphereCollider>(); // 추가할거에요 -> SphereCollider를
|
|
|
|
|
triggerSphere.center = sphere.center; // 복사할거에요 -> 중심점을
|
|
|
|
|
triggerSphere.radius = sphere.radius * 1.1f; // 설정할거에요 -> 약간 크게
|
|
|
|
|
triggerSphere.isTrigger = true; // 켤거에요 -> 트리거로
|
|
|
|
|
}
|
|
|
|
|
else if (mainCol is CapsuleCollider capsule) // 조건이 맞으면 실행할거에요 -> CapsuleCollider면
|
|
|
|
|
{
|
|
|
|
|
CapsuleCollider triggerCapsule = triggerObj.AddComponent<CapsuleCollider>(); // 추가할거에요 -> CapsuleCollider를
|
|
|
|
|
triggerCapsule.center = capsule.center; // 복사할거에요 -> 중심점을
|
|
|
|
|
triggerCapsule.radius = capsule.radius * 1.1f; // 설정할거에요 -> 약간 크게
|
|
|
|
|
triggerCapsule.height = capsule.height; // 복사할거에요 -> 높이를
|
|
|
|
|
triggerCapsule.direction = capsule.direction; // 복사할거에요 -> 방향을
|
|
|
|
|
triggerCapsule.isTrigger = true; // 켤거에요 -> 트리거로
|
|
|
|
|
}
|
|
|
|
|
else // 그 외 콜라이더 타입이면
|
|
|
|
|
{
|
|
|
|
|
BoxCollider fallbackTrigger = triggerObj.AddComponent<BoxCollider>(); // 추가할거에요 -> 기본 BoxCollider를
|
|
|
|
|
fallbackTrigger.size = Vector3.one; // 설정할거에요 -> 기본 크기로
|
|
|
|
|
fallbackTrigger.isTrigger = true; // 켤거에요 -> 트리거로
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Debug.Log($"[HealthAltar] 트리거 자식 생성 완료 (레이어: {LayerMask.LayerToName(itemLayer)})"); // 로그를 찍을거에요
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 상호작용 (PlayerInteraction에서 호출)
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
// ⭐ [핵심] PlayerInteraction에서 던져주는 'PlayerHealth' 자료형을 직접 받습니다!
|
2026-02-12 15:23:25 +00:00
|
|
|
public void Use(PlayerHealth playerHealth) // 함수를 선언할거에요 -> 제단을 사용하는 Use를
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (playerHealth == null) return; // 조건이 맞으면 중단할거에요 -> 플레이어 체력 스크립트가 없다면
|
2026-02-02 08:30:23 +00:00
|
|
|
|
|
|
|
|
// 플레이어의 Transform 정보 추출
|
2026-02-12 15:23:25 +00:00
|
|
|
Transform interactor = playerHealth.transform; // 값을 가져올거에요 -> 상호작용하는 대상의 위치 정보를
|
2026-02-02 08:30:23 +00:00
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
// 실제 몸통 중심점과의 거리 계산
|
2026-02-12 15:23:25 +00:00
|
|
|
float distance = Vector3.Distance(transform.position, interactor.position); // 거리를 계산할거에요 -> 제단과 플레이어 사이의 거리를
|
2026-02-02 08:30:23 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (distance <= interactRange) // 조건이 맞으면 실행할거에요 -> 거리가 상호작용 범위 이내라면
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-03-22 03:31:16 +00:00
|
|
|
ApplyHeal(playerHealth); // 함수를 실행할거에요 -> 회복 적용 함수 ApplyHeal을
|
2026-02-02 08:30:23 +00:00
|
|
|
}
|
2026-02-12 15:23:25 +00:00
|
|
|
else // 조건이 틀리면 실행할거에요 -> 거리가 너무 멀다면
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
Debug.Log($"[제단] 너무 멉니다! (거리: {distance:F1} / 제한: {interactRange})"); // 로그를 출력할거에요 -> 거리 부족 메시지를
|
2026-02-02 08:30:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 회복 적용
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
private void ApplyHeal(PlayerHealth targetHealth) // 함수를 선언할거에요 -> 실제로 체력을 회복시키는 ApplyHeal을
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-03-22 03:31:16 +00:00
|
|
|
targetHealth.Heal(healAmount); // 함수를 실행할거에요 -> 플레이어의 회복 함수인 Heal을
|
2026-02-02 08:30:23 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (healEffect != null) // 조건이 맞으면 실행할거에요 -> 이펙트가 설정되어 있다면
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
healEffect.transform.position = targetHealth.transform.position; // 위치를 옮길거에요 -> 플레이어 위치로
|
|
|
|
|
healEffect.Play(); // 실행할거에요 -> 이펙트 재생을
|
2026-02-02 08:30:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
Debug.Log($"[제단] {targetHealth.gameObject.name} 상호작용 성공! {healAmount} HP 회복."); // 로그를 출력할거에요 -> 회복 성공 메시지를
|
2026-02-02 08:30:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-22 03:31:16 +00:00
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
// 에디터 기즈모
|
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
private void OnDrawGizmosSelected() // 함수를 실행할거에요 -> 선택 시 기즈모를 그리는 OnDrawGizmosSelected를
|
2026-02-02 08:30:23 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
Gizmos.color = Color.cyan; // 색상을 설정할거에요 -> 하늘색으로
|
|
|
|
|
Gizmos.DrawWireSphere(transform.position, interactRange); // 그림을 그릴거에요 -> 상호작용 범위를 표시하는 원을
|
2026-02-02 08:30:23 +00:00
|
|
|
}
|
2026-03-22 03:31:16 +00:00
|
|
|
}
|