Реализовать систему способностей персонажа
Условие
Реализуйте систему способностей для action игры.
Требования
- Базовый класс Ability с методами Activate, Deactivate
- Кулдаун способности
- Стоимость маны/энергии
- Разные типы способностей: мгновенные, продолжительные, AOE
- UI отображение кулдаунов
- Привязка к кнопкам 1-4
Примеры способностей
- Рывок (dash)
- Щит (временная неуязвимость)
- Огненный шар (снаряд)
- Исцеление
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Система способностей для action игры
Эта задача требует создания гибкой системы способностей с различными типами, управлением ресурсами и UI-отображением кулдаунов. Расскажу о полной архитектуре.
Архитектура системы
Основные компоненты:
- Ability — базовый класс для всех способностей
- AbilityManager — управление способностями персонажа
- ResourceManager — управление маной/энергией
- 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
- Анимации — синхронизация с приложением способностей
Эта система обеспечивает гибкость, масштабируемость и удобство расширения новым функционалом.