Буду сваливать сюда всякие заметки по работе над своим "Адским движком" — фреймворком для 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 написал:
Осталось сделать визуальный конструктор, который будет генерировать такой код.
Получившийся код слишком сложен для ручного набора?
Неактивен
Получившийся код подсказывает моей интуиции, что именно такой "модульный" код можно было бы удобно "обернуть" с помощью визуального конструктора, наподобие TGE/Квестера. В отличие от обычного QSP-кода, который слишком многовариантен для конструктора. Только об этом я и хотел сказать.
Насколько он сложен для ручного набора, рассудят те, кто будет им помимо тебя пользоваться, если таковые найдутся.
Неактивен
Поработал над манипуляциями с объектами. В частности, немного автоматизировал этот процесс. Теперь, накидав всяческих действий и взаимодействий, можно сделать сборку меню при нажатии на объект автоматической.
Итак, код с помощью которого задаются возможные пункты для меню объектов "Яблоко" и "Чугунок":
!Добавляем пункт в меню, который должен присутствовать всегда 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"…
Неактивен