Скриптование игры
Строение скриптового движка игры
на примере 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". Я
им часто пользуюсь в самых разных местах.
Итак, для создания хоть какого-то скри─
птового движка важно определить для себя
несколько вещей:
- с какими структурами придётся рабо─
тать;
- как можно изменять эти структуры;
- какие элементарные действия нужно бу─
дет выполнять;
Когда всё это будет разрисовано на бу─
маге, получится некий мета-язык для игры.
Если им правильно пользоваться, можно соз─
дать настоящий шедевр.
(В приложении лежит полный список команд и
дефайны.)
Other articles: