Кодирование взаимодействий в текстовом приключении - PullRequest
10 голосов
/ 02 октября 2010

РЕДАКТИРОВАТЬ: Если вы не можете прочитать этот гигантский вопрос, я поместил резюме внизу.

В настоящее время я работаю над своего рода "структурой" для текстового приключения, которое я собираюсь сделать в C #, в качестве упражнения по кодированию. В этой структуре возможные действия определяются классом «Взаимодействие».

Потенциальными «Действующими» объектами являются Инвентарные предметы (палка, пистолет, меч), ​​Предметы окружающей среды (стена, дверь, окно) и Персонажи (люди, животные). У каждого из них есть свойство, представляющее собой список взаимодействий. На данный момент Interaction - это, по сути, пара имя-значение «действие / ответ». Когда вы набираете «разбить окно», он просматривает все возможные элементы, которые могут быть активны, которые есть у игрока, и соответствует теме (в данном случае «Окно»). Затем выясняется, что это действие «Smash», и ищет в списке взаимодействий в окне (элемент среды) ответ на действие «Smash», а затем записывает его в консоль.

Это все сделано, но вот что я застрял:

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

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

ЯВНО - субъект действия (предмет инвентаря, предмет окружающей среды или персонаж) меняет свое описание НАПРИМЕР. «пробивая стена» может изменить описание стены, чтобы описать вмятину в стене OR - Предмет действия заменяется другим предметом НАПРИМЕР. «разбить бутылку» приводит к замене «бутылки» на «разбитую бутылку» или «убить Джона», в результате чего персонажа Джона заменяет предмет окружающей среды «Труп Джона».

- возвращает ответ, описывающий предыдущее изменение НАПРИМЕР. «Разбитые кусочки бутылки разбросаны по полу.»

- Описание области изменено. НАПРИМЕР. "разбить лампочку" приводит к изменению описания комнаты, чтобы описать черную комнату

- Предметы добавляются / удаляются из инвентаря или окружающей среды НАПРИМЕР. "забрать бутылку". Теперь у вас есть бутылка в вашем инвентаре, и бутылка удалена из окружающей среды.

- Изменены доступные для движения направления и области, к которым они ведут НАПРИМЕР. «открыть дверь ключом» позволяет перейти на восток в другую комнату

- игрок перемещен в новую область НАПРИМЕР. «идти на север» перенесет вас в другой район.

Мне нужно как-то в общем определить, какое из этих последствий должно вызывать конкретное взаимодействие, и вызывать их. Действие может потенциально использовать несколько из этих последствий или только одно.

Например, если предметом является Бутылка:

« заполнить бутылку водой » сначала выдаст ответ, описывающий, что вы наполнили бутылку водой. Затем он заменяет элемент «бутылка» на элемент «бутылка воды». Это два последствия: возвращение ответа и замена элемента.

Скажем, вы тогда должны были сделать " бросить бутылку воды в окно ". Это сложнее. Сначала будет возвращен ответ, описывающий происходящие события, бутылка и окно будут и разбиты, и вода пойдет повсюду. Бутылка будет удалена из инвентаря Игрока. Затем «1059 * бутылка воды » будет заменена «разбитой бутылкой», а «Окно» будет заменено «Разбитым окном». Описание области также изменится, чтобы отразить это. Это пять последствий: возврат ответа, удаление предмета из инвентаря, замена двух предметов и обновление описания текущей области.

Как вы можете видеть, мне нужен общий способ, позволяющий определить для каждого «взаимодействия», каковы будут последствия этого действия, и соответствующим образом обновить другие объекты, такие как Предмет, Игрок (для инвентаря) и Область..

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

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

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

Для второго взаимодействия ясказал бы ему вернуть ответ («Бутылка летит по воздуху в ...»), вызвать RemoveFromInventory на предмет действия, вызвать UpdateStatus для бутылки («бутылка разбита») и окно («theокно разбито ») и вызовите UpdateAreaDescription, чтобы изменить описание текущей области (« Вы стоите в комнате с одним окном, стекло разбито на куски »).

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

РЕДАКТИРОВАТЬ 2: Чтобы прояснить дальше и попытаться суммировать проблему:

В моей игре есть объекты Actionable (бутылка, стена, Джон).Каждый объект Actionable имеет список объектов Interaction, которые описывают, как игрок может взаимодействовать с ними.На данный момент Interaction имеет свойство «Имя» («throw», «hit», «break») и возвращает ответ («You throw the»).

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

«бросить стеклянную бутылку»
- Ответ получен («Вы бросили стеклянную бутылку».)
- The »Бутылка ", удаляется из инвентаря Игрока.
- Заменяется новым, чтобы отразить изменение.(«Бутылка» заменена на «Разбитая бутылка»).
- возвращается второй ответ («Кусочки стеклянной бутылки разбросаны по полу»).

«бросить стеклянную бутылку в окно»
- ответ получен («Вы бросили стеклянную бутылку в окно»)убирается из инвентаря игрока.
- Объект заменяется новым объектом для отражения изменений.(«Бутылка» заменена на «Разбитая бутылка»).
- Второй необязательный объект заменяется новым, чтобы отразить изменение.(«Окно» заменено на «Разбитое окно»).
- Свойство «Описание» текущей области обновлено.(«Вы стоите в комнате с одним разбитым окном»).

Когда я создаю взаимодействия, как я могу варьировать дополнительные действия, которые они выполняют, такие как изменение статуса субъекта или изменения в описании текущей области?

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

Ответы [ 7 ]

2 голосов
/ 02 октября 2010

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

Блокировка объекта Распознаваемые глаголы

  • Взгляд
  • UseItemOn (Key001, LockPicks, Sledgehammer, ...)
  • Удар

Таким образом, вы можете обрабатывать глаголы в общем случае, но это не такузнайте ответом "Вы не можете , и обработайте глаголы, которые он распознает, с помощью событий или чего-либо еще.

Редактировать

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

Так что, если вы планируете, чтобы стена реагировала на ЛЮБОЙ брошенный объект, вам понадобитсядобавить CollIdeb глагол в своем списке.Вы захотите указать, с какими объектами он должен столкнуться, и, возможно, для каждого из них, как он должен реагировать на определенные величины силы и т. Д.

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

Бутылка участвует в Столкновении со Стеной.Бутылка имеет в своем списке глаголов тип взаимодействия Collide.У него может быть один объект, с которым он заботится о столкновении, или он может иметь значение Any, AnySolid или что-то еще.Есть миллион способов создать это.В любом случае, стена также участвует и может также иметь в своем списке глаголов тип взаимодействия Collide.Но он заботится только о столкновении с объектом Sledgehammer - или, может быть, AnySolid с массой 10 или более ...

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

Можетбыть заполненным жидкостью, чтобы он реализовал интерфейс IContainer с методом Spill, а также интерфейс IConsumeable с методом Drink.Это может быть блокировка, которая реализует интерфейс ILockable, который имеет метод Unlock (obj Key) и метод Pick (int PickSkill).Каждый из этих методов может вызвать определенные изменения в состоянии объекта и других участников взаимодействия.Вы можете сделать это с помощью событий, если хотите.

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

1 голос
/ 02 октября 2010

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

  • Глагол (например, "throw")
  • объект (например, "бутылка")
  • необязательный дополнительный параметр, описывающий дальнейшее действие (например, "at window")

Как насчет моделирования каждого действующего объекта как класса, производного от общего предка, и этот класс должен обрабатывать само действие? Что-то вроде

public interface IObjectBase
{
   bool HandleAction(string verb,string [] params)
}

public class Bottle: IObjectBase
{
   bool HandleAction(string verb,string [] params)
   {
     //analyze verb and params to look for appropriate actions
     //handle action and return true if a match has been found
   }
}
0 голосов
/ 03 октября 2010

Взаимодействие можно определить как «Глагол + {Список фильтров} + {Список ответов}» *

Для вашего примера "наполнить бутылку водой" взаимодействие будет:

  • Глагол: Fill ({"fill", "pour"})
  • Список фильтров: Have(player, "bottle"), Have(currentRoom, "water tap")
  • Список ответов: Print("You filled the bottle with water"), Remove(player, "bottle"), Add(player, "bottle of water")
  • альтернативно, список ответов может быть: SetAttribute(player.findInventory("bottle"), "fill", "water")

Тогда, если вам нужно «бросить бутылку воды в окна»:

  • Глагол: Бросить ({"throw", "smash"})
  • Список фильтров: Have(player, "bottle of water"), Have(currentRoom, "windows")
  • Список ответов: Print("The bottle smashed with the windows, and both of them are broken"), Remove(player, "bottle of water"), Add(curentRoom, "broken bottle"), Remove(currentRoom, "window"), Add(currentRoom, "broken window"), SetAttribute(currentRoom, "description", "There is water on the floor")

При входе в Комнату Framework будет запрашивать у всех объектов в комнате список действительных Глаголов и перечислять их. Когда игрок вводит команду, каркас ищет глагол, соответствующий команде; затем он проверит список фильтров, и если все они имеют значение True, то итерируйте список ответов, чтобы выполнить их по порядку.

Ответы будут функциональным объектом, который реализует интерфейс IResponse, который имеет несколько конструкторов, и метод IResponse.do (). Фильтры будут функциональным объектом, который реализует интерфейс IFilter, опять же с некоторыми конструкторами, и методом IFilter.check (), возвращающим логическое значение. Вы можете даже использовать фильтры And (), Or () и Not () для более сложных запросов.

Вы можете сделать вещи еще более читабельными, используя некоторые вспомогательные методы, у Player может быть удобный метод Player.have (Actionable), чтобы вы могли написать player.have ("бутылку воды"), которая возвращает не сам объект бутылки , но объект IFilter, который проверяет, есть ли у игрока «бутылка воды» при вызове его метода .check (). В основном, делайте объекты ленивыми.

0 голосов
/ 03 октября 2010

Похоже, ваша проблема заключается в управлении распространением событий. Microsoft решает эту проблему (для менее ярких целей), используя шаблон / события Observer.

Я думаю, что комбинирование шаблонов проектирования Observer и Mediator из книги Gamma и др. "Шаблоны проектирования" было бы очень полезным для вас. В книге есть пример класса ChangeManager, который может быть полезен, но я приложил некоторые другие ссылки, которые должны вам помочь.

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

Статья Microsoft по шаблону наблюдателя: http://msdn.microsoft.com/en-us/library/ee817669.aspx

DoFactory на шаблоне Mediator (с диаграммами UML): http://www.dofactory.com/Patterns/PatternMediator.aspx

Шаблон DoFactory on Observer (с диаграммами UML): http://www.dofactory.com/Patterns/PatternObserver.aspx

Документация по интерфейсу IObserver в .Net 4, http://msdn.microsoft.com/en-us/library/dd783449.aspx

еще одна статья о наблюдателе. http://www.devx.com/cplus/Article/28013/1954

0 голосов
/ 03 октября 2010

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

Я добавил метод Invoke () в класс Interaction, новый интерфейс под названием IActionResult, который определил метод «Initiate ()», и несколько различных типов ActionResult для каждого возможного последствия. Я также добавил список ActionResults для взаимодействия. Метод Invoke просто перебирает все объекты IActionResult и вызывает метод Initiate ().

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

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

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

0 голосов
/ 02 октября 2010

Ха, я тоже работаю над чем-то похожим. Мне интересно, если ваш фреймворк станет текстовым приключением создателем , каковым является мой проект.

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

  • Распечатать сообщение
  • Изменить описание объекта / комнаты
  • «Блокировать» или «разблокировать» объект. Это означает, что «осмотр пояса» скажет: «Вы не видели здесь никакого ремня». До тех пор, пока «осмотр трупа» не был выполнен, чтобы узнать, что «У трупа есть блестящий пояс вокруг талии».
  • Блокировка или разблокировка выходов из комнаты
  • Переместить игрока в какую-то комнату
  • Добавить / удалить что-то из инвентаря игрока
  • Установить / изменить некоторые игровые переменные, например. "MoveGlowingRock = true" или "numBedroomVisits = 13" и т. д.

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

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

delegate void Script();

class GameObject
{
    public Dictionary<string, Script> Scripts {get; set;}
    public string Name {get; set;}

    //etc...
}

А ваши скрипты, хранящиеся в соответствующем экземпляре Room:

    //In my project, I plan to have such an abstract class, and since it is a game _creator_, the app will generate a C# file that contains derived types containing info that users will specify using a GUI Editor.
abstract class Room
{
    protected Dictionary<string, GameObject> objects;

    public GameObject GetObject(string objName) {...//get relevant object from dictionary}
}


class FrontYard : Room
{

    public FrontYard()
    {
        GameObject bottle;
        bottle.Name = "Bottle";
        bottle.Scripts["FillWithWater"] = Room1_Fill_Bottle_With_Water;
        bottle.Scripts["ThrowAtWindow"] = Room1_Throw_Bottle_At_Window;
        //etc...
    }

    void void Room1_Fill_Bottle_With_Water()
    {
         API.Print("You fill the bottle with water from the pond");
         API.SetVar("bottleFull", "true");         
    }

    void Room1_Throw_Bottle_At_Window()
    {
         API.Print("With all your might, you hurl the bottle at the house's window");
         API.RemoveFromInventory("bottle");
         API.UnlockExit("north");
         API.SetVar("windowBroken", "true");    
         //etc...     
    }    
}

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

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

Я думаю, что потратил слишком много времени, набирая все это> _>

EDIT: PS: мой скелетный пример не совсем показывает, как управлять командами, включающими несколько игровых объектов (это лишь одна из многих тонкостей, на которые я намекал). Для таких вещей, как «выбросить бутылку в окно», вам нужно подумать, как управлять таким синтаксисом, например. вкус моего решения этого состоит в том, чтобы разобрать и выяснить, какая команда выдается ... "бросить GO в GO". Узнайте, что представляют собой игровые объекты, а затем посмотрите, есть ли в текущей комнате их. и т. д.

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

Прошу прощения за мои разговоры ...> _>

0 голосов
/ 02 октября 2010

У вас есть две вещи: игрок и окружение (у вас могут быть и другие игроки).

Передайте их обоим каждому взаимодействию:

interaction.ActOn(environment, player);

//eg:
smash.ActOn(currentRoom, hero);

Тогда пусть каждое взаимодействие решает, что делать:

environment.ReplaceObject("window", new Window("This window is broken. Watch out for the glass!");
player.Inventory.RemoveObject("bottle");
player.Hears("The window smashes. There is glass all over the floor! If only John McLane were here...").

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

player.Inventory.ReplaceObject("bottle", new BottleOfWater());

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

См. Также Двойная отправка .

...