Как сделать атаку разного вида в одном классе [РЕШЕНО]
Я делаю 2д рпг.Я хочу сделать врагов с разными типами атак например: атака вблизи,вдали,и призыв существ пока что.Как реализовать все типы атак в 1 классе(я имею в виду что в 1 классе тот класс который будет висеть на врагах, а так можно много классов создать на на враге должен быть один класс что бы было проще добавлять новых врагов),а не делать для каждого врага класс(это очень тупо т.к классы будут отличатся только методом атаки) так же желательно без огромной if else лапши типо если я в инспекторе задал что враг атакует вблизи то такой метод если вдали то такой и т.д, т.к это приведет к огромной лапше в будущем т.к планирую еще много вид атак.
Ответы (2 шт):
Как сделать в одном классе?
Заводим enum с перечислением видов атак:
public enum AttackType
{
Ax,
Bow,
Sword
}
Добавляем в класс врага сериализуемое поле с типом атаки:
public class Enemy : MonoBehaviour
{
[SerializeField] private AttackType attackType;
private void OnAttack()
{
switch (attackType)
{
case AttackType.Ax:
break;
case AttackType.Bow:
break;
case AttackType.Sword:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
Реализуем выбор способа атаки через switch-условие
В инспекторе это будет выглядеть так:

Это хороший способ для простых игр, НО, если вы планируете ещё много видов атак, то лучше заранее озаботиться способами кастомизации атак.
Например, это можно сделать через систему компонентов.
Создаем базовый компонент:
public abstract class AttackEnemyComponent : MonoBehaviour
{
public abstract void OnAttack();
}
либо:
public class AttackEnemyComponent : MonoBehaviour
{
public virtual void OnAttack()
{
}
}
Соответственно первый вариант абстрактный, а второй виртуальный, содержащий вариант атаки по умолчанию.
Создаем наследника:
public class AttackEnemyAx : AttackEnemyComponent
{
[SerializeField] private int power;
public override void OnAttack()
{
;
}
}
Тюним нашего врага:
[RequireComponent(typeof(AttackEnemyComponent))]
public class Enemy : MonoBehaviour
{
private AttackEnemyComponent _attackEnemyComponent;
private void Awake()
{
_attackEnemyComponent = GetComponent<AttackEnemyComponent>();
}
private void OnAttack()
{
_attackEnemyComponent.OnAttack();
}
}
Теперь мы можем не запихивать все особенности атаки в тип, а гибко настраивать всё что требуется
Если у тебя есть вопрос, как что-то описать в рамках одного класса, ответ только один, это должно быть описана как абстракция, а реализация вынесена в других классах. Ничего лучше пока не придумали (спойлер! и не придумают).
Атакующий не должен знать деталей реализации атаки, его дело станцевать нужную анимацию и выдать урон, если атаке это вообще нужно.
Как-то примерно так
public enum AttackRange { None, Meelee, Range }
public struct Damage
{
public object Sorce;
public float Value;
public AttackRange Range;
public Damage (object sorce, float value, AttackRange range)
{
Sorce = sorce;
Value = value;
Range = range;
}
}
public interface IAttacker
{
Damage GetDamage(AttackRange range);
}
public interface IDamageable
{
void ApplyDamage (Damage damage);
}
public class Unit : MonoBehaviour, IAttacker, IDamageable
{
[SerializeField] private Ability[] _abilitys; // список атак указывается в редакторе, ссылками
public void UseAbility (IDamageable target, int index = 0)
{
Ability UsedAbility = _abilitys[index];
UsedAbility.Execute(this, target);
PlayAnimation(UsedAbility.animation);
}
public void ApplyDamage (Damage damage)
{
}
public Damage GetDamage (AttackRange range)
{
return new Damage(this, 100, range);
}
private void PlayAnimation (string anim) { }
}
public abstract class Ability: MonoBehaviour
{
[SerializeField] protected string animName; // какую анимацию проигрывать атакующему
public string Animation => animName;
public abstract void Execute (IAttacker attacker, IDamageable target);
}
public class MeeleeAttack : Ability
{
// бдыщь с партикалс и звуком
[SerializeField] private GameObject _hitEffectTemplate;
public override void Execute (IAttacker attacker, IDamageable target)
{
target.ApplyDamage(attacker.GetDamage(AttackRange.Meelee));
Component Target = (Component)target;
Instantiate(_hitEffectTemplate, Target.transform);
}
}
public class RangeAttack : Ability
{
[SerializeField] private Shell _sheelTemplate; // префаб снаряда
public override void Execute (IAttacker attacker, IDamageable target)
{
Shell NewShell = Instantiate(_sheelTemplate);
NewShell.Deliver(target, attacker.GetDamage(AttackRange.Range));
}
}
Код грубо упрощён.
