Цель поста — в максимально простой форме описать основные этапы разработки с помощью фреймворка LOVE, на примере классической игры atari-автоматов Asteroids.
Уголок почемучки
Что такое LOVE и почему именно это?
LOVE — фреймворк для двухмерных игр. Он не является движком, только прослойкой между Lua и SDL2, с дополнительными приятными фишками, вроде чистоты синтаксиса, минимумом дополнительных телодвижений чтобы заставить работать OpenGL, и набором библиотек (вроде Box2d), позволяющих сразу сделать что-то забавное, и, не сходя с места, поковырять то что получилось. Но, притом, LOVE отличается минимумом отсебятины и низким уровнем взаимодействия с железом, что позволяет делать свой движок вокруг фреймворка (для самообучения/дальнейшего применения) или сразу хардкодить игрушку.
Простота фреймворка позволяет писать простые прототипы или даже мини-игры тем, кто не является программистом, концентрируясь на процессе программирования, а не на освоении конкретной движковой технологии. Моя практика показала, что обучаемые возраста 14-17 лет, с гораздо большим удовольствием занимаются разработкой простых игр, чем выполняют классические лабораторные работы по вычислению корней квадратных уравнений или подсчёта кредитных ставок, а некоторые ученики начинают самостоятельно углубляться в материал, после чего, порой, становятся неплохими программистами.
Почему Lua? Язык достаточно прост для освоения, проще чем JavaScript и Python, но с него достаточно просто переходить как на вышеуказанные, так и на низкоуровневые (С/С++). Так же он достаточно популярен в разработке видеоигр, как часть чего-то более крупного (cryEngine, GMod, OpenComputers в Minecraft, etc), и если в какой-то игре присутствует моддинг — с очень высокой вероятностью, он использует Lua.
Пусть не пугает бедность стандартной библиотеки, под большую часть задач существуют сторонние разработки (настолько популярные, чтобы стать практически стандартом языка), но в бедности есть и обратная сторона, такая как скорость освоения и возможность запихнуть интерпретатор языка в микроконтроллер, чем некоторые и пользуются, со всеми преимуществами и недостатками скриптов.
Плюс LOVE по умолчанию поставляется с виртуальной машиной LuaJIT, которая многократно ускоряет исполнение (критично для игр), и позволяет использовать FFI: подключение библиотек написанных на C, инициализация и использование C-структур, которые, с метатаблицами, можно превратить в lua-объекты, и которые экономят время создания/память и т.п.
Чуть ближе к делу
Для дальнейшей работы, нам потребуется выполнить следующий набор действий:
- Загружаем последнюю версию LOVE с официального сайта;
- Настраиваем запуск текущего проекта в LOVE, стандартный метод тестового запуска — открыть директорию с файлом main.lua в исполняемом файле love. Так же, можно паковать содержимое директории с файлом main.lua в zip-архив, и или перетаскивать на исполняемый файл, или переименовать .zip в .love и настроить ассоциации файлов. Я считаю что проще настроить шорткат для текущего редактора, у notepad++ это, например:
Примеры для sublime можно найти в соседней статье;<Command name=...>path/to/love.exe $(CURRENT_DIRECTORY)</Command>
- Создаём пустую директорию и добавляем в неё файл с именем main.lua. Желательно чтобы в пути не было пробелов и кириллицы, а то некоторые напихают пробелов, а потом жалуются, но для обхода можно чуть изменить шорткат или метод запуска;
- Открываем в любимом редакторе наш чистый и незапятнанный файл main.lua, и LOVE-Wiki в любимом браузере.
Ещё ближе, но не совсем
Первое что стоит узнать, это то, что фреймворк функционирует через набор колбеков, которые мы пишем в глобальную таблицу love, которая уже объявлена:
function love.load(arg)
-- Код в функции love.load будет вызван один раз,
-- как только проект будет запущен.
end
function love.update(dt)
-- Код функций update и draw будут запускаться каждый кадр,
-- чередуясь, в бесконечном цикле:
-- "посчитали->нарисовали->посчитали->нарисовали->"
-- пока не будет вызван выход из приложения.
end
function love.draw()
-- Все функции взаимодействия с модулями фреймворка -
-- аналогично прячутся внутри таблицы love.
love.graphics.print('Hello dear Love user!', 100, 100)
end
После запуска данного кода, вы должны ощутить просветление и приступить к следующему этапу: что-то, отдалённо напоминающее нечто полезное.
Уже что-то похожее на дело
У Lua, по умолчанию, отсутствует «нормальное ООП», поэтому в данном материале будет довольно сложная для начинающих конструкция отсюда, пункт 3.2, хотя если вы незнакомы с таблицами, стоит прочитать весь третий пункт.
Первым делом, так как мы делаем Asteroids, мы хотим получить кораблик, которым крайне желательно ещё и рулить.
Далее, мы хотим чем-то стрелять и цели, в которые можно попасть.
Аналогично, хотелось бы чтобы где-то вёлся подсчёт очков и манипулирование всем подряд.
Далее будет очень много кода, но надеюсь, комментарии будут достаточно содержательными.
-- Заранее инициализируем ссылки на имена классов, которые понадобятся,
-- ибо вышестоящие классы будут использовать часть нижестоящих.
local Ship, Bullet, Asteroid, Field
Ship = {}
-- У всех таблиц, метатаблицей которых является ship,
-- дополнительные методы будут искаться в таблице ship.
Ship.__index = Ship
-- Задаём общее поле для всех членов класса, для взаимодействия разных объектов
Ship.type = 'ship'
-- Двоеточие - хитрый способ передать таблицу первым скрытым аргументом 'self'.
function Ship:new(field, x, y)
-- Сюда, в качестве self, придёт таблица Ship.
-- Переопределяем self на новый объект, self как таблица Ship больше не понадобится.
self = setmetatable({}, self)
-- Мы будем передавать ссылку на игровой менеджер, чтобы командовать им.
self.field = field
-- Координаты:
self.x = x or 100 -- 100 - дефолт
self.y = y or 100
-- Текущий угол поворота:
self.angle = 0
-- И заполняем всё остальное:
-- Вектор движения:
self.vx = 0
self.vy = 0
-- Ускорение, пикс/сек:
self.acceleration = 200
-- Скорость поворота:
self.rotation = math.pi
-- Всякие таймеры стрельбы:
self.shoot_timer = 0
self.shoot_delay = 0.3
-- Радиус, для коллизии:
self.radius = 30
-- Список вершин полигона, для отрисовки нашего кораблика:
self.vertexes = {0, -30, 30, 30, 0, 20, -30, 30}
--[[
Получится что-то такое, только чуть ровнее:
/ / /_/\_\
]]
-- Возвращаем свежеиспечёный объект.
return self
end
function Ship:update(dt)
-- Декрементов нема, и инкрементов тоже, но это не очень страшно, правда?
-- dt - дельта времени, промежуток между предыдущим и текущим кадром.
self.shoot_timer = self.shoot_timer - dt
-- Управление:
-- "Если зажата кнопка и таймер истёк" - спавним новую пулю.
if love.keyboard.isDown('x') and self.shoot_timer < 0 then
self.field:spawn(Bullet:new(self.field, self.x, self.y, self.angle))
-- И сбрасываем таймер, потому что мы не хотим непрерывных струй из пуль,
-- хоть это и забавно.
self.shoot_timer = self.shoot_delay
end
if love.keyboard.isDown('left') then
-- За секунду, сумма всех dt - почти ровно 1,
-- соответственно, за секунду, кораблик повернётся на угол Pi,
-- полный оборот - две секунды, все углы в радианах.
self.angle = self.angle - self.rotation * dt
end
if love.keyboard.isDown('right') then
self.angle = self.angle + self.rotation * dt
end
if love.keyboard.isDown('up') then
-- Вычисляем вектор ускорения, который мы приобрели за текущий кадр.
local vx_dt = math.cos(self.angle) * self.acceleration * dt
local vy_dt = math.sin(self.angle) * self.acceleration * dt
-- Прибавляем к собственному вектору движения полученный.
self.vx = self.vx + vx_dt
self.vy = self.vy + vy_dt
end
-- Прибавляем к текущим координатам вектор движения за текущий кадр.
self.x = self.x + self.vx * dt
self.y = self.y + self.vy * dt
-- Пусть это и космос, но торможение в пространстве никто не отменял:
-- мы тормозим в классике, и тут должны.
-- Торможение получается прогрессивным -
-- чем быстрее двигаемся, тем быстрее тормозим.
self.vx = self.vx - self.vx * dt
self.vy = self.vy - self.vy * dt
--Тут уже проверки координат на превышение полномочий:
--как только центр кораблика вылез за пределы экрана,
--мы его тут же перебрасываем на другую сторону.
local screen_width, screen_height = love.graphics.getDimensions()
if self.x < 0 then
self.x = self.x + screen_width
end
if self.y < 0 then
self.y = self.y + screen_height
end
if self.x > screen_width then
self.x = self.x - screen_width
end
if self.y > screen_height then
self.y = self.y - screen_height
end
end
function Ship:draw()
-- Говорим графической системе,
-- что всё следующее мы будем рисовать белым цветом.
love.graphics.setColor(255,255,255)
-- Вот сейчас будет довольно сложно,
-- грубо говоря, это трансформации над графической системой.
-- Запоминаем текущее состояние графической системы.
love.graphics.push()
-- Переносим центр графической системы на координаты кораблика.
love.graphics.translate (self.x, self.y)
-- Поворачиваем графическую систему на нужный угол.
-- Прибавляем Pi/2 потому, что мы задавали вершины полигона
-- острым концом вверх а не вправо, соответственно, при отрисовке
-- нам нужно чуть довернуть угол чтобы скомпенсировать.
love.graphics.rotate (self.angle + math.pi/2)
-- Рендерим вершины полигона, line - контур, fill - заполненный полигон.
love.graphics.polygon('line', self.vertexes)
-- И, наконец, возвращаем топологию в исходное состояние
-- (перед love.graphics.push()).
love.graphics.pop()
-- Это было слегка сложно,
-- рисовать кружочки/прямоугольнички значительно проще:
-- там можно прямо указать координаты, и сразу получить результат
-- и так мы будем рисовать астероиды/пули.
-- Но на такой методике можно без проблем сделать игровую камеру.
-- За полной справкой лучше залезть в вики,
end
-- "Пушка! Они заряжают пушку! Зачем? А, они будут стрелять!"
-- Мы тоже хотим стрелять.
-- Для стрельбы, нам необходимы пули, которыми мы будем стрелять.
-- Всё почти то же самое что у кораблика:
Bullet = {}
Bullet.__index = Bullet
-- Это - общие параметры для всех членов класса,
-- пули летят с одинаковой скоростью и имеют один тип,
-- поэтому можем выделить это в класс:
Bullet.type = 'bullet'
Bullet.speed = 300
function Bullet:new(field, x, y, angle)
self = setmetatable({}, self)
-- Аналогично задаём параметры
self.field = field
self.x = x
self.y = y
self.radius = 3
-- время жизни
self.life_time = 5
-- Нам надо бы вычислить
-- вектор движения из угла поворота и скорости:
self.vx = math.cos(angle) * self.speed
self.vy = math.sin(angle) * self.speed
-- Так как у объекта self нет поля speed,
-- поиск параметра продолжится в таблице под полем
-- __index у метатаблицы
return self
end
function Bullet:update(dt)
-- Управляем временем жизни:
self.life_time = self.life_time - dt
if self.life_time < 0 then
-- У нас пока нет такого метода,
-- но это тоже неплохо.
self.field:destroy(self)
return
end
-- Те же векторы
self.x = self.x + self.vx * dt
self.y = self.y + self.vy * dt
-- Пулям тоже не стоит улетать за границы экрана
local screen_width, screen_height = love.graphics.getDimensions()
if self.x < 0 then
self.x = self.x + screen_width
end
if self.y < 0 then
self.y = self.y + screen_height
end
if self.x > screen_width then
self.x = self.x - screen_width
end
if self.y > screen_height then
self.y = self.y - screen_height
end
end
function Bullet:draw()
love.graphics.setColor(255,255,255)
-- Обещанная простая функция отрисовки.
-- Полигоны, увы, так просто вращать не получится
love.graphics.circle('fill', self.x, self.y, self.radius)
end
-- В кого стрелять? В мимопролетающие астероиды, конечно.
Asteroid = {}
Asteroid.__index = Asteroid
Asteroid.type = 'asteroid'
function Asteroid:new(field, x, y, size)
self = setmetatable({}, self)
-- Аналогично предыдущим классам.
-- Можно было было бы провернуть наследование,
-- но это может быть сложно для восприятия начинающих.
self.field = field
self.x = x
self.y = y
-- Размерность астероида будет варьироваться 1-N.
self.size = size or 3
-- Векторы движения будут - случайными и неизменными.
self.vx = math.random(-20, 20)
self.vy = math.random(-20, 20)
self.radius = size * 15 -- модификатор размера
-- Тут вводится параметр здоровья,
-- ибо астероид может принять несколько ударов
-- прежде чем сломаться. Чуть рандомизируем для интереса.
-- Чем жирнее астероид, тем потенциально жирнее он по ХП:
self.hp = size + math.random(2)
-- Пусть они будут ещё и разноцветными.
self.color = {math.random(255), math.random(255), math.random(255)}
return self
end
-- Тут сложный метод, поэтому выделяем его отдельно
function Asteroid:applyDamage(dmg)
-- если урон не указан - выставляем единицу
dmg = dmg or 1
self.hp = self.hp - 1
if self.hp < 0 then
-- Подсчёт очков - самое главное
self.field.score = self.field.score + self.size * 100
self.field:destroy(self)
if self.size > 1 then
-- Количество обломков слегка рандомизируем.
for i = 1, 1 + math.random(3) do
self.field:spawn(Asteroid:new(self.field, self.x, self.y, self.size - 1))
end
end
-- Если мы были уничтожены, вернём true, это удобно для некоторых случаев.
return true
end
end
-- Мы довольно часто будем применять эту функцию ниже
local function collide(x1, y1, r1, x2, y2, r2)
-- Измеряем расстояния между точками по Теореме Пифагора:
local distance = (x2 - x1) ^ 2 + (y2 - y1) ^ 2
-- Коль это расстояние оказалось меньше суммы радиусов - мы коснулись.
-- Возводим в квадрат чтобы сэкономить пару тактов на невычислении корней.
local rdist = (r1 + r2) ^ 2
return distance < rdist
end
function Asteroid:update(dt)
self.x = self.x + self.vx * dt
self.y = self.y + self.vy * dt
-- Астероиды у нас взаимодействуют и с пулями и с корабликом,
-- поэтому можно запихнуть обработку взаимодействия в класс астероидов:
for object in pairs(self.field:getObjects()) do
-- Вот за этим мы выставляли типы.
if object.type == 'bullet' then
if collide(self.x, self.y, self.radius, object.x, object.y, object.radius) then
self.field:destroy(object)
-- А за этим - возвращали true.
if self:applyDamage() then
-- если мы были уничтожены - прерываем дальнейшие действия
return
end
end
elseif object.type == 'ship' then
if collide(self.x, self.y, self.radius, object.x, object.y, object.radius) then
-- Показываем messagebox и завершаем работу.
-- Лучше выделить отдельно, но пока и так неплохо.
local head = 'You loose!'
local body = 'Score is: '..self.field.score..'\nRetry?'
local keys = {"Yea!", "Noo!"}
local key_pressed = love.window.showMessageBox(head, body, keys)
-- Была нажата вторая кнопка "Noo!":
if key_pressed == 2 then
love.event.quit()
end
self.field:init()
return
end
end
end
-- Границы экрана - закон, который не щадит никого!
local screen_width, screen_height = love.graphics.getDimensions()
if self.x < 0 then
self.x = self.x + screen_width
end
if self.y < 0 then
self.y = self.y + screen_height
end
if self.x > screen_width then
self.x = self.x - screen_width
end
if self.y > screen_height then
self.y = self.y - screen_height
end
end
function Asteroid:draw()
-- Указываем текущий цвет астероида:
love.graphics.setColor(self.color)
-- Полигоны, увы, так просто вращать не получится
love.graphics.circle('line', self.x, self.y, self.radius)
end
-- Наконец, пишем класс который соберёт всё воедино:
Field = {}
Field.type = 'Field'
-- Это будет синглтон, создавать много игровых менеджеров мы не собираемся,
-- поэтому тут даже __index не нужен, ибо не будет объектов,
-- которые ищут методы в этой таблице.
-- А вот инициализация/сброс параметров - очень даже пригодятся.
function Field:init()
self.score = 0
-- Таблица для всех объектов на поле
self.objects = {}
local ship = Ship:new(self, 100, 200)
print(ship)
self:spawn(ship)
end
function Field:spawn(object)
-- Это немного нестандартное применение словаря:
-- в качестве ключа и значения указывается сам объект.
self.objects[object] = object
end
function Field:destroy(object)
-- Зато просто удалять.
self.objects[object] = nil
end
function Field:getObjects()
return self.objects
end
function Field:update(dt)
-- Мы хотим создавать новые астероиды, когда все текущие сломаны.
-- Сюда можно добавлять любые игровые правила.
local asteroids_count = 0
for object in pairs(self.objects) do
-- Проверка на наличие метода
if object.update then
object:update(dt)
end
if object.type == 'asteroid' then
asteroids_count = asteroids_count + 1
end
end
if asteroids_count == 0 then
for i = 1, 3 do
-- Будем создавать новые на границах экрана
local y = math.random(love.graphics.getHeight())
self:spawn(Asteroid:new(self, 0, y, 3))
end
end
end
function Field:draw()
for object in pairs(self.objects) do
if object.draw then
object:draw()
end
end
love.graphics.print('\n Score: '..self.score)
end
-- Последние штрихи: добавляем наши классы и объекты в игровые циклы:
function love.load()
Field:init()
end
function love.update(dt)
Field:update(dt)
end
function love.draw()
Field:draw()
end
При попытке копипасты и первого запуска вышеуказанной простыни, мы можем получить что-то похожее на классический asteroids.
Смотрится неплохо, но можно сделать лучше:
1. Пространственная индексация, для ускорения обсчёта объектов;
2. Более качественная организация менеджера, с ключами-идентификаторами;
3. Всё таки, применить наследование в классах игровых объектов, наследовать их от «сферического в вакууме» (буквально) объекта, имеющего координаты и радиус, и т.п.
Реализация данных пунктов останется домашним заданием тем, кто всё таки решится раскопать простыню и чуть углубиться.
Да, данный материал написан для версии LOVE 0.10.2.
Для людей из будущего, которые застанут версии 0.11.X и старше: в данном исходном коде, необходимо поправить таблицу цветов, изменив значения с диапазона 0-255 на соответствующие пропорции 0-1, т.е. например:
-- Цвет вроде такого:
color = {0, 127, 255}
-- Преобразовать во что-то похожее на:
color = {0, 0.5, 1}
P. S.: Буду рад фидбеку и ответам на тему «будут ли иметь ценность статьи про создание маленьких игрушек и/или инструментов для данного фреймворка».
Комментарии (23)
sotnikdv
07.01.2018 01:28За без малого 20 лет в индустрии заметил странную штуку. Если в названии есть нелатинские символы, то у проекта меньше вероятности быть успешным.
То ли пользователи, коих большинство англоязычных, менее любят.
То ли сложнее обсуждать и поэтому выпадает из коммуникаций.
То ли использование таких символов коррелирует с какой то чертой создателя, которая мешает развитию и продвижению продукта.
То ли использование таких символов показывает что-то сугубо национальное и ставит психологический барьер для основной части коммьюнити.
В общем, название ИМХО увеличивает шансы, что фреймворк тихо умрет.16tomatotonns Автор
07.01.2018 01:44Фреймворк развивается, без одной недели, десять лет.
Тут дело не в названии или ещё чём-то, а в простоте и комфорте применения, с чем тут всё хорошо. Есть несколько игрушек в Steam/GooglePlay, крупных проектов немного, но это не очень страшно.
Фреймворк делался и назывался «по приколу», и этим он тоже цепляет, как зацепил меня, четыре года назад, когда я не задумывался чтобы вообще стать программистом. Названия пользовательских библиотек — тоже забавные (но не всегда цензурные, всё таки love-тематика), и это тоже вклад в развитие общества.
Цитируя Спольски:
В 2003 году Nullsoft выпустили новую версию плеера Winamp, сопроводив выпуск вот такими комментариями на веб-сайте:
- Клевый новый дизайн!
- Навороченные фичи!
- Большинство функций таки работает!
Я о последнем… «Большинство функций таки работает!» — это забавно. И все счастливы, довольны плеером, используют его, говорят всем своим знакомым и друзьям. И они полагают, что Winamp — это хороший продукт, потому что они таки взяли и написали на своем сайте: «Большинство функций таки работает». Круто, да?
…
Вот такие вещи и заставили нас влюбиться в Winamp.
А с тех пор, как корпоративные слизняки из AOL Time Warner прибрали эту штуку к рукам, весь юмор с сайта исчез. Вы теперь просто-таки явно можете представить их себе, этих сальери, убийственно подавляющих малейшие признаки творческого подхода, который может отпугнуть какую-нибудь немолодую даму из Минессоты. Ценой уничтожения всего того, что заставило людей реально полюбить этот продукт.
В данном случае, LOVE «утирает нос конкурентам» своим дружелюбием, взаимопомощью комьюнити, не в последнюю очередь — отличной документацией и возможностью повлиять на развитие забавного и жизнеспособного опенсурс-проекта с десятилетней историей.
Не стоит цепляться к мелочам, вроде «Нелатинских символов», они не играют роли в сравнении со всем остальным.
sova
07.01.2018 04:51то ли гуглить инфу про это в разы сложнее
16tomatotonns Автор
07.01.2018 06:27Как правило, можно заменить нелатинскую букву на схожую латинскую.
По LOVE, запросы составляются как «love2d spartial hash map», или похожим образом.
В обиходе, применяется название «love» или «love2d», это нормально.
yarric
07.01.2018 11:39Ну как сказать — на том же маке такие символы набираются практически без лишних телодвижений, как и на iOS с Android.
16tomatotonns Автор
07.01.2018 21:31Тут вопрос про лёгкость гуглинга, а не про простоту набора символов.
isnofreedom
07.01.2018 09:42API до смешного прост, почему love2d до сих пор не стал мейнстримом?
GuMondi
07.01.2018 10:08Потому что есть Unity и Unreal
isnofreedom
07.01.2018 10:10но они компилят громоздкие билды, а тут — аккуратненько и емко, особенно это критично для мобильных платформ.
16tomatotonns Автор
07.01.2018 21:42Для того чтобы написать что чуть более крупное на LOVE и не превратить это в кашу, необходим высокий уровень навыков программирования и проектирования приложений, а так же значительно больше усилий: API прост и позволяет вытворять сногсшибательные штуки, но все инструменты придется или брать у сообщества, или писать самостоятельно. Это все равно что делать адаптивные веб-сайты, с ajax'ом на каждый чих, без движков и библиотек: маленькие получаются сравнительно быстро и просто, но что-то крупное, при лимитах по времени, растягивается на всю твою жизнь (требуя высоких навыков проектирования), и или ты берешь библиотеки/фреймворки, или ты впихиваешь готовый движок.
Bookvarenko
10.01.2018 10:39Ну, не такие уж и громоздкие билды. Просто сами Unity и Unreal весят по несколько гигабайт. А Lua- интерпретатор с sdl2 компактный. Если вдруг захотелось странного, например закодить прототип на подручном телефоне, то Love или Instead весьма кстати будут.
jimmyjonezz
07.01.2018 20:03Как всегда жду подобные статьи, с более глубоким погружением в дебри LOVE.
16tomatotonns Автор
07.01.2018 21:29Вот тут я попробовал слегка погрузиться в ООП и способы хранения игровых объектов. В "похожих публикациях" есть более простые примеры, так что это — лёгкое нарастание сложности.
pangolin
09.01.2018 06:50Почему-то во всех этих статьях использут Саблайм/Блокнот/и тому подобные редакторы общего назначения, хотя есть замечательный ZeroBrane Studio с отладчиком и live-кодингом.
16tomatotonns Автор
09.01.2018 07:01Лично я пользуюсь notepad++ потому, что привык с самого начала.
В моём варианте, когда постоянно переключаешься между lua/python/C(малые модули)/json/xml и т.п. — проще пользоваться одним инструментом, у которого уже помнишь все шорткаты, всё настроено и уже написаны все необходимые аддоны.
Zerobrane — хорош и восхитителен, в своей узкой области — lua-программирование. Но где ты найдёшь lua-разработчиков, которые пишут только и исключительно на Lua?
Хоть я таких и знаю, сам таким был пару лет назад, но с тех пор многое изменилось, notepad++ и lua/LOVE просто оказались для меня первыми попавшимися ЯП/редактором/игровым фреймворком, за исключением DevC++ (мои самые первые попытки что-то писать), который я тогда не осилил. Утка? Ну что поделать, зато хорошо выучился.
Но вот к чему всё: редакторов много, люди выбирают под свой вкус или потребности.
Я даю описание того с чем работаю сам (жутко популярная фигня на венде, сложно найти машинку разработчика без NP++), и ссылку на крайне известную альтернативу.
Если хочешь инструкций для своей среды — можешь или загуглить, или отдельно спросить, это нормально.
roller
09.01.2018 15:37Love еще жив?
Когда я последний раз его пробовал он жутко тупил при трех-четырех одновременных касаниях экрана на ipad, после чего был сразу выкинут16tomatotonns Автор
10.01.2018 08:57Боюсь что ты использовал не love а что-то ещё (на ipad довольно сложно билдить, сомневаюсь что ради одной пробы кто-то будет этим морочиться), плюс, на моей памяти (всеобъемлющей), love2d никогда не тупил на тачскринах в т.ч. ipad. Или это было особо специфичное использование фреймворка, тут, как на сишке — за корректность и скорость исполнения отвечает программист.
KumoKairo
Глянул в очередной раз на сайт. Не понял до конца — можно на мобилки билдить или нет? Там есть какая-то Android version, но это вроде сам редактор.
И наверное общий вопрос: чем принципиально отличаются Lua движки типа Defold / Corona / Love? На что в первую очередь обратить внимание при выборе?
16tomatotonns Автор
Для тестирования, в google play есть порт, с яблоками слегка сложнее, но, вроде, тоже цепляет. С ПК всё гораздо проще, подробнее можно посмотреть тут.
В заключение, могу сказать что сравнение LOVE и Defold/Corona, аналогично сравнению Lua и Python: Python старается дать всё что можно, Lua даёт всё что необходимо.
k12th
LOVE for Android Wiki