Projext/Assets/02_Scripts/Systems/Scene/BossPortal.cs
hydrozen e989d20668 카툰 쉐이더 추가 + 중복 스크립트 수정 + 전체 업데이트
- ToonPostProcess.shader: 횃불 고딕 스타일 후처리 쉐이더 (Built-in RP)
- ToonCameraEffect.cs: 카메라 자동 부착 후처리 스크립트
- 중복 UI 스크립트 제거 (MenuIntroController, ToggleCustom)
- 씬, 프리팹, 애니메이션 등 전체 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:31:16 +09:00

188 lines
13 KiB
C#

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 초기 설정을
}
/// <summary>
/// 상호작용 UI의 CanvasGroup 캐싱 + 초기 비활성화
/// </summary>
private void SetupInteractionUI() // 함수를 정의할거에요 -> UI 초기 설정을
{
if (interactionUI == null) return; // 중단할거에요 -> UI가 연결 안 됐으면 (UI 없이도 작동 가능)
_uiInitialLocalPos = interactionUI.transform.localPosition; // 저장할거에요 -> UI의 초기 로컬 위치를
_uiCanvasGroup = interactionUI.GetComponent<CanvasGroup>(); // 가져올거에요 -> CanvasGroup 컴포넌트를
if (_uiCanvasGroup == null) // 조건이 맞으면 실행할거에요 -> CanvasGroup이 없으면
{
_uiCanvasGroup = interactionUI.AddComponent<CanvasGroup>(); // 추가할거에요 -> 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] 플레이어 포탈 범위 이탈"); // 로그를 찍을거에요
}
// ─────────────────────────────────────────────────────────────
// 씬 전환 실행
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 보스전 씬으로 전환 — SceneLoader(페이드) 우선, 없으면 직접 로드
/// </summary>
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 연출 (페이드 + 둥실거림)
// ─────────────────────────────────────────────────────────────
/// <summary>
/// 상호작용 UI의 페이드 인/아웃 + 둥실둥실 효과
/// GC 할당 없음 — Update에서 매 프레임 호출
/// </summary>
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<Collider>(); // 가져올거에요 -> 콜라이더를 (범위 시각화용)
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); // 그릴거에요 -> 구 외곽선을
}
}
}