← Назад к вопросам

Реализовать puzzle matching game (Match-3)

2.2 Middle🔥 91 комментариев
#C# и ООП#Unity Core#Анимация#Коллекции и структуры данных#Физика и математика

Условие

Реализуйте базовую механику Match-3 игры.

Требования

  1. Сетка с элементами разных типов
  2. Swap элементов по свайпу
  3. Проверка на матч (3+ в ряд)
  4. Удаление matched элементов с анимацией
  5. Падение элементов сверху
  6. Генерация новых элементов
  7. Подсчет очков

Бонус

  • Специальные элементы при 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
Реализовать puzzle matching game (Match-3) | PrepBro