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

Реализовать систему урона с типами элементов

2.0 Middle🔥 191 комментариев
#C# и ООП#Unity Core#Анимация#Паттерны проектирования

Условие

Реализуйте систему урона с элементами (огонь, лед, молния и т.д.).

Требования

  1. Разные типы урона
  2. Сопротивления и уязвимости к типам
  3. Эффекты статусов (горение, заморозка, паралич)
  4. Комбинации элементов усиливают урон
  5. Визуальные эффекты для каждого типа

Пример

  • Огонь против льда = x2 урона
  • Молния против воды = x2 урона

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Система урона с типами элементов

Эта задача требует создания гибкой системы урона с поддержкой различных элементов, сопротивлений, статус-эффектов и комбинаций. Расскажу о полной архитектуре.

Архитектура системы

Основные компоненты:

  1. DamageType — enum и конфигурация типов урона
  2. ElementalDamage — класс для хранения информации об уроне
  3. DamageCalculator — расчёт финального урона
  4. StatusEffect — система статус-эффектов
  5. Health — система здоровья с защитой

DamageType и конфигурация

public enum ElementType
{
    Physical,
    Fire,
    Ice,
    Lightning,
    Water,
    Wind,
    Earth,
    Light,
    Dark
}

public enum StatusEffect
{
    None,
    Burning,        // Огонь
    Frozen,         // Лед
    Shocked,        // Молния
    Poisoned,       // Яд
    Stunned,        // Оглушение
    Bleeding,       // Кровотечение
    Weakened        // Ослабление
}

[System.Serializable]
public class ElementalResistance
{
    public ElementType elementType;
    public float resistance = 1f; // 1.0 = 100% урона, 0.5 = 50% урона
    public float vulnerability = 1f; // 1.0 = 100% урона, 1.5 = 150% урона
}

[System.Serializable]
public class ElementalDamage
{
    public float baseDamage;
    public ElementType primaryElement;
    public ElementType secondaryElement = ElementType.Physical;
    public float secondaryElementFactor = 0f; // 0-1
    public StatusEffect statusEffect = StatusEffect.None;
    public float statusEffectChance = 0f; // 0-1
    public float statusEffectDuration = 2f;
    
    public ElementalDamage(float damage, ElementType element)
    {
        baseDamage = damage;
        primaryElement = element;
    }
}

DamageCalculator - Расчёт урона

public class DamageCalculator
{
    // Матрица комбинаций элементов
    private static Dictionary<(ElementType, ElementType), float> elementalComboMultipliers = new Dictionary<(ElementType, ElementType), float>
    {
        // Огонь комбинации
        { (ElementType.Fire, ElementType.Ice), 2.0f },      // Огонь против льда
        { (ElementType.Fire, ElementType.Wind), 1.5f },     // Огонь с ветром
        { (ElementType.Fire, ElementType.Water), 0.5f },    // Огонь против воды
        
        // Лед комбинации
        { (ElementType.Ice, ElementType.Fire), 0.5f },      // Лед против огня
        { (ElementType.Ice, ElementType.Water), 1.5f },     // Лед с водой
        
        // Молния комбинации
        { (ElementType.Lightning, ElementType.Water), 2.0f },   // Молния в воде
        { (ElementType.Lightning, ElementType.Wind), 1.5f },    // Молния с ветром
        
        // Вода комбинации
        { (ElementType.Water, ElementType.Lightning), 0.5f },   // Вода против молнии
        { (ElementType.Water, ElementType.Fire), 2.0f },        // Вода против огня
        
        // Земля комбинации
        { (ElementType.Earth, ElementType.Water), 1.5f },       // Земля с водой
        { (ElementType.Earth, ElementType.Wind), 0.7f },        // Земля с ветром
    };
    
    /// Расчёт финального урона
    public static float CalculateDamage(
        ElementalDamage damage,
        float defenderArmor,
        List<ElementalResistance> defenderResistances,
        StatusEffectManager defenderStatusEffects)
    {
        float finalDamage = damage.baseDamage;
        
        // 1. Применяем сопротивления и уязвимости
        finalDamage = ApplyElementalDefenses(finalDamage, damage, defenderResistances);
        
        // 2. Применяем комбинации элементов
        finalDamage = ApplyElementalCombos(finalDamage, damage, defenderStatusEffects);
        
        // 3. Применяем броню
        finalDamage = ApplyArmor(finalDamage, defenderArmor);
        
        return Mathf.Max(finalDamage, 0.1f); // Минимум 0.1 урона
    }
    
    private static float ApplyElementalDefenses(
        float baseDamage,
        ElementalDamage damage,
        List<ElementalResistance> resistances)
    {
        float primaryMultiplier = 1f;
        float secondaryMultiplier = 1f;
        
        // Находим сопротивление основного элемента
        foreach (var resistance in resistances)
        {
            if (resistance.elementType == damage.primaryElement)
            {
                primaryMultiplier = resistance.vulnerability; // Если уязвимость
                primaryMultiplier /= resistance.resistance;   // Если сопротивление
            }
            
            if (damage.secondaryElementFactor > 0 &&
                resistance.elementType == damage.secondaryElement)
            {
                secondaryMultiplier = resistance.vulnerability;
                secondaryMultiplier /= resistance.resistance;
            }
        }
        
        float primaryDamage = baseDamage * primaryMultiplier;
        float secondaryDamage = baseDamage * damage.secondaryElementFactor * secondaryMultiplier;
        
        return primaryDamage + secondaryDamage;
    }
    
    private static float ApplyElementalCombos(
        float baseDamage,
        ElementalDamage damage,
        StatusEffectManager defenderStatusEffects)
    {
        float comboMultiplier = 1f;
        
        // Ищем активные статус-эффекты и проверяем комбинации
        var activeEffects = defenderStatusEffects.GetActiveEffects();
        
        foreach (var activeEffect in activeEffects)
        {
            // Преобразуем статус-эффект в элемент
            ElementType effectElement = StatusEffectToElement(activeEffect);
            
            if (elementalComboMultipliers.TryGetValue(
                (damage.primaryElement, effectElement),
                out float multiplier))
            {
                comboMultiplier = Mathf.Max(comboMultiplier, multiplier);
            }
        }
        
        return baseDamage * comboMultiplier;
    }
    
    private static float ApplyArmor(float damage, float armor)
    {
        // Броня уменьшает урон по формуле
        float damageReduction = armor / (100f + armor);
        return damage * (1f - damageReduction);
    }
    
    private static ElementType StatusEffectToElement(StatusEffect effect)
    {
        return effect switch
        {
            StatusEffect.Burning => ElementType.Fire,
            StatusEffect.Frozen => ElementType.Ice,
            StatusEffect.Shocked => ElementType.Lightning,
            StatusEffect.Poisoned => ElementType.Dark,
            _ => ElementType.Physical
        };
    }
}

StatusEffect - Система статус-эффектов

[System.Serializable]
public class ActiveStatusEffect
{
    public StatusEffect effectType;
    public float duration;
    public float remainingTime;
    public float damagePerSecond = 0f;
    public float speedMultiplier = 1f;
    
    public ActiveStatusEffect(StatusEffect type, float dur, float dps = 0)
    {
        effectType = type;
        duration = dur;
        remainingTime = dur;
        damagePerSecond = dps;
    }
}

public class StatusEffectManager : MonoBehaviour
{
    private List<ActiveStatusEffect> activeEffects = new List<ActiveStatusEffect>();
    private Dictionary<StatusEffect, float> effectTimers = new Dictionary<StatusEffect, float>();
    
    [SerializeField] private ParticleSystem burnParticles;
    [SerializeField] private ParticleSystem frozenParticles;
    [SerializeField] private ParticleSystem shockParticles;
    
    private Health health;
    private Animator animator;
    
    public event System.Action<StatusEffect> OnEffectAdded;
    public event System.Action<StatusEffect> OnEffectRemoved;
    
    private void Start()
    {
        health = GetComponent<Health>();
        animator = GetComponent<Animator>();
    }
    
    private void Update()
    {
        for (int i = activeEffects.Count - 1; i >= 0; i--)
        {
            ActiveStatusEffect effect = activeEffects[i];
            effect.remainingTime -= Time.deltaTime;
            
            // Применяем периодический урон
            if (effect.damagePerSecond > 0)
            {
                health.TakeDamage(effect.damagePerSecond * Time.deltaTime);
            }
            
            // Удаляем эффект если время истекло
            if (effect.remainingTime <= 0)
            {
                RemoveStatusEffect(effect.effectType);
            }
        }
    }
    
    public void AddStatusEffect(StatusEffect effectType, float duration, float dps = 0f)
    {
        // Удаляем старый эффект того же типа
        RemoveStatusEffect(effectType);
        
        ActiveStatusEffect newEffect = new ActiveStatusEffect(effectType, duration, dps);
        activeEffects.Add(newEffect);
        
        ApplyEffectVisuals(effectType, true);
        OnEffectAdded?.Invoke(effectType);
        
        Debug.Log($"Added status effect: {effectType} for {duration} seconds");
    }
    
    public void RemoveStatusEffect(StatusEffect effectType)
    {
        activeEffects.RemoveAll(e => e.effectType == effectType);
        ApplyEffectVisuals(effectType, false);
        OnEffectRemoved?.Invoke(effectType);
    }
    
    private void ApplyEffectVisuals(StatusEffect effect, bool isActive)
    {
        switch (effect)
        {
            case StatusEffect.Burning:
                if (burnParticles != null)
                    isActive ? burnParticles.Play() : burnParticles.Stop();
                break;
            
            case StatusEffect.Frozen:
                if (frozenParticles != null)
                    isActive ? frozenParticles.Play() : frozenParticles.Stop();
                animator?.SetBool("Frozen", isActive);
                break;
            
            case StatusEffect.Shocked:
                if (shockParticles != null)
                    isActive ? shockParticles.Play() : shockParticles.Stop();
                break;
        }
    }
    
    public bool HasEffect(StatusEffect effectType)
    {
        return activeEffects.Exists(e => e.effectType == effectType);
    }
    
    public List<StatusEffect> GetActiveEffects()
    {
        return activeEffects.ConvertAll(e => e.effectType);
    }
    
    public float GetSpeedMultiplier()
    {
        float multiplier = 1f;
        foreach (var effect in activeEffects)
        {
            multiplier *= effect.speedMultiplier;
        }
        return multiplier;
    }
}

Health - Система здоровья

public class Health : MonoBehaviour, IDamageable
{
    [SerializeField] private float maxHealth = 100f;
    [SerializeField] private float currentHealth;
    [SerializeField] private float armor = 10f;
    [SerializeField] private List<ElementalResistance> resistances = new List<ElementalResistance>();
    
    private StatusEffectManager statusEffectManager;
    private bool isDead = false;
    
    public event System.Action<float, float> OnHealthChanged;
    public event System.Action<ElementType> OnTakeDamage;
    public event System.Action OnDeath;
    
    public float CurrentHealth => currentHealth;
    public float MaxHealth => maxHealth;
    public bool IsDead => isDead;
    
    private void Start()
    {
        currentHealth = maxHealth;
        statusEffectManager = GetComponent<StatusEffectManager>();
    }
    
    public void TakeDamage(ElementalDamage damage)
    {
        if (isDead)
            return;
        
        float finalDamage = DamageCalculator.CalculateDamage(
            damage,
            armor,
            resistances,
            statusEffectManager
        );
        
        TakeDamage(finalDamage);
        OnTakeDamage?.Invoke(damage.primaryElement);
        
        // Применяем статус-эффект
        if (damage.statusEffect != StatusEffect.None &&
            Random.value < damage.statusEffectChance)
        {
            float dps = 0f;
            
            // Специфичные параметры для каждого эффекта
            switch (damage.statusEffect)
            {
                case StatusEffect.Burning:
                    dps = damage.baseDamage * 0.2f;
                    break;
                case StatusEffect.Poisoned:
                    dps = damage.baseDamage * 0.1f;
                    break;
            }
            
            statusEffectManager.AddStatusEffect(
                damage.statusEffect,
                damage.statusEffectDuration,
                dps
            );
        }
    }
    
    public void TakeDamage(float damage)
    {
        currentHealth = Mathf.Max(currentHealth - damage, 0);
        OnHealthChanged?.Invoke(currentHealth, maxHealth);
        
        if (currentHealth <= 0)
        {
            Die();
        }
    }
    
    public void Heal(float amount)
    {
        currentHealth = Mathf.Min(currentHealth + amount, maxHealth);
        OnHealthChanged?.Invoke(currentHealth, maxHealth);
    }
    
    private void Die()
    {
        isDead = true;
        OnDeath?.Invoke();
        gameObject.SetActive(false);
    }
}

public interface IDamageable
{
    void TakeDamage(ElementalDamage damage);
    void TakeDamage(float damage);
}

Пример использования

public class DamageDealer : MonoBehaviour
{
    [SerializeField] private float baseDamage = 25f;
    [SerializeField] private ElementType damageElement = ElementType.Fire;
    
    public void DealDamage(GameObject target)
    {
        Health targetHealth = target.GetComponent<Health>();
        if (targetHealth == null)
            return;
        
        ElementalDamage damage = new ElementalDamage(baseDamage, damageElement)
        {
            statusEffect = StatusEffect.Burning,
            statusEffectChance = 0.3f,
            statusEffectDuration = 3f
        };
        
        targetHealth.TakeDamage(damage);
    }
}

Ключевые особенности

1. Элементная система — 9 типов элементов с уникальными свойствами

2. Комбинации элементов — использование матрицы для синергий

3. Статус-эффекты — горение, заморозка, паралич с визуальной обратной связью

4. Броня и сопротивления — многоуровневая защита с математической базой

5. Периодический урон — поддержка DoT эффектов (damage over time)

6. События — полная система оповещений для UI и логики

Расширение возможностей

  • Комбо множители — за несколько эффектов одновременно
  • Иммунитеты — враги могут быть невосприимчивы к эффектам
  • Взаимодействия с окружением — лёд на огне создаёт пар
  • Усиления от элементов — урон увеличивается от совпадения типов

Эта система обеспечивает глубокую и интересную механику боевых взаимодействий.

Реализовать систему урона с типами элементов | PrepBro