Разбор проектов на Defold 8. Фабрики и свойства

Всем привет :waving_hand:
Продолжаем серию разборов чужого кода с этой страницы: Публичный пример Defold .

Сегодня разбираем пример от britzlФабрики и свойства.
Попробуйте этот проект в действии: запустите пример .
Исходная папка с проектом: ссылка на github .
Исходная папка с публичными примера Defold на github: ссылка на github.

Обращение к новичкам:

Я предполагаю, что у вас уже установлен Defold, если нет, перейдите по этой ссылке: Добро пожаловать в Defold.
Также, будет плюсом, если вы хотя бы поверхностно знакомы со строительными блоками Defold. Если нет, ознакомиться с основными концепциями Defold можно на официальном сайте — перейдите по этой ссылке.6.

Пример того, как скачать и открыть готовый проект:

Скачиваем архив с примерами проектов из github
Распаковываем скачанный ZIP архив примеров в любую папку во вашему усмотрению.
Переходим в папку examples.
Ищем проект play_animation.
Открываем game.project.

Внимание: скриншоты представлены ниже, это пример скачивания и открытия проекта, в этом примере мы рассматриваем пPreformatted textроект с названием play_animation, потому название папок проекта будет отличаться.


Скриншот 3. Открываем разархивированную папку(наименование может отличаться от вашего названия скаченной папки)
Скриншот 4. Открываем папу с примерами.

Этот пример демонстрирует эффективный способ передачи параметров, включая строки, при создании объектов через фабрики в Defold.

Файловая структура проекта:

factory_and_properties.collection

factory_and_properties.collection содержит в себе только 2 игровых объекта: bg и go.
bg служит в качество игрового объекта под фон.
go хранит в себе фабрику и скрипт, с помощью этих двух компонентов будут создаваться игровые объекты и выполнять логику примера.

factory_and_properties.atlas

factory_and_properties.atlas содержит в себе одно изображение и одну анимацию.
Этот “хранилище изображений” нужно для того, чтобы мы могли использовать изображения в игре.

thing_with_properties.go

thing_with_properties.go — этот файл является прототипом игрового персонажа. Фабрика будет ссылаться на этот шаблон создания игрового объекта.

Рассмотрим lookup.lua:

lookup.lua — Lua-модуль, реализующий таблицу соответствия (lookup table). Он позволяет сохранять, получать и удалять значения по ключам. Defold не позволяет передавать строки через go.property, но с помощью таблицы соответствия можно обойти это ограничение:

  • В момент создания объекта мы сохраняем строку по хэшу.
  • После создания строки объект знает только хэш, но может восстановить строку через lookup.get(...).
local M = {}

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

local values = {}

Создаётся внутренняя таблица values, где:

Возьмём в качестве примера пару-строк: hash("player1") и "Player One"

  • ключи — это значения, передаваемые как key (в нашем случае: hash("player1"))
  • значения — это реальные строки или другие данные, которые ты хочешь сохранить (в нашем случае: "Player One")

Эта таблица не доступна снаружи, только внутри модуля.

function M.add(key, value)
	assert(key, "You must provide a key")
	assert(value, "You must provide a value")
	values[key] = value
end
  • Добавляет или обновляет запись в values
  • assert проверяет, что ключ и значение действительно переданы (иначе будет ошибка)
    Как пример:M.add(hash("player1"), "Player One"). Добавили хэш-строку hash("player1") и соответствующую ей строку "Player One".
function M.remove(key)
	assert(key, "You must provide a key")
	values[key] = nil
end
  • Удаляет значение по ключу
    Например: M.remove(hash("player1")). Ни хэш-строки по ключу “player”, ни строки соответствующей этому ключу не будет.
function M.get(key)
	assert(key, "You must provide a key")
	return values[key]
end
  • Возвращает значение по ключу
    Например:
local name = M.get(hash("player1"))
print(name) --> "Player One"

Модуль возвращает таблицу M, чтобы другие скрипты могли его использовать:

return M
Рассмотрим thing_with_properties.script:
-- properties can be of type number|hash|url|vector3|vector4|quaternion
go.property("move_to", vmath.vector3())
go.property("move_speed", 2)
go.property("cool", false)
go.property("key", hash(""))
go.property("url", msg.url())
go.property("color", vmath.vector4(1, 1, 1, 1))
go.property("rotation", vmath.quat())

Здесь объявляются пользовательские свойства, которые можно передать через factory.create(...).

Поддерживаемые типы:

  • number, hash, url, vector3, vector4, quaternion

Примеры:

  • move_to: целевая позиция для движения (в этом коде не используется, но подготовлено)
  • move_speed: скорость движения
  • cool: флаг (передаётся случайно)
  • key: хэш от строки имени (используется для получения строки через lookup)
  • url: ссылка на объект-контроллер, чтобы отправить ему сообщение
  • color: цвет объекта
  • rotation: финальный поворот объекта
local lookup = require "factory_and_properties.lookup"

Подключаем модуль с таблицей соответствия, разобранный ранее.

local width = tonumber(sys.get_config("display.width"))
local height = tonumber(sys.get_config("display.height"))

Получаем ширину и высоту экрана игры.

При инициализации:

print("My name is " .. lookup.get(self.key))
print(self.cool and "I'm cool" or "I'm not cool")
  • Выводит строковое имя через lookup.get(self.key)
  • Показывает “I’m cool” или “I’m not cool” в зависимости от флага cool
go.animate("#sprite", "tint", go.PLAYBACK_LOOP_PINGPONG, self.color, ..., 2)
  • Анимирует цвет спрайта до указанного (self.color), и делает это пинг-понгом (туда-обратно)
go.animate(".", "rotation", ..., self.rotation, ..., 2)
  • Анимирует вращение объекта до кватерниона self.rotation
msg.post(self.url, "thing_created")
  • Отправляет сообщение обратно создателю, например, чтобы сообщить: “Я создан!”
self.direction = vmath.vector3(math.random(-1,1), math.random(-1,1), 0)
  • Задаёт случайное направление движения

Каждый кадр:

local distance = (self.direction * self.move_speed * dt)
local pos = go.get_position() + distance
  • Вычисляется, куда сдвинуть объект с учётом времени и скорости
if pos.x < 0 or pos.x > width then
	self.direction.x = -self.direction.x
end
if pos.y < 0 or pos.y > height then
	self.direction.y = -self.direction.y
end
  • Если объект выходит за пределы экрана — отражается от стенки
go.set_position(pos)
  • Перемещает объект
Рассмотрим factory.script:

Этот скрипт — контроллер, который:

  1. создаёт 10 объектов через фабрику,
  2. задаёт им разные параметры (скорость, цвет, поворот, и т.п.),
  3. при нажатии клавиш Up и Down увеличивает или уменьшает скорость движения всех этих объектов.
lookup = require "factory_and_properties.lookup"

Подключает модуль с таблицей соответствия (lookup.lua), чтобы потом использовать строковые имена через хэши.

local function modify_speed_of_things(things, delta)
	for _, id in pairs(things) do
		local url = msg.url(nil, id, "script")
		local move_speed = go.get(url, "move_speed")
		go.set(url, "move_speed", move_speed + delta)
	end
end
  • Получает список объектов (things) (Он создаётся в init())
  • У каждого получает текущую move_speed и изменяет её на +delta или -delta (получаем дельту из on_input
  • Используется при нажатии клавиш (смотри on_input).

Важно: go.get и go.set работают по URL, указывающему на компонент скрипта.

local url = msg.url(nil, id, "script")

Создаёт адрес игрового объекта, по которому можно отправить сообщение или получить данные.

local move_speed = go.get(url, "move_speed")

Получаем скорость игрового объекта, используя полученный раннее адрес.

go.set(url, "move_speed", move_speed + delta)

Изменяем свойство-скорость игровому объекту, используя всё тот же адрес.

local function random_position()
	return vmath.vector3(
		math.random(1, sys.get_config("display.width")),
		math.random(1, sys.get_config("display.height")),
		0)
end

Возвращает случайную позицию на экране (с учётом размеров экрана).

Во время инициализации игрового объекта:

  • Инициализирует список объектов: self.things = {}
  • Создаёт 10 объектов через factory.create
  • Каждому объекту передаёт свойства:
    • move_to: куда двигаться
    • move_speed: начальная скорость
    • cool: случайный флаг true/false
    • key: хэш от строки имени, добавляется в lookup
    • color, rotation: внешний вид
    • url: URL этого управляющего объекта (может быть нужен для сообщений)

Затем:

  • Добавляет каждый объект в self.things, чтобы позже менять им свойства
  • Активирует фокус ввода: acquire_input_focus()
self.things = {}

Создаётся пустой список things, куда будут сохраняться id сгенерированных объектов.

math.randomseed(os.time())

Это нужно, чтобы значения math.random() были действительно случайными каждый запуск.

for i = 1, 10 do

Внутри цикла происходит создание каждого отдельного объекта:

local name = “Foo” .. i
local key = hash(name)
Создание уникального имени и хеша. Например, “Foo1”, “Foo2” и т.д. Затем создаётся хеш этой строки — он нужен как идентификатор.

lookup.add(key, name)

Добавляем в наш список пару: “хеш/строка”.

local id = factory.create("#factory", random_position(), nil, {
	move_to = random_position(),
	move_speed = math.random(100, 300),
	cool = math.random(1,2) == 1 and true or false,
	key = key,
	color = vmath.vector4(...),
	rotation = vmath.quat_rotation_z(...),
	url = msg.url()
})

"#factory" — это ссылка на компонент фабрики в текущем объекте.
random_position() — задаёт случайную позицию.
move_to — позиция, куда объект будет двигаться.
move_speed — скорость движения.
cool — случайный булев флаг.
key — уникальный идентификатор.
color — случайный цвет.
rotation — случайный поворот.
url — URL текущего объекта (используется для связи с ним).
Эти свойства передаются новому объекту, и он может использовать их в своём init() через go.property().


function final(self)
	msg.post(".", "release_input_focus")
end

Отменяет фокус ввода при удалении игрового объекта.

msg.post("@render:", "draw_text", { text = "Up/Down to modify move_speed", position = vmath.vector3(20, 20, 0) })

Каждый кадр рисует текст на экране.

function on_message(self, message_id, message, sender)
	print(message_id, sender)
end

Печатает входящие сообщения в консоль, не используется для логики.

if action_id == hash("up") and action.released then
	modify_speed_of_things(self.things, 100)
elseif action_id == hash("down") and action.released then
	modify_speed_of_things(self.things, -100)
end
  • При отпускании клавиш Up и Down увеличивает или уменьшает скорость всех объектов на ±100.
  • Использует ранее определённую функцию modify_speed_of_things.

Надеюсь, кому-нибудь этот материал будет полезен.
Всем спасибо за внимание :light_blue_heart:

Другие разборы:
  1. Разбор проектов на Defold 1.Tilemap Collisions [tilemap]
  2. Разбор проектов на Defold 2. Параллакс [parallax]
  3. Разбор проектов на Defold 3. Движение игровых объектов [animation, movement, input]
  4. Разбор проектов на Defold 4. Воспроизвести анимацию [animation, movement, input]
  5. Разбор проектов на Defold 5. Меню и игра. Прокси-коллекции [proxy-collection, gameloop, collection, gui]
  6. Разбор проектов на Defold 6. Пауза [пауза]
  7. Разбор проектов на Defold 7. Простая кнопка [простая кнопка]
3 Likes

так и хочется сохранить себе в obsidian. Репозитория не будет да? в будущем.

Если действительно нужен перенос на github, то в скором времени это сделаю.