← Назад к вопросам
Реализовать puzzle matching game (Match-3)
2.2 Middle🔥 91 комментариев
#C# и ООП#Unity Core#Анимация#Коллекции и структуры данных#Физика и математика
Условие
Реализуйте базовую механику Match-3 игры.
Требования
- Сетка с элементами разных типов
- Swap элементов по свайпу
- Проверка на матч (3+ в ряд)
- Удаление matched элементов с анимацией
- Падение элементов сверху
- Генерация новых элементов
- Подсчет очков
Бонус
- Специальные элементы при 4+ матче
- Combo система
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Система волн врагов для Tower Defense/Survival
Полнофункциональная система управления волнами с конфигурацией, масштабированием сложности, мультиплексированием спавна врагов и поддержкой бесконечного режима.
Структура конфигурации волны
[System.Serializable]
public struct EnemySpawnData
{
public EnemyType enemyType;
public int quantity;
public float spawnInterval; // Интервал между спавном одного типа врагов
}
public class WaveConfig : ScriptableObject
{
[SerializeField] private int waveNumber;
[SerializeField] private List<EnemySpawnData> enemies;
[SerializeField] private float baseDifficulty = 1f;
[SerializeField] private bool isBossWave;
[SerializeField] private EnemyType bossType;
public int WaveNumber => waveNumber;
public List<EnemySpawnData> Enemies => enemies;
public float BaseDifficulty => baseDifficulty;
public bool IsBossWave => isBossWave;
public EnemyType BossType => bossType;
}
Manager волн с State Machine
public enum WaveState { Idle, Spawning, Active, Complete, GameWon, GameLost }
public class WaveManager : MonoBehaviour
{
[SerializeField] private List<WaveConfig> waveConfigs;
[SerializeField] private List<Transform> spawnPoints;
[SerializeField] private EnemyFactory enemyFactory;
[SerializeField] private float timeBetweenWaves = 10f;
[SerializeField] private bool infiniteMode;
[SerializeField] private int playerHealth = 20;
private WaveState currentState = WaveState.Idle;
private int currentWaveIndex = 0;
private float waveTimer;
private List<Enemy> activeEnemies = new();
private int totalEnemiesInWave;
private int defeatedEnemiesInWave;
public WaveState CurrentState => currentState;
public int CurrentWaveNumber => currentWaveIndex + 1;
public float RemainingWaveTime => Mathf.Max(0, waveTimer);
public List<Enemy> ActiveEnemies => activeEnemies;
public float WaveProgress => totalEnemiesInWave > 0 ?
(float)defeatedEnemiesInWave / totalEnemiesInWave : 0f;
private void Start()
{
currentState = WaveState.Idle;
waveTimer = timeBetweenWaves;
}
private void Update()
{
switch (currentState)
{
case WaveState.Idle:
UpdateIdleState();
break;
case WaveState.Active:
UpdateActiveState();
break;
}
}
private void UpdateIdleState()
{
waveTimer -= Time.deltaTime;
OnTimerTick?.Invoke(waveTimer);
if (waveTimer <= 0)
{
StartNextWave();
}
}
private void StartNextWave()
{
// Условие завершения
if (currentWaveIndex >= waveConfigs.Count && !infiniteMode)
{
currentState = WaveState.GameWon;
OnGameWon?.Invoke();
return;
}
// Загрузка конфига волны
int waveIndexToLoad = infiniteMode ?
currentWaveIndex % waveConfigs.Count : currentWaveIndex;
var waveConfig = Instantiate(waveConfigs[waveIndexToLoad]);
// Увеличение сложности в бесконечном режиме
float difficultyMultiplier = 1f;
if (infiniteMode && currentWaveIndex > 0)
{
difficultyMultiplier = 1f + (currentWaveIndex * 0.15f);
}
currentState = WaveState.Active;
waveTimer = timeBetweenWaves;
StartCoroutine(SpawnWaveCoroutine(waveConfig, difficultyMultiplier));
OnWaveStarted?.Invoke(CurrentWaveNumber, waveConfig.IsBossWave);
currentWaveIndex++;
}
private IEnumerator SpawnWaveCoroutine(WaveConfig config, float difficultyMultiplier)
{
totalEnemiesInWave = 0;
defeatedEnemiesInWave = 0;
// Спавн обычных врагов
foreach (var spawnData in config.Enemies)
{
totalEnemiesInWave += spawnData.quantity;
for (int i = 0; i < spawnData.quantity; i++)
{
var spawnPoint = spawnPoints[Random.Range(0, spawnPoints.Count)];
var enemy = enemyFactory.CreateEnemy(
spawnData.enemyType,
spawnPoint.position,
difficultyMultiplier
);
activeEnemies.Add(enemy);
enemy.OnDeath += () => OnEnemyDefeated(enemy);
yield return new WaitForSeconds(spawnData.spawnInterval);
}
}
// Спавн босса
if (config.IsBossWave)
{
totalEnemiesInWave++;
var spawnPoint = spawnPoints[0];
var boss = enemyFactory.CreateBoss(
config.BossType,
spawnPoint.position,
difficultyMultiplier * 2f
);
activeEnemies.Add(boss);
boss.OnDeath += () => OnEnemyDefeated(boss);
OnBossSpawned?.Invoke(boss);
}
}
private void UpdateActiveState()
{
OnWaveProgress?.Invoke(WaveProgress);
// Проверка условия поражения
if (playerHealth <= 0)
{
currentState = WaveState.GameLost;
OnGameLost?.Invoke();
return;
}
// Проверка завершения волны
if (activeEnemies.Count == 0 && totalEnemiesInWave > 0)
{
currentState = WaveState.Complete;
OnWaveComplete?.Invoke(CurrentWaveNumber);
waveTimer = timeBetweenWaves;
currentState = WaveState.Idle;
}
}
private void OnEnemyDefeated(Enemy enemy)
{
activeEnemies.Remove(enemy);
defeatedEnemiesInWave++;
OnEnemyDefeated?.Invoke(enemy);
}
public void ReceiveDamage(int damage)
{
playerHealth -= damage;
if (playerHealth <= 0)
{
currentState = WaveState.GameLost;
OnGameLost?.Invoke();
}
}
public int GetPlayerHealth() => playerHealth;
public event System.Action<int, bool> OnWaveStarted;
public event System.Action<int> OnWaveComplete;
public event System.Action<float> OnWaveProgress;
public event System.Action<float> OnTimerTick;
public event System.Action<Enemy> OnEnemyDefeated;
public event System.Action<Enemy> OnBossSpawned;
public event System.Action OnGameWon;
public event System.Action OnGameLost;
}
Factory для создания врагов
public class EnemyFactory : MonoBehaviour
{
[SerializeField] private Dictionary<EnemyType, GameObject> enemyPrefabs;
public Enemy CreateEnemy(EnemyType type, Vector3 position, float difficultyMultiplier)
{
var prefab = enemyPrefabs[type];
var instance = Instantiate(prefab, position, Quaternion.identity);
var enemy = instance.GetComponent<Enemy>();
enemy.ScaleStats(difficultyMultiplier);
return enemy;
}
public Enemy CreateBoss(EnemyType type, Vector3 position, float difficultyMultiplier)
{
var enemy = CreateEnemy(type, position, difficultyMultiplier);
enemy.SetBossMode(true);
return enemy;
}
}
public abstract class Enemy : MonoBehaviour
{
[SerializeField] protected int health;
[SerializeField] protected float speed;
[SerializeField] protected int damageToPlayer;
protected float difficultyMultiplier = 1f;
protected bool isBoss;
public event System.Action OnDeath;
public void ScaleStats(float multiplier)
{
difficultyMultiplier = multiplier;
health = (int)(health * multiplier);
speed *= multiplier;
damageToPlayer = (int)(damageToPlayer * multiplier);
}
public void SetBossMode(bool bossMode) => isBoss = bossMode;
public void TakeDamage(int damage)
{
health -= damage;
if (health <= 0)
Die();
}
protected virtual void Die()
{
OnDeath?.Invoke();
Destroy(gameObject);
}
public int GetDamageToPlayer() => damageToPlayer;
}
UI волн
public class WaveUI : MonoBehaviour
{
[SerializeField] private Text waveNumberText;
[SerializeField] private Text timerText;
[SerializeField] private Text enemyCountText;
[SerializeField] private Image waveProgressBar;
[SerializeField] private Text playerHealthText;
[SerializeField] private GameObject bossIndicator;
[SerializeField] private WaveManager waveManager;
private void Start()
{
waveManager.OnWaveStarted += UpdateWaveUI;
waveManager.OnWaveProgress += UpdateProgressBar;
waveManager.OnTimerTick += UpdateTimer;
waveManager.OnBossSpawned += ShowBossIndicator;
}
private void Update()
{
enemyCountText.text = $"Врагов осталось: {waveManager.ActiveEnemies.Count}";
playerHealthText.text = $"Здоровье: {waveManager.GetPlayerHealth()}";
}
private void UpdateWaveUI(int waveNumber, bool isBoss)
{
waveNumberText.text = isBoss ?
$"ВОЛНА {waveNumber} - БОСС" :
$"Волна {waveNumber}";
}
private void UpdateProgressBar(float progress)
{
waveProgressBar.fillAmount = progress;
}
private void UpdateTimer(float timeRemaining)
{
timerText.text = timeRemaining > 0 ?
$"Следующая волна: {timeRemaining:F1}s" :
"Волна начинается...";
}
private void ShowBossIndicator(Enemy boss)
{
bossIndicator.SetActive(true);
}
}
Ключевые особенности
- Конфигурация волн через ScriptableObject
- Динамический спавн с контролем интервалов
- Масштабирование сложности через DifficultyMultiplier
- Boss волны с двойным множителем сложности
- Бесконечный режим с экспоненциальным ростом
- Условия победы/поражения через State Machine
- Event система для слабой связанности
- Отслеживание прогресса волны в UI