Шаблон для выполнения игровых действий


11

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

В настоящее время у меня есть абстрактный BaseAction, который использует универсальные .NET для определения различных объектов, которые возвращаются различными действиями. Все это реализовано по схеме, аналогичной команде, где каждое действие отвечает за себя и делает все, что ему нужно.

Мои соображения об абстрактности таковы, что у меня может быть один ActionHandler, и AI может просто поставить в очередь различные действия, реализующие baseAction. И причина, по которой он является универсальным, заключается в том, что различные действия могут возвращать информацию о результате, относящуюся к действию (поскольку разные действия могут иметь совершенно разные результаты в игре), наряду с некоторыми распространенными реализациями beforeAction и afterAction.

Итак ... есть более приемлемый способ сделать это, или это звучит нормально?


Звучит хорошо, вопрос в том, что вы подразумеваете под очередью? Большинство игр имеют очень быстрый ответ? «ИИ может
поставить в

Хорошая точка зрения. Там нет очереди. Нужно просто знать, занят ли он, а если нет, выполнить действие.
Аркиликнам

Ответы:


18

Я не думаю, что есть один приемлемый способ реализации этой концепции, но я действительно хотел бы поделиться тем, как я обычно справляюсь с этим в своих играх. Это что-то вроде комбинации шаблона проектирования Command и шаблона Composite .

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

abstract class Action
{
    abstract void Update(float elapsed);
    bool Finished;
}

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

abstract class CompositeAction : Action
{
    void Add(Action action) { Actions.Add(action); }
    List<Action> Actions;
}

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

class Parallel : CompositeAction
{
    override void Update(float elapsed) 
    {
        Actions.ForEach(a=> a.Update(elapsed));
        Actions.RemoveAll(a => a.Finished);
        Finished = Actions.Count == 0;
    }
}

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

class Sequence : CompositeAction
{
    override void Update(float elapsed) 
    {
        if (Actions.Count > 0) 
        {
            Actions[0].Update(elapsed);
            if (Actions[0].Finished)
                Actions.RemoveAt(0);
        }
        Finished = Actions.Count == 0;
    }
 }

При этом в месте , это просто вопрос создания реализации конкретных действий, а также с использованием Parallelи Sequenceдействий для управления потоком выполнения. Я закончу примером:

// Create a parallel action to work as an action manager
Parallel actionManager = new Parallel();

// Send character1 to destination
Sequence actionGroup1 = new Sequence();
actionGroup1.Add(new MoveAction(character1, destination));
actionGroup1.Add(new TalkAction(character1, "Arrived at destination!"));
actionManager.Add(actionGroup1);

// Make character2 use a potion on himself
Sequence actionGroup2 = new Sequence();
actionGroup2.Add(new RemoveItemAction(character2, ItemType.Potion));
actionGroup2.Add(new SetHealthAction(character2, character2.MaxHealth));
actionGroup2.Add(new TalkAction(character2, "I feel better now!"));
actionManager.Add(actionGroup2);

// Every frame update the action manager
actionManager.Update(elapsed);

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


Это выглядит как очень хорошее решение. Из любопытства, как же вы позволяете UI знать, что рисовать? Содержат ли ваши игровые объекты (например, персонажи) состояние, которое используется для определения того, что произошло для целей рендеринга, или это делает само действие?
Arkiliknam

1
Обычно мои действия только изменяют состояние сущностей, и любые изменения в отображаемом выводе происходят как следствие этого изменения состояния, а не посредством самих действий. Например, в средстве рендеринга с непосредственным режимом не требуется никаких дополнительных шагов, поскольку Drawметод уже построен поверх состояния сущности, а изменения происходят автоматически. В рендерере с сохранением режима, таком как Flash, вы можете использовать наблюдаемый шаблон, чтобы вносить изменения в ваши объекты, распространяющиеся на экранные объекты, или устанавливать соединение вручную внутри самого объекта.
Дэвид Гувейя

1
В первой ситуации предположим, что у вашего Characterкласса есть Positionсвойство и Drawметод, который считывает текущее значение Positionи рисует там правильное изображение. В этой ситуации вам нужно только обновить значение Position, чтобы результат автоматически отображался на экране.
Дэвид Гувейя

1
Вторая ситуация, когда у вас Characterесть Positionсвойство, но оно делегирует рендеринг некоторому Spriteобъекту, который автоматически рендерится графом сцены или чем-то еще. В этой ситуации вы должны убедиться, что и позиция персонажа, и позиция спрайта всегда синхронизированы, что требует немного больше работы. Тем не менее, в любом случае я не понимаю, почему менеджер действий должен иметь к этому какое-либо отношение. :)
Дэвид Гувея

1
Оба метода имеют свои преимущества и недостатки. Я выбрал второй метод для своей 2D-игры и время от времени жалел об этом, потому что синхронизировать все значительно сложнее. Но есть и преимущества, например, при попытке определить, на какой объект щелкнули, или на том, что следует или не нужно нарисовать, потому что все, что будет отображено, содержится в одной структуре данных, а не разбросано по N типам объектов.
Дэвид Гувейя
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.