image

Цель поста — в максимально простой форме описать основные этапы разработки с помощью фреймворка 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-объекты, и которые экономят время создания/память и т.п.

Чуть ближе к делу



Для дальнейшей работы, нам потребуется выполнить следующий набор действий:
  1. Загружаем последнюю версию LOVE с официального сайта;
  2. Настраиваем запуск текущего проекта в LOVE, стандартный метод тестового запуска — открыть директорию с файлом main.lua в исполняемом файле love. Так же, можно паковать содержимое директории с файлом main.lua в zip-архив, и или перетаскивать на исполняемый файл, или переименовать .zip в .love и настроить ассоциации файлов. Я считаю что проще настроить шорткат для текущего редактора, у notepad++ это, например:
    <Command name=...>path/to/love.exe $(CURRENT_DIRECTORY)</Command>
    Примеры для sublime можно найти в соседней статье;
  3. Создаём пустую директорию и добавляем в неё файл с именем main.lua. Желательно чтобы в пути не было пробелов и кириллицы, а то некоторые напихают пробелов, а потом жалуются, но для обхода можно чуть изменить шорткат или метод запуска;
  4. Открываем в любимом редакторе наш чистый и незапятнанный файл 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.

image

Смотрится неплохо, но можно сделать лучше:

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)


  1. KumoKairo
    06.01.2018 17:21

    Глянул в очередной раз на сайт. Не понял до конца — можно на мобилки билдить или нет? Там есть какая-то Android version, но это вроде сам редактор.

    И наверное общий вопрос: чем принципиально отличаются Lua движки типа Defold / Corona / Love? На что в первую очередь обратить внимание при выборе?


    1. 16tomatotonns Автор
      06.01.2018 17:56

      1. Билдить на мобилки — можно, хоть и с некоторыми телодвижениями. Модуль touch и фунции вроде vibrate из вики — ориентированы на мобильные телефоны.
        Для тестирования, в google play есть порт, с яблоками слегка сложнее, но, вроде, тоже цепляет. С ПК всё гораздо проще, подробнее можно посмотреть тут.
      2. Принципиальные различия — defold является движком, со всеми причитающимися: менеджеры сцен, сущности, коллизии (без box2d) и т.п. Он заставляет отвлекаться от программирования в сторону изучения самой движковой технологии. Corona SDK — тоже набор функций/библиотек, на начале своего развития, частично списывалась с LOVE, а потом пошла куча своих (сложных) ништяков. Это хорошо, если твоя задача — быстро-быстро делать игры, пока солнце ещё высоко и ты уже знаешь технологию, но не очень, если ты хочешь учиться/учить, или иметь почти полностью своё приложение без чьих-либо претензий на «использование чужого кода», хоть корона и бесплатна, код закрыт, и расширять/фиксить не всегда получится. Ну, я не использую потому, что нет LuaJIT, без этого — медленно, и я не могу делать игровые тайловые карты 100500х9000 пикселей битмапой, как с FFI.
        В заключение, могу сказать что сравнение LOVE и Defold/Corona, аналогично сравнению Lua и Python: Python старается дать всё что можно, Lua даёт всё что необходимо.



  1. sotnikdv
    07.01.2018 01:28

    За без малого 20 лет в индустрии заметил странную штуку. Если в названии есть нелатинские символы, то у проекта меньше вероятности быть успешным.

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

    В общем, название ИМХО увеличивает шансы, что фреймворк тихо умрет.


    1. 16tomatotonns Автор
      07.01.2018 01:44

      Фреймворк развивается, без одной недели, десять лет.
      Тут дело не в названии или ещё чём-то, а в простоте и комфорте применения, с чем тут всё хорошо. Есть несколько игрушек в Steam/GooglePlay, крупных проектов немного, но это не очень страшно.
      Фреймворк делался и назывался «по приколу», и этим он тоже цепляет, как зацепил меня, четыре года назад, когда я не задумывался чтобы вообще стать программистом. Названия пользовательских библиотек — тоже забавные (но не всегда цензурные, всё таки love-тематика), и это тоже вклад в развитие общества.
      Цитируя Спольски:

      В 2003 году Nullsoft выпустили новую версию плеера Winamp, сопроводив выпуск вот такими комментариями на веб-сайте:
      • Клевый новый дизайн!
      • Навороченные фичи!
      • Большинство функций таки работает!

      Я о последнем… «Большинство функций таки работает!» — это забавно. И все счастливы, довольны плеером, используют его, говорят всем своим знакомым и друзьям. И они полагают, что Winamp — это хороший продукт, потому что они таки взяли и написали на своем сайте: «Большинство функций таки работает». Круто, да?

      Вот такие вещи и заставили нас влюбиться в Winamp.

      А с тех пор, как корпоративные слизняки из AOL Time Warner прибрали эту штуку к рукам, весь юмор с сайта исчез. Вы теперь просто-таки явно можете представить их себе, этих сальери, убийственно подавляющих малейшие признаки творческого подхода, который может отпугнуть какую-нибудь немолодую даму из Минессоты. Ценой уничтожения всего того, что заставило людей реально полюбить этот продукт.


      В данном случае, LOVE «утирает нос конкурентам» своим дружелюбием, взаимопомощью комьюнити, не в последнюю очередь — отличной документацией и возможностью повлиять на развитие забавного и жизнеспособного опенсурс-проекта с десятилетней историей.

      Не стоит цепляться к мелочам, вроде «Нелатинских символов», они не играют роли в сравнении со всем остальным.


    1. sova
      07.01.2018 04:51

      то ли гуглить инфу про это в разы сложнее


      1. 16tomatotonns Автор
        07.01.2018 06:27

        Как правило, можно заменить нелатинскую букву на схожую латинскую.
        По LOVE, запросы составляются как «love2d spartial hash map», или похожим образом.
        В обиходе, применяется название «love» или «love2d», это нормально.


      1. yarric
        07.01.2018 11:39

        Ну как сказать — на том же маке такие символы набираются практически без лишних телодвижений, как и на iOS с Android.


        1. 16tomatotonns Автор
          07.01.2018 21:31

          Тут вопрос про лёгкость гуглинга, а не про простоту набора символов.


          1. yarric
            08.01.2018 10:26

            Гугл как раз выдаёт его сходу, благодаря этому символу.


  1. isnofreedom
    07.01.2018 09:42

    API до смешного прост, почему love2d до сих пор не стал мейнстримом?


    1. GuMondi
      07.01.2018 10:08

      Потому что есть Unity и Unreal


      1. isnofreedom
        07.01.2018 10:10

        но они компилят громоздкие билды, а тут — аккуратненько и емко, особенно это критично для мобильных платформ.


        1. 16tomatotonns Автор
          07.01.2018 21:42

          Для того чтобы написать что чуть более крупное на LOVE и не превратить это в кашу, необходим высокий уровень навыков программирования и проектирования приложений, а так же значительно больше усилий: API прост и позволяет вытворять сногсшибательные штуки, но все инструменты придется или брать у сообщества, или писать самостоятельно. Это все равно что делать адаптивные веб-сайты, с ajax'ом на каждый чих, без движков и библиотек: маленькие получаются сравнительно быстро и просто, но что-то крупное, при лимитах по времени, растягивается на всю твою жизнь (требуя высоких навыков проектирования), и или ты берешь библиотеки/фреймворки, или ты впихиваешь готовый движок.


        1. Bookvarenko
          10.01.2018 10:39

          Ну, не такие уж и громоздкие билды. Просто сами Unity и Unreal весят по несколько гигабайт. А Lua- интерпретатор с sdl2 компактный. Если вдруг захотелось странного, например закодить прототип на подручном телефоне, то Love или Instead весьма кстати будут.


  1. yarric
    07.01.2018 13:56

    А есть список игр, написанных на LOVE?



  1. jimmyjonezz
    07.01.2018 20:03

    Как всегда жду подобные статьи, с более глубоким погружением в дебри LOVE.


    1. 16tomatotonns Автор
      07.01.2018 21:29

      Вот тут я попробовал слегка погрузиться в ООП и способы хранения игровых объектов. В "похожих публикациях" есть более простые примеры, так что это — лёгкое нарастание сложности.


  1. pangolin
    09.01.2018 06:50

    Почему-то во всех этих статьях использут Саблайм/Блокнот/и тому подобные редакторы общего назначения, хотя есть замечательный ZeroBrane Studio с отладчиком и live-кодингом.


    1. 16tomatotonns Автор
      09.01.2018 07:01

      Лично я пользуюсь notepad++ потому, что привык с самого начала.
      В моём варианте, когда постоянно переключаешься между lua/python/C(малые модули)/json/xml и т.п. — проще пользоваться одним инструментом, у которого уже помнишь все шорткаты, всё настроено и уже написаны все необходимые аддоны.
      Zerobrane — хорош и восхитителен, в своей узкой области — lua-программирование. Но где ты найдёшь lua-разработчиков, которые пишут только и исключительно на Lua?
      Хоть я таких и знаю, сам таким был пару лет назад, но с тех пор многое изменилось, notepad++ и lua/LOVE просто оказались для меня первыми попавшимися ЯП/редактором/игровым фреймворком, за исключением DevC++ (мои самые первые попытки что-то писать), который я тогда не осилил. Утка? Ну что поделать, зато хорошо выучился.

      Но вот к чему всё: редакторов много, люди выбирают под свой вкус или потребности.
      Я даю описание того с чем работаю сам (жутко популярная фигня на венде, сложно найти машинку разработчика без NP++), и ссылку на крайне известную альтернативу.
      Если хочешь инструкций для своей среды — можешь или загуглить, или отдельно спросить, это нормально.


  1. roller
    09.01.2018 15:37

    Love еще жив?
    Когда я последний раз его пробовал он жутко тупил при трех-четырех одновременных касаниях экрана на ipad, после чего был сразу выкинут


    1. 16tomatotonns Автор
      10.01.2018 08:57

      Боюсь что ты использовал не love а что-то ещё (на ipad довольно сложно билдить, сомневаюсь что ради одной пробы кто-то будет этим морочиться), плюс, на моей памяти (всеобъемлющей), love2d никогда не тупил на тачскринах в т.ч. ipad. Или это было особо специфичное использование фреймворка, тут, как на сишке — за корректность и скорость исполнения отвечает программист.