Партитура игрового действа: Скриптовые языки вокруг нас
Вас никогда не мучила неудержимая жажда творчества? Вам не казалось, что мир вокруг несовершенен, но вы можете его изменить? Вы не задумывались над тем, как просто порой это сделать? Натыкались на преграду элементарного незнания основ? Даже по отношению к компьютерным играм? И хотите изменить их, изменив себя? Нет ничего проще. "Игромания" уже давно пытается разжечь искру творчества в разгоряченных геймерских умах. С помощью рубрики "Вскрытие" вы можете творчески вмешаться в работу любой игры, изменить ее по своему вкусу. Наша постоянная рубрика "Самопал" помогает вам не только изменять старое, но и создавать что-то новое. Однако порой для воплощения самых интересных замыслов не хватает теоретической базы. Вот этот пробел мы и попытаемся сегодня восполнить. Речь пойдет о скриптовых языках — основе основ любой уважающей себя игры. На скриптовые языки нет никаких стандартов. Разработчики компьютерных игр создают их так, как это удобно им, а не нам. Однако любой, даже самый экзотический скриптовый язык укладывается в общие принципы программирования. Следовательно, зная эти принципы, можно легко понять любой скриптовый язык любой игры, как бы ни извратились программисты при ее написании. Эта статья будет полезна и тем, кто хочет создать свою игру, но испытывает затруднения с игровыми скриптами. Ведь это одна из самых сложных частей хорошей игры! Что выбрать: классический язык программирования или гремучую смесь из дюжины популярных скриптовых? И как все это реализовать? Ответы на эти, а также на многие другие вопросы, вы найдете в этой статье. Покажите свой язык Скриптовая система любой игры обязательно основывается на алгоритмическом принципе программирования. Это значит, что скрипт представляет собой последовательность операторов и языковых конструкций, четко разделенных на команды, их параметры и результаты. Есть и неалгоритмические языки программирования, например Пролог, которые предназначены для решения узкоспециальных задач (интеллектуальная обработка текстов, нейронные сети). Но такие языки программирования не применяются при разработке скриптов. Языков программирования великое множество. Но есть несколько основных языков, или языков-прародителей. Именно из смеси этих китов состоит любой скриптовый язык. Их три: С++, Pascal и Basic. Все они имеют свои инкарнации и под Windows, и под DOS, все образуют языковые семьи, и на всех них может базироваться скриптовый язык. Это как бы три начала программирования, разные подходы и разные реализации. Зная все три "базовых языка", можно легко понять любой скрипт. Однако для простого понимания порой достаточно знать элементарные основы программирования. Исходный объект любого языка — программа. Она состоит из последовательности команд и элементарных конструкций. Команды, выполняющие одно логически связанное и неразрывное действие, объединяются в блок. Такие блоки называются процедурами и функциями. Процедуры и функции можно запускать (вызывать) из других блоков. Одно из ключевых понятий программирования — переменная. Это слово в программировании имеет несколько другой смысл, нежели в математике. Переменная — это своеобразный именной контейнер, который может хранить данные определенного типа. Например, у нас есть переменная A. Мы заносим в нее значение 2. На языке программирования операция приcваивания выглядит так: "A=2". Над переменными мы можем совершать любые арифметические и логические операции. Например, в переменной С находится значение 5. После выполнения команды "C=(C-2)*C" в переменной С окажется значение 15. Чтобы вы не подумали, что переменные могут содержать только численные значения, приведу такой пример: "A="Игромания";B=" — рулеззз!";С=A+B". Что будет в переменной С? Правильно — "Игромания — рулеззз!". Кроме простых переменных, содержащих единичные значения, бывают сложные переменные, или так
называемые объекты. Объект — представитель определенного класса. У объекта (равно как и у класса) могут быть свойства (которые также являются переменными), процедуры (методы) и события. Название объекта отделяется от названия события или метода точкой.
Попробую привести пример перевода всей нашей редакции в объектно-ориентированную систему. У нас есть класс "Журналист", а также дочерний класс "Редактор" (не в смысле подчинения Редактора Журналисту, а в смысле того, что Редактор — тоже Журналист, только с расширенным составом свойств и методов, как пример — может дать Журналисту по репе). Класс Журналист имеет свойства "Имя", "Адрес", "Текущая статья", "Гонорар за текущую статью", методы "Написать статью", "Получить гонорар" и события "Пришло письмо от читателя", "Пришел нагоняй от Главвреда" и "Во всей редакции отрубили свет". Класс Редактор имеет также метод "Проверить статью Журналиста" и событие "Пришла статья от Журналиста". Главный редактор, как водится, сидит на вершине иерархии. Его свойства окутаны завесой тайны, а методы воинственны. О событиях история умалчивает. Классы у нас есть, теперь создаем объекты классов. У нас будет по одному объекту на каждого штатного Журналиста и Редактора и один-единственный объект одинокого класса "Главный редактор". Вот такая объектно-ориентированная картина у нас вырисовывается. Но не объектами едиными жив программист. Есть еще разнообразные конструкции. Самая простая из них — цикл. Это повтор определенного блока команд несколько раз. Иногда количество повторов известно заранее, а иногда оно зависит от какого-то условия. В качестве примера приведем алгоритм приготовления бутербродов на N человек. запросить количество человек и записать это значение в N; для P делать N раз { отрезать кусок хлеба; отрезать кусок колбасы; положить кусок колбасы на кусок хлеба; отдать получившуюся конструкцию человеку № P; }; P и N тут не что иное, как переменные. Причем P — так называемая управляющая переменная. С каждым шагом цикла она увеличивается от 1 до N, и это значение может быть использовано внутри тела цикла. Тело цикла — это команды, которые находятся внутри фигурных скобок (хотя не факт, что фигурных, и не факт, что скобок — в каждом языке программирования по-разному). Другая важная конструкция любого скриптового языка — условия. Общий вид условия такой: "Если (A=B), то делать {что-то}". В части, отвечающей за сравнение, допускается использование логических операторов. Например: "Если (((A>B) и (С=3)) или (D=1)), то...". Оператор И означает, что все выражение истинно (то есть условие выполняется), если все части выражения истинны. Оператор ИЛИ означает, что все выражение истинно, если хотя бы одна часть выражения истинна. Оператор НЕ означает, что выражение истинно, когда входящее в него подвыражение ложно. Для примера составим более сложный алгоритм изготовления бутербродов, основываясь на степени голода людей. ввести число человек и занести это значение в N; для P делать N раз { если (человек № P голоден), то { отрезать кусок хлеба; отрезать кусок колбасы; положить кусок колбасы на кусок хлеба; отдать получившуюся конструкцию человеку № P; } }; Управляющая модель Игровой скрипт — своего рода управляющая модель,
виртуальный режиссер событий, происходящих в игре. Благодаря гибкости большинства скриптовых языков сценарии получаются сложными и интересными. Разработчик может предусмотреть действия игрока и прописать соответствующую "реакцию системы", если выражаться научным языком. Не забывайте, что скрипт — это как бы продолжение основного программного кода игры, который заключен в ее exe-файле. Это значит, что структура скрипта соответствует структуре самой игры. Обычно разработчик стремится создать удобную и понятную систему классов и объектов, основывающихся на том, что происходит в игре. Манипулируя ими в скрипте, мы манипулируем отождествленными с ними явлениями в самой игре. В этом и состоит концепция скриптового управления. Вообще, сам метод скриптового программирования очень подходит под определение "виртуальной машины". Кстати, этот термин появился еще до возникновения современных компьютеров. Виртуальная машина — некая математическая модель, среда, которая исполняет написанную для нее программу, хотя сама, в сущности, является программой. Однако для того, чтобы понять родной скриптовый язык игры, надо его распознать и сравнить с тремя китами.
C++ Многие спросят, почему я написал именно С++, а не просто Си — ведь именно он является родоначальником. Дело в том, что в стандартном Си не было объектно-ориентированного программирования, а без него современные скриптовые языки и гроша ломаного не стоят. Очень многие скриптовые языки являют собой те или иные вариации C++. Вот его синтаксис. 1. Почти каждая строчка оканчивается на ";". Блок выделяется фигурными скобками. 2. Присвоение переменной значения — классическая конструкция "A=B". 3. Условие: "if (a>b) {команды}. Обратите внимание: строгое равенство обозначается не одинарным знаком "=", как мы все привыкли, а двойным (без пробела, как здесь в журнале, где пробел поставлен для того, чтобы было понятно, что знака два, а не один). То есть команда строгого сравнения выглядит так: "if (a= =b) {команды}". 4. Простой цикл: "for (int x=1;x<5;x++)". Обратите внимание на этот пункт. Циклы здесь оформлены нетривиальным способом. После слова for в скобках пишется условие, составленное из трех частей, разделенных символом ";". Первая часть — инициализация переменной (объявление и присвоение исходного значения). Слово int перед названием переменной указывает на то, что переменная упоминается в тексте скрипта впервые и имеет тип "целое число". Вторая часть — условие, при невыполнении которого цикл кончается. Третья часть исполняется каждый шаг цикла и содержит команду приращения переменной x на единицу. Кстати, именно из-за удобной конструкции приращения переменной на единицу, "переменная++", язык прозвали С++. 5. Процедуры и функции оформляются таким образом: <тип возвращаемого значения> <имя функции>(<ее параметры) {<текст функции>}; 6. Вызов процедур или функций осуществляется так: "<имя процедуры>(<параметры через запятую>);". Pascal Язык Паскаль тоже необычайно популярен в качестве скриптового. В первую очередь из-за своей простоты и доступности начинающим творцам. Ведь Паскаль значительно проще С++, хотя многие это утверждение не преминут оспорить. А вот и его синтаксис. 1. Как и в С++, почти каждая строчка оканчивается на ";". И на этом сходство Паскаля с С++ заканчивается. Дальше одни различия. Одно из самых главных — командный блок. Он оформляется двумя командными словами. В начале блока стоит слово begin, причем после него точка с запятой не ставится. В конце блока стоит слово end с ";" на конце. 2. Присвоение значения переменной осуществляется символами ":=", например "A:=B". 3. Условие выглядит так: "if (a>b) then begin <команды>;end;". 4. Конструкция цикла в Паскале выглядит намного более естественно для человека, чем в С++: "for x:=1 to 5 do begin <команды>;end;". 5. Процедуры и функции оформляются таким образом: function <имя процедуры>(<ее параметры>):<тип возвращаемого значения>; begin <текст функции> end; 6. Вызов процедур и функций осуществляется точно так же, как и в С++. Visual Basic Visual Basic — очень неоднозначный язык. С одной стороны, его ругают все кому не лень за примитивизм, за медленную работу (в исполнении Microsoft), за ограниченные возможности. Как бы то ни было, Visual Basic до сих пор остается актуальным, особенно в скриптовых системах. На моей памяти нет ни одного скриптового языка, полностью основанного на Visual Basic. Зато есть очень много гибридов, языков, основанных на С++ или
Pascal, но со значимыми элементами VB. Поэтому знакомство с его синтаксисом будет полезным для понимания таких гибридных скриптов.
1. В отличие от многих других языков программирования в VB нет разделителей между строками. Разделителем служит невидимый маркер возврата каретки, то есть каждая новая команда обязательно пишется через enter с новой строчки. Стандартизованных блоков в VB тоже нет. 2. Присвоение значения переменной осуществляется просто и без заморочек: "A=B". 3. Конструкция условия выглядит так: IF A=B THEN <команды> ENDIF 4. Циклы очень похожи на паскалевские во всем, кроме окончания: for I=1 to 10 <команды> next I 5. Процедуры и функции очень нетипичны, а их объявления во многом двусмысленны. Они никогда не используются в скриптовых языках, а заменяются на паскалевские или из С++. Не надо вам забивать голову этими конструкциями: все равно нигде не пригодятся. 6. А вот вызов процедур в VB очень нетипичен, но распространен в скриптах. Сначала пишется командное слово, потом пробел, а дальше параметры через запятую. Никаких скобок. Например: "power 5,7" Практикум начинающих кодеров Любая теория без практики — бесполезная трата времени. Так почему бы не попрактиковаться? Благо объектов для тренировочных пыток кругом полно — например, недавние "Гномы". Скрипты у них — загляденье. Они контролируют практически всю работу игры. И хранятся они в открытых форматах. Возьмем кусочек кода и попытаемся его разоблачить. Вот выдержка из скрипта zwerg.tcl, отвечающего за обработку жизнедеятельности гномов в игре: obj_init { set info_string "" set logon 0 set state_log 0 set state_shell 0 set event_log 0 set workannounce_log 0 set was_baby 0 set is_campaign 0 set is_tutorial 0 set is_human 1 set is_counterwiggle 0 set myref [get_ref this] set gnome_initialized 0 set gnome_gender "unset" set birthtime [expr {[gettime]-1200}] set_attrib this GnomeAge $birthtime set at_Hi 1.0 set at_Nu 1.0 set at_Al 1.0 set at_Mo 1.0 set seq_idle_anims [list] lappend seq_idle_anims {10 {stand_anim_a}} lappend seq_idle_anims {10 {stand_anim_b}} lappend seq_idle_anims {10 {stand_anim_c}} lappend seq_idle_anims {1 {spaehen}} lappend seq_idle_anims {1 {zeigen_rechts}} lappend seq_idle_anims {1 {strippe_ziehen}} call data/scripts/misc/seq_idle.tcl auto_choose_workingtime this set_weapon_class this 0 set_shield_class this 0 set_texturevariation this [hf2i [random 4]] 0 set_attrib this carrycap 1 set_attrib this hitpoints 1 state_reset this state_trigger this idle state_enable this timer_event this evt_timer0 -repeat 1 -interval 1 -userid 1 -attime 3 timer_event this evt_zwerg_attribupdate -repeat -1 -interval 1 -userid 2 timer_event this evt_zwerg_workannounce -repeat -1 -interval 1 -userid 3 timer_event this evt_talkissue_update -repeat -1 -interval 5 -userid 4 -attime [expr {[gettime]+2}] timer_event this evt_sparewish_update -repeat -1 -interval 10 -userid 5 -attime [expr {[gettime]+3}] } handle_event evt_timer0 { call_method this init } state trapped { if {$state_log} {log "[get_objname this] passing state code TRAPPED"} if {$trap_mode==0} { gnome_failed_work this tasklist_clear this hide_tools kill_all_ghosts set trap_mode 1 if {$trap_type=="petrify"} { set_anim this medusa_dead 0 1 state_disable this action this wait 0.6 {state_enable this} } else { state_disable this if {[get_attrib this atr_Hitpoints]>=0.01} { set_anim this trappedtostand 0 1 action this wait 0.6 {state_enable this} } else { set_anim this gettrapped 0 1 action this wait 1 } } return } state_leave trapped { set_physic this 0 set trap_mode 0 }} Естественно, я привел текст скрипта с ОЧЕНЬ большими сокращениями, оставив только самые интересные части. Итак, приступаем к расшифровке. Сначала окидываем весь скрипт орлиным взором. Сразу замечаем строки вида "set state_log 0". Такая запись очень похожа на VB, где параметры всех процедур, методов и
функций пишутся без скобок, а пробелы имеют большое значение. На принадлежность скрипта к VB указывает также отсутствие разделителя между строками. Очевидно, каждая новая команда пишется с новой строчки, и это критично. Видим явные признаки Visual Basic. Следует ли язык скрипта безоговорочно отнести к VB? Пока нет, посмотрим, что дальше. А вот дальше начинаются вещи, для VB не характерные. Во-первых, весь скрипт строго разделен на смысловые блоки, заключенные во взвешенные фигурные скобки (то есть число открывающих скобок равно числу закрывающих). Во-вторых, структура условий ну никак не соответствует спецификации VB. Обе особенности — явные признаки C++. Внимательно осматриваем весь скрипт еще раз и убеждаемся, что кроме конструкций VB и С++ в тексте больше ничего не заметно. Хотя нет. Есть несколько очень мелких деталей, взятых из других языков. Настолько мелких, что считаться с ними не будем. Например, во всех условиях обращение к переменным осуществляется через значок "$". С одной стороны, это напоминает Perl, а с другой — классические указатели. Но и то, и другое — сложные материи для новичка, поэтому просто будем считать, что этого значка там нет. Мы разложили структуру скриптового языка по полочкам и пришли к выводу, что имеем дело с гремучей смесью VB и C++ в равных пропорциях. Так как основные языковые конструкции мы уже знаем, то можем легко понять скрипт и изменить его. Советую личностям, не обремененным хотя бы элементарным знанием английского языка, запастись словариком. Чаще всего по названиям процедур, функций, свойств и иже с ними легко определить, что они означают. А посему приступаем к анализу текста срипта. Первые несколько строчек начинаются с процедуры set, которая, как это видно из параметров, устанавливает значение глобальных свойств. Сначала обнуляются некоторые системные переменные (info_string, logon и т. д.). Потом устанавливаются начальные параметры гнома (at_Hi, at_Nu, at_Al, at_Mo). Далее следует подозрительная строчка "set seq_idle_anims [list]". Это больше всего похоже на объявление массива или списка анимаций. Наше предположение подтверждается следующими строчками, где список заполняется конкретными анимациями. Далее следует строчка "call data/scripts/misc/seq_idle.tcl". Это вызов какого-то внешнего скрипта из текста основного скрипта. Да, скриптовая система в "Гномах" развесиста, если даже такое может. Следующая знаменательная строчка выглядиттак: "timer_event this evt_timer0 -repeat 1 -interval 1 -userid 1 -attime 3". Что бы это могло быть? Совершенно верно — это таймер. Причем не просто таймер, а таймер с ассоциированным обработчиком событий. Что значат эти непонятные слова? А то, что этой командой устанавливается специальный объект, отвечающий за тайминг. Каждые N секунд (ну или внутриигровых тактов) запускается какая-то процедура-обработчик. В ней могут быть любые команды, которые должны сработать в строго определенное время. Это и есть события. Причем события могут быть не только временными. Например, выделил игрок гриб — это тоже событие. И на него тоже можно назначить какую-нибудь процедуру-обработчик командой наподобие этой. Если есть команда запуска таймера, где-нибудь неподалеку должна быть и процедура обработки. Ага, вотона: "handle_event evt_timer0 {call_method this init}".То есть при каждом срабатывании таймера выполняется эта команда. А далее идут другие обработчики событий. Первое — "state trapped" выполняется, очевидно, когда гнома что-то задерживает. Следующий обработчик срабатывает, когда гном, наоборот, освобождается: "state_leave trapped". В теле обеих процедур-обработчиков выполняются соответствующие моменту действия. Если гном задержан, ставится соответствующий флаг, потом выясняется причина и генерируется адекватная реакция. Когда задержка ликвидирована, флаг сбрасывается. Как видите, игровые скрипты — это очень просто. А от скрипта до языка программирования столько же, сколько от модификации чего-то чужого к собственному свободному творчеству. Дерзайте!
1229 Прочтений • [Партитура игрового действа: Скриптовые языки вокруг нас] [19.05.2012] [Комментариев: 0]