포탈이 글로벌 타이머 사이클에 맞춰 웨이브를 소환하도록 함

시작 코스트 4, 웨이브당 증가량 10%
This commit is contained in:
2026-01-31 21:36:56 +09:00
parent d055591dcc
commit b54e016283
12 changed files with 252 additions and 55 deletions

View File

@@ -0,0 +1,77 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
[CustomEditor(typeof(EnemyPortal))]
public class EnemyPortalEditor : Editor
{
private const string MONSTER_DATA_FOLDER = "Assets/Data/ScriptableObjects/Monster";
public override void OnInspectorGUI()
{
DrawDefaultInspector();
EnemyPortal portal = (EnemyPortal)target;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Monster Data Loader", EditorStyles.boldLabel);
if (GUILayout.Button("Load Monster Data"))
{
LoadMonsterData(portal);
}
EditorGUILayout.HelpBox("Click 'Load Monster Data' to automatically load all monsters from " + MONSTER_DATA_FOLDER, MessageType.Info);
}
private void LoadMonsterData(EnemyPortal portal)
{
string[] guids = AssetDatabase.FindAssets("t:MonsterData", new[] { MONSTER_DATA_FOLDER });
List<EnemyPortal.MonsterEntry> entries = new List<EnemyPortal.MonsterEntry>();
int loadedCount = 0;
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
Northbound.Data.MonsterData monsterData = AssetDatabase.LoadAssetAtPath<Northbound.Data.MonsterData>(assetPath);
if (monsterData != null)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(monsterData.prefabPath);
if (prefab != null)
{
entries.Add(new EnemyPortal.MonsterEntry
{
monsterData = monsterData,
prefab = prefab
});
loadedCount++;
}
else
{
Debug.LogWarning($"[EnemyPortal] Could not load prefab at path: {monsterData.prefabPath} for monster {monsterData.id}");
}
}
}
serializedObject.Update();
SerializedProperty entriesProperty = serializedObject.FindProperty("monsterEntries");
entriesProperty.ClearArray();
entriesProperty.arraySize = entries.Count;
for (int i = 0; i < entries.Count; i++)
{
SerializedProperty element = entriesProperty.GetArrayElementAtIndex(i);
element.FindPropertyRelative("monsterData").objectReferenceValue = entries[i].monsterData;
element.FindPropertyRelative("prefab").objectReferenceValue = entries[i].prefab;
}
serializedObject.ApplyModifiedProperties();
AssetDatabase.SaveAssets();
Debug.Log($"[EnemyPortal] Loaded {loadedCount} monsters from {MONSTER_DATA_FOLDER}");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 70cde0c89ff88c149b6535cc2e9903b3

View File

@@ -2,43 +2,120 @@ using Northbound;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using static Northbound.ObstacleSpawner;
using static UnityEditor.FilePathAttribute;
public class EnemyPortal : MonoBehaviour
{
[Header("Spawn Settings")]
[Tooltip("소환할 몬스터 목록")]
[SerializeField] private List<GameObject> Enemies = new();
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
[System.Serializable]
public class MonsterEntry
{
GlobalTimer.Instance.OnCycleComplete += SpawnEnemy;
public Northbound.Data.MonsterData monsterData;
public GameObject prefab;
}
private void SpawnEnemy()
{
foreach (GameObject obj in Enemies)
{
GameObject enemy = Instantiate(obj, transform);
[Header("Spawn Settings")]
[Tooltip("몬스터 데이터 목록 (Editor에서 자동 로드 가능)")]
[SerializeField] private List<MonsterEntry> monsterEntries = new();
// Add FogOfWarVisibility component to hide enemies in unexplored areas
if (enemy.GetComponent<FogOfWarVisibility>() == null)
[Header("Cost Settings")]
[Tooltip("시간당 코스트 시작 값")]
[SerializeField] private float initialCost = 4f;
[Tooltip("사이클마다 코스트 증가율 (%)")]
[SerializeField] private float costIncreaseRate = 10f;
private float currentCost;
void Start()
{
currentCost = initialCost;
GlobalTimer.Instance.OnCycleStart += OnCycleStart;
}
private void OnCycleStart(int cycleNumber)
{
SpawnMonsters();
IncreaseCost();
}
private void SpawnMonsters()
{
float remainingCost = currentCost;
int spawnedCount = 0;
while (remainingCost > 0 && monsterEntries.Count > 0)
{
MonsterEntry selectedEntry = SelectMonsterByWeight();
if (selectedEntry.monsterData.cost > remainingCost)
{
var visibility = enemy.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = false; // Enemies hidden when not visible
visibility.updateInterval = 0.2f;
if (!CanSpawnAnyMonster(remainingCost))
{
break;
}
continue;
}
enemy.GetComponent<NetworkObject>().Spawn();
Debug.Log(enemy);
SpawnEnemy(selectedEntry.prefab);
remainingCost -= selectedEntry.monsterData.cost;
spawnedCount++;
}
if (spawnedCount > 0)
{
Debug.Log($"[EnemyPortal] Spawned {spawnedCount} monsters (Cost used: {currentCost - remainingCost:F2})");
}
}
// Update is called once per frame
void Update()
private bool CanSpawnAnyMonster(float remainingCost)
{
foreach (var entry in monsterEntries)
{
if (entry.monsterData.cost <= remainingCost)
{
return true;
}
}
return false;
}
private MonsterEntry SelectMonsterByWeight()
{
float totalWeight = 0f;
foreach (var entry in monsterEntries)
{
totalWeight += entry.monsterData.weight;
}
float randomValue = Random.Range(0f, totalWeight);
float cumulativeWeight = 0f;
foreach (var entry in monsterEntries)
{
cumulativeWeight += entry.monsterData.weight;
if (randomValue <= cumulativeWeight)
{
return entry;
}
}
return monsterEntries[0];
}
private void SpawnEnemy(GameObject prefab)
{
GameObject enemy = Instantiate(prefab, transform);
if (enemy.GetComponent<FogOfWarVisibility>() == null)
{
var visibility = enemy.AddComponent<FogOfWarVisibility>();
visibility.showInExploredAreas = false;
visibility.updateInterval = 0.2f;
}
enemy.GetComponent<NetworkObject>().Spawn();
}
private void IncreaseCost()
{
currentCost *= (1f + costIncreaseRate / 100f);
}
}