using UnityEngine; // 임포트할거에요 -> Unity 엔진 기본 기능을 using UnityEngine.SceneManagement; // 임포트할거에요 -> 씬 전환 기능을 // ══════════════════════════════════════════════════════════════════ // BossPortal — 보스전 씬 이동 포탈 // 역할: 플레이어가 트리거 안에 진입 → F키 입력 → 보스전 씬으로 전환 // 의존: SceneLoader (페이드 전환, 싱글톤) // → SceneLoader가 없으면 SceneManager 직접 호출로 폴백 // ══════════════════════════════════════════════════════════════════ public class BossPortal : MonoBehaviour // 클래스를 정의할거에요 -> 보스전 포탈 컴포넌트를 { // ───────────────────────────────────────────────────────────── // Inspector 설정 // ───────────────────────────────────────────────────────────── [Header("씬 설정")] [Tooltip("이동할 보스전 씬 이름 (Build Settings에 등록 필수)")] [SerializeField] private string bossSceneName = "BossScene"; // 변수를 선언할거에요 -> 이동할 보스전 씬 이름을 (Inspector에서 변경 가능) [Header("상호작용 키")] [SerializeField] private KeyCode interactKey = KeyCode.F; // 변수를 선언할거에요 -> 상호작용 키를 (기본값 F) [Header("UI 연결 (선택)")] [Tooltip("트리거 안에 있을 때 표시할 안내 UI (예: 'F키를 눌러 입장')")] [SerializeField] private GameObject interactionUI; // 변수를 선언할거에요 -> 상호작용 안내 UI 오브젝트를 [Header("UI 연출")] [SerializeField] private float uiFadeSpeed = 5f; // 변수를 선언할거에요 -> UI 페이드 속도를 [SerializeField] private float uiFloatSpeed = 2f; // 변수를 선언할거에요 -> UI 둥실거리는 속도를 [SerializeField] private float uiFloatAmplitude = 0.15f; // 변수를 선언할거에요 -> UI 둥실거리는 폭을 [Header("플레이어 태그")] [SerializeField] private string playerTag = "Player"; // 변수를 선언할거에요 -> 플레이어 판별용 태그를 // ───────────────────────────────────────────────────────────── // 내부 상태 (캐싱) // ───────────────────────────────────────────────────────────── private bool _isPlayerInside = false; // 변수를 초기화할거에요 -> 플레이어가 트리거 안에 있는지 여부를 private bool _isTransitioning = false; // 변수를 초기화할거에요 -> 씬 전환 중인지 여부를 (중복 전환 방지) private CanvasGroup _uiCanvasGroup; // 변수를 선언할거에요 -> UI 페이드용 CanvasGroup 캐시를 private Vector3 _uiInitialLocalPos; // 변수를 선언할거에요 -> UI 초기 위치를 (둥실거림 기준점) private float _currentUIAlpha = 0f; // 변수를 초기화할거에요 -> 현재 UI 투명도를 // ───────────────────────────────────────────────────────────── // 초기화 // ───────────────────────────────────────────────────────────── private void Awake() // Awake에서 초기화할거에요 -> 컴포넌트 캐싱을 { SetupInteractionUI(); // 실행할거에요 -> 상호작용 UI 초기 설정을 } /// /// 상호작용 UI의 CanvasGroup 캐싱 + 초기 비활성화 /// private void SetupInteractionUI() // 함수를 정의할거에요 -> UI 초기 설정을 { if (interactionUI == null) return; // 중단할거에요 -> UI가 연결 안 됐으면 (UI 없이도 작동 가능) _uiInitialLocalPos = interactionUI.transform.localPosition; // 저장할거에요 -> UI의 초기 로컬 위치를 _uiCanvasGroup = interactionUI.GetComponent(); // 가져올거에요 -> CanvasGroup 컴포넌트를 if (_uiCanvasGroup == null) // 조건이 맞으면 실행할거에요 -> CanvasGroup이 없으면 { _uiCanvasGroup = interactionUI.AddComponent(); // 추가할거에요 -> CanvasGroup을 자동으로 } _uiCanvasGroup.alpha = 0f; // 설정할거에요 -> 초기 투명도를 0(완전 투명)으로 interactionUI.SetActive(true); // 활성화할거에요 -> UI 오브젝트를 (alpha로 보이기/숨기기 제어) } // ───────────────────────────────────────────────────────────── // 매 프레임 처리 // ───────────────────────────────────────────────────────────── private void Update() // 매 프레임 실행할거에요 -> 입력 감지와 UI 업데이트를 { UpdateInteractionUI(); // 실행할거에요 -> UI 페이드/둥실거림 연출을 if (!_isPlayerInside) return; // 중단할거에요 -> 플레이어가 트리거 밖이면 if (_isTransitioning) return; // 중단할거에요 -> 이미 씬 전환 중이면 (중복 방지) if (Input.GetKeyDown(interactKey)) // 조건이 맞으면 실행할거에요 -> F키를 눌렀으면 { StartSceneTransition(); // 실행할거에요 -> 씬 전환을 } } // ───────────────────────────────────────────────────────────── // 트리거 감지 // ───────────────────────────────────────────────────────────── private void OnTriggerEnter(Collider other) // 트리거 진입 시 실행할거에요 { if (other == null) return; // 방어할거에요 -> null 체크를 if (!other.CompareTag(playerTag)) return; // 중단할거에요 -> 플레이어가 아니면 _isPlayerInside = true; // 설정할거에요 -> 플레이어가 트리거 안에 있음으로 Debug.Log("[BossPortal] 플레이어 포탈 범위 진입 — F키로 보스전 입장 가능"); // 로그를 찍을거에요 } private void OnTriggerExit(Collider other) // 트리거 이탈 시 실행할거에요 { if (other == null) return; // 방어할거에요 -> null 체크를 if (!other.CompareTag(playerTag)) return; // 중단할거에요 -> 플레이어가 아니면 _isPlayerInside = false; // 설정할거에요 -> 플레이어가 트리거 밖으로 나감으로 Debug.Log("[BossPortal] 플레이어 포탈 범위 이탈"); // 로그를 찍을거에요 } // ───────────────────────────────────────────────────────────── // 씬 전환 실행 // ───────────────────────────────────────────────────────────── /// /// 보스전 씬으로 전환 — SceneLoader(페이드) 우선, 없으면 직접 로드 /// private void StartSceneTransition() // 함수를 정의할거에요 -> 씬 전환 처리를 { // 씬 이름 유효성 검사 if (string.IsNullOrEmpty(bossSceneName)) // 조건이 맞으면 실행할거에요 -> 씬 이름이 비어있으면 { Debug.LogError("[BossPortal] bossSceneName이 비어있어요! Inspector에서 씬 이름을 설정해주세요."); // 에러를 찍을거에요 return; // 중단할거에요 -> 전환 불가 } _isTransitioning = true; // 설정할거에요 -> 전환 중 플래그를 (중복 전환 차단) Debug.Log($"[BossPortal] 보스전 씬으로 이동 시작: {bossSceneName}"); // 로그를 찍을거에요 // SceneLoader 싱글톤이 있으면 페이드 전환 사용 if (SceneLoader.Instance != null) // 조건이 맞으면 실행할거에요 -> SceneLoader가 존재하면 { SceneLoader.Instance.LoadSceneWithFade(bossSceneName); // 실행할거에요 -> 페이드 효과와 함께 씬 로드를 } else // 조건이 틀리면 실행할거에요 -> SceneLoader가 없으면 { Debug.LogWarning("[BossPortal] SceneLoader를 찾을 수 없어요. 직접 씬 로드합니다."); // 경고를 찍을거에요 SceneManager.LoadScene(bossSceneName); // 실행할거에요 -> 동기 씬 로드를 (페이드 없음, 폴백) } } // ───────────────────────────────────────────────────────────── // UI 연출 (페이드 + 둥실거림) // ───────────────────────────────────────────────────────────── /// /// 상호작용 UI의 페이드 인/아웃 + 둥실둥실 효과 /// GC 할당 없음 — Update에서 매 프레임 호출 /// private void UpdateInteractionUI() // 함수를 정의할거에요 -> UI 연출 업데이트를 { if (_uiCanvasGroup == null) return; // 중단할거에요 -> UI가 없으면 // ── 페이드 인/아웃 ── float targetAlpha = _isPlayerInside ? 1f : 0f; // 계산할거에요 -> 목표 투명도를 (안에 있으면 1, 밖이면 0) _currentUIAlpha = Mathf.MoveTowards(_currentUIAlpha, targetAlpha, Time.deltaTime * uiFadeSpeed); // 보간할거에요 -> 현재 알파를 목표로 _uiCanvasGroup.alpha = _currentUIAlpha; // 적용할거에요 -> 계산된 투명도를 UI에 // ── 둥실거림 (알파가 0에 가까우면 연산 스킵) ── if (_currentUIAlpha > 0.01f && interactionUI != null) // 조건이 맞으면 실행할거에요 -> UI가 보이면 { float yOffset = Mathf.Sin(Time.time * uiFloatSpeed) * uiFloatAmplitude; // 계산할거에요 -> Y축 오프셋을 (사인파 둥실거림) interactionUI.transform.localPosition = _uiInitialLocalPos + new Vector3(0f, yOffset, 0f); // 적용할거에요 -> 둥실거리는 위치를 } } // ───────────────────────────────────────────────────────────── // Gizmo (씬 뷰에서 포탈 범위 시각화) // ───────────────────────────────────────────────────────────── private void OnDrawGizmos() // 씬 뷰에서 그릴거에요 -> 포탈 범위 기즈모를 { Collider col = GetComponent(); // 가져올거에요 -> 콜라이더를 (범위 시각화용) Gizmos.color = new Color(0.5f, 0f, 1f, 0.25f); // 설정할거에요 -> 기즈모 색상을 (보라색 반투명) if (col is BoxCollider box) // 조건이 맞으면 실행할거에요 -> 박스 콜라이더면 { Gizmos.matrix = transform.localToWorldMatrix; // 설정할거에요 -> 기즈모 좌표계를 로컬 기준으로 Gizmos.DrawCube(box.center, box.size); // 그릴거에요 -> 박스 범위를 Gizmos.DrawWireCube(box.center, box.size); // 그릴거에요 -> 박스 외곽선을 } else if (col is SphereCollider sphere) // 조건이 맞으면 실행할거에요 -> 구형 콜라이더면 { Gizmos.DrawSphere(transform.position + sphere.center, sphere.radius); // 그릴거에요 -> 구 범위를 Gizmos.DrawWireSphere(transform.position + sphere.center, sphere.radius); // 그릴거에요 -> 구 외곽선을 } } }