[Enemy] 보스 적 시스템 구현
Ultraworked with [Sisyphus](https://github.com/code-yeonggu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
314
Assets/Scripts/Editor/BossEnemyEditor.cs
Normal file
314
Assets/Scripts/Editor/BossEnemyEditor.cs
Normal file
@@ -0,0 +1,314 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using Colosseum.Enemy;
|
||||
|
||||
namespace Colosseum.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// BossEnemy 커스텀 인스펙터.
|
||||
/// 페이즈 정보, HP, 상태를 시각적으로 표시합니다.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(BossEnemy))]
|
||||
public class BossEnemyEditor : UnityEditor.Editor
|
||||
{
|
||||
private BossEnemy boss;
|
||||
private bool showPhaseDetails = true;
|
||||
private bool showDebugTools = true;
|
||||
private int selectedPhaseIndex = 0;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
boss = (BossEnemy)target;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
// 기본 인스펙터 그리기
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox("런타임 디버그 정보는 플레이 모드에서만 표시됩니다.", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 상태 요약
|
||||
DrawStatusSummary();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 페이즈 정보
|
||||
DrawPhaseInfo();
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
// 디버그 도구
|
||||
DrawDebugTools();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상태 요약 표시
|
||||
/// </summary>
|
||||
private void DrawStatusSummary()
|
||||
{
|
||||
EditorGUILayout.LabelField("상태 요약", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// HP 바
|
||||
float hpPercent = boss.MaxHealth > 0 ? boss.CurrentHealth / boss.MaxHealth : 0f;
|
||||
DrawProgressBar("HP", hpPercent, GetHealthColor(hpPercent), $"{boss.CurrentHealth:F0} / {boss.MaxHealth:F0}");
|
||||
|
||||
// 상태 정보
|
||||
EditorGUILayout.LabelField("현재 페이즈", $"{boss.CurrentPhaseIndex + 1} / {boss.TotalPhases}");
|
||||
EditorGUILayout.LabelField("상태", GetStatusText());
|
||||
|
||||
if (boss.CurrentPhase != null)
|
||||
{
|
||||
EditorGUILayout.LabelField("페이즈명", boss.CurrentPhase.PhaseName);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이즈 상세 정보 표시
|
||||
/// </summary>
|
||||
private void DrawPhaseInfo()
|
||||
{
|
||||
showPhaseDetails = EditorGUILayout.Foldout(showPhaseDetails, "페이즈 상세 정보", true);
|
||||
|
||||
if (!showPhaseDetails)
|
||||
return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
var phasesProp = serializedObject.FindProperty("phases");
|
||||
if (phasesProp == null || phasesProp.arraySize == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("등록된 페이즈가 없습니다.", MessageType.Warning);
|
||||
EditorGUI.indentLevel--;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < phasesProp.arraySize; i++)
|
||||
{
|
||||
var phaseProp = phasesProp.GetArrayElementAtIndex(i);
|
||||
var phase = phaseProp.objectReferenceValue as BossPhaseData;
|
||||
|
||||
if (phase == null)
|
||||
continue;
|
||||
|
||||
bool isCurrentPhase = i == boss.CurrentPhaseIndex;
|
||||
bool isCompleted = i < boss.CurrentPhaseIndex;
|
||||
|
||||
// 페이즈 헤더
|
||||
GUIStyle phaseStyle = new GUIStyle(EditorStyles.foldout);
|
||||
if (isCurrentPhase)
|
||||
phaseStyle.fontStyle = FontStyle.Bold;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// 상태 아이콘
|
||||
string statusIcon = isCurrentPhase ? "▶" : (isCompleted ? "✓" : "○");
|
||||
GUIContent phaseLabel = new GUIContent($"{statusIcon} Phase {i + 1}: {phase.PhaseName}");
|
||||
|
||||
EditorGUILayout.LabelField(phaseLabel, GUILayout.Width(200));
|
||||
|
||||
// 전환 조건
|
||||
EditorGUILayout.LabelField($"[{phase.TransitionType}]", EditorStyles.miniLabel, GUILayout.Width(100));
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (isCurrentPhase)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.LabelField($"전환 조건: {GetTransitionConditionText(phase)}");
|
||||
EditorGUILayout.LabelField($"경과 시간: {boss.PhaseElapsedTime:F1}초");
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(2);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 디버그 도구 표시
|
||||
/// </summary>
|
||||
private void DrawDebugTools()
|
||||
{
|
||||
showDebugTools = EditorGUILayout.Foldout(showDebugTools, "디버그 도구", true);
|
||||
|
||||
if (!showDebugTools)
|
||||
return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.HelpBox("이 도구는 서버에서만 작동합니다.", MessageType.Info);
|
||||
|
||||
// 페이즈 강제 전환
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("페이즈 강제 전환", GUILayout.Width(120));
|
||||
selectedPhaseIndex = EditorGUILayout.IntSlider(selectedPhaseIndex, 0, Mathf.Max(0, boss.TotalPhases - 1));
|
||||
if (GUILayout.Button("전환", GUILayout.Width(60)))
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
boss.ForcePhaseTransition(selectedPhaseIndex);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// 현재 페이즈 재시작
|
||||
if (GUILayout.Button("현재 페이즈 재시작"))
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
boss.RestartCurrentPhase();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// HP 조작
|
||||
EditorGUILayout.LabelField("HP 조작", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("HP 10%"))
|
||||
{
|
||||
SetBossHP(0.1f);
|
||||
}
|
||||
if (GUILayout.Button("HP 30%"))
|
||||
{
|
||||
SetBossHP(0.3f);
|
||||
}
|
||||
if (GUILayout.Button("HP 50%"))
|
||||
{
|
||||
SetBossHP(0.5f);
|
||||
}
|
||||
if (GUILayout.Button("HP 100%"))
|
||||
{
|
||||
SetBossHP(1f);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// 커스텀 조건
|
||||
EditorGUILayout.LabelField("커스텀 조건 설정", EditorStyles.boldLabel);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("조건 ID:", GUILayout.Width(60));
|
||||
string conditionId = EditorGUILayout.TextField("Enraged");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button("활성화"))
|
||||
{
|
||||
boss.SetCustomCondition(conditionId, true);
|
||||
}
|
||||
if (GUILayout.Button("비활성화"))
|
||||
{
|
||||
boss.SetCustomCondition(conditionId, false);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HP 설정 (서버에서만)
|
||||
/// </summary>
|
||||
private void SetBossHP(float percent)
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
return;
|
||||
|
||||
float targetHP = boss.MaxHealth * percent;
|
||||
float damage = boss.CurrentHealth - targetHP;
|
||||
|
||||
if (damage > 0)
|
||||
{
|
||||
boss.TakeDamage(damage);
|
||||
}
|
||||
else if (damage < 0)
|
||||
{
|
||||
boss.Heal(-damage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 진행 바 그리기
|
||||
/// </summary>
|
||||
private void DrawProgressBar(string label, float value, Color color, string text = "")
|
||||
{
|
||||
Rect rect = EditorGUILayout.GetControlRect();
|
||||
rect.height = 20f;
|
||||
|
||||
// 레이블
|
||||
Rect labelRect = new Rect(rect.x, rect.y, 60, rect.height);
|
||||
EditorGUI.LabelField(labelRect, label);
|
||||
|
||||
// 바
|
||||
Rect barRect = new Rect(rect.x + 65, rect.y, rect.width - 65, rect.height);
|
||||
EditorGUI.DrawRect(barRect, new Color(0.2f, 0.2f, 0.2f));
|
||||
|
||||
Rect fillRect = new Rect(barRect.x, barRect.y, barRect.width * Mathf.Clamp01(value), barRect.height);
|
||||
EditorGUI.DrawRect(fillRect, color);
|
||||
|
||||
// 텍스트
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
GUIStyle centeredStyle = new GUIStyle(EditorStyles.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter
|
||||
};
|
||||
EditorGUI.LabelField(barRect, text, centeredStyle);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HP 비율에 따른 색상 반환
|
||||
/// </summary>
|
||||
private Color GetHealthColor(float percent)
|
||||
{
|
||||
if (percent > 0.6f)
|
||||
return new Color(0.2f, 0.8f, 0.2f); // 녹색
|
||||
if (percent > 0.3f)
|
||||
return new Color(0.9f, 0.7f, 0.1f); // 노란색
|
||||
return new Color(0.9f, 0.2f, 0.2f); // 빨간색
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상태 텍스트 반환
|
||||
/// </summary>
|
||||
private string GetStatusText()
|
||||
{
|
||||
if (boss.IsDead)
|
||||
return "<color=red>사망</color>";
|
||||
if (boss.IsTransitioning)
|
||||
return "<color=yellow>페이즈 전환 중</color>";
|
||||
return "<color=green>활성</color>";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 전환 조건 텍스트 반환
|
||||
/// </summary>
|
||||
private string GetTransitionConditionText(BossPhaseData phase)
|
||||
{
|
||||
return phase.TransitionType switch
|
||||
{
|
||||
PhaseTransitionType.HealthPercent => $"HP ≤ {phase.HealthPercentThreshold * 100:F0}%",
|
||||
PhaseTransitionType.TimeElapsed => $"시간 ≥ {phase.TimeThreshold:F0}초",
|
||||
PhaseTransitionType.CustomCondition => $"조건: {phase.CustomConditionId}",
|
||||
PhaseTransitionType.Manual => "수동 전환",
|
||||
_ => "알 수 없음"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Assets/Scripts/Editor/BossEnemyEditor.cs.meta
Normal file
2
Assets/Scripts/Editor/BossEnemyEditor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 867ffff975b9a7a4694783f4a5ee1c6e
|
||||
291
Assets/Scripts/Enemy/BossEnemy.cs
Normal file
291
Assets/Scripts/Enemy/BossEnemy.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
using Unity.Behavior;
|
||||
using Colosseum.Stats;
|
||||
|
||||
namespace Colosseum.Enemy
|
||||
{
|
||||
/// <summary>
|
||||
/// 보스 캐릭터. 페이즈 시스템과 동적 AI 전환을 지원합니다.
|
||||
/// Unity Behavior 패키지를 사용하여 Behavior Tree 기반 AI를 구현합니다.
|
||||
/// </summary>
|
||||
public class BossEnemy : EnemyBase
|
||||
{
|
||||
[Header("Boss Settings")]
|
||||
[Tooltip("보스 페이즈 데이터 목록 (순서대로 전환)")]
|
||||
[SerializeField] private List<BossPhaseData> phases = new();
|
||||
|
||||
[Tooltip("초기 Behavior Graph")]
|
||||
[SerializeField] private BehaviorGraph initialBehaviorGraph;
|
||||
|
||||
[Header("Phase Settings")]
|
||||
[Tooltip("페이즈 전환 시 무적 시간")]
|
||||
[Min(0f)] [SerializeField] private float phaseTransitionInvincibilityTime = 2f;
|
||||
|
||||
[Tooltip("페이즈 전환 연출 시간")]
|
||||
[Min(0f)] [SerializeField] private float phaseTransitionDuration = 3f;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool debugMode = true;
|
||||
|
||||
// 컴포넌트
|
||||
private BehaviorGraphAgent behaviorAgent;
|
||||
|
||||
// 페이즈 상태
|
||||
private int currentPhaseIndex = 0;
|
||||
private bool isTransitioning = false;
|
||||
private float phaseStartTime;
|
||||
private float phaseElapsedTime;
|
||||
private bool isInvincible = false;
|
||||
|
||||
// 커스텀 조건 딕셔너리
|
||||
private Dictionary<string, bool> customConditions = new Dictionary<string, bool>();
|
||||
|
||||
// 이벤트
|
||||
public event System.Action<int> OnPhaseChanged; // phaseIndex
|
||||
public event System.Action<float> OnPhaseTransitionStart; // transitionDuration
|
||||
public event System.Action OnPhaseTransitionEnd;
|
||||
|
||||
// Properties
|
||||
public int CurrentPhaseIndex => currentPhaseIndex;
|
||||
public BossPhaseData CurrentPhase => phases.Count > currentPhaseIndex ? phases[currentPhaseIndex] : null;
|
||||
public int TotalPhases => phases.Count;
|
||||
public bool IsTransitioning => isTransitioning;
|
||||
public float PhaseElapsedTime => phaseElapsedTime;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
base.OnNetworkSpawn();
|
||||
|
||||
// BehaviorGraphAgent 컴포넌트 확인/추가
|
||||
behaviorAgent = GetComponent<BehaviorGraphAgent>();
|
||||
if (behaviorAgent == null)
|
||||
{
|
||||
behaviorAgent = gameObject.AddComponent<BehaviorGraphAgent>();
|
||||
}
|
||||
|
||||
// 초기 AI 설정
|
||||
if (IsServer && initialBehaviorGraph != null)
|
||||
{
|
||||
behaviorAgent.Graph = initialBehaviorGraph;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void InitializeStats()
|
||||
{
|
||||
base.InitializeStats();
|
||||
phaseStartTime = Time.time;
|
||||
phaseElapsedTime = 0f;
|
||||
currentPhaseIndex = 0;
|
||||
isTransitioning = false;
|
||||
isInvincible = false;
|
||||
customConditions.Clear();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsServer || IsDead || isTransitioning)
|
||||
return;
|
||||
|
||||
phaseElapsedTime = Time.time - phaseStartTime;
|
||||
|
||||
// 다음 페이즈 전환 조건 확인
|
||||
CheckPhaseTransition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이즈 전환 조건 확인
|
||||
/// </summary>
|
||||
private void CheckPhaseTransition()
|
||||
{
|
||||
int nextPhaseIndex = currentPhaseIndex + 1;
|
||||
if (nextPhaseIndex >= phases.Count)
|
||||
return;
|
||||
|
||||
BossPhaseData nextPhase = phases[nextPhaseIndex];
|
||||
if (nextPhase == null)
|
||||
return;
|
||||
|
||||
if (nextPhase.CheckTransitionCondition(this, phaseElapsedTime))
|
||||
{
|
||||
StartPhaseTransition(nextPhaseIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이즈 전환 시작
|
||||
/// </summary>
|
||||
private void StartPhaseTransition(int newPhaseIndex)
|
||||
{
|
||||
if (newPhaseIndex >= phases.Count || isTransitioning)
|
||||
return;
|
||||
|
||||
isTransitioning = true;
|
||||
isInvincible = true;
|
||||
|
||||
StartCoroutine(PhaseTransitionCoroutine(newPhaseIndex));
|
||||
}
|
||||
|
||||
private System.Collections.IEnumerator PhaseTransitionCoroutine(int newPhaseIndex)
|
||||
{
|
||||
BossPhaseData newPhase = phases[newPhaseIndex];
|
||||
|
||||
// 전환 이벤트
|
||||
OnPhaseTransitionStart?.Invoke(phaseTransitionDuration);
|
||||
|
||||
// 전환 연출
|
||||
yield return PlayPhaseTransitionEffect(newPhase);
|
||||
|
||||
// AI 그래프 교체
|
||||
if (newPhase.BehaviorGraph != null && behaviorAgent != null)
|
||||
{
|
||||
behaviorAgent.End();
|
||||
behaviorAgent.Graph = newPhase.BehaviorGraph;
|
||||
}
|
||||
|
||||
// 페이즈 전환 완료
|
||||
currentPhaseIndex = newPhaseIndex;
|
||||
phaseStartTime = Time.time;
|
||||
phaseElapsedTime = 0f;
|
||||
|
||||
// 무적 해제
|
||||
yield return new WaitForSeconds(phaseTransitionInvincibilityTime);
|
||||
isInvincible = false;
|
||||
isTransitioning = false;
|
||||
|
||||
OnPhaseTransitionEnd?.Invoke();
|
||||
OnPhaseChanged?.Invoke(currentPhaseIndex);
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
Debug.Log($"[Boss] Phase transition: {currentPhaseIndex} ({newPhase.PhaseName})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이즈 전환 연출 재생
|
||||
/// </summary>
|
||||
private System.Collections.IEnumerator PlayPhaseTransitionEffect(BossPhaseData newPhase)
|
||||
{
|
||||
// 애니메이션 재생
|
||||
if (animator != null && newPhase.PhaseStartAnimation != null)
|
||||
{
|
||||
animator.Play(newPhase.PhaseStartAnimation.name);
|
||||
}
|
||||
|
||||
// 이펙트 생성
|
||||
if (newPhase.PhaseTransitionEffect != null)
|
||||
{
|
||||
var effect = Instantiate(newPhase.PhaseTransitionEffect, transform.position, transform.rotation);
|
||||
Destroy(effect, phaseTransitionDuration);
|
||||
}
|
||||
|
||||
// 전환 시간 대기
|
||||
yield return new WaitForSeconds(phaseTransitionDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대미지 적용 (무적 상태 고려)
|
||||
/// </summary>
|
||||
public override float TakeDamage(float damage, object source = null)
|
||||
{
|
||||
if (isInvincible)
|
||||
return 0f;
|
||||
|
||||
return base.TakeDamage(damage, source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 커스텀 조건 설정
|
||||
/// </summary>
|
||||
public void SetCustomCondition(string conditionId, bool value)
|
||||
{
|
||||
customConditions[conditionId] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 커스텀 조건 확인
|
||||
/// </summary>
|
||||
public bool CheckCustomCondition(string conditionId)
|
||||
{
|
||||
return customConditions.TryGetValue(conditionId, out bool value) && value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 수동으로 페이즈 전환
|
||||
/// </summary>
|
||||
public void ForcePhaseTransition(int phaseIndex)
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
if (phaseIndex >= 0 && phaseIndex < phases.Count && phaseIndex != currentPhaseIndex)
|
||||
{
|
||||
StartPhaseTransition(phaseIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 페이즈 재시작
|
||||
/// </summary>
|
||||
public void RestartCurrentPhase()
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
phaseStartTime = Time.time;
|
||||
phaseElapsedTime = 0f;
|
||||
|
||||
if (behaviorAgent != null)
|
||||
{
|
||||
behaviorAgent.Restart();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void HandleDeath()
|
||||
{
|
||||
// 마지막 페이즈에서만 사망 처리
|
||||
if (currentPhaseIndex < phases.Count - 1 && !isTransitioning)
|
||||
{
|
||||
// 아직 페이즈가 남아있으면 강제로 다음 페이즈로
|
||||
StartPhaseTransition(currentPhaseIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
base.HandleDeath();
|
||||
|
||||
// AI 정지
|
||||
if (behaviorAgent != null)
|
||||
{
|
||||
behaviorAgent.End();
|
||||
}
|
||||
}
|
||||
|
||||
#region Debug
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!debugMode)
|
||||
return;
|
||||
|
||||
// 현재 페이즈 정보 표시
|
||||
#if UNITY_EDITOR
|
||||
if (phases != null && currentPhaseIndex < phases.Count)
|
||||
{
|
||||
var phase = phases[currentPhaseIndex];
|
||||
if (phase != null)
|
||||
{
|
||||
UnityEditor.Handles.Label(
|
||||
transform.position + Vector3.up * 3f,
|
||||
$"Phase {currentPhaseIndex + 1}/{phases.Count}\n{phase.PhaseName}"
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Enemy/BossEnemy.cs.meta
Normal file
2
Assets/Scripts/Enemy/BossEnemy.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a49d1cf004a0c944be905fe6fabf936
|
||||
Reference in New Issue
Block a user