Как вызвать объект после вызова метода другого объекта?
Можно ли вызвать объект, его метод, а потом другой объект? Речь не о сложных способах,а об элегантных возможностях самого языка C#. Например:
cruiser.Attack().fighter;
Вместо
private class ship {
private int _totalHP = 0;
private int _hp = 0;
private int _force = 0;
public void Attack(Ship target) { target.TakeAttack(force); }
public void TakeAttack(int damage) { _hp -= damage; } }
cruiser.Attack(fighter);
Или
battle003117.Attack(cruiser, fighter);
cruiser.hp = fighter.force; // get-set
Всё это далеко не так красиво в контексте игр, где всегда что-то к чему-то выполняет однотипные действия. Ту же атаку можно проводить на игрока (нападение), на персонажа, на устройство или часть тела... И такой унифицированный код очень уж соблазнительно удобен.
Ответы (3 шт):
Используйте интерфейс и общий класс
public interface IAttack
{
int TotalHP { get; set; }
int HP { get; set; }
int Force { get; set; }
public void DoAttack(IAttack target);
}
public abstract class General
{
public int TotalHP { get; set; }
public int HP { get; set; }
public int Force { get; set; }
public void DoAttack(IAttack target)
{
target.HP -= this.Force;
}
}
public class Ship : General, IAttack
{
}
public class Fort : General, IAttack
{
}
В таком случае, вы сможете выполнить приведение к интерфейсу
IAttack ship1 = new Ship
{
TotalHP = 100,
HP = 100,
Force = 10
};
IAttack fort1 = new Fort
{
TotalHP = 1000,
HP = 500,
Force = 50
};
ship1.DoAttack(fort1);
fort1.DoAttack(ship1);
Вы можете использовать:
- Интерфейс
- (Абстрактный) класс родитель
Интерфейс
Вы можете создать интерфейс с методом получения урона, который все объекты, способные получать урон, (игрок, враги...) будут реализовывать.
// Интерфейс
public interface IDamagable {
public void GetDamage(float damage); // 1
}
// Класс игрока
public class Player : MonoBehaviour, IDamagable
{
float HP; // 2
public void GetDamage(float damage) { // 3
// Уменьшение переменной HP
// Покраснение экрана или модельки игрока
// Уменьшение количества сердечек
// ...
}
}
public class Enemy : MonoBehaviour, IDamagable
{
public float HP; // 2
public void GetDamage (float damage) { // 3
// Уменьшение переменной HP
// Какие-то визуальные отображения: отвалилась рука, изменилась текстурка врага и так далее...
}
// 1 — функция урона в интерфейсе
// 2 — переменная здоровья
// 3 — функция урона в каждом классе, реализующем интерфейс
Такой подход хорош, если каждый класс, реализующий интерфейс, будет реализовывать его по-своему, то есть методы GetDamage() в разных классах будут отличаться.
Вызывать их можно как будто они являются "наследниками интерфейса":
Player player;
Enemy enemy1, enemy2;
...
player.GetDamage(10); // игрок получит урон
enemy1.GetDamage(20); // враг получит урон
То, что все объекты, которые могут получить урон, реализуют интерфейс получения урона, гарантирует, что в каждом таком классе точно будет прописан метод GetDamage()
P.S. Так как переменная HP скорее всего будет у всех объектов, способных получать урон, ее тоже можно занести в интерфейс.
(Абстрактный) класс-родитель
Если все объекты, способные получать урон, будут получать его одинаково, то можно сделать так:
public /*abstract*/ class Actor : MonoBehaviour // Абстрактный или не абстрактный
{
protected float HP;
public void GetDamage (float damage) {
HP -= damage;
// Другие команды, например, проверка смерти
}
}
public class Player : Actor
{
}
public class Enemy : Actor
{
}
public class Chest : Actor
{
public override void GetDamage () {
// Реализуем по-другому
}
}
Итак, теперь и Player, и Enemy имеют функцию урона, хотя она явно и не прописана. В данном случае все объекты, унаследованные от Actor будут иметь одинаковый метод GetDamage(). Однако, если вам вдруг понадобится все-таки поменять его, то это можно сделать через override. Это я продемонстрировал на классе Chest (например, сундук не должен отпрыгивать при получении урона -> переопределим его метод урона)
Теперь функции вызываем так:
Player player;
Enemy enemy;
Chest chest;
...
player.GetDamage(10);
enemy.GetDamage(10);
chest.GetDamage(10);
P.S. Класс-родитель можно сделать абстрактным, что запретит создание его экземпляров напрямую — только его наследников, если вы уверены, что действительно, от него не должны создаваться экземпляры. \n\n
Помимо этого в обоих случаях, если вам не известен точный тип обекта (игрок, враг, сундук), то вы можете создать его с типом данных соответственно интерфейса или класса-родителя:
IDamagable Player ( = new Player())
Actor Player ( = new Player())
Ничего элегантно в желаемом тобой варианте нет от слова совсем.
public struct Damage
{
public object Sorce;
public float Value;
public Damage (object sorce, float value)
{
Sorce = sorce;
Value = value;
}
}
public interface IDamagable
{
public void ApplyDamage (Damage damage);
}
public interface IAttacker
{
public Damage GetDamage ();
}
public static class AttackTarget
{
// метод расширения
public static void Attack (this IAttacker attacker, IDamagable target) {
target.ApplyDamage(attacker.GetDamage());
}
}
Применение:
public class Hero : MonoBehaviour, IAttacker
{
[SerializeField] private float _damageValue = 10;
public Damage GetDamage () {
return new Damage(this, _damageValue);
}
}
public class Goblin : MonoBehaviour, IDamagable
{
[SerializeField] private float _health = 10;
public void ApplyDamage (Damage damage) {
_health -= damage.Value;
}
}
public class Chest : MonoBehaviour, IDamagable
{
[SerializeField] private int _durability = 3;
public void ApplyDamage (Damage damage) {
_durability -= 1;
}
}
public class Foo : MonoBehaviour
{
[SerializeField] private Hero _hero;
[SerializeField] private Goblin _goblin;
[SerializeField] private Chest _chest;
public void Start () {
_hero.Attack(_goblin);
_hero.Attack(_chest);
}
}
Для TotalHealth, CurrentHelath, обработки и события Die пишется свой класс используемый всеми у кого есть жизни и другой класс для окружения типа сундуков или бочек.