Projext/Assets/Scripts/Enemy/BossAI/BossCounterFeedback.cs
2026-02-13 00:23:25 +09:00

185 lines
11 KiB
C#

using System.Collections; // 코루틴 기능을 사용할거에요 -> System.Collections를
using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을
using UnityEngine.UI; // UI 기능을 사용할거에요 -> UnityEngine.UI를
using TMPro; // 텍스트메쉬프로 기능을 사용할거에요 -> TMPro를
/// <summary>
/// 보스 카운터 시스템의 연출/UI를 담당합니다.
/// </summary>
public class BossCounterFeedback : MonoBehaviour // 클래스를 선언할거에요 -> MonoBehaviour를 상속받는 BossCounterFeedback을
{
[Header("참조")] // 인스펙터 창에 제목을 표시할거에요 -> 참조 를
[SerializeField] private BossCounterSystem counterSystem; // 변수를 선언할거에요 -> 카운터 시스템 스크립트를 연결할 counterSystem을
[Header("UI 요소 (선택)")] // 인스펙터 창에 제목을 표시할거에요 -> UI 요소 (선택) 을
[SerializeField] private TextMeshProUGUI bossDialogueText; // 변수를 선언할거에요 -> 보스 대사를 표시할 텍스트 UI를
[SerializeField] private CanvasGroup dialogueCanvasGroup; // 변수를 선언할거에요 -> 대사창의 투명도를 조절할 캔버스 그룹을
[SerializeField] private float dialogueDisplayDuration = 3f; // 변수를 선언할거에요 -> 대사 표시 시간(3초)을 dialogueDisplayDuration에
[SerializeField] private float dialogueFadeDuration = 0.5f; // 변수를 선언할거에요 -> 대사 페이드 시간(0.5초)을 dialogueFadeDuration에
[Header("카운터별 보스 대사")] // 인스펙터 창에 제목을 표시할거에요 -> 카운터별 보스 대사 를
[SerializeField]
private string[] dodgeCounterDialogues = new string[] // 배열을 초기화할거에요 -> 회피 카운터 대사 목록을
{
"...또 도망치려는 건가.",
"네 발은 이미 읽었다.",
"아무리 피해도 소용없어."
};
[SerializeField]
private string[] aimCounterDialogues = new string[] // 배열을 초기화할거에요 -> 조준 카운터 대사 목록을
{
"그렇게 오래 노려봐야...",
"느린 조준은 빈틈이지.",
"시간을 줄 생각은 없다."
};
[SerializeField]
private string[] pierceCounterDialogues = new string[] // 배열을 초기화할거에요 -> 관통 카운터 대사 목록을
{
"그 화살은 더 이상 통하지 않아.",
"관통? 이번엔 막아주지.",
"같은 수를 반복하다니."
};
[SerializeField]
private string[] habitChangeDialogues = new string[] // 배열을 초기화할거에요 -> 습관 변경 보상 대사 목록을
{
"...다른 수를 쓰는 건가.",
"흥, 조금은 배운 모양이군.",
"이번엔 다르군... 재밌어."
};
// ── 잠금 해제 여부에 따른 첫 발동 대사 ──
[Header("첫 잠금 해제 시 대사 (서사 강조)")] // 인스펙터 창에 제목을 표시할거에요 -> 첫 잠금 해제 시 대사 (서사 강조) 를
[SerializeField]
private string[] firstUnlockDialogues = new string[] // 배열을 초기화할거에요 -> 첫 해금 시 출력할 대사 목록을
{
"...기억했다. 네 버릇을.",
"이제 알겠어. 네 전투 방식이.",
"한 번이면 충분해. 패턴을 읽었다."
};
private Coroutine dialogueCoroutine; // 변수를 선언할거에요 -> 실행 중인 대사 코루틴을 저장할 dialogueCoroutine을
private void OnEnable() // 함수를 실행할거에요 -> 오브젝트 활성화 시 호출되는 OnEnable을
{
if (counterSystem != null) // 조건이 맞으면 실행할거에요 -> 카운터 시스템이 연결되어 있다면
{
counterSystem.OnCounterActivated.AddListener(OnCounterActivated); // 구독할거에요 -> 카운터 활성 이벤트에 OnCounterActivated를
counterSystem.OnCounterDeactivated.AddListener(OnCounterDeactivated); // 구독할거에요 -> 카운터 비활성 이벤트에 OnCounterDeactivated를
counterSystem.OnHabitChangeRewarded.AddListener(OnHabitChangeRewarded); // 구독할거에요 -> 보상 이벤트에 OnHabitChangeRewarded를
}
}
private void OnDisable() // 함수를 실행할거에요 -> 오브젝트 비활성화 시 호출되는 OnDisable을
{
if (counterSystem != null) // 조건이 맞으면 실행할거에요 -> 카운터 시스템이 연결되어 있다면
{
counterSystem.OnCounterActivated.RemoveListener(OnCounterActivated); // 구독 해제할거에요 -> 카운터 활성 이벤트를
counterSystem.OnCounterDeactivated.RemoveListener(OnCounterDeactivated); // 구독 해제할거에요 -> 카운터 비활성 이벤트를
counterSystem.OnHabitChangeRewarded.RemoveListener(OnHabitChangeRewarded); // 구독 해제할거에요 -> 보상 이벤트를
}
}
// ═══════════════════════════════════════════
// 이벤트 핸들러
// ═══════════════════════════════════════════
private void OnCounterActivated(CounterType type) // 함수를 선언할거에요 -> 카운터 활성 시 호출될 OnCounterActivated를
{
var persistence = BossCounterPersistence.Instance; // 변수를 가져올거에요 -> 영구 저장소 인스턴스를 persistence에
// 첫 잠금 해제인지 확인 (총 발동 횟수가 1이면 방금 처음 잠금 해제된 것)
bool isFirstUnlock = false; // 변수를 초기화할거에요 -> 첫 해금 여부를 거짓(false)으로
if (persistence != null) // 조건이 맞으면 실행할거에요 -> 저장소가 있다면
{
int activations = type switch // 값을 가져올거에요 -> 타입에 따른 발동 횟수를
{
CounterType.Dodge => persistence.Data.dodgeCounterActivations,
CounterType.Aim => persistence.Data.aimCounterActivations,
CounterType.Pierce => persistence.Data.pierceCounterActivations,
_ => 0
};
isFirstUnlock = (activations <= 1); // 판단할거에요 -> 발동 횟수가 1 이하라면 첫 해금이라고
}
// 대사 선택
string dialogue; // 변수를 선언할거에요 -> 출력할 대사를 담을 dialogue를
if (isFirstUnlock) // 조건이 맞으면 실행할거에요 -> 첫 해금이라면
{
dialogue = firstUnlockDialogues[Random.Range(0, firstUnlockDialogues.Length)]; // 선택할거에요 -> 첫 해금 대사 중 하나를 랜덤으로
}
else // 조건이 틀리면 실행할거에요 -> 이미 해금된 상태라면
{
dialogue = type switch // 분기할거에요 -> 카운터 타입에 따라
{
CounterType.Dodge => dodgeCounterDialogues[Random.Range(0, dodgeCounterDialogues.Length)], // 선택할거에요 -> 회피 대사를
CounterType.Aim => aimCounterDialogues[Random.Range(0, aimCounterDialogues.Length)], // 선택할거에요 -> 조준 대사를
CounterType.Pierce => pierceCounterDialogues[Random.Range(0, pierceCounterDialogues.Length)], // 선택할거에요 -> 관통 대사를
_ => "" // 기본값은 빈 문자열로
};
}
ShowDialogue(dialogue); // 함수를 실행할거에요 -> 선택된 대사를 화면에 띄우는 ShowDialogue를
Debug.Log($"[BossCounterFeedback] {type} 카운터 연출 재생" + (isFirstUnlock ? " (첫 잠금 해제!)" : "")); // 로그를 출력할거에요 -> 연출 재생 알림을
}
private void OnCounterDeactivated(CounterType type) // 함수를 선언할거에요 -> 카운터 종료 시 호출될 OnCounterDeactivated를
{
// 카운터 해제 시 연출 (이펙트 종료 등)
Debug.Log($"[BossCounterFeedback] {type} 카운터 연출 종료"); // 로그를 출력할거에요 -> 연출 종료 알림을
}
private void OnHabitChangeRewarded() // 함수를 선언할거에요 -> 보상 지급 시 호출될 OnHabitChangeRewarded를
{
string dialogue = habitChangeDialogues[Random.Range(0, habitChangeDialogues.Length)]; // 선택할거에요 -> 보상 대사 중 하나를 랜덤으로
ShowDialogue(dialogue); // 함수를 실행할거에요 -> 대사를 띄우는 ShowDialogue를
Debug.Log("[BossCounterFeedback] 습관 변경 보상 연출!"); // 로그를 출력할거에요 -> 보상 연출 알림을
}
// ═══════════════════════════════════════════
// 대사 표시
// ═══════════════════════════════════════════
private void ShowDialogue(string text) // 함수를 선언할거에요 -> 텍스트를 UI에 표시하는 ShowDialogue를
{
if (bossDialogueText == null || dialogueCanvasGroup == null) return; // 조건이 맞으면 중단할거에요 -> UI 요소가 연결되지 않았다면
if (dialogueCoroutine != null) // 조건이 맞으면 실행할거에요 -> 이미 실행 중인 대사가 있다면
StopCoroutine(dialogueCoroutine); // 중단할거에요 -> 기존 코루틴을
dialogueCoroutine = StartCoroutine(DialogueRoutine(text)); // 코루틴을 시작할거에요 -> 새 대사를 출력하는 DialogueRoutine을
}
private IEnumerator DialogueRoutine(string text) // 코루틴 함수를 선언할거에요 -> 페이드 효과와 함께 대사를 출력하는 DialogueRoutine을
{
bossDialogueText.text = text; // 값을 설정할거에요 -> UI 텍스트 내용을 전달받은 text로
dialogueCanvasGroup.alpha = 0f; // 값을 설정할거에요 -> 투명도를 0(안 보임)으로
// 페이드 인
float t = 0f; // 변수를 초기화할거에요 -> 경과 시간을 0으로
while (t < dialogueFadeDuration) // 반복할거에요 -> 경과 시간이 페이드 시간보다 작을 동안
{
t += Time.deltaTime; // 값을 더할거에요 -> 경과 시간에 프레임 시간을
dialogueCanvasGroup.alpha = t / dialogueFadeDuration; // 값을 조절할거에요 -> 투명도를 서서히 1로
yield return null; // 대기할거에요 -> 다음 프레임까지
}
dialogueCanvasGroup.alpha = 1f; // 값을 확정할거에요 -> 투명도를 완전 불투명(1)으로
// 유지
yield return new WaitForSeconds(dialogueDisplayDuration); // 기다릴거에요 -> 대사 유지 시간만큼
// 페이드 아웃
t = 0f; // 변수를 초기화할거에요 -> 경과 시간을 다시 0으로
while (t < dialogueFadeDuration) // 반복할거에요 -> 경과 시간이 페이드 시간보다 작을 동안
{
t += Time.deltaTime; // 값을 더할거에요 -> 경과 시간에 프레임 시간을
dialogueCanvasGroup.alpha = 1f - (t / dialogueFadeDuration); // 값을 조절할거에요 -> 투명도를 서서히 0으로
yield return null; // 대기할거에요 -> 다음 프레임까지
}
dialogueCanvasGroup.alpha = 0f; // 값을 확정할거에요 -> 투명도를 완전 투명(0)으로
}
}