Буду сваливать сюда всякие заметки по работе над своим "Адским движком" — фреймворком для QSP.
Если чего-то непонятно — это нормально, уже проделана большая работа, о которой задним числом писать времени просто нет.
Неактивен
Итак, решил проблему с выводом содержимого предмета при выводе его описания.
Возьмём предмет "Сундук". Игрок может как осмотреть Сундук снаружи, так и посмотреть, как он выглядит изнутри. При этом перечень находящихся в Сундуке предметов нужно выводить только во втором случае.
Первое, что пришло на ум — доработать систему "упоминаний", расширив их синтаксис и добавив туда ключевое слово "ПереченьОбъектов". Далее, нужно указать, содержимое какого именно контейнера нужно выводить. Однако и это не всё — хорошо бы, чтобы перечень объектов сопровождался какой-нибудь фразой, характерной для этого объекта (в данном случае: "На дне сундука лежат").
В общем справился.
Как это сейчас выглядит в коде:
Блок описаний:
GS 'УстановитьОписание','Сундук:Снаружи','Громоздкий сундук',' п. Здоровенный деревянный ((сундук|Сундук)), обтянутый железными полосками. ' !---------- GS 'УстановитьОписание','Сундук:Содержимое','Громоздкий сундук',' п. Изнутри ((сундук|Сундук)) обит потёртой выцветшей тканью. [[ПереченьОбъектов:Содержимое:На дне сундука лежат]] '
А так описывается меню, которое формируется при нажатии на ссылку "Сундук":
GS 'ПриНажатииНаСсылку','Сундук',{ GS 'Меню.ДобавитьПунктСМодулем','Меню','Осмотреть сундук','',{ GS 'ДействиеНаЭкран',$ARGS[0] GS 'ТекстНаЭкран',func('Описание','Сундук:Снаружи','Текст') } !---------- GS 'Меню.ДобавитьПунктСМодулем','Меню','Покопаться в сундуке','',{ GS 'ДействиеНаЭкран',$ARGS[0] GS 'ТекстНаЭкран',func('Описание','Сундук:Содержимое','Текст') } }
Что в итоге:
Неактивен
Что я подразумеваю под "Упоминанием" объекта?
Чтобы не заморачиваться со встраиванием информации об игровых объектах в том или ином тексте посредством программного кода (возьмём любимый пример [1, 2] Некса):
*P 'Вы на кухне.' IF роджер=1: ' Здесь находится Роджер.'
(и так — в каждой локации, где может появиться Роджер)
я пошёл по другому пути. Описание локаций у меня содержит ссылку на упоминание о Роджере, а у объекта "Роджер" расписаны упоминания о нём в зависимости от того, в какой локации он находится:
GS 'УстановитьОписание', 'Кухня', 'Кухня', ' п. Вы на кухне. [[Роджер]] ' GS 'УстановитьОписание', 'Туалет', 'Туалет', ' п. Вы в туалете. [[Роджер]] ' !---------- GS 'УстановитьУпоминание', 'Роджер', 'Локации:Описания', '', { $Локация=$ARGS[0] $Текст=$ARGS[1] !---------- if $Локация='кухня' and роджер=1: $Текст='Здесь находится Роджер.' elseif $Локация='туалет' and роджер=2: $Текст='Здесь спит Роджер.' end !---------- $Result=$Текст }
Теперь обращение к описанию локации:
*NL func('Описание', $ТекущаяЛокация, 'Текст')
возвращает мне законченный текст с учётом упоминания о Роджере.
На маленьком примере с одной локацией и одним условием этот подход выглядит более громоздко, чем пример Некса, однако в проекте побольше и при многообразии условий уже легко можно что-нибудь упустить (так Евг в "Луддитах" наткнулся на имя летательного аппарата до того, как персонаж узнал его от графа).
Неактивен
В рамках вопроса, поднятого в дискуссии "Как лучше выводить перечень предметов/персонажей игры в описании?", пока остановился у себя на таком порядке вывода:
По пунктам 3 и 4: по тем объектам, упоминания которых не были задействованы в тексте описания, ищется упоминание (для использования в описании) и выводится по каждому объекту с новой строки.
Таким образом можно не включать упоминание некоторых объектов в текст описания. Описания локаций могут выглядеть и так:
GS 'УстановитьОписание', 'Кухня', 'Кухня', ' п. Вы на кухне. ' GS 'УстановитьОписание', 'Туалет', 'Туалет', ' п. Вы в туалете. '
При формировании описания, если Роджер находится в описываемой локации, его упоминание автоматически добавится в конец описания (с новой строки).
Ну а те объекты, которые не попали ни в само описание, ни в конец его, — перечисляются просто через запятую в самом низу.
Неактивен
Доделал вывод описания объектов (локаций, предметов, персонажей) со всеми потрохами, т.е. находящимися "внутри них" объектами. Всё работает именно так, как описано в предыдущем сообщении.
Чтобы при выводе описания появилось перечисление "хранящихся" внутри объектов, нужно это описание связать с тем или иным контейнером. Например:
GS 'УстановитьОписание', 'Сундук:Основное', 'Громоздкий сундук', ' п. Здоровенный деревянный ((сундук|Сундук)), обтянутый железными полосками. ' !---------- GS 'УстановитьОписание', 'Сундук:ЧтоВнутри', 'Громоздкий сундук', ' п. Изнутри ((сундук|Сундук)) обит потёртой выцветшей тканью. ', '', '', 'Содержимое'
Первое описание ("Основное") выводится при внешнем осмотре сундука. Второе ("ЧтоВнутри") — при осмотре содержимого. При этом второе описание связано с контейнером "Содержимое", следовательно, при выводе описания текст будет дополнен перечислением всех объектов, находящихся в контейнере "Содержимое" объекта "Сундук".
Неактивен
Так, рабочий день закончился, поэтому самое время размять пальчики и мозги и приступить такой хитрой штуке, как гашение/восстановление ссылок на объекты в тексте.
Неактивен
Так, самая простая часть — технически погасить или восстановить ссылки — работает и работает отлично.
Осталось реализовать саму функцию проверки доступности объекта в текущей ситуации (как заглушку я использовал проверку наличия объекта в инвентаре).
Неактивен
М-да… С объектами ладно, но с ссылками на локации (для вызова меню переходов) что делать? их как бы нигде ведь нет…
Неактивен
Так, приехали. Кажется, QSP-плеер начал тормозить…
Неактивен
Так, кое-что удалось реализовать:
Перебор ссылок и анализ доступности каждого объекта (если это не локация):
Неактивен
Olegus t.Gl. написал:
Так, кое-что удалось реализовать:
- При первоначальном входе в локацию или осмотре её заново (т.е. на экране нет ничего, кроме описания локации), перечень доступных контейнеров очищается.
- При выводе описания объекта (которое связано с контейнером этого объекта) этот контейнер добавляется в перечень доступных.
Перебор ссылок и анализ доступности каждого объекта (если это не локация):
- Если объект находится в инвентаре, то ссылка на экране не нужна (объект доступен через инвентарь) — блокируем.
- Если объект находится в содержимом локации, то ссылка нужна — активизируем.
- Если объект находится в одном из доступных к этому времени контейнеров, то ссылка также пригодится — активизируем.
4. Если текущее местоположение объекта соответствует сохранённому на момент формирования ссылки — активизируем.
Неактивен
Ну и слайды.
В тесте я создал три объекта-контейнера: Лужайка, Сумка и Чугунок, и один предмет — Яблоко, и последовательно перекладывал яблоко: Лужайка -> Инвентарь -> Сумка -> Инвентарь -> Чугунок -> Лужайка.
На последнем слайде я пометил участки, где находятся ссылки на объект "Яблоко": красным — активные, зелёным — заблокированные (т.е. это уже даже не ссылки, а просто слова). Можно увидеть, что когда Яблоко вернулось на Лужайку — активизировалась самая первая ссылка.
Чтобы оценить участие в технической стороне процесса (никакого, собственно) автора — приложен файл с исходным текстом примера.
P.S. Ссылки в примере я специально сделал жёлтыми, чтобы лучше было видно :-)
Неактивен
Nex написал:
Осталось сделать визуальный конструктор, который будет генерировать такой код.
Получившийся код слишком сложен для ручного набора?
Неактивен
Поработал над манипуляциями с объектами. В частности, немного автоматизировал этот процесс. Теперь, накидав всяческих действий и взаимодействий, можно сделать сборку меню при нажатии на объект автоматической.
Итак, код с помощью которого задаются возможные пункты для меню объектов "Яблоко" и "Чугунок":
!Добавляем пункт в меню, который должен присутствовать всегда GS 'УстановитьДействие', 'Яблоко', 'Осмотреть яблоко', $ico['осмотреть снаружи'], '', '', { GS 'ОписаниеНаЭкран', 'Яблоко' } !---------- !Добавляем пункт в меню, который должен присутствовать только если яблоко не в инвентаре игрока GS 'УстановитьДействие', 'Яблоко', 'Взять яблоко', $ico['взять'], { ARGS['Результат']=IIF(func('Местонахождение', 'Игрок', 'Яблоко', 'Игрок')=НЕТ, Использовать, Пропускать) }, '' ,{ if func('Переместить', 'Игрок', 'Яблоко', '', 'Игрок')=Продолжить: GS 'ТекстНаЭкран', 'п. Вы взяли ((яблоко|Яблоко)).' end } !---------- !Устанавливаем фразы для обеспечения процесса взаимодействия яблока с другими объектами GS 'УстановитьПриглашениеКВзаимодействию', 'Яблоко', 'Что вы хотите сделать с яблоком?', 'Что-нибудь сделать с яблоком' !---------- !Добавляем взаимодействие в чугунком — яблоко можно только положить в чугунок, да и то, только если его там нет GS 'ДобавитьВзаимодействие', 'Яблоко', 'Чугунок', 'Положить яблоко в чугунок', $ico['положить'], { ARGS['Результат']=IIF(func('Местонахождение', '', 'Яблоко', 'Чугунок')=ДА, Пропускать, Использовать) }, '', { if func('Переместить', 'Игрок', 'Яблоко', '', 'Чугунок')=Продолжить: GS 'ТекстНаЭкран', 'п. Вы положили ((яблоко|Яблоко)) в ((чугунок|Чугунок)).' end } !---------- !Ну и пару постоянных пунктов в меню Чугунка добавим: GS 'УстановитьДействие', 'Чугунок', 'Осмотреть чугунок', $ico['осмотреть снаружи'], '', '', { GS 'ОписаниеНаЭкран', 'Чугунок' } !---------- GS 'УстановитьДействие', 'Чугунок', 'Заглянуть внутрь чугунка', $ico['осмотреть внутри'], '', '', { GS 'ОписаниеНаЭкран', 'Чугунок:Содержимое' }
Теперь, при нажатии на ссылку "Чугунок" в тексте получаем такое меню:
При нажатии на ссылку "Яблоко" в тексте получаем такое меню:
Выбрав пункт "Что-нибудь сделать с яблоком", включаем режим "взаимодействия" объектов (в нижнем левом углу появляется фраза-приглашение, напоминающая с каким предметом мы сейчас проводим манипуляции):
Ну и при нажатии на ссылку какого-нибудь другого объекта, в случае, если взаимодействие с данным объектом возможно, в меню появляется соответствующий пункт (в данной ситуации — "Положить яблоко в чугунок"):
Кстати, добавил в меню иконки, чтобы посмотреть, как будет выглядеть. Весёленько так, да…
Неактивен
Ajenta, вертикальный разделитель в меню появляется в Windows 7 (виден на скриншотах из предыдущего поста), в WinXP его нет.
Неактивен
Я был настолько шокирован уходом со сцены главного конкурента по части описания непонятных наработок, что несколько забросил работу над Адским движком™, да и сюда писать стимула поубавилось — с кем же будут сравнивать?! Однако, впереди ещё много интересного, чем бы хотелось попугать и ужаснуть адептов классического менюшного подхода, приверженцев идеологии "всё своё пишу я сам", ну и проповедников чистоты Кусп-языка. Я даже придумал классное и многообещающее название для одного из модулей: "самовозбуждающаяся система".
Так-то ©
Неактивен
Так, что же делать с объектом, который хочется рассовать в разные места: разложить по разным локациям, засунуть в разные предметы, распихать по карманам разных персонажей?..
Когда объект существует в игре в единственном экземпляре, то проблем нет — в момент любых манипуляций просто берём объект и его текущее местоположение. Если же объект один, но расположен в нескольких местах, то нужно точно знать в каком именно месте производятся манипуляции с этим объектом. Проблема усугубляется тем, что у меня может существовать множество контейнеров в одном объекте. Так, у персонажа могут быть одновременно контейнеры "Карманы" и "Шляпа", в каждый из которых можно положить, скажем, по сигарете. Допустим, игрок обыскивает персонажа: он осматривает сначала карманы, а затем шляпу. В таком случае перед ним на экране выведено содержимое обоих контейнеров, и в обоих текстах упоминается сигарета. Само собой, ткнув на сигарету в описании шляпы и выбрав пункт "Взять сигарету" нужно переместить сигарету именно из шляпы, а не из карманов. При этом хочется чтобы автору игры нужно было как можно меньше знать подобных нюансов, да и в параметрах команд указывать лишь самое необходимое.
Вот, например, рабочий код для ситуации, когда объект может быть только в единственном экземпляре:
! У объекта "Сигарета" добавляем пункт в меню… GS 'УстановитьДействие', 'Сигарета', 'Взять сигарету', { ! …который будет появляться, только если объект "Сигарета" отсутствует у игрока ARGS['Результат']=IIF(func('Местонахождение', 'Сигарета', 'Игрок')=НЕТ, Использовать, Пропускать) }, { ! Если игрок выбрал этот пункт, то перемещаем объект в инвентарь игрока if func('Переместить', 'Сигарета', 'Игрок')=Продолжить: GS 'ТекстНаЭкран', 'п. Вы взяли ((сигарету|Сигарета)).' end }
В случае с многочисленными экземплярами объекта возникают следующие моменты:
Если допустить, что все условия и авторские модули передаются как строки или через {…}, то при реализации пунктов 1 и 2 можно провернуть такой финт: во все авторские модули передаётся параметр, в котором в запакованном виде хранится связка ID-Объекта + ID-Места, а в начало каждого авторского модуля автоматически добавляется строка:
ARGS['Объект']=ARGS[0]
В этом случае появляется стимул использовать в авторском модуле именно эту переменную вместо имени объекта, т.е. пример кода с сигаретой будет выглядеть, например, так:
! У объекта "Сигарета" добавляем пункт в меню… GS 'УстановитьДействие', 'Сигарета', 'Взять сигарету', { ! …который будет появляться, только если объект "Сигарета" отсутствует у игрока ARGS['Результат']=IIF(func('Местонахождение', ARGS['Объект'], 'Игрок')=НЕТ, Использовать, Пропускать) }, { ! Если игрок выбрал этот пункт, то перемещаем объект в инвентарь игрока if func('Переместить', ARGS['Объект'], 'Игрок')=Продолжить: GS 'ТекстНаЭкран', 'п. Вы взяли ((сигарету|Сигарета)).' end }
Однако, это всё же частный случай. Ситуаций, когда нужно будет ссылаться на конкретный объект в конкретном месте, всяко больше — как в этом случае выходить из ситуации, надо ещё подумать.
Неактивен
Кажется, я нащупал решение.
Неактивен
Всё хорошо — пока всё получается как и задумывалось.
Неактивен
В общем, отдельными слайдами результат продемонстрировать уже невозможно. Поэтому — видео:
На видео — демонстрация работы с несколькими экземплярами одного и того же объекта. В данном примере я, имея две сигареты, перекладываю сперва одну из них из портсигара на стол, затем другую — из инвентаря в портсигар, и напоследок — забираю из портсигара обратно в инвентарь.
Можно увидеть, как в процессе этих манипуляций на экране меняется доступность ссылок на те или иные экземпляры сигареты. Кроме того в DEBUG-версии в меню можно увидеть название и, что самое важное, расположение того или иного экземпляра объекта.
Само собой, чтобы можно было оценить объём трудозатрат автора, прикладываю файл с исходником тестового примера.
Неактивен
Чтобы не отпугивать благовоспитанных людей "Адский Движок" на публике будет выступать под мирским именем "Az"…
Неактивен
Что делать с контейнерами объекта, который может быть в нескольких экземплярах?
Есть объект "Река", который находится ("протекает") в нескольких локациях (этот пример мы обсуждали с Чеширом на канале #ifrus). Почему есть смысл сделать реку одним объектом? Да чтобы модули ("осмотреть", "посмотреться", "попить", "набрать воды") не дублировать — надо всего лишь один раз прописать взаимодействие с одним объектом "Река". Однако тут есть нюанс — а что, если нужно что-то положить в реку?
Можно реализовать это так — при перемещении каких-нибудь объектов в "Реку" на самом деле размещать их в специально выделенном для этой цели контейнере "ДноРеки", но принадлежащем не объекту "Река", а текущей локации:
GS 'ДобавитьВзаимодействие', 'Пистолет', 'Река', 'Бросить пистолет в реку', $ico['положить'], { Result = IIF(func('Местонахождение', ИГРОК, $ОБЪЕКТ1, '$ЛОКАЦИЯ:ДноРеки'), Пропускать, Использовать) }, 0, { if func('Переместить', ИГРОК, $ОБЪЕКТ1, 0, '$ЛОКАЦИЯ:ДноРеки') = Продолжить: GS 'ТекстНаЭкран', 'п. Вы бросили ((пистолет|$ОБЪЕКТ1)) в ((реку|$ОБЪЕКТ2)).' end }
При попытке взаимодействия "Пистолета" и "Реки" (при условии, что Пистолет уже не находится в контейнере "ДноРеки" текущей локации) "Пистолет" перемещается в контейнер "ДноРеки" текущей локации.
Соответственно, при выполнении осмотра дна "Реки" (что-то типа действия "Пошарить по дну реки") нужно просто дополнительно вывести содержимое контейнера "ДноРеки" текущей локации.
Наверное, стоит пока остановиться на этом обходном манёвре…
Неактивен
Из-за введения понятия "Экземпляр объекта" (Объект + Местонахождение — пока это значит именно это) пришлось переделать большую часть модулей. Однако результатом доволен.
Неактивен
Кстати, раз уж последнее, что я переделывал — это обработчик "ПриОпределенииМестонахождения", то расскажу немного о нём.
Обработчик "ПриОпределенииМестонахождения" относится к системным обработчикам, то есть вызывается фреймворком автоматически при отработке тех или иных ситуаций, связанных с определением местонахождения объекта.
Синтаксис обработчика пока следующий:
GS 'ПриОпределенииМестонахождения', <Инициатор>, <Объект>, <Местонахождение>, <Модуль>
Инициатор — кто определяет местонахождение.
Объект — местонахождение чего определяется.
Местонахождение — где именно определяется местонахождение объекта.
Вызов данного обработчика происходит во всех возможных комбинациях параметров, включая пустые, что позволяет обрабатывать такие ситуации, как, например, определение игроком (Инициатор="ИГРОК") местонахождения чего угодно (Объект=0) в локации "Зал" (Местонахождение="Зал").
Попробую проиллюстрировать как задаётся и используется данный обработчик на примере. Допустим, игрок должен вынести из помещения статуэтку. На выходе стоит сканер, который срабатывает в случае, если обнаруживает что-нибудь у игрока при выходе из помещения. Однако, если у сканера отключить питание, то ничего обнаруживать он не должен. В этом случае как раз и пригодится обработчик "ПриОпределенииМестонахождения":
GS 'ПриОпределенииМестонахождения', 'Сканер', 0, ИГРОК, { if func('Свойство', 0, 'Сканер', 'Питание'): ARGS['Результат']=ДА else ARGS['Результат']=НЕТ end }
Данный обработчик вызывается при определении объектом "Сканер" наличия какого угодно объекта у текущего персонажа. Если свойство "Питание" объекта "Сканер" в этот момент равно 0, то в результате определения местонахождения вернётся НЕТ вне зависимости от наличия искомого объекта у персонажа "Игрок".
Неактивен