Как вводить данные до старта сцены Unity, если классы наследуются от MonoBehaviour?
Делаю tower defence игру на юнити. В ней нужно, чтобы определенные мобы спавнились по волнам.
Во всех гайдах, которые я смотрел, между спавном каждого моба одинаковая задержка, а надо, чтобы было возможно спавнить их по группам без задержки.
Вот скриншоты, чтобы было понятнее.
Как в гайдах

Для этого я сделал менеджер волн.
public class WavesManager : MonoBehaviour
{
public LevelData levelData;
public List<Wave> waves;
private int currentWaveIndex = -1;
private void Awake()
{
waves = levelData.waves;
}
public void StartNextWave()
{
currentWaveIndex++;
if (currentWaveIndex == waves.Count) return;
waves[currentWaveIndex].Spawn(levelData.pieces);
}
}
Вот класс самой волны
[Serializable]
public class Wave : MonoBehaviour
{
public List<Squad> squads = new();
public void Spawn(List<Piece> pieces)
{
StartCoroutine(nameof(SpawnSquads), pieces);
}
private IEnumerator SpawnSquads(List<Piece> pieces)
{
foreach (var squad in squads)
{
squad.Spawn(pieces);
yield return new WaitForSeconds(squad.otherSquadDelay);
}
}
}
И отряд
[Serializable]
public class Squad : MonoBehaviour
{
public string enemyTitle;
public int amount;
public int innerDelay;
public int otherSquadDelay;
public void Spawn(List<Piece> pieces)
{
StartCoroutine(nameof(SpawnEnemies), pieces);
}
private IEnumerator SpawnEnemies(IEnumerable<Piece> pieces)
{
var piece = pieces.Select(x => x)
.First(x => x.title == enemyTitle);
for (var i = 0; i < amount; i++)
{
piece.Spawn();
yield return new WaitForSeconds(innerDelay);
}
}
}
Проблема в том, что я не могу понять, как мне настраивать заранее кто и как будет спавниться в сквадах из-за наследования от MonoBehaviour, а убрать его нельзя так как не будет работать корутина.
Вот еще скрипт, в который я заранее ввожу данные волн и сквадов.
[CreateAssetMenu(fileName = "LevelData", menuName = "ScriptableObjects/LevelData")]
public class LevelData : ScriptableObject
{
public List<Piece> pieces;
public List<Wave> waves;
}
Ответы (1 шт):
Предлагаю следующую реализацию.
Классы Wave и Squad пусть просто хранят информацию. Они не будут MonoBehaviour.
public class Wave
{
[SerializeField] private List<Squad> squads = new List<Squad>();
// Количество сквадов в волне
public int CountSquads => squads.Count;
public Squad GetSquad (int index)
{
return squads[index];
}
}
[Serializable]
public class Squad
{
public string enemyTitle;
public int amount;
public int innerDelay;
public int otherSquadDelay;
}
Зачем в LevelData нужен список Pieces я не совсем понял, поэтому удалил его, он мне не пригодился. Если он вам нужен, добавьте.
[CreateAssetMenu(fileName = "LevelData", menuName = "ScriptableObjects/LevelData", order =51)]
public class LevelData : ScriptableObject
{
public List<Wave> waves;
}
Я создал класс EnemiesSpawner, который хранит ссылки на префабы разных врагов и умеет спавнить этих самых врагов:
public class EnemiesSpawner : MonoBehaviour
{
// Префабы врагов
[SerializeField] private GameObject GoblinPrefab, OrcPrefab, HellHoundPrefab;
public void SpawnPiece (GameObject piece)
{
Vector3 positions = new Vector3(0, 0, 0); // Настройки, которые вам нужны
Instantiate(piece, positions, Quaternion.identity); // Спавним определенного врага
}
// Функция спавна врага по его имени title
public void SpawnPiece(string title)
{
SpawnPiece(GetPieceByTitle(title));
}
// Функция получения префаба врага по его имени title
private GameObject GetPieceByTitle (string title)
{
// Здесь нужно получать префаб врага (piece) по его имени (title)
// Простейший способ -- switch case, но, наверно, вам стоит сделать словарик.
switch (title)
{
case "Goblin":
return GoblinPrefab;
case "Orc":
return OrcPrefab;
case "HellHound":
return HellHoundPrefab;
// И т.д.
default:
throw new System.Exception("Unknown nemy title");
}
}
}
Класс WavesManager будет, исходя из названия, контролировать процесс спавна волн:
public class WavesManager : MonoBehaviour
{
[Header("Level data")]
[SerializeField] private LevelData _levelData;
[Header("References")]
[SerializeField] private EnemiesSpawner _spawner;
private List<Wave> _waves;
private void Awake()
{
_waves = _levelData.waves;
StartCoroutine(SpawningProcess());
}
private IEnumerator SpawningProcess()
{
// Проходим по волнам
foreach (Wave wave in _waves)
{
// Проходим по сквадам
for (int i = 0; i < wave.CountSquads; i++)
{
Squad currSquad = wave.GetSquad(i);
// Проходим по врагам в скваде
for (int j = 0; j < currSquad.amount; j++)
{
_spawner.SpawnPiece(currSquad.enemyTitle); // Спавним врага
if (j < currSquad.amount - 1)
yield return new WaitForSeconds(currSquad.innerDelay); // Задержка между врагами в скваде
}
if (i < wave.CountSquads - 1)
yield return new WaitForSeconds(currSquad.otherSquadDelay); // Задержка между сквадами
}
yield return new WaitForSeconds(3); // Время между волнами
}
}
}
Некоторые комментарии:
Не советую хранить название врагов в строке string. Во-первых, легко опечататься, во-вторых, вообще плохая практика. Советую выбрать или перечисления enum, или вообще указывать в инспекторе сразу префабы врагов, то есть вместо переменной
string enemyTitleсделатьGameObject EnemyPrefabи его же и спавнить. Так нам даже не понадобится функция GetPieceByTitle.Я не увидел в вашем коде задержку между волнами, поэтому просто поставил ее равной 2 секундам. Думаю, она мжет отличаться от уровня к уровню, поэтому можно сделать переменную
otherWaveDealyв классеWave. Тогда в классуWaveManagerстрокуyield return new WaitForSeconds(3);нужно заменить наyield return new WaitForSeconds(wave.otherWaveDelay);Я сделал переменную squads приватной, ибо нефиг ей быть публичной, чтобы ее можно было изменять из сторонних классов. Я добавил геттер количества волн CountSquad и геттер конкретной i-й волны
GetSquad(index). Получается более надежный механизм: из других классов получать волны можно, но изменять нельзя.
