Как вызвать объект после вызова метода другого объекта?

Можно ли вызвать объект, его метод, а потом другой объект? Речь не о сложных способах,а об элегантных возможностях самого языка 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 шт):

Автор решения: rahmanny

Используйте интерфейс и общий класс

    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())

→ Ссылка
Автор решения: Yaroslav

Ничего элегантно в желаемом тобой варианте нет от слова совсем.

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 пишется свой класс используемый всеми у кого есть жизни и другой класс для окружения типа сундуков или бочек.

→ Ссылка