Кодирование разных состояний в приключенческих играх

12

Я планирую приключенческую игру и не могу понять, как правильно реализовать поведение уровня в зависимости от состояния развития сюжета.

Моя однопользовательская игра представляет собой огромный мир, в котором игрок должен общаться с людьми в городе на разных этапах игры. Однако, в зависимости от развития сюжета, игроку будут представлены разные вещи, например, лидер гильдии изменит локацию с городской площади на разные локации вокруг города; Двери будут открываться только в определенное время дня после завершения определенной процедуры; Различные события на экране / триггере происходят только после достижения определенного этапа.

Я наивно думал о том, чтобы изначально использовать оператор switch {}, чтобы решить, что должен сказать NPC или где его можно найти, и сделать задачи квестов взаимодействующими только после проверки состояния глобальной переменной game_state. Но я понял, что быстро столкнусь с множеством различных игровых состояний и переключателей, чтобы изменить поведение объекта. Этот оператор switch также будет сложно отладить, и я думаю, его также сложно использовать в редакторе уровней.

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

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

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

PS: у меня еще нет редактора уровней - я думаю об использовании чего-то вроде JME SDK или создании своего собственного.

Cardin
источник

Ответы:

9

Я думаю, что вам нужно в этом случае Государственный шаблон проектирования . Вместо того, чтобы иметь несколько экземпляров каждого игрового объекта, создайте один экземпляр, но инкапсулируйте его поведение в отдельный класс. Создайте несколько классов, по одному для каждого возможного поведения, и предоставьте всем классам один и тот же интерфейс. Свяжите один объект с вашим игровым объектом (начальное состояние), и, когда условия изменятся (достигнут рубеж, время дня и т. Д.), Вы переключите состояние этого объекта (т.е. свяжите его с другим объектом в зависимости от логики вашей игры). и обновите его свойства, если применимо.

Один пример того, как будет выглядеть интерфейс состояния (полностью составленный - просто для иллюстрации уровня контроля, который дает эта схема):

interface NPCState {
    Scene whereAmI(NPC o);
    String saySomething(NPC o);
}

И два реализующих класса:

class Busy implements NPCState {
    Scene whereAmI(NPC o) {
        return o.getWorkScene();
    }
    String saySomething(NPC o) {
        return "Can't talk now, I'm busy!";
    }
}

class Available implements NPCState {
    Scene whereAmI(NPC o) {
        return TAVERN;
    }
    String saySomething(NPC o) {
        String[] choices = o.getRandomChat();
        return choices[RANDOM.getInt(choices.length)];
    }
}

И переключение состояний:

// The time of day passed from "afternoon" to "evening"
NPCState available = new Available();
for ( NPC o : list ) {
    Scene oldScene = o.state.whereAmI(o);
    o.state = available;
    Scene newScene = o.state.whereAmI(o);
    moveGameObject(o, oldScene, newScene);
    ...

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

Это хорошо работает и с редакторами уровней: у вас может быть простое поле со списком, чтобы переключать «глобальное» состояние уровня, а затем добавлять и перемещать игровые объекты так, как вы хотите, чтобы они отображались в этом состоянии. Игровой движок будет отвечать только за «добавление» этого объекта на сцену, когда он имеет правильное состояние - но его параметры будут по-прежнему доступны для редактирования удобным для пользователя способом.

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

mgibsonbr
источник
знаете, вы могли бы объединить этот шаблон проектирования State с ассоциативным массивом, который я описал. Вы можете кодировать объекты состояния, как описано здесь, а затем выбирать между различными объектами состояния, используя ассоциативный массив, как я предлагал.
Джоккинг
Я согласен, это также хорошо, чтобы отделить игру от ее движка, и жесткая логика игры усиливает связь между ними (уменьшая возможность повторного использования). Однако есть компромисс, поскольку в зависимости от сложности вашего предполагаемого поведения попытка «мягкого кода» может привести к ненужному беспорядку . В этом случае может быть желателен смешанный подход (т.
Е. Иметь
Так что, насколько я понимаю, существует взаимно-однозначное сопоставление между NPCState и GameState. Затем я помещаю NPC в массив и перебираю их, назначая новое состояние NPCS, когда наблюдается изменение игрового состояния. NPCState должен уметь знать, как обрабатывать каждый отправляемый ему NPC различий, поэтому, по сути, NPCState содержит поведение всех NPC для данного состояния? Мне нравится, что все поведения хранятся в одном NPCState, который четко отображается в реализации редактора игры, но это делает NPCState довольно большим.
Cardin
О, я думаю, что я неправильно понял твои ответы. Я немного изменил, чтобы включить наблюдателей. Таким образом, это один diff NPCState для каждого diff NPC, кроме супер-общих, таких как Crowd NPC, которые могут делить состояние. Для каждого игрового состояния NPC будет регистрировать себя и свое NPCS-состояние с помощью Observer. Следовательно, Обозреватель будет точно знать, какой NPC зарегистрирован, чтобы изменить поведение и состояние игры, и просто перебирать их. А на стороне редактора игры, редактор игры просто должен передать Обозревателю сигнал об изменении состояния всего уровня.
Cardin
1
Да, это идея! У важных NPC будет много состояний, и переход между состояниями будет зависеть в основном от пройденных этапов. Обычные NPC могут также реагировать на вехи иногда, и даже их выбранное состояние зависит от внутреннего свойства (скажем, все NPC имеют начальное состояние по умолчанию, и когда вы впервые разговариваете с одним из них, он вводит себя, затем вводите нормальное состояние переключения цикла).
mgibsonbr
2

Выбор, который я бы рассмотрел, - это заставить отдельные объекты реагировать на разные игровые состояния или обслуживать разные уровни в разных игровых состояниях. Выбор между этими двумя будет зависеть от того, что именно я пытаюсь сделать в игре (каковы различные состояния? Как будет переход игры между состояниями? И т. Д.)

В любом случае, однако, я бы не стал делать это путем жесткого кодирования состояний в коде игры. Вместо массивного оператора switch в объектах NPC я бы вместо поведения NPC загружал в ассоциативный массив из файла данных, а затем использовал этот ассоциативный массив для запуска другого поведения для связанных состояний, что-то вроде этого:

if (state in behaviors) {
  behaviors[state]();
}
jhocking
источник
Будет ли этот файл данных своего рода скриптовым языком? Я думаю, что простого текстового файла данных может быть недостаточно для описания поведения. В любом случае, вы правы, что он должен быть загружен динамически. Я не могу придумать, как использовать игровой редактор для генерации правильного Java-кода, его определенно нужно проанализировать.
Cardin
1
Это была моя первоначальная мысль, но, увидев ответ mgibsonbr, я понял, что вы можете кодировать различные bevaviors как отдельные классы, а затем в файле данных просто сказать, какие классы поведения соответствуют какому состоянию. Загрузите эти данные в ассоциативный массив во время выполнения.
Джоккинг
Ох .. Да, это определенно проще! : D По сравнению со сценарием встраивания чего-то вроде Lua haha ​​..
Cardin
2

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

Вместо упомянутого шаблона проектирования состояния я бы использовал шаблон стратегии.

Если у NPC есть n способов взаимодействия с персонажем и m позиций, где он может быть, существует максимум (m * n) +1 классов, которые вы должны разработать. Используя шаблон стратегии, вы получите n + m + 1 классов, но эти стратегии также могут быть использованы другими npcs.

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

TOAOGG
источник
Шаблон Observer кажется интересным. Я думаю, что он может просто оставить все обязанности у NPC, чтобы зарегистрироваться у государственного наблюдателя. Паттерн «Стратегия» во многом напоминает поведение триггера и ИИ в Unity Engine, которое используется для обмена поведением между объектами различий (я думаю). Это звучит выполнимо. Я не уверен, каковы плюсы / минусы сейчас, но то, что Unity использует тот же метод, тоже несколько обнадеживает, ха-ха ..
Cardin
Я просто использовал эти два шаблона несколько раз, поэтому я не могу рассказать вам о минусах: - / Но я думаю, что было бы неплохо в случае единоличной ответственности и наличия возможности проверить каждую стратегию :) Это может сбить вас с толку, если ваш используя стратегию во многих разных классах, и вы хотите найти каждый класс, который ее использует.
TOAOGG
0

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

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

OTOH, если персонажи ходят по карте и делают разные вещи в разных местах, у вас часто есть один актер, который вращается через различные объекты поведения (например, идти прямо вперед / нет разговоров против стоять здесь / разговор о смерти Митча), который может включать «скрытые», если их цель была выполнена.

Тем не менее, обычно наличие дубликатов объекта, который вы создаете вручную, не должно вызывать никаких проблем. Сколько объектов вы можете создать? Если вы можете создать больше объектов, чем может зациклить ваша игра, посмотрите на их «скрытое» свойство и пропустите, ваш движок работает слишком медленно. Так что я бы не стал слишком беспокоиться об этом. Многие онлайн игры действительно делают это. Определенные персонажи или предметы всегда присутствуют, но не отображаются персонажам, у которых нет соответствующей миссии.

Вы даже можете комбинировать подходы: есть две двери в вашем жилом доме. Один ведет к квартире «до взыскания долгов», другой - к квартире после. Когда вы входите в коридор, на самом деле отображается только тот, который относится к вашему прогрессу в истории. Таким образом, вы можете просто иметь общий механизм для «элемента, видимого в текущий момент истории» и дверь с одним пунктом назначения. С другой стороны, вы можете сделать более сложные двери, которые могут иметь поведение, которое можно заменить, и одна из них - «перейти в полную квартиру», другая - «перейти в пустую квартиру». Это может показаться бессмысленным, если на самом деле меняется только назначение двери, но если меняется и ее внешний вид (например, большой замок перед дверью, который вы сначала должны взломать),

uliwitness
источник