Чтобы ошибки не сыпало при старте на gameplay сцене А то OnDestroy отрабатывают и спамят консоль
151 lines
4.5 KiB
C#
151 lines
4.5 KiB
C#
using Cysharp.Threading.Tasks;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading;
|
||
using UnityEngine;
|
||
|
||
public class EnemySpawner : MonoBehaviour
|
||
{
|
||
public event Action<EnemyShip> EnemySpawned;
|
||
public List<EnemyShip> EnemyPrefabs;
|
||
|
||
[SerializeField] private List<BezierWay> ways;
|
||
[SerializeField] private float SpawnInterval = 5;
|
||
|
||
[SerializeField] private int minBudget = 3;
|
||
[SerializeField] private int maxBudget = 100;
|
||
[SerializeField] private int minBudgetScore = 0;
|
||
[SerializeField] private int maxBudgetScore = 5000;
|
||
[SerializeField] private AnimationCurve budgetCurve;
|
||
|
||
[SerializeField] private int currentBudget = 0;
|
||
[SerializeField] private int usedBudget = 0;
|
||
|
||
[SerializeField] private int maxSpawnedEnemies = 3;
|
||
|
||
private CancellationToken _token;
|
||
private int activeEnemies = 0;
|
||
|
||
public bool Initialized { get; private set; }
|
||
|
||
private int remainingBudget => currentBudget - usedBudget;
|
||
|
||
public void Initialize()
|
||
{
|
||
if (Initialized) return;
|
||
|
||
if (!Validate())
|
||
{
|
||
Debug.LogError("Валидация настроек спавнера не прошла", this);
|
||
return;
|
||
}
|
||
|
||
currentBudget = minBudget;
|
||
usedBudget = 0;
|
||
|
||
|
||
|
||
EnemyShip.ShipReleased += UntouchShipFromBudget;
|
||
EnemyShip.ShipDefeated += UntouchShipFromBudget;
|
||
G.ScoreModel.OnUpdate += UpdateBudgetByScore;
|
||
|
||
_token = this.GetCancellationTokenOnDestroy();
|
||
SpawnProcessAsync(_token).Forget();
|
||
|
||
Initialized = true;
|
||
}
|
||
|
||
private void UntouchShipFromBudget(EnemyShip releasedShip)
|
||
{
|
||
if (releasedShip == null) return;
|
||
usedBudget = Mathf.Max(0, usedBudget - releasedShip.SpawnCost);
|
||
activeEnemies--;
|
||
}
|
||
private void OnDisable()
|
||
{
|
||
if (!Initialized) return;
|
||
EnemyShip.ShipReleased -= UntouchShipFromBudget;
|
||
EnemyShip.ShipDefeated -= UntouchShipFromBudget;
|
||
G.ScoreModel.OnUpdate -= UpdateBudgetByScore;
|
||
}
|
||
|
||
private bool Validate()
|
||
{
|
||
if (EnemyPrefabs == null || EnemyPrefabs.Count == 0)
|
||
{
|
||
Debug.LogError("Не задан список врагов на спавнере", this);
|
||
return false;
|
||
}
|
||
foreach (var enemy in EnemyPrefabs)
|
||
{
|
||
if (!enemy.TryGetComponent<EnemyShip>(out _))
|
||
{
|
||
Debug.LogError($"Префаб врага {enemy.name} не содержит компонент AbstractEnemyShip");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private async UniTaskVoid SpawnProcessAsync(CancellationToken token)
|
||
{
|
||
while(!token.IsCancellationRequested)
|
||
{
|
||
await UniTask.Yield(PlayerLoopTiming.Update);
|
||
if (!token.IsCancellationRequested)
|
||
TrySpawn();
|
||
await UniTask.Delay(delayTimeSpan: TimeSpan.FromSeconds(SpawnInterval),
|
||
delayTiming: PlayerLoopTiming.Update,
|
||
cancellationToken: token);
|
||
}
|
||
}
|
||
|
||
private BezierWay GetNotBusyWay() => ways.FirstOrDefault(way => !way.Busy);
|
||
|
||
private EnemyShip GetRandomAccessibleShip()
|
||
{
|
||
var accessibleShips = EnemyPrefabs.Where(e => e.SpawnCost <= remainingBudget).ToList();
|
||
if (accessibleShips.Count == 0) return null;
|
||
var randomIndex = UnityEngine.Random.Range(0, accessibleShips.Count);
|
||
return accessibleShips[randomIndex];
|
||
}
|
||
|
||
private EnemyShip GetTopAccessibleShip()
|
||
{
|
||
return EnemyPrefabs
|
||
.Where(e => e.SpawnCost <= remainingBudget)
|
||
.OrderByDescending(e => e.SpawnCost)
|
||
.FirstOrDefault();
|
||
}
|
||
|
||
private void TrySpawn()
|
||
{
|
||
if (activeEnemies >= maxSpawnedEnemies) return;
|
||
|
||
var way = GetNotBusyWay();
|
||
if(way == null) return;
|
||
|
||
//var shipPrefab = GetRandomAccessibleShip();
|
||
var shipPrefab = GetTopAccessibleShip();
|
||
if (shipPrefab == null) return;
|
||
|
||
var go = Instantiate(shipPrefab.gameObject, way.FirstPoint.position, Quaternion.identity);
|
||
var ship = go.GetComponent<EnemyShip>();
|
||
|
||
ship.AssignToWay(way);
|
||
ship.Initialize();
|
||
|
||
EnemySpawned?.Invoke(ship);
|
||
usedBudget += ship.SpawnCost;
|
||
activeEnemies++;
|
||
}
|
||
|
||
private void UpdateBudgetByScore(ScoreModel scoreModel)
|
||
{
|
||
float t = Mathf.InverseLerp(minBudgetScore, maxBudgetScore, scoreModel.Score);
|
||
currentBudget = Mathf.RoundToInt(Mathf.Lerp(minBudget, maxBudget, budgetCurve.Evaluate(t)));
|
||
}
|
||
}
|