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

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

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

Условие

Реализуйте систему способностей для action игры.

Требования

  1. Базовый класс Ability с методами Activate, Deactivate
  2. Кулдаун способности
  3. Стоимость маны/энергии
  4. Разные типы способностей: мгновенные, продолжительные, AOE
  5. UI отображение кулдаунов
  6. Привязка к кнопкам 1-4

Примеры способностей

  • Рывок (dash)
  • Щит (временная неуязвимость)
  • Огненный шар (снаряд)
  • Исцеление

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

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

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

Решение: Система способностей для action игры

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

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

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

  1. Ability — базовый класс для всех способностей
  2. AbilityManager — управление способностями персонажа
  3. ResourceManager — управление маной/энергией
  4. AbilityUI — отображение кулдаунов и статуса

Базовый класс Ability

public abstract class Ability : MonoBehaviour
{
    [SerializeField] protected string abilityName = "Ability";
    [SerializeField] protected float cooldown = 5f;
    [SerializeField] protected float manaCost = 10f;
    [SerializeField] protected float castTime = 0.2f;
    [SerializeField] protected int slotIndex = 0;
    [SerializeField] protected Sprite abilityIcon;
    
    protected float cooldownTimer = 0f;
    protected bool isActive = false;
    protected Character character;
    
    public string AbilityName => abilityName;
    public float Cooldown => cooldown;
    public float ManaCost => manaCost;
    public float CooldownTimer => cooldownTimer;
    public bool IsOnCooldown => cooldownTimer > 0f;
    public Sprite Icon => abilityIcon;
    public int SlotIndex => slotIndex;
    
    protected virtual void Start()
    {
        character = GetComponent<Character>();
    }
    
    protected virtual void Update()
    {
        if (cooldownTimer > 0)
        {
            cooldownTimer -= Time.deltaTime;
        }
    }
    
    public virtual bool CanActivate()
    {
        return !IsOnCooldown && character.HasEnoughMana(manaCost) && !isActive;
    }
    
    public virtual void Activate()
    {
        if (!CanActivate())
            return;
        
        // Отнимаем ману
        character.ConsumeMana(manaCost);
        
        // Ставим кулдаун
        cooldownTimer = cooldown;
        
        // Вызываем специфичную реализацию
        ExecuteAbility();
        
        // Оповещаем систему
        AbilityManager.Instance?.OnAbilityUsed(this);
    }
    
    public virtual void Deactivate()
    {
        isActive = false;
    }
    
    protected abstract void ExecuteAbility();
    
    public virtual float GetCooldownPercent()
    {
        return 1f - (cooldownTimer / cooldown);
    }
}

Типы способностей

// Мгновенная способность
public class InstantAbility : Ability
{
    protected override void ExecuteAbility()
    {
        Debug.Log($"{abilityName} activated instantly!");
    }
}

// Способность с продолжительностью
public class DurationAbility : Ability
{
    [SerializeField] protected float duration = 3f;
    protected float durationTimer = 0f;
    
    protected override void Update()
    {
        base.Update();
        
        if (isActive)
        {
            durationTimer -= Time.deltaTime;
            if (durationTimer <= 0)
            {
                Deactivate();
            }
        }
    }
    
    protected override void ExecuteAbility()
    {
        isActive = true;
        durationTimer = duration;
        OnDurationStart();
    }
    
    public override void Deactivate()
    {
        if (isActive)
        {
            OnDurationEnd();
        }
        base.Deactivate();
    }
    
    protected virtual void OnDurationStart() { }
    protected virtual void OnDurationEnd() { }
}

// AOE способность
public class AOEAbility : Ability
{
    [SerializeField] protected float aoeRadius = 5f;
    [SerializeField] protected float damage = 25f;
    [SerializeField] protected LayerMask damageLayer;
    [SerializeField] protected GameObject aoePrefab;
    
    [SerializeField] protected Vector3 targetPosition = Vector3.zero;
    
    protected override void ExecuteAbility()
    {
        if (aoePrefab != null)
        {
            GameObject aoeInstance = Instantiate(aoePrefab, targetPosition, Quaternion.identity);
            AOEEffect aoeEffect = aoeInstance.GetComponent<AOEEffect>();
            if (aoeEffect != null)
            {
                aoeEffect.Initialize(aoeRadius, damage, damageLayer);
            }
            Destroy(aoeInstance, 2f);
        }
        
        DealAOEDamage(targetPosition, aoeRadius, damage);
    }
    
    protected virtual void DealAOEDamage(Vector3 position, float radius, float damageAmount)
    {
        Collider[] hitColliders = Physics.OverlapSphere(position, radius, damageLayer);
        
        foreach (var collider in hitColliders)
        {
            IDamageable damageable = collider.GetComponent<IDamageable>();
            if (damageable != null && damageable != character)
            {
                damageable.TakeDamage(damageAmount);
            }
        }
    }
}

Конкретные реализации

// Рывок
public class DashAbility : InstantAbility
{
    [SerializeField] private float dashSpeed = 30f;
    [SerializeField] private float dashDuration = 0.3f;
    
    protected override void ExecuteAbility()
    {
        base.ExecuteAbility();
        character.Dash(dashSpeed, dashDuration);
    }
}

// Щит (неуязвимость)
public class ShieldAbility : DurationAbility
{
    [SerializeField] private float damageReduction = 0.7f;
    [SerializeField] private GameObject shieldVisual;
    
    protected override void OnDurationStart()
    {
        character.SetInvulnerable(true);
        if (shieldVisual != null)
        {
            shieldVisual.SetActive(true);
        }
    }
    
    protected override void OnDurationEnd()
    {
        character.SetInvulnerable(false);
        if (shieldVisual != null)
        {
            shieldVisual.SetActive(false);
        }
    }
}

// Огненный шар
public class FireballAbility : AOEAbility
{
    [SerializeField] private GameObject fireballPrefab;
    [SerializeField] private float projectileSpeed = 20f;
    [SerializeField] private float projectileLifetime = 3f;
    
    protected override void ExecuteAbility()
    {
        Vector3 spawnPosition = character.transform.position + character.transform.forward;
        GameObject projectile = Instantiate(fireballPrefab, spawnPosition, Quaternion.identity);
        
        Rigidbody rb = projectile.GetComponent<Rigidbody>();
        if (rb != null)
        {
            rb.velocity = character.transform.forward * projectileSpeed;
        }
        
        Fireball fireball = projectile.GetComponent<Fireball>();
        if (fireball != null)
        {
            fireball.Initialize(aoeRadius, damage, damageLayer);
        }
        
        Destroy(projectile, projectileLifetime);
    }
}

// Исцеление
public class HealAbility : InstantAbility
{
    [SerializeField] private float healAmount = 30f;
    [SerializeField] private float healRadius = 8f;
    [SerializeField] private LayerMask allyLayer;
    
    protected override void ExecuteAbility()
    {
        base.ExecuteAbility();
        HealAllies();
    }
    
    private void HealAllies()
    {
        Collider[] hitColliders = Physics.OverlapSphere(character.transform.position, healRadius, allyLayer);
        
        foreach (var collider in hitColliders)
        {
            Character ally = collider.GetComponent<Character>();
            if (ally != null)
            {
                ally.Heal(healAmount);
            }
        }
    }
}

AbilityManager - Управление способностями

public class AbilityManager : MonoBehaviour
{
    public static AbilityManager Instance { get; private set; }
    
    [SerializeField] private Ability[] abilities = new Ability[4];
    [SerializeField] private Character character;
    
    private Dictionary<int, Ability> abilitySlots = new Dictionary<int, Ability>();
    
    public event System.Action<Ability> OnAbilityUsed;
    
    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
        }
        else
        {
            Instance = this;
        }
    }
    
    private void Start()
    {
        // Инициализируем слоты способностей
        for (int i = 0; i < abilities.Length; i++)
        {
            if (abilities[i] != null)
            {
                abilitySlots[i + 1] = abilities[i];
            }
        }
    }
    
    private void Update()
    {
        // Проверяем нажатия клавиш 1-4
        for (int i = 1; i <= 4; i++)
        {
            if (Input.GetKeyDown(KeyCode.Alpha0 + i))
            {
                ActivateAbility(i);
            }
        }
    }
    
    public void ActivateAbility(int slotIndex)
    {
        if (abilitySlots.TryGetValue(slotIndex, out Ability ability))
        {
            ability.Activate();
        }
    }
    
    public Ability GetAbility(int slotIndex)
    {
        abilitySlots.TryGetValue(slotIndex, out Ability ability);
        return ability;
    }
    
    public void SetAbility(int slotIndex, Ability ability)
    {
        abilitySlots[slotIndex] = ability;
    }
}

ResourceManager - Управление ресурсами

public class ResourceManager : MonoBehaviour
{
    [SerializeField] private float maxMana = 100f;
    [SerializeField] private float currentMana;
    [SerializeField] private float manaRegenRate = 10f;
    
    private Character character;
    
    public event System.Action<float, float> OnManaChanged;
    
    private void Start()
    {
        currentMana = maxMana;
        character = GetComponent<Character>();
    }
    
    private void Update()
    {
        // Регенерация маны
        if (currentMana < maxMana)
        {
            currentMana = Mathf.Min(currentMana + manaRegenRate * Time.deltaTime, maxMana);
            OnManaChanged?.Invoke(currentMana, maxMana);
        }
    }
    
    public bool HasEnoughMana(float manaCost)
    {
        return currentMana >= manaCost;
    }
    
    public void ConsumeMana(float amount)
    {
        currentMana = Mathf.Max(currentMana - amount, 0);
        OnManaChanged?.Invoke(currentMana, maxMana);
    }
    
    public void RestoreMana(float amount)
    {
        currentMana = Mathf.Min(currentMana + amount, maxMana);
        OnManaChanged?.Invoke(currentMana, maxMana);
    }
    
    public float GetManaPercent() => currentMana / maxMana;
}

AbilityUI - Интерфейс

public class AbilityUI : MonoBehaviour
{
    [SerializeField] private AbilitySlotUI[] abilitySlots = new AbilitySlotUI[4];
    [SerializeField] private Image manaBar;
    [SerializeField] private Text manaText;
    
    private void Start()
    {
        AbilityManager.Instance.OnAbilityUsed += UpdateUI;
        ResourceManager.Instance.OnManaChanged += UpdateManaUI;
        
        // Инициализируем слоты
        for (int i = 0; i < abilitySlots.Length; i++)
        {
            Ability ability = AbilityManager.Instance.GetAbility(i + 1);
            if (ability != null)
            {
                abilitySlots[i].SetAbility(ability);
            }
        }
    }
    
    private void Update()
    {
        // Обновляем кулдауны
        for (int i = 0; i < abilitySlots.Length; i++)
        {
            Ability ability = AbilityManager.Instance.GetAbility(i + 1);
            if (ability != null)
            {
                abilitySlots[i].UpdateCooldown(ability.GetCooldownPercent());
            }
        }
    }
    
    private void UpdateUI(Ability ability)
    {
        // UI обновляется автоматически через кулдаун
    }
    
    private void UpdateManaUI(float currentMana, float maxMana)
    {
        if (manaBar != null)
        {
            manaBar.fillAmount = currentMana / maxMana;
        }
        if (manaText != null)
        {
            manaText.text = $"{currentMana:F0} / {maxMana:F0}";
        }
    }
}

public class AbilitySlotUI : MonoBehaviour
{
    [SerializeField] private Image cooldownImage;
    [SerializeField] private Image iconImage;
    [SerializeField] private Text cooldownText;
    
    private Ability ability;
    
    public void SetAbility(Ability newAbility)
    {
        ability = newAbility;
        if (iconImage != null && ability.Icon != null)
        {
            iconImage.sprite = ability.Icon;
        }
    }
    
    public void UpdateCooldown(float cooldownPercent)
    {
        if (cooldownImage != null)
        {
            cooldownImage.fillAmount = cooldownPercent;
        }
        
        if (cooldownText != null)
        {
            float remainingTime = ability.CooldownTimer;
            if (remainingTime > 0)
            {
                cooldownText.text = remainingTime.ToString("F1");
                cooldownText.enabled = true;
            }
            else
            {
                cooldownText.enabled = false;
            }
        }
    }
}

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

1. Наследование и полиморфизм — различные типы способностей через базовый класс

2. Управление ресурсами — система маны с регенерацией

3. Кулдауны — гибкая система кулдаунов для каждой способности

4. UI-интеграция — отображение иконок, прогресса кулдауна, запаса маны

5. Горячие клавиши — привязка способностей к клавишам 1-4

6. Расширяемость — легко добавлять новые типы способностей

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

  • Комбо способностей — использование одной способности усиливает другую
  • Уровни способностей — улучшение урона, кулдауна, стоимости
  • Предметы силы — артефакты, которые активируют доп. эффекты
  • Сохранение умений — запись активных способностей в PlayerPrefs
  • Анимации — синхронизация с приложением способностей

Эта система обеспечивает гибкость, масштабируемость и удобство расширения новым функционалом.