Мысли по поводу скриптовой системы #1: возможная структура и юзкейсы
Цитата:
послушаем, что Тхамбс предложит.
Буду исходить из того, что скриптовая система не должна заменять или ломать существующий набор сущностей, а призвана скорее её дополнить и расширить, сделав более гибкой. Вообще-то говоря, сущности — довольно удобная штука и в 90% (условно) игровых механик какие-то дополнительные движения не требуются. Проблемы же начинаются тогда, когда из этих сущностей приходится городить сложные цепочки, вводить "логические энтити" и пр чертовщину. По моему опыту, использование более чем 5 сущностей в одном механизме создаёт путанницу, и чем дальше тем хуже. Особенно неудобно бывает, когда одна и та же цепочка сущностей используется в разных местах карты — приходится вручную переименовывать ключи, что тоже приводит ошибкам. Отчасти, это решается с помощью пре-процессинга .map-файла, но это не то что бы сильно универсальное решение, да и к тому же не избавляет от необходимости писать эти цепочки в .ent-файле, который остаётся сильно многословным в сравнении с той же функциональностью, выраженной на любом императивном яп.
О зле. На мой взгляд, основной источник зла — это т.н. "логические энтити", а точнее черезмерное их использование. Игровая логика в doom или quake была довольно проста: расставил монстров, связал кнопку с дверью, а дверь с ключ-картой, триггер — с ловушкой, и готово. Более комплексные игровые механики привели к разрастанию и непомерному усложнению всей системы. И самая пакость связана с тем, что, помимо сложности, система стала фактически "write only" — из-за того что при редактировании работаешь с одной энтитей за раз, то отладка/изменение сценария напоминает работу с древним БАСИКом — тем самым, в котором строки нумеровались цифрами, и редактор позволял работать только с одной строкой. А ведь, зачастую, эти самые сущности на карте расположены в разных местах и совершенно не обязательно как-то логически упорядочены.
О команде fire. Кстати, ещё в sauerbraten видел интересную штуку, оно конечно только proof of concept, для реальных применений не годится, но тем не менее. Ведь там совсем небыло энтить, а для манипуляции игровой логики был внутренний скриптовый язык. Причём скрипты на нём могли быть как во внешних файлах, подключаемых к карте, так и вызываться непосредственно из консоли, с такими же результатами. Мне это напомнило команду fire из спирита/ксаша в связке с trigger_command и те совершенно невозможные штуки, которые с помощью них делал доктортресни. Одного fire, конечно маловато.
О интерфейсе.Команда fire и поле target, фактически, позволяют передать некоторые параметры/сигналы сущности, но с большими ограничениями. Ещё есть changetarget, changeparent, env_render и всё, собственно. Что бы наблюдать за состоянием энтить — multi_watcher, но ему тоже доступен весьма ограниченный набор параметров. Наверное, имело бы смысл некоторое универсальное API, позволяющее передавать и получать от энтить любые допустимые параметры и проводить с ними операции. Не ломая существующую систему, можно было бы, например, ввести вызовы:
C++ Source Code:
$set(*entity, key, value) -> status
$get(*entity, key) -> value
$find(*world, mask) -> [*entity]
Из названий понятно: $set — установить пару ключ-значение для сущности
*entity; вернуть статус или неудачу, $get — получить значение, $find — сформировать список указателей на сущности с именем/класснеймом таким-то маске или значению, а world -- указатель на текущую карту. Сами функции, естественно, не содержат внутренних состояний.
Предположим, теперь, что у нас есть какой-то механизм сохранения состояний и обычные языковые конструкции. Добавим функцию асинхронной задержки:
C++ Source Code:
$sleep(time)
Тогда эти 4-вызова, тогда позволяют полностью заменить мульти_манаджеры/вотчеры, реле, энв_рендеры и пр. Возможные примеры использования дам дальше, пока о памяти.
При работе с памятью видится несколько кейсов. Предполагается, что скрипт может быть или глобальным (в масштабах карты), или связанным с какой-либо сущностью. При этом, у последней получается свой локальный нэймспейс. Так-как хочется работать с чистыми функциями и использовать обычную систему сохранений, то хранить какие-то состояния наверное лучше всего было-бы в каком ни будь специальном поле внутри самой сущности. Но я, к сожалению, так и не разбирался как у тебя сериализация устроена, возможно-ли туда затолкать полиморфный, изменяемый объект? И, если в функции есть вызовы $sleep, то, видимо нужно будет сохранять весь её стек...
Что же касается низкоуровнего выделения/освобождения, то наверное, самым разумным было-бы использовать RAII: выделяем память под переменную в том случае, если она синтаксически связалась с литералом, освобождаем (по мере необходимости) как литерал вышел из поля видимости. Например, пустой вызов:
C++ Source Code:
$find(*world, "func_door:storage*")
Не делает ничего,
C++ Source Code:
lst = $find(*world, "func_door:storage*")
связывает литерал lst с результатом вызова и храним его пока он в области видимости, или пока не вызвали какой ни будь $del(lst). При этом, соответственно, синтаксис не должен позволять существование несвязанного литерала, а если литералу присвоено новое значение, то старое удаляется.
Вызовы. Есть ещё момент, когда нужно вызвать скрипт автоматически, по наступлении определённых условий. Наверное, тут было-бы удобно тогда добавить внешний обработчкик. Например, указать в корне скрипта
C++ Source Code:
1
//следим за условиями
2
watch(){
3
if cond1 { ... }
4
if cond2 { ... }
5
}
Общая структура. Таким образом, вырисовывается какая-то такая структура скрипта:
C++ Source Code:
1
/* определяем сохраняемые поля */
2
@static var1 = ...
3
@static var2 = ...
4
/* определяем обработчики */
5
spawn(...)
6
watch(...)
7
call(...)
8
/* определяем всякие вспомогательные функции */
9
...
Юзкейс: поезд с колёсиками. Есть, например, префаб фанк_трейна, к нему приделаны колёсики, env_shake, фонарики, убивалки. Хочется, что бы колёсики синхронно вращались и частота тряски менялась в зависимости от скорости, фонарики красные зажигались на заднем ходу, дым там из трубы ну и всё такое. Пишем у поезда в строке скрипт имя скрипта а в самом нём что-то такое:
Юзкейс: универсальная станция для поезда с дверкой. Поезд активирует скрипт станции, а станция узнаёт имя поезда, имя дверки, открывает её, закрывает e`, вносит поправки в расписание и пр.
Юзкейс: управление персоналом. Есть, например, некоторая зона, на которой расставлены scripted_sequence и бегают доктора. Хочется создать иллюзию жизни на этой локации. Каждая скриптед секвенция по завершению вызывает наш скрипт и передаёт локус доктора. Внутри выбираем что ему делать, куда идти и пр.
Юзкейс: диалоги. Цепочки из диалогов та ещё беда. По хорошему, всё это бы просто в команду $speak(*actor, *target, SENTENCE, time). Делаем какие угодно цепочки с каким угодно ветвлениями и рандомами, диалоговый скрипт можно привязать непосредственно к персонажу тем самым делая его уникальным.
Добавлено 19-11-2019 в 02:45:
Хм, да, сейчас понял, что семантика $find небезопасна, т.к. может возникнуть ситуация, когда в сохранении указатели на уже удалённые сущности. Значит этот момент надо по другому.
thambs писал: отладка/изменение сценария напоминает работу с древним БАСИКом
Да, это точное сравнение.
Цитата:
thambs писал: когда в сохранении указатели на уже удалённые сущности.
ну это решено еще Вальвой. Нет такой проблемы.
Синтаксис конечно будет си-подобный, но основную мысль я понял - чтобы колёсики в такт тряслись поезду. Только вот поезд "перейдет" на другую карту, а как он там найдет этот скрипт?
Дядя Миша
Ну основная идея что скрипт намертво привязывается к сущности. Т.е. у неё есть, например, поле script, в котором ссылка на текстовик, который читаем при загрузке карты, и есть какое-то скрытое поле, хранящее стек скрипта. Соответственно, паровозик переходит с карты на карту вместе со всей своей требухой что в скрипте насчиталась.
ps: я ещё обдумываю семантику и возможные варианты.
Ну и дичь лютую вы удумали, писать скрипт под конкретную карту, для каждой сущности. Ещё и трахаться с ДЕБАЖИТЬ отладкой этих финдов.
Что мешает просто подгружать файл скрипта как отдельную сущность со своими полями? Написали какой нибудь trigger_butthurt.qc, если есть на карте он, то он подгрузился, если нет, то нет. Как во всех современных движках. А наследования в скриптах делать по классике, через объявление public/private и т.п.
__________________
У котёнка мокрый нос и гладенькая шерсть, у него забавный хвост и быстрых лапок шесть. Две задних, две средних и две передних лапы, такая многоножка получилася у папы.
Он ученый — папа мой — зверушек изучает, гуляет по помойкам, ловит крыс и чаек. Две крысы белокрылые и чайки две унылые покрытые пупырчатою кожей лягушат без пёрышек тоскуют и ускакать спешат.
А ещё есть муравей большой размером с гуся он пугает всех зверей, и я его боюся, когда он ковыляет на лапках на своих.
И в двери ударяет, и начинает стих: Я — муравей, воды налей! Не меньше ведра, напиться мне пора!
FiEctro
Ответь, почему ты так нагло ПЕРЕВИРАЕШЬ то что я писал?
Цитата:
Написали какой нибудь trigger_butthurt.qc
А ведь, я вообще не писал о том что бы создавать новые сущности, только о том как мог бы выглядеть механизм более тонкого и настраиваемого взаимодействия между существующими.
Сгенерил патчи на кутришной карте, ну вроде норм - язык пропал (на q3dm1), как корова в реке утопила. Да что за бред, начал разбираться.
Оптимизатор отключаешь - язык на месте, остальное в порядке. Ну где я мог накосячить? Очевидно нигде. Расковырял функцию оптимизатора - там ошибка. Это походу уже становится традицией.
thambs писал: FiEctro
Ответь, почему ты так нагло ПЕРЕВИРАЕШЬ то что я писал?
Да потому что это не интуитивно и запутанно. Что даже здесь я видимо не понял твоей полной задумки.
Цитата:
thambs писал: FiEctro
А ведь, я вообще не писал о том что бы создавать новые сущности, только о том как мог бы выглядеть механизм более тонкого и настраиваемого взаимодействия между существующими.
Сущность это класс или скрипт, с одним единственным отличием - он прописан прямо в дллке. Ставя его на карту, мы лишь указываем ему значения переменных, абстрогируйся от этого. По мне очень здорово представлять сущность именно как отдельный скрипт, а не логические операции. Механизм каждый сможет подстроить под себя.
__________________
У котёнка мокрый нос и гладенькая шерсть, у него забавный хвост и быстрых лапок шесть. Две задних, две средних и две передних лапы, такая многоножка получилася у папы.
Он ученый — папа мой — зверушек изучает, гуляет по помойкам, ловит крыс и чаек. Две крысы белокрылые и чайки две унылые покрытые пупырчатою кожей лягушат без пёрышек тоскуют и ускакать спешат.
А ещё есть муравей большой размером с гуся он пугает всех зверей, и я его боюся, когда он ковыляет на лапках на своих.
И в двери ударяет, и начинает стих: Я — муравей, воды налей! Не меньше ведра, напиться мне пора!
Финальный результат.
Кутришные шейдеры прекрасно заменяются моей системой материалов, даже при отсутствии специализированных GLSL-шейдеров для кутришных заморочек. ну типа мигающих текстур, пидарасов, которые, как известно, ползут по трубам итак далее. Но всё это реализуется в пользовательской части, рендерер трогать не надо.
Для сравнения, тепичный кутришный шейдер, который ничего не делает, а просто заменяет путь к текстуре: