Если вы решили освоить разработку игр с использованием Corona SDK, эта статья даст вам необходимые основы самого движка и языка Lua, на котором вам придется разрабатывать. По своему этот язык прекрасен и во многих отношениях необычен. Я постарался собрать в одну статью все наиболее необходимые сведения, но их оказалось больше чем можно размещать в одной публикации и мне пришлось разделить статью на 3 части, эта первая часть и в ней мы рассмотрим следующие вопросы:
- Порядок комментирования исходников
- Переменные, константы, область видимости
- Модули и организация проекта
- Условные операторы
Порядок комментирования исходников
Почти в любом языке программирования имеется понятие комментариев, Lua не исключение. Что вообще такое комментарий — это пояснения к исходному тексту программы, находящиеся непосредственно внутри комментируемого кода. Синтаксис комментариев определяется языком программирования. С точки зрения компилятора или интерпретатора, комментарии — часть текста программы, не влияющая на её семантику. Т.о. комментарии ни как не влияют на порядок выполнения кода и вы их пишите для себя и тех кто через какое-то время может начать изучать ваш исходник. Комментарии могут использоваться в качестве инструмента отладки, т.е. вы можете разместить в комментарий кусок кода временно его исключив из выполнения и тем самым упростив момент поиска ошибки в другом участке. В Lua есть 2 вида комментариев.
Однострочный комментарий
Все написанное от последовательности символов "--" до конца строки считается комментарием.
Многострочный комментарий
Многострочный комментарий начинается последовательностью "--[[" и заканчивается через любое количество строк последовательностью "]]" Далее примеры применения комментариев:
--[[Функция принимает 2 параметра a и b
возвращает сумму параметров]]
function summ(a,b)
return a + b
end
print(summ(5,7))--результат 12
Я не буду уделять излишнего внимания вопросу качества комментирования кода, это решает каждый сам для себя приведу лишь несколько простых советов:
- Хороший код сам себя комментирует — т.е. если вы пишите очевидно и называете переменные обдуманно в большинстве случаев комментарии не требуются
- Не пишите комментарии — для комментария. Т.е. не стоит писать комментарии там где и так все ясно.
- Краткость — сестра таланта. Делайте комментарии простыми и понятными.
- Комментируйте назначение файла и функций.
Переменные, константы, область видимости
Большинство языков программирования имеют переменные. Под этим понятием в большинстве случаев понимается — поименованная, либо адресуемая иным способом область памяти, адрес или имя которой можно использовать для осуществления доступа к данным и изменять значение в ходе выполнения программы. В Lua переменные могут создавать прямо там где их нужно применять, т.е. нет определенного блока (как в языках типа Pascal) где необходимо заранее объявлять переменные перед тем как их использовать. Приведу пример объявления переменных:
a = 1
b = 3
local c = 'example'
d,pi = 2, 3.14
a = 'stroka'
Знатоки других языков думаю заметили несколько особенностей:
- тип переменных не нужно указывать, значение определяет её тип
- тип переменных можно менять, т.е. если переменная была численной (в примере «a»), а далее ей присвоили строчное значение она стала строчной
- доступно параллельное присвоение через один знак "=" (переменные d,pi). Это свойство может значительно укоротить момент стартовой инициализации, так же позволяет выполнять обмен значений переменных без промежуточной переменной например так a,b = b,a
- ключевое слово local. Это слово используется для того что бы ограничить область видимости переменных, если переменная объявлена локальной внутри операционного блока (функция, цикл, условный оператор) она будет видна только внутри него. Если де локальная переменная объявлена в глобальном контексте файла она будет видна с этой строки и до конца файла (подробней об области видимости вы узнаете в немного ниже)
Остановимся на константах. Под константой понимается — переменная значение которой не меняется в процессе использования. Часты бывает удобно некоторые значения многократно применяемы в проекте (например версия приложения) хранить в константах, в некоторых языках имеется инструмент создания констант в прямом смысле этого слова, т.е. переменных которые были единожды объявлены и в дальнейшем их значение невозможно изменить, в Lua такой инструмент отсутствует т.е. пенять можно все, (даже реализацию функции) и по этой причине удобным вариантом для защиты от смены констант можно считать — порядок их именования. Лучше всего константы именовать каким-то особым образом что бы в коде они были более заметными, например БОЛЬШИМИ_БУКВАМИ_СО_СЛОВАМИ_ЧЕРЕЗ_ПОДЧЕРКИВАНИЕ, но это не правило а скорее совет.
Кратно опишу формат допустимых имён переменных, констант и функций, некоторые вещи декларативны и их нельзя нарушать, другие просто советы:
- переменные могут содержать латинские прописные и строчные буквы, цифры и символ подчеркивания "_"
- переменные не могут начинаться с цифры
- переменные не могут иметь имена зарезервированных слов: and, break, do, else, elseif, end, false, for, function, if, in, local, nil, not, or, repeat, return, then, true, until, while
- зависимость от регистра. Как и в большинстве языков в Lua регистр является определяющим факторов в уникальности переменной, т.е. lua, LUA, lUa и LuA это все разные переменные.
- не стоит злоупотреблять названием переменных начинающихся с символа нижнего подчеркивания, если конечно вы уверены что эти имена точно не используются языком для именования внутренних механизмов
- используйте обдуманные имена переменных которые имеют широкую область видимости. Это стоит понимать так — если переменная используется в большой части проекта она не должна иметь имя ни чего не означающее и короткое
- разделяете формат именования переменных, констант и функций. Например для констант уможно применять ТАКОЙ_ФОРМАТ, для переменный примерно_такой, а для функция использоватьТакойФормат — это сделает код более удобным.
Под областью видимости понимается — пространство кода в пределах которого можно непосредственно получить доступ к данным. Неверное понимание принципов организации области видимости может привести к массе неприятных ошибок, которые обычно трудно устранить:
- переменную не видно — вам кажется что все хорошо, а переменная равна nil (не определена). Эту ошибку как правило легко выявить но не всегда легко устранить.
- переменную видно там где она не используется — эта на первый взгляд мелкая проблема может привести к крайне сложным багам, например вы не ожидаете что переменная из другого участка кода будет видна в этой области и делаете поверку на ее несуществование (что бы ее инициализировать) и проверка не заканчивается успехом и ваш инициализирующий код не выполняется что приводит к использованию значения которое было определено в другом месте. Что бы избежать этой проблемы всегда и все переменные старайтесь делать минимально видимыми используя слово local. Использование глобальных переменных всегда нужно стараться сводить к минимуму
Далее мы не раз еще столкнемся с примерами неверной организации области видимости, я буду делать в этих местах акценты по этой причине этот раздел можно закончить.
?
Модули и организация проекта
Программный модуль — функционально законченный фрагмент программы, оформленный в виде отдельного файла с исходным кодом. Как вы уже знаете выполнение любого проекта в Corona SDK начинается с файла main.lua. Если писать всё приложение в одном файле у вас может возникнуть масса трудностей, приведу пример:
- Большой объем кода. исходный текст почти любой полноценной игры может включать сотни, тысячи, десятки тысяч, а в некоторых случаях и более ста тысяч строк кода, если разместить весь код в одном файле на некотором этапе сопровождать этот проект станет просто невероятно сложно
- Множество сцен. в большинстве случаев реальные приложения содержат более одной сцены (функционального экран), например различные меню, экраны загрузки, возможно несколько боевых сцен, сцены открытого мира и т.д. опять же если разместить все в один файл придется при переходе между сценами нагромождать код десятками процедур создания и удаления сцены, т.е. код опять же будет сильно усложняться
- Универсальный переносимый код. В процессе создания своих прилодений вы часто будете сталкиваться с однотипными задачами, которые вы будете переносить в библиотеки, а далее эти библиотеки вы сможете легко переносить в ваши следующие проекты, если же писать все в одном файле вопрос о переносимости готовых библиотек будет весма затруднителен, так как полезный код придется вычленять фрагментами из огромного файла.
Возможны и другие проблемы если неверно организовать проект и писать все в один файл main.lua, но в наших уроках мы так делать не будем и далее я покажу тот способ который я выбрал для своих проектов и который не раз себя хорошо зарекомендовал, принимать его или придумывать свой — решать вам, но дальнейшие примеры проектов я буду оформлять таким способом. Таким образом мы пришли к заключению, что каким-то образом нам нужно разделать исходник проекта на несколько файлов, более того эти файлы будут иметь различное назначение и исходя из этого у каждого типа файлов будет свой порядок именования определяемые префиксом в имени. Этот способ лично мой и я не утверждаю что он лучший, но так как дальнейшие примеры игр которые вы увидите в моих статьях будут иметь такой механизм оформления проекта, я решил взять его за основу. Кратко пробежимся по типам файлов которые могут пригодится.
Сцена
Сцена — префикс sc. Формат имени файлов scName.lua. Где имя начинается с заглавной буквы и определяет назначение файла. Для остальных типов это правило так же будет действовать поэтому я буду указывать только их префиксы. Файл сцены имеет довольно строгий формат и он определяется требованиями библиотеки Corona SDK — composer. Рассмотрению этой библиотеки будет посвящен отдельный урок пока что прикладываю шаблон исходного файла:
local composer = require( "composer" )
local scene = composer.newScene()
-- -----------------------------------------------------------------------------------
-- Код за пределами функций сцены вополняется один
-- не удяляется в рамках "composer.removeScene()"
-- -----------------------------------------------------------------------------------
-- -----------------------------------------------------------------------------------
-- обработчик сцены
-- -----------------------------------------------------------------------------------
-- Создание сцены
function scene:create( event )
local sceneGroup = self.view
-- Выполняется при создании сцены, до ее загрузки
end
-- Отображение сцены
function scene:show( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
--Сцена выключена но скоро появится на экран
elseif ( phase == "did" ) then
-- Сцена полностью загружена на экран
end
end
-- Скрытие сцены
function scene:hide( event )
local sceneGroup = self.view
local phase = event.phase
if ( phase == "will" ) then
-- выполняется перед скрытием сцены
elseif ( phase == "did" ) then
-- выполняется после скрытия сцены
end
end
-- уничтожение сцены()
function scene:destroy( event )
local sceneGroup = self.view
-- код выполняется до удаления сцены
end
-- -----------------------------------------------------------------------------------
-- Создание обработчиком сцены
-- -----------------------------------------------------------------------------------
scene:addEventListener( "create", scene )
scene:addEventListener( "show", scene )
scene:addEventListener( "hide", scene )
scene:addEventListener( "destroy", scene )
-- -----------------------------------------------------------------------------------
return scene
Стоит отметить что файлы сцен не нужно каким-то образом подключать к проекту, так как их подключение будет производиться в рамках использования библиотеки composer.
Универсальная библиотека
Универсальная библиотека — префикс lib. В эти файлы нужно размещать готовые универсальные функции которые в дальнейшем можно будет применять в различных частях приложения, например тригонометрические функции ваших проектов можно разместить в файле libTriginom.lua а дальше эти функции будут пополняться и в следующих проектах их можно будет легко использовать просто переносом файла и подключением. Старайтесь избегать в универсальных библиотеках привязки к элементам графического дизайна приложения, а исходные значения передавайте параметрами вызова. Приведу пример простой библиотеки:
---------------------
-- ТРИГОНОМЕТРИЯ --
-- libTrigonom.lua --
---------------------
local M = {}
--длина гипотенузы при катетах a,b
M.hypotenuse = function(a,b)
--c = v(a?+b?)
local c = math.sqrt(a*a + b*b)
return c
end
--длина отрезка
M.line_len = function(x1,y1,x2,y2)
--length = v((X2-X1)?+(Y2-Y1)?)
local length = math.sqrt((x2 - x1)^2 + (y2 - y1)^2)
return length
end
--угол между двумя точками
M. get_angle = function(x1,y1,x2,y2)
return ((math.atan2(y1 - y2, x1 - x2) / (math.pi / 180)) + 270) % 360
end
return M
Эту библиотеку можно подключить поместив в main.lua следующий код:
Trigonom = require("libTrigonom")
Приведу пример использования функций в любой части проекта:
local a,b = 3,4--катеты
local c = Trigonom.hypotenuse(a,b) -- получаем гипотенузу
print(c)-->> 5
local x1,y1 = 34,-56 --точка A
local x2,y2 = -134,56 --точка B
local length = Trigonom.line_len(x1,y1,x2,y2) -- длина отрезка
print(length)-->> 201.91087142598
local angle = Trigonom.get_angle(x1,y1,x2,y2)
print(angle)-->> 236.30993247402
Виджет/сервис
Виджет/сервис — префикс svc. Виджет — аналог библиотеки, но основной функционал основан на создании графических компонентов. С помощью виджетов удобно создавать составной интерфейс, т.е. имеется основные статические компоненты сцены которые создаются в рамках основного кода в файлах sc и поверх статических компонентов могут создавать динамические виджеты в виде всплывающих окон, перемещаемых форм и прочих удобств. Удобней всего все виджеты проекта оформлять с единым интерфейсом(стандартным набором функций), например таким:
- show() — создает окно с функционалом виджета и помещает его в заданном месте экрана
- refresh() — обновляет состояние компонентов виджета делая его актуальным текущему моменту. Например если этот виджет является часами его необходимо обновлять раз в секунду или минуту (если нет секундной стрелки).
- hide() — скрывает или удаляет весь интерфейс виджета
Стоит заметить что нет ни какого риска в том что у вас в различных виджетах одинаковые названия методов (функций) — show/refresh/hide для доступа к функции необходимо добавление имени виджета (как и в случае с библиотеками) и по этой причине глобально пространство имен не будет засорено.
Приведу пример виджета (часы):
-------------------------
-- Виджет создает часы --
-------------------------
M = {
GR = {}, --массив групп
GUI = {},--массив хрянящий интерфейс
}
--получение текущей временой зоны
M.get_timezone = function()
return os.difftime(os.time(), os.time(os.date("!*t", os.time()))) / 3600
end
--обновление текущего состояния компонентов выбранного id
M.refresh = function(id)
local cur = M.GUI[id].capt
local tm = os.date("!*t", os.time() + 3600*cur.GTM)
--функция добавлет спереди 0 к числам меньше 10
local norn_dig = function(n)
return n < 10 and '0'..n or n
end
local sec = cur.is_sec and ":"..norn_dig(tm.sec) or ''
cur.text = norn_dig(tm.hour)..':' .. norn_dig(tm.min) .. sec
end
--Создание часов, возврат id созданных часов
--Параметры(все необязательные):
-- x - позиция центра по x (пр умолчанию центр экрана по x)
-- y - позиция центра по y (пр умолчанию центр экрана по y)
-- is_sec - отображать секунды (по умолчания да - true)
-- GTM - временой пояс, по умолчанию текущий
M.show = function(p)
--создаем значения п умолчанию
p = p and p or {}
p.x = p.x and p.x or _W * .5
p.y = p.y and p.y or _H * .5
if p.is_sec == nil then
p.is_sec = true
end
if p.GTM == nil then
p.GTM = M.get_timezone()
end
--созадем часы
local id = #M.GR + 1
M.GR[id] = display.newGroup()--группа экземпляра часов
M.GUI[id] = {}--место храние компонентов
--подкладка - скругленный прямоугольник
M.GUI[id].bg = display.newRoundedRect(M.GR[id], p.x, p.y, _W * .33, _H * .08, _H * .02)
M.GUI[id].bg.strokeWidth = _H * .005--ширина окаймления
M.GUI[id].bg:setStrokeColor(0,1,.5)--цвет окаймления
M.GUI[id].bg:setFillColor(1,.5,0)--цвет основания
--при клике по часам они уничтожаются
M.GUI[id].bg:addEventListener('touch',function(event)
if event.phase == 'began' then
M.hide(id)
end
end)
--текст часов
M.GUI[id].capt = display.newText(M.GR[id], '', p.x, p.y, nil, _H * .05)
M.GUI[id].capt:setFillColor(0)
M.GUI[id].capt.GTM = p.GTM
M.GUI[id].capt.is_sec = p.is_sec
M.refresh(id)
--таймер обновления экземпляра часов
timer.performWithDelay( 500, function(event)
if not M.GUI[id].capt.text then
timer.cancel( event.source )
else
M.refresh(id)
end
end, 0 )
end
--удаление часов с переданным id или всех если id не передан
M.hide = function(id)
if id then
display.remove(M.GR[id])
else
for i = 1,#M.GR do
display.remove(M.GR[i])
end
end
end
return M
порядок подключения и применения сервиса следующий:
_W = display.contentWidth
_H = display.contentHeight
Clock = require "svcClock"
Clock.show()
сервисы как и библиотеки стоит делать достаточно универсальными так как они то же кандидаты на повторное использование, большую часть параметров (в идеальном случае все) нужно делать не обязательными, т.е. имеющими значения по умолчанию.
Базы данных
Базы данных — префикс db. Эти файлы не содержат функций но хранят сведения о статических настройках объектов системы, например характеристики юнитов. Приведу пример базы данных с настройками юнитов:
return {
peasant = {
title = {ru = 'Крестьянин', en = 'Peasant'},
force = 1,
defence = 2,
speed = 1,
},
spearman = {
title = {ru = 'Копейщик', en = 'Spearman'},
force = 3,
defence = 2,
speed = 2,
},
archer = {
title = {ru = 'Лучник', en = 'Archer'},
force = 5,
defence = 1,
speed = 3,
},
knight = {
title = {ru = 'Рыцарь', en = 'Knight'},
force = 7,
defence = 3,
speed = 4,
},
}
стоит отметить что подобный тип реализации для более крупных баз бывает проще заменить на такую реализацию
local M = {
name = {'peasant','spearman','archer','knight'},
title = {
{ru = 'Крестьянин', en = 'Peasant'},
{ru = 'Копейщик', en = 'Spearman'},
{ru = 'Лучник', en = 'Archer'},
{ru = 'Рыцарь', en = 'Knight'},
},
stats = {
{force = 1, defence = 2, speed = 1,},
{force = 3, defence = 2, speed = 2,},
{force = 5, defence = 1, speed = 3,},
{force = 7, defence = 3, speed = 4,},
},
M.res = {},
}
for i = 1,#M.name do
M.res[M.name[i]] = {
title = M.title[i]
force = M.stats[i].force,
defence = M.stats[i].defence,
speed = M.stats[i].speed,
}
end
return M.res
Фактически это одно и то же просто в первом случае все инициализируется сразу, а во втором случае при подключении библиотеки выполняется цикл заполняющий точно такую же таблицу, второй способ более универсален так как позволяет во-первых в будущем более удобно редактировать эти данные, во-вторых если данных много позволяет значительно сократить запись. Подключать использовать такие базы данных необходимо следующим образом:
units = require('dbUnits')
print(units.archer.title.ru)--печатает русский заголовок одного из юнитов
Вставка кода
Вставка кода — префикс ins. Как бы мы не пытались сократить размер файлов не всегда это возможно, в этом случае можно некоторые объединенный общей целью участки вынести в отдельный файл. Приведу пример, имеется код:
--main.lua
local P = '123'
k = 23.2
local function sum(a,b)
return a+b
end
local function razn(a,b)
return a-b
end
local a,b = 2,3
print(sum(a,b))
print(razn(b,a))
теперь вынесем функции sum и razn в отдельный файл InsExample.lua имеем 2 файла:
-- InsExample.lua
function sum(a,b)
return a+b
end
function razn(a,b)
return a-b
end
--main.lua
local P = '123'
k = 23.2
require 'insExample'
local a,b = 2,3
print(sum(a,b))
print(razn(b,a))
Стоит обратить внимание, что при переносе в отдельный файл я сделал функции глобальными (убрал local), в противном случае они выйдут из области видимости main.lua. Так же нужно понимать что внутри файл insExample.lua не будет видна локальная переменная Р, но будет видна глобальная переменная k, а если объявить переменную k после require то и она не будет видна.
ВАЖНО: При любом варианте использования require для любых манипуляций с файлами этот вызов выполняется только один раз при первом проходе нити выполнения через этот участок кода, это нужно всегда учитывать если не хотите долго мучиться со сложными багами. Так же если вы будете как и я в модулях типа db/lib/svc использовать некую переменную M для сборки данных перед return (или любую другую) не забывайте делать эту переменную локальной, так как в противном случае каждый следующий require будет переписывать результат предыдущего и это будет приводить к еще более сложной ошибке.
?
Условные операторы
Как в большинстве других языков в Lua есть возможность создания операций условного ветвления кода, для этих целей используется несколько видов записи оператора if и достаточно удобный тернарный способ ветвления.
Операторы сравнения.
Для сравнения данных используются операции сравнения. Каждая такая операция состоит из двух переменных, между которым стоит оператор сравнения, всего их в Lua имеется 6:
- == равно
- ~= не равно
- < меньше
- > больше
- <= меньше или равно
- >= больше или равно
В сущности результатом любой операции сравнения является false или true (ложь или правда), эти значения характерны для типа ,boolean который наряду с другими типами будет рассмотрен ниже. Теперь рассмотрим с помощью каких конструкций языка применяются операции сравнения.
Оператор IF
Сразу к примеру — если a больше b, присваиваем cзначение true
if a > b then
c = true
end
Как видите порядок применения довольно простой. Но как обычно есть тонкости — содержимое блока if является операционным блоком т.е. областью ограничения локальной видимости, т.о. если переменную c сделать локальной она не будет видна после end, естественно выход есть если нам нужна локальная переменная c видна за блоком If ее нужно объявить перед if:
local c = false
f a > b then
c = true
end
Как я говорил выше имеется тернарный способ организации ветвления и описанный выше код можно легко записать этим способом
local c = a > b
Вот так просто:) Теперь пару слов о том как сделать более сложное условие в if. Имеются логические операторы and(и) и or(или). Они работает примерно так же как и в большинстве языков, хотя есть и свои особенности.
AND
Условия совмещенные операторами and должны выполниться все, условия выполняются слева направо если в процессе выполнения проверок не выполнилось одно условие, следующие проверки не будут проводиться. Это весьма важный момент и его нужно четко понимать, так как это можно использовать как в своих целях так и иметь из-за этой особенности массу сложных багов, приведу пример полезного использования. Имеется объект находящийся скажем на третей глубине вложения в другие объекты ну скажем такой obj1.obj2.obj3 мы хотим сделать проверку на его существование, и на первый взгяд можно сделать так:
if obj1.obj2.obj3 then
c = true
end
вам может показаться что это хорошая идея, но на самом деле это не так, допустим не существует объект obj1 или obj2 и в этом случае возникнет ошибка т.е. проверку нужно делать так:
if obj1 and obj1.obj2 and obj1.obj2.obj3 then
с = true
end
Теперь проверка будет выполнения безопасно и без ошибок. Достаточно похожий механизм и при реализации тернарным оператором
local c = obj1 and obj1.obj2 and obj1.obj2.obj3 and true
OR
Если выполнилось хотя бы одно условие разделенное оператором or дальнейшая проверка останавливается условие считается верным. Т.е. этот оператор работает по принципу схожему с and, но с другими требованиями, для and они — абсолютны, для or — минимальны. Приведу пример типичного сравнения с применением оператора or.
if a>b or c>b or k<0 then
c = true
end
полностью аналогична тернарная реализация:
local c = a>b or c>b or k<0
NOT
Логическая операция отрицания. Условие перед которым стоит not понимается наоборот, т.е. если a > b это понимается как a больше b, то not a > b понимается как a НЕ больше b.
IF ELSE
Часто возникает необходимость в следующем виде ветвления: если выполнилось условие делаем действие, в противном случае другое действие. Это реализуется оператором if else и реализация выглядит следующим образом:
if a>b then
c= true
else
c =false
end
Как видите ни чего сложного. Тернарная реализация этого примера будет иметь как оператор AND так и оператор OR:
local c = a>b and true or false
IF ELSEIF ELSE
Имеется еще один вариант условия: если выполнено условие1 делаем — действе1, если условие2 делаем — действеи2… если условиеN делаем — действиеN, в противном случае действиеX. Во многих языках этот тип ветвления реализован средствами специальных операторов множественного ветвления вроде switch, в Lua этого нет есть свой способ:
if a>b or d<b then
k = 1
elseif b>c and a<b then
k = 2
elseif c>d then
k = 3
else
k = 0
end
Как видите — все просто. Есть особенности в порядке выполнения составного оператора IF ELSEIF ELSE: во-первых else — не обязателен, во-вторых если произошло первое вхождение в условие, то дальнейшие проверки не выполняются, а происходит переход на строчку следующую за закрывающим end. Может показаться что досрочный выход из составного оператора не имеет значения и это не может вызвать ошибок, но это не так, приведу пример который показывает что неверное понимание этого принципа может дать неожидаемый результат:
k = 1
if k > 0 then
k = 2
print(k)
elseif k > 1 then
k = 3
print(k)
end
Результат выполнения этого действия будет только один print -> 2. В заключении покажу как реализовать тернарным оператором первый пример использования IF ELSEIF ELSE:
local k = (a>b or d<b) and 1 or (b>c and a<b) and 2 or c>d and 3 or 0
Обратите внимание на то что or имеет больший приоритет нежели and просто исходя из его функциональности, по это причине блоки с or были помещены в скобки, что сделало из них один оператор.
ВАЖНО: В этой главе я показал как использовать тернарное присвоение и скорее всего это показалось вам весьма заманчивым и простым способом организации кода, хочется сделать небольшие пояснения по поводу порядка использования этого механизма: во-первых — применяете тернарный оператор только там где это не сделать код менее читаемым, во-вторых на первых порах использования этого способа внимательно отлаживайте эти строки так как не всегда получается с первого раза написать верно.
Продолжение статьи (часть 2/3)
Комментарии (19)
shuhray
10.12.2017 02:09Когда привыкаешь к итераторам, других циклов уже писать не хочется. Прототипное наследование (метатаблицы) гораздо удобнее ООП. Ну и по мелочам хорошо, начиная с одновременного присваивания. Изумительная обработка регулярных выражений. Поскольку Lua замышлялся как небольшой встроенный язык, полную обработку регулярных выражений туда не вставили (места мало). Зато есть команда, позволяющая найти что-то по шаблону, применить к найденному произвольную функцию и результат подставить вместо. Сейчас Lua перерос исходный замысел, компилятор LuaJIT выдаёт программы, по скорости мало с чем сравнимые
blog.carlesmateo.com/2014/10/13/performance-of-several-languages
плюс позволяет использовать любые структуры данных C.
С яваскриптом сравнивать смешно, он написан за один вечер (как хвастался его создатель), а потом нарастал по принципу кучи компоста (хи-хи, за это мне снизили карму фанаты яваскрипта).
Deosis
11.12.2017 07:42Немного непоследовательное написание:
- Комментарии
- Переменные
- 15 minutes later
- компоновка сцены
- условные операторы.
shuhray
Lua — язык, в котором всё доведено до ума.
slonopotamus
Вы не могли бы слегка развернуть свою мысль? До чьего ума доведено, как вы измеряете степень доведённости до ума? Судя по тому что новые версии продолжают выходить, что-то там недоведено: www.lua.org/versions.html
Для меня Lua — это тот же JS. Строки, числа, key-value хранилище и указатели на функции. Всё, по большому счёту ничего больше в этих языках нет (да, я в курсе про всякие асинки, промисы и прочее. Это не основа языка, а так, второстепенные костыли которыми обмазали JS чтобы героически преодолевать его однопоточность)
Ну и соответственно почти все те претензии что у меня есть к JS так же применимы к Lua. Хотя бы даже идиотское правило что переменные по умолчанию попадают во внешнее пространство имён и надо писать var/local.
ZyXI
Ещё можно отметить то, что в таблицах нельзя хранить
nil
, зато отсутствующие в таблице ключи и неопределённые переменные имеют это значение. Иногда удобно, но слишком часто служит источником ошибок. Кроме того, языком полностью игнорируется расхождение в числе аргументов при объявлении и вызове функции — недостающие аргументы просто забиваются nil’ами (если только функция не с...
— тут вы всегда можете понять, использовали ли nil или просто не написали аргумент), «лишние» аргументы молча игнорируются.exe_com Автор
>>Ещё можно отметить то, что в таблицах нельзя хранить nil, зато отсутствующие в таблице ключи и неопределённые переменные имеют это значение
я постарался отметить. Это очень печальная сторона языка в отношении отладки, но я на стороне тех людей которые не боятся применять Lua в качестве основы своего пользовательского интерфейса, да неофитам придется пройти нелегкий путь что бы начать чувствовать язык кожей, но я против мнения что Lua это «обмазанный другим Г js» — это совершенно другой язык, да с проблемами любого языка с неявной типизацией, но к счастью без необходимости в тройных равенствах (===).
ZyXI
На JS я практически ничего не писал, поэтому сравнить не могу. Но второе негативное ощущение от lua — это стандартная библиотека, выглядящая как куча недоделок:
shellescape()
тоже нет.%{number}
или%b()
, но нет банального|
. И ещё регулярные выражения не компилируются, так что вы можете только надеяться на оптимизатор, либо на то, что компиляция не занимает долгое время (что, скорее всего, говорит о низкой скорости работы результата).pairs()
.Я не ожидаю Python’овского «batteries included», но мало что из того, что всё?таки есть, выполняет свои функции полностью.
Ещё, я совершенно не понимаю, почему нельзя писать
{}[key]
и"%d":format(num)
без дополнительной пары скобок. Полагаю, это потому, чтоfunc 'abc'.foo
работает как(func('abc')).foo
(с таблицей аналогично), но не понятно, зачем было делать выбор в пользу менее читаемого синтаксиса.exe_com Автор
IMHO из вышесказанного реальной проблемой можно считать только не достаточно широкий формат регулярок, можно было обойтись и вовсе без работы с файлами, каталогами, командной строкой и прочими нехорошими связями с ОС, то что это добавили в принципе сильно навредило языку, чистый скриптовый язык не должен иметь этих костылей. Как рассчитывалось применение lua в реальных проектах — есть серьезный и полновесный базовый движок системы например реализованный на С++, имеется связка со скриптами и предоставлен набор импортируемых функций которые уже могут включать в себя все полноценные атрибуты «красивой жизни» свойственные профессиональному языку для той или иной платформы. Есть у вас полноценная рекурсивная функция обхода всего дерева каталогов реализованная на C++ которая запускается из скрипта и возвращает в скрипт уже набор формализованный данный в виде таблицы со всем результатом поиска. А дальше с помощью скриптов происходит расширение функционала без изменения базовой реализации на C++. Такой был замысел, ну а если вы пытаетесь писать на lua что-то самостоятельно то приходится довольствоваться малым… Что касается синтаксического сахара, что тут сказать — на вкус и цвет… Существование хоть какой-то связи с ОС делает возможным создания плагина ну скажем для VLC с элементами трояна-шифровщика, что как бы плохо.
ivan386
Проверить таблицу на пустоту можно функцией next.
Размер массива можно узнать оператором #.
Пары ключь-значение только считать.
ZyXI
next()
— выглядит как более короткий хак, чем сpairs
. И не решает проблему если нужен именно размер, а не проверка на пустоту. Размер «массива» тут не в тему.PS: Размер таблицы?массива узнаётся за O(log(table_size)) (O(table_size), если размер таблицы больше MAX_INT/2 или таблица специальным образом построена) в lua. Не знаю, что в luajit (но тесты показывают, что чтобы там ни было, оно не даёт O(1)), но в lua-5.3.4 используется бинарный поиск; в лучшем случае бинарный поиск по массиву, в худшем — по хэшу. Они как?то ухитряются делать это быстро (скорее всего, мои тесты просто все прошли как бинарный поиск по массиву), но идея мне всё равно не нравится.
ivan386
Можно считать ключи во время записи значения через метатаблицы. И соответственно отдавать это количество.
shuhray
Они (бразильцы) из вредности на luajit не рассчитывают. Поставил IupLua (бразильский пакет для рисования окошек с кнопками) — не работает с LuaJIT. Так и этак — всё равно не работает. Пошёл на форум LuaJIT, там сказали: переименуйте lua51.dll в lua5.1.dll! Переименовал — заработало (правда, такая фигня, что немедленно удалил). Основной недостаток Lua — всё приходится делать самому, нет ничего готового. Хочешь рисовать — оберни OpenGL! Правда, обёртывать легко, опять же за счёт LuaJIT.
shuhray
Я писал диплом на функциональном языке (GARF) в 84-м году, затем изучал лямбда-исчисление, категории и монады как математик. Последнее время по необходимости стал программировать и из всех языков мне с большим отрывом нравится Lua. А я понимаю глубокую суть вещей! Вот песня про Lua на бразильском языке
youtu.be/NxzP1XPCGJE
Tarde uma nuvem rsea lenta e transparente.
Sobre o espao, sonhadora e bela!
Surge no infinito a Lua docemente,
Enfeitando a tarde, qual meiga donzela
Que se apresta e a linda sonhadoramente,
Em anseios d'alma para ficar bela
Grita ao cu e a terra toda a Natureza!
Cala a passarada aos seus tristes queixumes
E reflete o mar toda a Sua riqueza…
Suave a luz da Lua desperta agora
A cruel saudade que ri e chora!
Tarde uma nuvem rsea lenta e transparente
Sobre o espao, sonhadora e bela!
Что значит «светит Lua в темноте»
exe_com Автор
у меня есть свитер, с логотипом Lua, созданный по спецзаказу моей женой специально для меня и Луа мне то же светит в темноте)
shuhray
Итераторы и метатаблицы. А чего нет (например, массивов и структур), то теперь есть за счёт LuaJIT (компилятор, позволяющий в программе на Lua определять структуры данных на C)
TargetSan
Да, сравнение с JS напрашивается. Что нравится лично мне:
==
/===
. Заодно — отсутствие дуализмаnull
/undefined
.Мой личный вывод: в JS накрутили какой-то дикой, неочевидной логики, которая только усложняет жизнь. Особенно много веществ употребили, когда делали реализацию прототипного ООП.