Архитектура для игры - пару или не пара?

Я строю простую игру, состоящую из Mobiles - персонажей в игре (Mobs). Каждая моба может выполнять определенные функции. Чтобы дать эту функциональность Mob, я создал Поведение.

Например, скажем, что моб должен перемещаться по игровому полю, я бы дал ему MoveBehavior - это добавлено во внутренний список Behaviors для класса mob:

// Defined in the Mob class
List<Behavior> behaviors;

// Later on, add behavior...
movingMob.addBehavior(new MovingBehavior());

Мой вопрос в том, что. Большинство действий будут манипулировать чем-то о толпе. В примере с MoveBehavior он изменит положение mob X, Y в мире. Однако для каждого поведения требуется конкретная информация, такая как "movementRate" - где следует сохранять MoveRate?

Должен ли он храниться в классе Mob? Другие Мобы могут пытаться взаимодействовать с ним, замедляя/ускоряя толпу, и ему легче получить доступ на уровне мобов... но не у всех мобов есть движение, так что это может вызвать беспорядок.

Или он должен храниться в классе MoveBehavior? Это скрывает его, что затрудняет взаимодействие других мобов - но это не загромождает недвижущийся моб с дополнительными и неиспользуемыми свойствами (например, башня, которая не двигается, никогда не понадобится использовать MoveRate).

Ответ 1

Это классическая проблема "поведенческой композиции". Компромисс заключается в том, что чем более независимым является поведение, тем труднее им взаимодействовать друг с другом.

С точки зрения игрового программирования самым простым решением является выбор набора "основных" или "машинных" данных, и помещаем это в основной объект, тогда поведение может иметь доступ и изменять эти данные - потенциально через функциональный интерфейс.

Если вам нужны данные, специфичные для поведения, это хорошо, но чтобы избежать коллизий в именах переменных, вы можете захотеть, чтобы интерфейс для доступа к нему включал имя поведения. Как:

obj.getBehaviorValue("movement", "speed")

obj.setBehaviorValue("movement", "speed", 4)

Таким образом, два поведения могут определять свои собственные переменные, называемые скоростью и не сталкиваться. Этот тип кросс-поэтажных геттеров и сеттеров позволял бы общаться, когда это требуется.

Я бы предложил посмотреть на язык сценариев, например Lua или Python.

Ответ 2

Если это связано с поведением и имеет смысл только в контексте этого поведения, то оно должно храниться как часть его.

Скорость движения имеет смысл только для того, что может двигаться. Это означает, что он должен храниться как часть объекта, который представляет его способность двигаться, что кажется вашим MoveBehavior.

Если это затрудняет доступ, это больше похоже на проблему с вашим дизайном. Тогда вопрос заключается не в том, "должен ли я обманывать и помещать некоторые из переменных внутри класса Mob вместо того, что он принадлежит", а скорее "как мне облегчить взаимодействие с этими индивидуальными поведением".

Я могу подумать о нескольких способах реализации этого. Очевидным является простая функция-член в классе Mob, которая позволяет вам выбрать индивидуальное поведение, например:

class Mob {
  private List<Behavior> behaviors;
  public T Get<T>(); // try to find the requested behavior type, and return it if it exists
}

Другие могут тогда сделать что-то вроде этого:

Mob m;
MovementBehavior b = m.Get<MovementBehavior();
if (b != null) {
  b.SetMovementRate(1.20f);
}

Вы также можете поместить некоторые из них вне класса Mob, создавая вспомогательную функцию, которая изменяет скорость движения, если она существует, и ничего не делает:

static class MovementHelper {
  public static SetMovementRate(Mob m, float movementrate){
    MovementBehavior b = m.Get<MovementBehavior();
    if (b != null) {
      b.SetMovementRate(1.20f);
    }
  }
}

а затем другие могли бы использовать его следующим образом:

MovementHelper.SetMovementRate(m, 1.20f);

который обеспечит легкий доступ к изменению поведения, но без загромождения класса Mob. (Разумеется, было бы заманчиво превратить это в метод расширения, но это может привести к слишком большому беспорядку в открытом интерфейсе класса Mob. Может быть предпочтительнее дать понять, что это вспомогательная функциональность, которая находится за пределами сам класс Mob)

Ответ 3

Вы можете взять шаблон из WPF (прикрепленные свойства). Ребятам WPF нужен способ сортировки свойств прикрепления к элементам управления во время выполнения. (например, если вы поместили элемент управления внутри сетки, было бы неплохо, если бы элемент управления имел свойство Row - они это делали с добавленными свойствами.

Он работает примерно так: (обратите внимание, что это, вероятно, не совсем соответствует реализации WPF, и я не учитываю регистрацию свойств зависимостей, поскольку вы не используете XAML)


public class MoveBehavior: Behavior
{
  private static Dictionary<Mob, int> MovementRateProperty;

  public static void SetMovementRate(Mob theMob, int theRate)
  {
     MovementRateProperty[theMob] = theRate;
  }

  public static int GetMovementRate(Mob theMob)
  {
      // note, you will need handling for cases where it doesn't exist, etc
      return MovementRateProperty[theMob];
  }
}

Дело в том, что Behavior владеет свойством, но вам не нужно идти spelunking, чтобы получить его. Вот код, который извлекает скорость передвижения мобов:


// retrieve the rate for a given mob
int rate = MoveBehavior.GetMovementRate(theMob);
// set the rate for a given mob
MoveBehavior.SetMovementRate(mob, 5);

Ответ 4

Взгляните на дизайн систем компонентов/сущностей:

http://www.devmaster.net/articles/oo-game-design/

На сегодняшний день лучшее, что я видел до сих пор.

Умные люди говорят, что это единственный способ пойти с большими играми, но это требует сдвига в том, как вы думаете об ООП.

Ответ 5

ИМХО, скорость передвижения связана с движущимся поведением, а не с самой Моб, и, как вы сказали, она не обязательно перемещается. Таким образом, переменная должна быть связана с поведением, изменение в движенииRate является изменением его поведения, а не самой толпой.

Вы также можете создать базовый класс Mob и получить MovingMob. Но, я думаю, это действительно не так, как только вы, очевидно, можете иметь произвольную комбинацию разных поведений...

- EDIT -

Во-первых, очевидно, что у вас не будет такого же типа поведения дважды в одном и том же Mob (например, ни один моб не имеет двух движенийBehaviors в одном типе), поэтому в этом случае выбор является лучшим вариантом, поскольку он избегает дублирования

У вас может быть метод в каждой из них, например

    public Behavior GetBehavior(Type type)
    {
        foreach (var behavior in behaviorHashSet)
        {
            if ( behavior.GetType() == type)
                return behavior;

        }
        return null;
    }

Тогда вы можете делать все, что захотите, с таким поведением, когда у вас есть Mob. Кроме того, вы можете изменить метод GetHashCode() и Equals(), чтобы убедиться, что у вас нет дубликата, или еще более ускорите метод GetBehavior (постоянное время)

Ответ 6

Изменить: нижеприведенный ответ действительно имеет смысл, если вы не создаете новое MovingBehavior для каждой толпы, но просто имеете однотонный MovingBehavior.

Я бы сказал, что толпа (ugh, я ненавижу это слово для игровых NPC, но это не ваша ошибка), когда при вызове addBehavior() получает объект BehaviorState, возвращаемый из addBehavior(), и это он держится вокруг и вводит в действие добавленное поведение. Затем предоставим интерфейс для MovingBehavior, чтобы легко получить его объект BehaviorState из movingMob, и он сохраняет все, что ему нужно хранить там.

Ответ 7

Итак, что вы пытаетесь сделать?

Каков самый простой способ хранения данных о скорости движения?

Если это необходимо только в классе MoveBehavior, он должен быть там:

public class MoveBehavior {
    public int MovementRate { get; set; }
}

Если это необходимо по своему усмотрению классом Mob, тогда он будет легче раскрываться через класс Mob:

public class Mob {
    public int MovementRate { get; set; }
}

public class MoveBehavior {
    public MoveBehavior(Mob mob) { MobInEffect = mob; }

    public Mob MobInEffect {get; set;}

    // can access MovementRate through MovInEffect.MovementRate
}

Итак, все зависит от того, чего вы пытаетесь достичь с помощью этой логики поведения. Я бы порекомендовал вам вытолкнуть дизайнерское решение, пока вам действительно не нужно это делать так или иначе. Сосредоточьтесь на том, чтобы сделать это сначала проще и рефакторировать позже. Обычно чаще, чем раньше, выполнение раннего дизайна может привести к сложной архитектуре.


Более прагматичное решение...

Я имею в виду, что вы сначала реализуете все, что хотите, от движения в классе Mob:

public class Mob {

    // Constructors and stuff here

    public void Move(long ticks) 
    {
        // do some voodoo magic with movement and MovementRate here
    }

    protected int MovementRate { get; set; }
}

И когда это сработает, уничтожьте эту реализацию до класса MoveBehavior, если вам действительно нужно:

public class Mob {

    // Constructors and stuff here

    public MoveBehavior Moving { set; get; }

    public void Move(long ticks) 
    {
        Moving.Move(ticks, this);
    }
}

public class MoveBehavior {
    protected int MovementRate { get; set; }

    public void Move(long ticks, Mob mob)
    {
        // logic moved over here now
    }
}

После этого, если вам действительно нужно выполнить более одного типа поведения, но они совместно используют общий интерфейс, тогда создайте этот интерфейс к тому моменту, и пусть это реализует поведение.

Ответ 8

Если бы я разрабатывал что-то вроде этого, я бы попытался использовать интерфейсы для определения того, какое поведение имеет моб:

public interface IMovable
{
    int MovementRate { get; set; }
    void MoveTo(int x, int y);
}

public class Monster : Mob, IMovable
{
    public int MovementRate { get; set; }

    public void MoveTo(int x, int y)
    {
         // ...
    }
}

Таким образом, вы можете проверить, может ли моб двигаться, выполнив что-то вроде этого:

Monster m = new Monster();
if (m is IMovable)
{
    m.MoveTo(someX, someY);
}