Компонент Script позволяет создавать игровую логику, используя язык программирования Lua. Скрипты добавляются к игровым объектам точно так же, как и любой другой компонент, при этом Defold будет выполнять код Lua как часть функций жизненного цикла движка.
В Defold существует три типа Lua-скриптов, для каждого из которых доступны различные библиотеки.
Defold выполняет Lua-скрипты как часть жизненного цикла движка и раскрывает этот жизненный цикл через набор предопределенных функций обратного вызова. При добавлении компонента скрипта к игровому объекту скрипт становится частью жизненного цикла игрового объекта и его компонента(ов). Скрипт оценивается в Lua-контексте при его загрузке, затем движок выполняет следующие функции и передает ссылку на текущий экземпляр компонента скрипта в качестве параметра. Эту ссылку self
можно использовать для хранения состояния в экземпляре компонента.
self
— это объект типа userdata, который действует как Lua-таблица, однако его нельзя итерировать с помощью pairs()
или ipairs()
и нельзя распечатать с помощью pprint()
.
init(self)
function init(self)
-- Эти переменные доступны в течение всего времени существования экземпляра компонента
self.my_var = "something"
self.age = 0
end
final(self)
function final(self)
if self.my_var == "something" then
-- провести чистку
end
end
update(self, dt)
dt
содержит дельту времени с момента последнего кадра.
function update(self, dt)
self.age = self.age + dt -- increase age with the timestep
end
on_message(self, message_id, message, sender)
msg.post()
движок вызывает эту функцию компонента-приемника. За подробностями обращайтесь к руководству по передаче сообщений.
function on_message(self, message_id, message, sender)
if message_id == hash("increase_score") then
self.total_score = self.total_score + message.score
end
end
on_input(self, action_id, action)
acquire_input_focus
), движок вызывает эту функцию когда ввод зарегистрирован. За подробностями обращайтесь к руководству по вводу.
function on_input(self, action_id, action)
if action_id == hash("touch") and action.pressed then
print("Touch", action.x, action.y)
end
end
on_reload(self)
function on_reload(self)
print(self.age) -- вывести возраст данного игрового объекта
end
Игровой объект с компонентом Script реализует некоторую логику. Часто эта логика зависит от какого-либо внешнего фактора. Вражеский AI может реагировать на то, что игрок находится в определенном радиусе от него; дверь может отомкнуться и открыться в результате взаимодействия с игроком и т.д. и т.п.
Функция update()
позволяет реализовать комплексное поведение, определяемое как механизм состояний, запускаемый каждый кадр — иногда это вполне адекватный подход. Однако каждый вызов update()
сопряжен с определенными затратами. Если функция в действительности не нужна, ее следует удалить и вместо этого попытаться построить логику реактивно. Выгоднее пассивно ждать какого-либо сообщения с последующей реакцией, чем активно исследовать игровой мир в поисках данных для ответа. Более того, проектирование реактивным способом также часто приводит к более чистому и надежному дизайну и реализации.
Давайте рассмотрим конкретный пример. Предположим, вы хотите, чтобы скрипт отправил сообщение через 2 секунды после того, как он был инициирован. Затем он должен дождаться определенного ответного сообщения и после получения ответа отправить еще одно сообщение через 5 секунд. Нереактивный код для этого будет выглядеть примерно так:
function init(self)
-- Счетчик для учета времени.
self.counter = 0
-- Это необходимо, чтобы следить за состоянием.
self.state = "first"
end
function update(self, dt)
self.counter = self.counter + dt
if self.counter >= 2.0 and self.state == "first" then
-- отправить сообщение через 2 секунды
msg.post("some_object", "some_message")
self.state = "waiting"
end
if self.counter >= 5.0 and self.state == "second" then
-- отправить сообщение через 5 секунд после получения "ответа"
msg.post("another_object", "another_message")
-- Nil the state so we don’t reach this state block again.
self.state = nil
end
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- “first” state done. enter next
self.state = "second"
-- zero the counter
self.counter = 0
end
end
Даже в этом довольно простом случае мы получаем довольно запутанную логику. Это можно улучшить с помощью горутин в модуле (см. ниже), но давайте вместо этого попробуем сделать это реактивным и использовать встроенный механизм синхронизации.
local function send_first()
msg.post("some_object", "some_message")
end
function init(self)
-- Подождать 2 секунды, затем вызвать send_first()
timer.delay(2, false, send_first)
end
local function send_second()
msg.post("another_object", "another_message")
end
function on_message(self, message_id, message, sender)
if message_id == hash("response") then
-- Подождать 5 секунды, затем вызвать send_second()
timer.delay(5, false, send_second)
end
end
Так чище и проще для понимания. Мы избавляемся от внутренних переменных состояния, которые часто трудно проследить через логику, и которые могут привести к трудноуловимым ошибкам. Мы также полностью избавляемся от функции update()
. Это освобождает движок от необходимости вызывать скрипт 60 раз в секунду, даже если он простаивает.
Редактор Defold поддерживает редактирование Lua-скриптов с подсветкой синтаксиса и автозаполнением. Чтобы заполнить имена функций Defold, нажмите Ctrl+Space, чтобы вызвать список функций, соответствующих тому, что вы вводите.
Did you spot an error or do you have a suggestion? Please let us know on GitHub!
GITHUB