Скриптование игры Строение скриптового движка игры на примере L7 script engine SAM style, 2015 Ещё в начале 2000-х, когда я играл в "Звёздное наследие"на своём Скорпионе, я безнадёжно застрял, открывая нишу в пещере острова. Вероятно, я просто был невнимате─ лен и пропустил подсказку насчет кода, но пришлось лезть в теневой отладчик, чтобы преодолеть это место. Там я обнаружил за─ нятную особеность - по крайней мере то ме─ сто в игре было выполнено в виде интерпре─ татора некоего байткода, в котором были описаны меню, действия при выборе пунк─ тов... Тогда меня это заинтересовало,но не бо─ лее - игр в то время я ещё не писал. Однако потом начал,используя именно тот подсмотренный в "Звёздном наследии" приём - по большей части Anime Story и Nocturne были интерпретаторами байткодовых скрип─ тов. Затем я подался в другую сторону и начал развивать другие скрипты, где были описаны анимации. Подобные используются во всех частях Viking Quest и частично в Karlos'е. В начале 2014 года я написал небольшой тайловый движок, поддерживающий 7 слоёв графики - каждый со своим набором тайлов, с маской/без,с опциональным использованием цвета. Рабочим названием стало 7LT (7 Layers of Tiles). Изначально он создавался для другой игры, но идея быстро стухла, а движок остался. Теперь настало время объе─ динить всё, что было до этого - скрипты игровой логики, скрипты анимации и новояв─ ленный тайловый движок. Это скопище кода получило имя L7 script engine. Интерпретатор скриптов игровой логики представляет из себя обработчик некоего набора команд,представленных 1-2 байтами с параметрами. Одни команды изменяют поддер─ живаемые движком структуры,другие - отдают команды анимационной части движка, третьи предназначены для ветвления и переходов по каким-либо условиям. Т.к.движок наполнялся для воспроизведения квеста/jRPG,то и набор команд и структур у него соответствующий. Структуры сгруппированы в таблицы, движку передаются адреса этих таблиц. Движок на данный момент поддерживает следующие структуры: - NPC (non-player characters).Эти эле─ менты имеют спрайты, анимации,с ними можно поговорить, отдать им что-нибудь. - Активные элементы.Это по сути те же NPC, но на них можно воздействовать, если они это позволяют. - Предметы.Могут находиться либо на ка─ рте, либо в сумке героя. - Двери.Активируются, когда герой соби─ рается на них наступить, и переносят его в другое место карты. - Зоны.Активируются, когда герой на них наступает. В отличие от дверей, действие может быть любым. - Магии.Применяются в битвах, расходуют MP. Действие по применению - любое. - Зелья.Имеют количество и длительность действия. Само действие - любое. По сути зелья - это список налагаемых на героя эф─ фектов во время битвы. - Снаряжение.На данный момент - 4 слота (броня, щит, меч, амулет), до 8 наименова─ ний в каждом. Броня и щит поглощают урон, меч наносит его, амулет оказывает задавае─ мый эффект. - Таблица крафта. Из 2 предметов можно создать третий. - Таблица "говорить" - с кем + адрес скрипта. - Таблица "отдать" - что, кому + адрес скрипта. - Таблица "использовать" - что + адрес скрипта. Многие команды взаимодействуют с этими структурами. Например,задают NPC анимацию, ожидают её окончания, перемещают их в дру─ гие места,включают-отключают зоны и двери, добавляют или убирают магию, зелья, снаря─ жение... Есть команды,оперирующие зашитыми в движок 256 битами и 256 переменными - это тянется ещё с Anime Story, только там эти массивы были поменьше. Блок команд отвечает за ветвление - определяется условие и адрес, куда скрипт переходит, если условие выполнено. Но обо всём по порядку: Вначале замечу, что не хочу особо при─ вязываться к движку Wanderers - расскажу в общих чертах,чтобы была ясна идея. Но при─ меры будут оттуда. -работа с флагами и переменными. Здесь размещены такие команды, как устано─ вить/сбросить/инвертировать бит и набор бинарных операций над битами - OR,AND,XOR. Кроме того,операции над переменными,прини─ мающими значения 0..255 - задать перемен─ ную, инкремент, декремент, сложение и вы─ читание с числом или с другой переменной. Всё это надо для учета событий и условий в командах перехода. -ветвление и переходы. В этой группе сосредоточены команды,по ка─ кому-либо условию разветвляющие выполнение скрипта.В параметрах задаётся само условие и адрес,на который будет совершён переход. Условия могут быть самыми разными. Станда─ ртный набор - если бит установлен/сброшен, если переменная больше/меньше/равна числу или другой переменной,если случайное число больше/меньше какого-то значения. Кроме этого, можно придумать более специфичные условия - если у персонажа есть определён─ ный предмет/магия/зелье/снаряжение. -управление музыкой и звуковыми эффек─ тами. Здесь немного команд - вкл/выкл проигрыва─ ния музыки, запуск трека,проигрывание зву─ ка. -служебные команды. Тут собраны вспомогательные команды, кото─ рые не воздействуют напрямую на структуры данных. Такие,как - сделать паузу в задан─ ное кол-во прерываний;отсканировать клави─ атуру и выполнить закреплённый за клавишей скрипт, когда она нажата; сделать call скрипта или вызвать код. -управление структурами. В эту группу включены команды, взаимодейс─ твующие со структурами данных - активиро─ вать/деактивировать зоны/двери, добавить/ удалить герою предмет/зелье/магию/снаряже─ ние. Плюс управление видимыми элементами - задать NPC/элементу анимацию/спрайт, пере─ местить его куда-нибудь...Анимацией и ото─ бражением их на экране занимается аниматор в движке, это всё происходит параллельно с выполнением скрипта игры. -диалоговые окна и меню. По сути здесь одна команда - начать работу с меню. Задаётся положение, размер окошка меню, список пунктов и закреплённые за ним адреса,на которые будет совершён переход в случае выбора пункта.Любой печатный символ заставляет движок открыть диалоговое окно, если оно ещё не открыто, и начать выводить строку. -табличные действия. Это сканеры таблиц, в которых указаны 1-2 id и адрес перехода. К примеру, действие "говорить" имеет один параметр - id персо─ нажа, который стоит перед героем. Если при сканировании таблицы встретился этот id, вызывается закреплённый за ним скрипт. Из этих элементарных действий можно со─ брать нехитрый квест.В пример приведу кус─ ки из скрипта Wanderers. Все эти незнако─ мыеt* - это макросы, создающие смесь db и dw - сам байт-код для интерпретации. tSetMap 255 ;нарисовать экран, ;на котором находится герой ;(255 - спец.номер) gsLoop tHalt ;HALT. Вызывается аниматор, ;экран обновляется tKeyScan mKeyTab;сканируем ;клавиатуру tJump gsLoop ;возвращаемся к ;метке gsLoop mKeyTab dw 0x01fd,gsDown ; A dw 0x01fb,gsUp ; Q dw 0x017f,gsMenu ; Space - меню dw OxOЧdf,gsEquip; I - экипировка ... db 0 gsDown tNpcAni 0,cOOdn ;задать NPC 0 ;(герой) анимацию, описанную ;по адресу cOOdn (это шаг вниз) gsdOO tWaitNPC 0 ;подождать, пока ;анимация NPC 0 закончится tZoneScan gsdOO;отсканировать ;таблицу зон; ;если зона активировалась, ;выполнить скрипт зоны, ;потом вернуться к адресу gsdOO tEnd ;конец. Эта часть была ;вызвана как подпрограмма, ;поэтому это действует как ;RET в вызывавший скрипт gsUp tNpcAni 0,cOOup ;задать NPC 0 ;(герой) анимацию, описанную ;по адресу cOOup (это шаг вверх) tJump gsdOO;перейти к метке gsdOO gsEquip tSelectEquip ;это "объёмная" ;команда, которой занимается ;сам движок - ;работа с меню снаряжения tEnd ;RET gsMenu tMenu 255,255,0,0,dMainMenu ;это ;выводит меню действий. ;На dMainMenu лежит ;описание пунктов меню - ;что показывать, как называются, ;какие скрипты ;вызывать при выборе tEnd ;RET после выполнения ;скрипта одного из пунктов Теперь обратим внимание на аниматор. Благодаря этой части экран у нас не стати─ чен. Аниматор каждый INT сканирует таблицы NPC и активных элементов. Если встречаются таковые с действующей анимацией, аниматор берётся за исполнение скрипта,описывающего поведение спрайта. Эти скрипты очень схожи с описанным выше, но имеют свою специфику, т.к. заточены только для манипуляции гра─ фикой. Из команд общей направлености - пе─ реместить спрайт в указанное место, смес─ тить спрайт относительно текущей позиции и, пожалуй,всё :) Есть специфические кома─ нды и даже ветвления - проверить текущие координаты спрайта, проверить возможность сделать шаг в определенном направлении (сквозь стены лучше не ходить) и т.д. Сама последовательность кадров задаётся струк─ турами вида<длительность>,<адрес спрайта> Встретив такую, аниматор обновляет спрайт элемента и делает для него паузу на<дли─ тельность> INT-ов. Вот пример анимации. В Wanderers это пилигрим у великого древа, иногда поглажи─ вающий свою бородку. Так же,a* - это мак─ росы: ani13 DBW 30,npc13a ;30 INT'ов, спрайт ;npc13a aIfRndLess 200,ani13 ;если ;RND(255)<200, перейти к ani13. ;Этим обуславливается "иногда". DBW 20,npc13b;20 INT, спрайт npc13b DBW 20,npc13c;20 INT, спрайт npc13c ;Эти 2 спрайта - как раз сунулрукувбороду aJump ani13 ;перейти к адресу ani13 ;(анимация зациклена) макрос"DBW p1,p2" - это "DB p1; DW p2". Я им часто пользуюсь в самых разных местах. Итак, для создания хоть какого-то скри─ птового движка важно определить для себя несколько вещей: - с какими структурами придётся рабо─ тать; - как можно изменять эти структуры; - какие элементарные действия нужно бу─ дет выполнять; Когда всё это будет разрисовано на бу─ маге, получится некий мета-язык для игры. Если им правильно пользоваться, можно соз─ дать настоящий шедевр. (В приложении лежит полный список команд и дефайны.)