Реализация поведения в простой приключенческой игре

11

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

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

Пользовательский ввод принимает форму [action] [entity]. RoomИспользует имя лица , чтобы вернуть соответствующий Entityобъект, который затем использует имя действия , чтобы найти правильную функцию и выполняет его.

Чтобы сгенерировать описание комнаты, каждый Roomобъект отображает свою собственную строку описания, а затем добавляет строки описания каждого Entity. EntityОписание может меняться в зависимости от его состояния ( «Дверь открыта», «Дверь закрыта», «Дверь заперта», и т.д.).

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

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

РЕДАКТИРОВАТЬ 1: По запросу, примеры псевдокода этих функций действия.

string outsideDungeonBushesSearch(currentRoom, thisEntity, player)
    if thisEntity["is_searched"] then
        return "There was nothing more in the bushes."
    else
        thisEntity["is_searched"] := true
        currentRoom.setEntity("dungeonDoorKey")
        return "You found a key in the bushes."
    end if

string dungeonDoorKeyUse(currentRoom, thisEntity, player)
    if getEntity("outsideDungeonDoor")["is_locked"] then
        getEntity("outsideDungeonDoor")["is_locked"] := false
        return "You unlocked the door."
    else
        return "The door is already unlocked."
    end if

Функции описания действуют почти одинаково, проверяя состояние и возвращая соответствующую строку.

РЕДАКТИРОВАТЬ 2: пересмотрел формулировку моего вопроса. Предположим, что может существовать значительное количество внутриигровых объектов, которые не имеют общего поведения (реакции на конкретные состояния на конкретные действия) с другими объектами. Есть ли способ, которым я могу определить эти уникальные поведения более понятным и более понятным способом, чем написание пользовательской функции для каждого конкретного действия?

Эрик
источник
1
Я думаю, вам нужно объяснить, что делают эти «функции действия» и, возможно, опубликовать некоторый код, потому что я не уверен, о чем вы там говорите.
Джокинг
Добавил код.
Эрик

Ответы:

5

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

Один из подходов к моей голове - определить объект Entity, который расширяет все конкретные объекты в вашей игре. Каждый объект будет иметь таблицу (какую бы структуру данных ваш язык не использовал для ассоциативных массивов), которая связывает разные действия с разными результатами. Действия в таблице, скорее всего, будут Strings (например, «open»), в то время как связанный результат может даже быть частной функцией в объекте, если ваш язык поддерживает функции первого класса.

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

Между тем, один из публичных методов является то, как Entity.actOn (действие String) Затем в этом методе сравнения действия передается с использованием таблицей действий для этого объекта; если это действие в таблице затем возвращает результат.

Теперь все различные функции, необходимые для каждого объекта, будут содержаться внутри объекта, что позволяет легко повторять этот объект в других комнатах (например, создавать экземпляр объекта Door в каждой комнате, в которой есть дверь)

И, наконец, определить все номера в XML или JSON или независимо от того, так что вы можете иметь много уникальных номера, без необходимости писать отдельный код для каждой отдельной комнаты. Загрузите этот файл данных при запуске игры, и анализировать данные, чтобы создавать экземпляры объектов, которые населяют вашу игру. Что-то типа:

<rooms>
  <room id="room1">
    <description>Outside the dungeon you see some bushes and a heavy door over the entrance.</description>
    <entities>
      <bush>
        <description>The bushes are thick and leafy.</description>
        <contains>
          <key />
        </contains>
      </bush>
      <door connection="room2" isLocked="true">
        <description>It's an oak door with stout iron clasps.</description>
      </door>
    </entities>
  </room>

  <room id="room2">
    etc.

Сложение: ах, я только что прочитал ответ FXIII и это немного ближе к концу выскочил на меня:

(no things like <item triggerFlamesOnPicking="true"> that you will use just once)

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

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

<entity name="door">
  <description>It's an oak door with stout iron clasps.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room2" />
  </components>
</entity>

но одна двери с огненным шаром ловушкой будет

<entity name="door">
  <description>There are strange runes etched into the wood.</description>
  <components>
    <lock isLocked="true" />
    <portal connection="room7" />
    <fireballTrap />
  </components>
</entity>

и тогда единственный уникальный код, который мне пришлось бы написать для этой двери, - это компонент FireballTrap. Он будет использовать те же компоненты Lock и Portal, что и все другие двери, и если позже я решу использовать FireballTrap в сундуке с сокровищами или что-то такое же простое, как добавление компонента FireballTrap в этот сундук.

Независимо от того, определяете ли вы все компоненты в скомпилированном коде или на отдельном языке сценариев, я не вижу большого различия (в любом случае вы будете писать код где-нибудь ), но важно то, что вы можете значительно сократить количество уникального кода, который нужно написать. Черт возьми, если вы не беспокоитесь о гибкости для дизайнеров / моддеров уровней (в конце концов, вы сами пишете эту игру), вы можете даже заставить все сущности наследовать от Entity и добавлять компоненты в конструктор, а не в файл конфигурации или скрипт или без разницы:

Door extends Entity {
  public Door() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
  }
}

TrappedDoor extends Entity {
  public TrappedDoor() {
    addComponent(new LockComponent());
    addComponent(new PortalComponent());
    addComponent(new FireballTrap());
  }
}
jhocking
источник
1
Это работает для обычных, повторяемых элементов. Но как насчет сущностей, которые однозначно реагируют на ввод пользователя? Создание подкласса Entityтолько для одного объекта группирует код, но не уменьшает объем кода, который я должен написать. Или это неизбежная ловушка в этом отношении?
Эрик
1
Я обратился к примерам, которые вы привели. Я не могу читать твои мысли; какие объекты и входные данные вы хотите иметь?
Джокинг
Отредактировал мой пост, чтобы лучше объяснить мои намерения. Если я правильно понимаю ваш пример, похоже, что каждый тег сущности соответствует некоторому подклассу, Entityа атрибуты определяют его начальное состояние. Я предполагаю, что дочерние теги объекта действуют как параметры для любого действия, с которым связан этот тег, верно?
Эрик
да, это идея.
Джокинг
Я должен был подумать, что компоненты были бы частью решения. Спасибо за помощь.
Эрик
1

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

«Контейнер» (куст в ответе джокера) - это совпадающий путь, но вы видите, что он недостаточно гибок .

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

Мое предложение заключается в использовании в интерпретируемый язык для кода поведения.

Подумайте о примере куста: это контейнер, но в нашем кусте должны быть определенные предметы; контейнерный объект может иметь:

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

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

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

Теперь у вас есть много вариантов архитектуры: вы можете определить поведенческие инструменты в качестве базовых классов , используя язык кода или язык сценариев (вещи таких контейнеров, двери , как и так далее). Цель из theese вещей , чтобы позволить вам описать объекты Исли на общую простые модели поведения и их настройки с помощью привязок на языке сценариев .

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

Использование стратегии сценариев пусть вам сохранить ваши настройки простого (нет вещи , как , <item triggerFlamesOnPicking="true">что вы будете использовать только один раз), позволяя вам выразить нечетный beaviours (забавные из них) , добавив некоторые строки коды

В двух словах: скрипты как файл конфигурации, который может запускать код.

FXIII
источник