Lua для игр и не только. Часть 1

Если вы не любите кошек, значит, вы не умеете их готовить.

Если что-то должно быть сделано, то нужно это сделать качественно. О языке Lua написать должно, потому как он сопровождает мировые технологии топ-класса, а насчет качества… не хочется уподобляться быстрому обзору в стиле "я знаю Lua" или "Lua как явление с точки зрения профи", а хотелось бы объяснить суть процессов, связанных с этим языком, так чтобы было понятно, и вы смогли бы на нем программировать. Причем для начального освоения вам понадобится немногое: скачать бесплатную программу в 1,2 Мб и уделить немного времени.

Эпиграф к этой статье очень хорошо может быть отнесен к прекрасному и своеобразному Lua. Для многих Lua — это свободно распространяемый и далеко не всем понятный язык программирования бразильского происхождения, причем серьезно меняющийся. Но его нельзя не любить, он уникален и широко используется в высокопроизводительных мультиплатформенных разработках, в том числе и в компьютерных играх топ-класса (World of Warcraft и т.д.).

Эту серию материалов можно назвать продолжением популярной в прошлом году "Разработке компьютерных игр" и ответвлением от "Популярно об ИИ" (искусственный интеллект очень часто пишется на Lua, и вскоре вы поймете, почему). Изучение Lua можно рекомендовать всем, кто связан с разработкой ПО на современном уровне (при разном уровне подготовки), язык достаточно необычен и очень интересен, синтаксис прост. Lua по изначальной своей концепции является встраиваемым языком, то есть может внедренно работать в рамках низкоуровневых API. Основное предназначение — написание сценариев и обработка сложных данных, хотя… не только. Изначально Lua и был придуман как язык расширения для включения в программы- хозяева, написанные на С, но может использоваться и автономно.

Lua.org является лабораторией подразделения компьютерных наук при Папском Католическом Университете (Pontifical Catholic University) Рио-де- Жанейро. Авторы проекта — R. Ierusalimschy, L. H. de Figueiredo и W. Celes. По-португальски слово lua обозначает "Луна".

Намек на принадлежность

Функциональную принадлежность того или иного языка при первом знакомстве обычно легко определить по имеющимся в нем типам данных. В Lua их восемь: nil (неопределенный), boolean (логический), number (числовой), string (строковый), function (функция), userdata (пользовательские данные, получаемые из внешнего API), thread (поток), table (таблицы, а по существу — единственный механизм структурирования данных). Все это подробно мы обсудим позже. Сложно? На самом деле только на первоначальном уровне понимания основной концепции. То есть это перечисление будет понятным для опытных программистов, а начинающим достаточно просто принять эту информацию к сведению, потом мы все хорошо и полно расшифруем. Также в языке предусмотрена автоматизация работы с памятью (мусороуборщик) и так далее.

Для чего нужен Lua?

Обычно дилемма в программировании всегда одна: как совместить высокую производительность кода с продуктивностью и скоростью разработки. Первое нам предлагают низкоуровневые компилируемые языки типа С/С++, второе — интерпретируемые или языки сценариев. Причем иногда хочется просто отделить "котлеты от мух", а точнее — компилируемый код от сценариев.

С этим столкнулись практически все области программирования, а наиболее остро такая проблема встала перед разработчиками компьютерных игр. Специфика создания этой продукции заключается в том, что спектр задач велик, но решать их нужно оперативно. Можно, конечно, ждать у моря погоды, когда кто-то решит все за вас, но это — время. Поэтому такой достаточно слабо известный широкому кругу обывателей язык Lua стал неким технологическим ноу-хау в отрасли.

Суть его заключается в том, что он является встраиваемым, высокоуровневым и data-oriented, то есть предназначенным для обработки сложных данных. Приложения, включающие Lua, представляют собой комбинацию компилируемого кода и Lua-сценариев. Компилируемый код может при необходимости заняться железом и, в то же время, может вызывать Lua-сценарии для обработки сложных данных. Эти сценарии могут писаться и модифицироваться независимо от компилируемого кода.
Ситуация выгодна и тем, что сценарии обладают своего рода переносимостью. То есть, например, при переводе игры/программы на другую платформу вам нужно менять только компилируемую часть.

С чего лучше начать знакомство?

Для первого знакомства и предварительного изучения синтаксиса рекомендуется скачать русифицированную версию программы SciTE (1,2 Мб) c сайт , в которой мы и начнем работать. Это универсальный текстовый редактор/компилятор для Windows/OSX, поддерживающий синтаксис более 70 языков программирования благодаря использованию лексеров (lexer) — программных модулей для анализа и обработки конкретного языка программирования. Лексеры подобны плагинам, и пишутся, в основном, независимыми разработчиками. SciTE удобен для изучения тем, что он имеет встроенную полноценную версию Lua, и мало того, сам базируется на Lua-сценариях, а в Help'е к нему вы найдете ничто иное как перевод родной документации к последней версии этого языка. Кстати, самый качественный перевод из всех существующих в рунете.

Помимо этого, последние версии языка Lua (на момент написания материала 5.1.4) можно скачать с сайта www.lua.org, что пригодится нам потом. В поставку включена простая хост-программа lua, которая использует библиотеку Lua и представляет собой полную и автономную реализацию интерпретатора языка, а также компилятор luac. Lua является свободно распространяемым программным средством, поэтому предоставляется без каких- либо гарантий в соответствии с лицензией.

Информации по Lua в Интернете достаточно много, в том числе и русскоязычной. Хотя описания старых версий абсолютно не актуальны, так как Lua сильно меняется. Англоязычный сегмент также велик, причем очень часто советуют обратиться к книге соавтора проекта Роберто Иерусалимского (Roberto Ierusalimschy) "Программирование на Lua", второе издание (Programming in Lua (Second Edition)). Как мы уже сказали, от версии к версии язык претерпевает существенные изменения, и не только сам язык, но и его концептуальные основы (например, два года назад оперировали ключевым понятием тэгов (tag), использовали множество функций, которых сейчас нет, вернее, все видоизменено, и это произошло за два года!). То есть нужно, как говорится, держать нос по ветру, поскольку специфики очень много. Есть и сложности с первоначальным освоением, потому как большинство описаний и руководств носит формальный характер, чего мы попытаемся в данной серии избежать. Поэтому начнем со SciTE и автономного использования Lua. После переключимся на более сложный уровень.

Если есть необходимость использования другого компилятора для языка Lua, в том числе и под другие ОС (Linux i386, BSD i386 (FreeBSD, NetBSD или OpenBSD), Mac OS X на Intel'ах, Solaris x86 (только GCC, не SunCC), MinGW (Win32), Cygwin, POSIX на x86, Generic x86), то можно рекомендовать LuaJIT (Lua Just-In-Time Compiler), который есть по адресу сайт .

Общие данные

Lua — типичный процедурный язык (библиотека его функций написана на подмножестве ANSI C и C++), он может компилироваться без всяких модификаций на всех известных платформах, имеющих компилятор ANSI C. В данном случае мы немного забежим вперед и скажем, что работа с функциями Lua не сильно отличается от работы с обычными С-библиотеками, причем часто само понятие "функции Lua" приравнивают к макросам, что действительно так, по поведению они схожи. Как мы уже отметили, Lua является встраиваемым, то есть интегрируемым, и в этом случае для функционирования кода, на нем написанного, нужна хост-программа. Ей, в свою очередь доступно ряд функций на С для связи с Lua. Все функции API, связанные типы и константы объявлены в файле заголовка lua.h (также хорошо описаны в документации).

Все инструкции в Lua выполнены в глобальной среде. В ее рамках позволяется запускать части кода, написанные на Lua, модифицировать переменные Lua и регистрировать С-функции для использования непосредственно в коде Lua. Эта среда будет инициализирована обращением lua_newstate и сохранится до обращения lua_close или до завершения ведущей программы. Глобальная среда может управляться Lua-кодом или ведущей программой, которая может читать и писать глобальные переменные, используя функции API из библиотеки, которая предоставлена Lua. Все переменные в Lua являются глобальными, если отдельно не указано, что они локальны. Дальнейшие тонкости рассмотрим в процессе, а сейчас начнем с основ.

Hello, world!

Запускаем SciTE, ничего не меняем, просто создаем новый файл, который у вас по умолчанию обозначится как Untitled.txt. Вводим в него строку: print("Hello, world, from ",_VERSION,"!\n")

Поскольку у нас не обозначена принадлежность этого файла к чему-либо, ведь расширение *.txt ни о чем не говорит, команда "Tools -> Выполнить" (или F5) ни к чему не приведет. Пересохраняем файл под новым именем, например, Hello.lua (расширение *.lua является типичным для файлов сценариев, написанных на этом языке). Теперь нажимаем F5 и в окне вывода видим результат:
Hello, world, from Lua 5.1!

При этом отметим, что print() является базовой функцией Lua, она удобна для быстрого отображения значения и обычно используется для отладки. Она не очень удобна для отформатированного вывода, для которого уместнее использовать string.format().

Немного расширим наш пример с использованием функции, форматирующей строку:
function printf(...)
print(string.format(...))
end
printf("Hello from %s on %s\n",_VERSION,os.date())

По нажатии F5 выдается результат:
Hello from Lua 5.1 on 04/14/09 10:19:53

Теперь прокомментируем, вернее, расскажем о котах и их приготовлении:). Синтаксис языка поначалу не совсем привычен, особенно для тех, кто привык к ECMA-262 (С++ подобное семейство языков сценариев, куда входит JavaScript, ActionScript и т.п.). Чем-то Lua близок к С (не С++), хотя все здесь перемешано. К тому же синтаксис Lua довольно свободен и очень часто одно и то же выражение можно записать по-разному.

Для первоначального изучения лексики, синтаксиса и семантики языка лучше порекомендовать использовать SciTE и программирование на Lua в автономном режиме (что мы и начали делать), пройдя стандартный путь от Hello, world! до сложных вычислений. Впрочем, много тонкостей вы можете узнать и сейчас.

Синтаксис

М-да, а начнем мы с веселого, которое многие просто пробегают глазами, но тут важно. В переводе документации по Lua для SciTE есть, пожалуй, единственная, но очень досадная и коварная ошибка (в англоязычной документации она отсутствует). Итак, короткие комментарии помечаются двойным дефисом "--", что соответствует С-шному "//". Для ввода длинных комментариев вместо С-варианта "/*…*/" предполагается в простейшем случае "-- [[…]]". Обратите внимание на то, что квадратная скобка двойная, а не одинарная, как опрометчиво указано в русскоязычном переводе документации. Причем эта ошибка фатальна, и часто можно увидеть, как начинающие программисты, просто не захотев разобраться с ситуацией, пользуются короткими комментариями, считая вариант с длинными — глюком. А как быть, когда нужно оперативно отключить большую часть кода?

Так вот, на самом деле Lua гораздо умнее. Двойными квадратными скобками обозначается уровень вложенности строки. В варианте [[…]] мы имеем нулевой уровень вложенности, а все последующие указываются с помощью количества знаков "=" между открывающимися или закрывающимися квадратными скобками. Например, четвертый уровень вложенности обозначается как [====[…]====] (четыре символа "=" при открытии и закрытии). С длинными комментариями такой вариант работает даже лучше, чем в варианте С с его "/*…*/", потому как можно делать множество уровней этих самых комментариев.

Почему это так важно? Дело в том, что вложенность строк может распространяться не только на комментарии, но и при работе с литеральными строками (в том числе как вариант замены некоторых С-подобных escape-поледовательностей). Например, нам нужно вывести вариант в двух строках:
apple
117"

Синтаксис С в данном случае также будет работать, то есть мы запишем:
a = 'apple\n117"'
print(a)
… и получим необходимое. "\n" — это перевод на новую строку. Можно использовать и двойные кавычки, используя "\"" (символ двойной кавычки): a = "apple\n117\""

Можно использовать и ASCII-код, в котором, например: символ "a" — это 97, "1" — 049, символ перевода строки — 10:
a = "\97pple\10\049\0497\""

Обратным слэшем также можно для удобства разделять длинную непрерывную строку на несколько, что действует только в рамках кода. А теперь обратимся к нашим квадратным скобкам:
a = [=[apple
117"]=]

Все. Необходимый результат будет выведен. Причем такая ситуация с вложениями на разных уровнях во многих вариантах бывает весьма актуальной. То есть литеральную строку мы открываем/закрываем на одном уровне, комментарий делаем на другом и так далее.

Опытный программист, не работавший с Lua, внимательно посмотрев на то, что написано, заметит, что по завершении любой из операций напрочь отсутствует ";", вызывающий особую любовь у представителей других языков. В качестве разделителя может выступать обыкновенный пробел или переход на другую строку, хотя, что интересно, если по привычке вы напишете точку с запятой, то сообщения об ошибке не появится. То есть применение этого разделителя факультативно, хотя иногда и уместно. Пустого оператора в языке нет, поэтому выражение ';;' недопустимо.

А еще опытный программист, взглянув на обозначение комментария, скажет, что в Lua отсутствует декремент, который в С++ и ему подобных записывается как "--". Это действительно так, в Lua нет операций ++, --, +=, -=, *=, /=. И это не единственные перлы с упрощением, о многом вы узнаете во второй части материала.

Промежуточное завершение

Lua — один из самых интересных современных языков. Его применение простирается не только на компьютерные игры, хотя стоит отметить, что последние являются представителями самого сложного сегмента программирования. Вашего покорного слугу в свое время Lua удивил по классическим правилам шоу-бизнеса: сначала ошарашил, а потом озадачил.

Причем, изучая новации в Microsoft Visual Studio и в языках, с ней связанных, можно увидеть схожее движение в сторону интегрирования высоко- и низкоуровневых подходов (то же "многоязыковое программирование"), при котором один язык расширяет возможности другого. Такое же мы можем наблюдать во взаимодействии Lua и C API. Хотя последний вариант по современным меркам — высший пилотаж.

Кристофер christopher@tut.by


Компьютерная газета. Статья была опубликована в номере 16 за 2009 год в рубрике программирование

©1997-2024 Компьютерная газета