2026-02-12 15:23:25 +00:00
|
|
|
using System.Collections.Generic; // 리스트와 딕셔너리 기능을 사용할거에요 -> System.Collections.Generic을
|
|
|
|
|
using UnityEngine; // 유니티 엔진의 기본 기능을 불러올거에요 -> UnityEngine을
|
|
|
|
|
using UnityEngine.Events; // 유니티 이벤트 기능을 사용할거에요 -> UnityEngine.Events를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 보스 카운터 시스템 메인 컨트롤러.
|
2026-02-11 13:47:53 +00:00
|
|
|
/// (수정됨: Config나 Player가 없어도 에러가 나지 않도록 안전장치가 추가된 버전)
|
2026-02-10 15:29:22 +00:00
|
|
|
/// </summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
public class BossCounterSystem : MonoBehaviour // 클래스를 선언할거에요 -> MonoBehaviour를 상속받는 BossCounterSystem을
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
[Header("참조")] // 인스펙터 창에 제목을 표시할거에요 -> 참조 를
|
|
|
|
|
[SerializeField] private BossCounterConfig config; // 변수를 선언할거에요 -> 카운터 설정 파일(BossCounterConfig)을 담을 config를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
[Header("이벤트 (연출/UI 연동)")] // 인스펙터 창에 제목을 표시할거에요 -> 이벤트 (연출/UI 연동) 을
|
|
|
|
|
[Tooltip("카운터 모드가 활성화될 때 발생 (CounterType 전달)")] // 마우스를 올리면 설명을 보여줄거에요 -> 툴팁 내용을
|
|
|
|
|
public UnityEvent<CounterType> OnCounterActivated; // 이벤트를 선언할거에요 -> 카운터가 켜질 때 알릴 OnCounterActivated를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
[Tooltip("카운터 모드가 비활성화될 때 발생")] // 마우스를 올리면 설명을 보여줄거에요 -> 툴팁 내용을
|
|
|
|
|
public UnityEvent<CounterType> OnCounterDeactivated; // 이벤트를 선언할거에요 -> 카운터가 꺼질 때 알릴 OnCounterDeactivated를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
[Tooltip("보스가 카운터 패턴을 선택했을 때 발생 (패턴 이름 전달)")] // 마우스를 올리면 설명을 보여줄거에요 -> 툴팁 내용을
|
|
|
|
|
public UnityEvent<string> OnCounterPatternSelected; // 이벤트를 선언할거에요 -> 패턴이 선택됐을 때 알릴 OnCounterPatternSelected를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
[Tooltip("플레이어가 습관을 바꿔서 보상받을 때 발생")] // 마우스를 올리면 설명을 보여줄거에요 -> 툴팁 내용을
|
|
|
|
|
public UnityEvent OnHabitChangeRewarded; // 이벤트를 선언할거에요 -> 습관 변경 보상을 알릴 OnHabitChangeRewarded를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// ── 카운터 모드 상태 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
private Dictionary<CounterType, bool> activeCounters = new Dictionary<CounterType, bool>() // 변수를 선언하고 초기화할거에요 -> 각 카운터 타입의 활성 상태를 저장할 딕셔너리 activeCounters를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
{ CounterType.Dodge, false }, // 값을 넣을거에요 -> 회피 카운터 초기값을 거짓(false)으로
|
|
|
|
|
{ CounterType.Aim, false }, // 값을 넣을거에요 -> 조준 카운터 초기값을 거짓(false)으로
|
|
|
|
|
{ CounterType.Pierce, false } // 값을 넣을거에요 -> 관통 카운터 초기값을 거짓(false)으로
|
2026-02-10 15:29:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── Decay 타이머 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
private Dictionary<CounterType, float> counterTimers = new Dictionary<CounterType, float>() // 변수를 선언하고 초기화할거에요 -> 각 카운터의 남은 지속 시간을 저장할 딕셔너리 counterTimers를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
{ CounterType.Dodge, 0f }, // 값을 넣을거에요 -> 회피 카운터 타이머를 0으로
|
|
|
|
|
{ CounterType.Aim, 0f }, // 값을 넣을거에요 -> 조준 카운터 타이머를 0으로
|
|
|
|
|
{ CounterType.Pierce, 0f } // 값을 넣을거에요 -> 관통 카운터 타이머를 0으로
|
2026-02-10 15:29:22 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ── 쿨타임 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
private float lastCounterPatternTime = -100f; // 변수를 선언할거에요 -> 마지막 카운터 패턴 사용 시간을 lastCounterPatternTime에 (초기값 -100)
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// ── 습관 변경 보상 추적 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
private HashSet<CounterType> previousRunCounters = new HashSet<CounterType>(); // 변수를 선언할거에요 -> 지난 런에서 당했던 카운터 목록을 previousRunCounters에
|
|
|
|
|
private HashSet<CounterType> currentRunActivatedCounters = new HashSet<CounterType>(); // 변수를 선언할거에요 -> 이번 런에서 발동된 카운터 목록을 currentRunActivatedCounters에
|
|
|
|
|
private bool habitChangeRewardGranted = false; // 변수를 선언할거에요 -> 보상이 이미 지급되었는지 여부를 habitChangeRewardGranted에
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// ── 보스 패턴 가중치 정의 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
[System.Serializable] // 직렬화할거에요 -> 인스펙터에서 수정할 수 있게 BossPattern 클래스를
|
|
|
|
|
public class BossPattern // 내부 클래스를 선언할거에요 -> 보스 패턴 정보를 담을 BossPattern을
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
public string patternName; // 변수를 선언할거에요 -> 패턴 이름을 저장할 patternName을
|
|
|
|
|
public float baseWeight = 1f; // 변수를 선언할거에요 -> 기본 가중치(확률)를 baseWeight에
|
|
|
|
|
[Tooltip("이 패턴이 카운터 패턴인 경우 해당 타입")] // 마우스를 올리면 설명을 보여줄거에요 -> 툴팁 내용을
|
|
|
|
|
public CounterType counterType = CounterType.None; // 변수를 선언할거에요 -> 이 패턴이 어떤 카운터 타입인지 지정할 counterType을
|
|
|
|
|
[Tooltip("카운터 발동 시 보조적으로 가중치가 올라가는 타입")] // 마우스를 올리면 설명을 보여줄거에요 -> 툴팁 내용을
|
|
|
|
|
public CounterType subCounterType = CounterType.None; // 변수를 선언할거에요 -> 보너스 가중치를 받을 보조 카운터 타입 subCounterType을
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
[Header("보스 패턴 목록")] // 인스펙터 창에 제목을 표시할거에요 -> 보스 패턴 목록 을
|
|
|
|
|
[SerializeField] private List<BossPattern> bossPatterns = new List<BossPattern>(); // 리스트를 선언할거에요 -> 보스의 모든 패턴 정보를 담을 bossPatterns를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
// 초기화
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
private void Start() // 함수를 실행할거에요 -> 게임 시작 시 호출되는 Start를
|
2026-02-11 13:47:53 +00:00
|
|
|
{
|
|
|
|
|
// 🚨 [안전장치] 설정 파일이 없으면 경고 출력
|
2026-02-12 15:23:25 +00:00
|
|
|
if (config == null) // 조건이 맞으면 실행할거에요 -> 설정 파일(config)이 연결되지 않았다면
|
2026-02-11 13:47:53 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
Debug.LogWarning("⚠ [BossCounterSystem] Config(설정 파일)가 없습니다! AI가 정상 작동하지 않을 수 있습니다."); // 경고 로그를 출력할거에요 -> 설정 파일 누락 경고를
|
2026-02-11 13:47:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 15:29:22 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// 보스 전투 시작 시 호출. 이전 런에서 발동된 카운터 정보를 기반으로 초기화.
|
|
|
|
|
/// </summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
public void InitializeBattle() // 함수를 선언할거에요 -> 전투 시작 시 초기화를 담당할 InitializeBattle을
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
|
|
|
|
// 이전 런에서 잠금 해제된 카운터들을 기록 (습관 변경 보상 판단용)
|
2026-02-12 15:23:25 +00:00
|
|
|
previousRunCounters.Clear(); // 비울거에요 -> 이전 런 카운터 목록을
|
|
|
|
|
currentRunActivatedCounters.Clear(); // 비울거에요 -> 이번 런 카운터 목록을
|
|
|
|
|
habitChangeRewardGranted = false; // 초기화할거에요 -> 보상 지급 여부를 거짓(false)으로
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
var persistence = BossCounterPersistence.Instance; // 변수를 가져올거에요 -> 영구 저장소 인스턴스를 persistence에
|
|
|
|
|
if (persistence != null) // 조건이 맞으면 실행할거에요 -> 저장소가 존재한다면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (persistence.Data.dodgeCounterActivations > 0 && persistence.IsUnlocked(CounterType.Dodge)) // 조건이 맞으면 실행할거에요 -> 회피 카운터 기록이 있고 해제되었다면
|
|
|
|
|
previousRunCounters.Add(CounterType.Dodge); // 추가할거에요 -> 회피 타입을 이전 런 목록에
|
|
|
|
|
if (persistence.Data.aimCounterActivations > 0 && persistence.IsUnlocked(CounterType.Aim)) // 조건이 맞으면 실행할거에요 -> 조준 카운터 기록이 있고 해제되었다면
|
|
|
|
|
previousRunCounters.Add(CounterType.Aim); // 추가할거에요 -> 조준 타입을 이전 런 목록에
|
|
|
|
|
if (persistence.Data.pierceCounterActivations > 0 && persistence.IsUnlocked(CounterType.Pierce)) // 조건이 맞으면 실행할거에요 -> 관통 카운터 기록이 있고 해제되었다면
|
|
|
|
|
previousRunCounters.Add(CounterType.Pierce); // 추가할거에요 -> 관통 타입을 이전 런 목록에
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 모든 카운터 모드 OFF
|
2026-02-12 15:23:25 +00:00
|
|
|
foreach (var type in new[] { CounterType.Dodge, CounterType.Aim, CounterType.Pierce }) // 반복할거에요 -> 모든 카운터 타입(회피, 조준, 관통)에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
activeCounters[type] = false; // 상태를 바꿀거에요 -> 해당 카운터를 비활성화(false)로
|
|
|
|
|
counterTimers[type] = 0f; // 값을 바꿀거에요 -> 해당 카운터 타이머를 0으로
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
lastCounterPatternTime = -100f; // 값을 초기화할거에요 -> 마지막 패턴 사용 시간을 -100으로
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
Debug.Log($"[BossCounter] 전투 시작. 이전 런 카운터: [{string.Join(", ", previousRunCounters)}]"); // 로그를 출력할거에요 -> 전투 시작 알림과 이전 런 정보를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
// 매 프레임 업데이트
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
private void Update() // 함수를 실행할거에요 -> 매 프레임마다 호출되는 Update를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-11 13:47:53 +00:00
|
|
|
// 🚨 [안전장치] 플레이어 트래커나 설정 파일이 없으면 실행 중지 (NullReferenceException 방지)
|
2026-02-12 15:23:25 +00:00
|
|
|
if (PlayerBehaviorTracker.Instance == null || config == null) return; // 조건이 맞으면 중단할거에요 -> 플레이어 추적기나 설정 파일이 없다면
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
EvaluateCounters(); // 함수를 실행할거에요 -> 카운터 발동 조건을 검사하는 EvaluateCounters를
|
|
|
|
|
DecayCounters(); // 함수를 실행할거에요 -> 카운터 지속 시간을 관리하는 DecayCounters를
|
|
|
|
|
CheckHabitChangeReward(); // 함수를 실행할거에요 -> 습관 변경 보상을 체크하는 CheckHabitChangeReward를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>플레이어 행동 데이터를 읽고 카운터 모드 ON/OFF 판단</summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
private void EvaluateCounters() // 함수를 선언할거에요 -> 카운터 발동 여부를 판단하는 EvaluateCounters를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-11 13:47:53 +00:00
|
|
|
// 🚨 [안전장치] Config가 없으면 계산 불가
|
2026-02-12 15:23:25 +00:00
|
|
|
if (config == null) return; // 조건이 맞으면 중단할거에요 -> 설정 파일이 없다면
|
2026-02-11 13:47:53 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
var tracker = PlayerBehaviorTracker.Instance; // 변수를 가져올거에요 -> 플레이어 행동 추적기를 tracker에
|
|
|
|
|
var persistence = BossCounterPersistence.Instance; // 변수를 가져올거에요 -> 영구 저장소를 persistence에
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// ── 회피 카운터 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
bool dodgeUnlocked = persistence != null && persistence.IsUnlocked(CounterType.Dodge); // 값을 확인할거에요 -> 회피 카운터가 해금되었는지 여부를
|
|
|
|
|
int dodgeThreshold = config.GetEffectiveDodgeThreshold(dodgeUnlocked); // 값을 가져올거에요 -> 현재 적용할 회피 임계값을
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (tracker.DodgeCount >= dodgeThreshold) // 조건이 맞으면 실행할거에요 -> 플레이어 회피 횟수가 임계값 이상이라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
|
|
|
|
// 잠금 해제 안 된 상태면 첫 발동 시 잠금 해제 (첫 런에서도 발동 가능)
|
|
|
|
|
// 잠금 해제된 상태면 더 낮은 임계치로 발동
|
2026-02-12 15:23:25 +00:00
|
|
|
ActivateCounter(CounterType.Dodge); // 함수를 실행할거에요 -> 회피 카운터를 발동시키는 ActivateCounter를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 조준 카운터 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
bool aimUnlocked = persistence != null && persistence.IsUnlocked(CounterType.Aim); // 값을 확인할거에요 -> 조준 카운터가 해금되었는지 여부를
|
|
|
|
|
float aimThreshold = config.GetEffectiveAimThreshold(aimUnlocked); // 값을 가져올거에요 -> 현재 적용할 조준 임계값을
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (tracker.AimHoldTime >= aimThreshold) // 조건이 맞으면 실행할거에요 -> 플레이어 조준 시간이 임계값 이상이라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
ActivateCounter(CounterType.Aim); // 함수를 실행할거에요 -> 조준 카운터를 발동시키는 ActivateCounter를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── 관통 카운터 ──
|
2026-02-12 15:23:25 +00:00
|
|
|
bool pierceUnlocked = persistence != null && persistence.IsUnlocked(CounterType.Pierce); // 값을 확인할거에요 -> 관통 카운터가 해금되었는지 여부를
|
|
|
|
|
float pierceThreshold = config.GetEffectivePierceThreshold(pierceUnlocked); // 값을 가져올거에요 -> 현재 적용할 관통 임계값을
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (tracker.TotalShotsInWindow >= config.minShotsForPierceCheck // 조건이 맞으면 실행할거에요 -> 최소 발사 횟수를 충족하고
|
|
|
|
|
&& tracker.PierceRatio >= pierceThreshold) // 조건이 맞으면 실행할거에요 -> 관통 공격 비율이 임계값 이상이라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
ActivateCounter(CounterType.Pierce); // 함수를 실행할거에요 -> 관통 카운터를 발동시키는 ActivateCounter를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
private void ActivateCounter(CounterType type) // 함수를 선언할거에요 -> 특정 카운터를 활성화하는 ActivateCounter를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-11 13:47:53 +00:00
|
|
|
// 🚨 [안전장치] Config 확인
|
2026-02-12 15:23:25 +00:00
|
|
|
if (config == null) return; // 조건이 맞으면 중단할거에요 -> 설정 파일이 없다면
|
2026-02-11 13:47:53 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
counterTimers[type] = config.counterDecayTime; // 값을 설정할거에요 -> 해당 카운터의 지속 시간을 설정값으로
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (!activeCounters[type]) // 조건이 맞으면 실행할거에요 -> 해당 카운터가 현재 꺼져 있다면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
activeCounters[type] = true; // 상태를 바꿀거에요 -> 카운터 활성 상태를 참(true)으로
|
|
|
|
|
currentRunActivatedCounters.Add(type); // 추가할거에요 -> 이번 런 발동 목록에 해당 타입을
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// 영구 잠금 해제
|
2026-02-12 15:23:25 +00:00
|
|
|
var persistence = BossCounterPersistence.Instance; // 변수를 가져올거에요 -> 영구 저장소를 persistence에
|
|
|
|
|
if (persistence != null) // 조건이 맞으면 실행할거에요 -> 저장소가 있다면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
persistence.UnlockCounter(type); // 함수를 실행할거에요 -> 해당 카운터를 영구 해금하는 UnlockCounter를
|
|
|
|
|
persistence.RecordActivation(type); // 함수를 실행할거에요 -> 발동 횟수를 기록하는 RecordActivation을
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
OnCounterActivated?.Invoke(type); // 이벤트를 실행할거에요 -> 카운터 활성화 알림 이벤트를
|
|
|
|
|
Debug.Log($"[BossCounter] {type} 카운터 활성화!"); // 로그를 출력할거에요 -> 카운터 활성화 메시지를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>시간이 지나면 카운터 모드 자동 해제</summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
private void DecayCounters() // 함수를 선언할거에요 -> 시간이 지나면 카운터를 끄는 DecayCounters를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
foreach (var type in new[] { CounterType.Dodge, CounterType.Aim, CounterType.Pierce }) // 반복할거에요 -> 모든 카운터 타입에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (!activeCounters[type]) continue; // 조건이 맞으면 건너뛸거에요 -> 이미 꺼져 있는 카운터라면
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
counterTimers[type] -= Time.deltaTime; // 값을 뺄거에요 -> 남은 시간에서 프레임 시간을
|
|
|
|
|
if (counterTimers[type] <= 0f) // 조건이 맞으면 실행할거에요 -> 남은 시간이 0 이하라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
activeCounters[type] = false; // 상태를 바꿀거에요 -> 카운터 활성 상태를 거짓(false)으로
|
|
|
|
|
OnCounterDeactivated?.Invoke(type); // 이벤트를 실행할거에요 -> 카운터 비활성화 알림 이벤트를
|
|
|
|
|
Debug.Log($"[BossCounter] {type} 카운터 Decay로 비활성화"); // 로그를 출력할거에요 -> 시간 만료로 인한 비활성화 메시지를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
// 습관 변경 보상
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 이전 런에서 카운터 당한 습관을 이번 런에서 보이지 않으면 보상.
|
|
|
|
|
/// 전투 중반(30초 이후) 한 번 체크.
|
|
|
|
|
/// </summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
private float battleTimer = 0f; // 변수를 초기화할거에요 -> 전투 진행 시간을 0으로
|
|
|
|
|
private const float HABIT_CHECK_TIME = 30f; // 상수를 정의할거에요 -> 습관 체크 시점을 30초로
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
private void CheckHabitChangeReward() // 함수를 선언할거에요 -> 습관 변경 보상을 확인하는 CheckHabitChangeReward를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (habitChangeRewardGranted || previousRunCounters.Count == 0) return; // 조건이 맞으면 중단할거에요 -> 이미 보상을 받았거나 이전 런 기록이 없다면
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
battleTimer += Time.deltaTime; // 값을 더할거에요 -> 전투 진행 시간에 프레임 시간을
|
|
|
|
|
if (battleTimer < HABIT_CHECK_TIME) return; // 조건이 맞으면 중단할거에요 -> 아직 30초가 안 지났다면
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// 이전 런에서 발동된 카운터 중, 이번 런에서 아직 발동 안 된 것이 있으면 보상
|
2026-02-12 15:23:25 +00:00
|
|
|
foreach (var prevCounter in previousRunCounters) // 반복할거에요 -> 이전 런 카운터 목록에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (!currentRunActivatedCounters.Contains(prevCounter)) // 조건이 맞으면 실행할거에요 -> 이번 런 목록에 해당 카운터가 없다면 (습관 고침)
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
habitChangeRewardGranted = true; // 상태를 바꿀거에요 -> 보상 지급 여부를 참(true)으로
|
|
|
|
|
OnHabitChangeRewarded?.Invoke(); // 이벤트를 실행할거에요 -> 보상 지급 알림 이벤트를
|
|
|
|
|
Debug.Log($"[BossCounter] 플레이어가 {prevCounter} 습관을 바꿈! 보상 부여"); // 로그를 출력할거에요 -> 보상 부여 메시지를
|
|
|
|
|
break; // 중단할거에요 -> 보상은 한 번만 주므로 반복문을
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
// 패턴 선택 (보스 AI에서 호출)
|
|
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 현재 카운터 상태를 반영하여 보스 패턴을 가중치 랜덤으로 선택합니다.
|
|
|
|
|
/// 보스 AI의 패턴 선택 시점에 호출하세요.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>선택된 패턴 이름</returns>
|
2026-02-12 15:23:25 +00:00
|
|
|
public string SelectBossPattern() // 함수를 선언할거에요 -> 보스 패턴을 선택해서 반환하는 SelectBossPattern을
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (bossPatterns.Count == 0) // 조건이 맞으면 실행할거에요 -> 등록된 패턴이 하나도 없다면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
Debug.LogWarning("[BossCounter] 보스 패턴이 등록되지 않았습니다!"); // 경고 로그를 출력할거에요 -> 패턴 없음 경고를
|
|
|
|
|
return "Normal"; // 값을 반환할거에요 -> 기본값 "Normal"을
|
2026-02-11 13:47:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 🚨 [안전장치] Config가 없으면 그냥 랜덤 패턴 반환 (멈춤 방지)
|
2026-02-12 15:23:25 +00:00
|
|
|
if (config == null) // 조건이 맞으면 실행할거에요 -> 설정 파일이 없다면
|
2026-02-11 13:47:53 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
int randomIndex = Random.Range(0, bossPatterns.Count); // 값을 랜덤으로 뽑을거에요 -> 0부터 패턴 개수 사이의 인덱스를
|
|
|
|
|
return bossPatterns[randomIndex].patternName; // 값을 반환할거에요 -> 랜덤하게 뽑힌 패턴 이름을
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 가중치 계산
|
2026-02-12 15:23:25 +00:00
|
|
|
float[] weights = new float[bossPatterns.Count]; // 배열을 만들거에요 -> 각 패턴의 가중치를 담을 weights 배열을
|
|
|
|
|
float totalWeight = 0f; // 변수를 초기화할거에요 -> 전체 가중치 합계를 0으로
|
|
|
|
|
float counterWeight = 0f; // 변수를 초기화할거에요 -> 카운터 패턴들의 가중치 합계를 0으로
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
for (int i = 0; i < bossPatterns.Count; i++) // 반복할거에요 -> 모든 패턴에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
weights[i] = bossPatterns[i].baseWeight; // 값을 넣을거에요 -> 기본 가중치를 배열에
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// 카운터 패턴 가중치 증가
|
2026-02-12 15:23:25 +00:00
|
|
|
if (bossPatterns[i].counterType != CounterType.None // 조건이 맞으면 실행할거에요 -> 카운터 타입이 설정되어 있고
|
|
|
|
|
&& activeCounters.ContainsKey(bossPatterns[i].counterType) // 조건이 맞으면 실행할거에요 -> 딕셔너리에 키가 존재하며
|
|
|
|
|
&& activeCounters[bossPatterns[i].counterType]) // 조건이 맞으면 실행할거에요 -> 해당 카운터가 활성 상태라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
|
|
|
|
// 쿨타임 체크
|
2026-02-12 15:23:25 +00:00
|
|
|
if (Time.time - lastCounterPatternTime >= config.counterCooldown) // 조건이 맞으면 실행할거에요 -> 마지막 사용 후 쿨타임이 지났다면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
weights[i] += config.counterWeightBonus; // 값을 더할거에요 -> 카운터 보너스 가중치를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 보조 카운터 가중치
|
2026-02-12 15:23:25 +00:00
|
|
|
if (bossPatterns[i].subCounterType != CounterType.None // 조건이 맞으면 실행할거에요 -> 보조 카운터 타입이 설정되어 있고
|
|
|
|
|
&& activeCounters.ContainsKey(bossPatterns[i].subCounterType) // 조건이 맞으면 실행할거에요 -> 딕셔너리에 키가 존재하며
|
|
|
|
|
&& activeCounters[bossPatterns[i].subCounterType]) // 조건이 맞으면 실행할거에요 -> 해당 보조 카운터가 활성 상태라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
weights[i] += config.counterSubWeightBonus; // 값을 더할거에요 -> 보조 카운터 보너스 가중치를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 습관 변경 보상: 일반 패턴 가중치 감소 (= 난이도 완화)
|
2026-02-12 15:23:25 +00:00
|
|
|
if (habitChangeRewardGranted && bossPatterns[i].counterType == CounterType.None) // 조건이 맞으면 실행할거에요 -> 보상을 받았고 일반 패턴이라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
weights[i] *= (1f - config.habitChangeRewardRatio); // 값을 곱할거에요 -> 가중치를 감소 비율만큼 줄여서
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
totalWeight += weights[i]; // 값을 더할거에요 -> 전체 가중치 합계에 현재 가중치를
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
if (bossPatterns[i].counterType != CounterType.None) // 조건이 맞으면 실행할거에요 -> 카운터 패턴이라면
|
|
|
|
|
counterWeight += weights[i]; // 값을 더할거에요 -> 카운터 가중치 합계에
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 빈도 상한 체크: 카운터 패턴 총 비율이 maxCounterFrequency를 넘지 않도록
|
2026-02-12 15:23:25 +00:00
|
|
|
if (totalWeight > 0f && counterWeight / totalWeight > config.maxCounterFrequency) // 조건이 맞으면 실행할거에요 -> 카운터 패턴 비율이 최대 허용치를 초과한다면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
float allowedCounterWeight = (totalWeight - counterWeight) * config.maxCounterFrequency / (1f - config.maxCounterFrequency); // 값을 계산할거에요 -> 허용 가능한 카운터 총 가중치를
|
|
|
|
|
float scale = allowedCounterWeight / counterWeight; // 값을 계산할거에요 -> 가중치를 줄일 비율(scale)을
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
for (int i = 0; i < bossPatterns.Count; i++) // 반복할거에요 -> 모든 패턴에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (bossPatterns[i].counterType != CounterType.None) // 조건이 맞으면 실행할거에요 -> 카운터 패턴이라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
float bonus = weights[i] - bossPatterns[i].baseWeight; // 값을 계산할거에요 -> 추가된 보너스 가중치를
|
|
|
|
|
weights[i] = bossPatterns[i].baseWeight + bonus * scale; // 값을 수정할거에요 -> 보너스를 비율대로 줄여서 다시 적용
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 총 가중치 재계산
|
2026-02-12 15:23:25 +00:00
|
|
|
totalWeight = 0f; // 값을 초기화할거에요 -> 전체 합계를 0으로
|
|
|
|
|
for (int i = 0; i < weights.Length; i++) // 반복할거에요 -> 모든 가중치에 대해
|
|
|
|
|
totalWeight += weights[i]; // 값을 더할거에요 -> 전체 합계에
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 가중치 랜덤 선택
|
2026-02-12 15:23:25 +00:00
|
|
|
float roll = Random.Range(0f, totalWeight); // 값을 랜덤으로 뽑을거에요 -> 0부터 전체 가중치 사이의 값을 roll에
|
|
|
|
|
float cumulative = 0f; // 변수를 초기화할거에요 -> 누적 가중치를 0으로
|
2026-02-10 15:29:22 +00:00
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
for (int i = 0; i < bossPatterns.Count; i++) // 반복할거에요 -> 모든 패턴에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
cumulative += weights[i]; // 값을 더할거에요 -> 누적 가중치에 현재 가중치를
|
|
|
|
|
if (roll <= cumulative) // 조건이 맞으면 실행할거에요 -> 랜덤값이 누적치보다 작거나 같다면 (당첨)
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
string selected = bossPatterns[i].patternName; // 값을 저장할거에요 -> 선택된 패턴 이름을 selected에
|
2026-02-10 15:29:22 +00:00
|
|
|
|
|
|
|
|
// 카운터 패턴이면 쿨타임 기록
|
2026-02-12 15:23:25 +00:00
|
|
|
if (bossPatterns[i].counterType != CounterType.None) // 조건이 맞으면 실행할거에요 -> 카운터 패턴이라면
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
lastCounterPatternTime = Time.time; // 값을 저장할거에요 -> 현재 시간을 마지막 사용 시간으로
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
OnCounterPatternSelected?.Invoke(selected); // 이벤트를 실행할거에요 -> 패턴 선택 알림 이벤트를
|
|
|
|
|
return selected; // 값을 반환할거에요 -> 선택된 패턴 이름을
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 15:23:25 +00:00
|
|
|
return bossPatterns[bossPatterns.Count - 1].patternName; // 값을 반환할거에요 -> 만약 선택되지 않았다면 마지막 패턴을 (안전장치)
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════
|
2026-02-11 13:47:53 +00:00
|
|
|
// 외부 조회 (여기가 아까 빠졌던 부분입니다!)
|
2026-02-10 15:29:22 +00:00
|
|
|
// ═══════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
/// <summary>특정 카운터 모드가 현재 활성 상태인지 확인</summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
public bool IsCounterActive(CounterType type) // 함수를 선언할거에요 -> 특정 카운터 활성 여부를 반환하는 IsCounterActive를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
return activeCounters.ContainsKey(type) && activeCounters[type]; // 값을 반환할거에요 -> 딕셔너리에 키가 있고 값이 참(true)인지를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>현재 활성화된 모든 카운터 타입 반환</summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
public List<CounterType> GetActiveCounters() // 함수를 선언할거에요 -> 활성화된 모든 카운터 목록을 반환하는 GetActiveCounters를
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
var result = new List<CounterType>(); // 리스트를 만들거에요 -> 결과를 담을 result 리스트를
|
|
|
|
|
foreach (var kvp in activeCounters) // 반복할거에요 -> 모든 카운터 상태에 대해
|
2026-02-10 15:29:22 +00:00
|
|
|
{
|
2026-02-12 15:23:25 +00:00
|
|
|
if (kvp.Value) result.Add(kvp.Key); // 조건이 맞으면 실행할거에요 -> 활성화(true) 상태라면 리스트에 추가하기를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
2026-02-12 15:23:25 +00:00
|
|
|
return result; // 값을 반환할거에요 -> 완성된 리스트를
|
2026-02-10 15:29:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>습관 변경 보상이 활성화되었는지 확인</summary>
|
2026-02-12 15:23:25 +00:00
|
|
|
public bool IsHabitChangeRewarded => habitChangeRewardGranted; // 프로퍼티를 선언할거에요 -> 보상 지급 여부를 외부에서 읽을 수 있는 IsHabitChangeRewarded를
|
2026-02-11 13:47:53 +00:00
|
|
|
}
|