Реализовать систему урона с типами элементов
Условие
Реализуйте систему урона с элементами (огонь, лед, молния и т.д.).
Требования
- Разные типы урона
- Сопротивления и уязвимости к типам
- Эффекты статусов (горение, заморозка, паралич)
- Комбинации элементов усиливают урон
- Визуальные эффекты для каждого типа
Пример
- Огонь против льда = x2 урона
- Молния против воды = x2 урона
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Система урона с типами элементов
Эта задача требует создания гибкой системы урона с поддержкой различных элементов, сопротивлений, статус-эффектов и комбинаций. Расскажу о полной архитектуре.
Архитектура системы
Основные компоненты:
- DamageType — enum и конфигурация типов урона
- ElementalDamage — класс для хранения информации об уроне
- DamageCalculator — расчёт финального урона
- StatusEffect — система статус-эффектов
- 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 и логики
Расширение возможностей
- Комбо множители — за несколько эффектов одновременно
- Иммунитеты — враги могут быть невосприимчивы к эффектам
- Взаимодействия с окружением — лёд на огне создаёт пар
- Усиления от элементов — урон увеличивается от совпадения типов
Эта система обеспечивает глубокую и интересную механику боевых взаимодействий.