Read this manual in English

Компонент Script

Компонент Script позволяет создавать игровую логику, используя язык программирования Lua. Скрипты добавляются к игровым объектам точно так же, как и любой другой компонент, при этом Defold будет выполнять код Lua как часть функций жизненного цикла движка.

Типы скриптов

В Defold существует три типа Lua-скриптов, для каждого из которых доступны различные библиотеки.

Скрипты логики
Расширение .script. Запускаются компонентами скриптов в игровых объектах. Как правило, используются для управления игровыми объектами и логикой, которая связывает игру с загрузкой уровней, правилами игры и так далее. Скрипты логики имеют доступ ко всем функциям библиотеки Defold, кроме функций GUI и Render.
GUI-скрипты
Расширение .gui_script. Запускаются компонентами GUI и обычно содержат логику, необходимую для отображения элементов GUI, таких как HUD, меню и т.д. GUI-скрипты имеют доступ к функциям библиотеки GUI.
Render-скрипты
Расширение .render_script. Запускаются конвейером рендеринга и содержит логику, необходимую для рендеринга графики приложения/игры в каждом кадре. Рендер-скрипты имеют доступ к функциям библиотеки.

Script execution, callbacks and self

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)
При отправке сообщений компоненту Script через 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)
Эта функция вызывается при перезагрузке скрипта с помощью горячей перезагрузки в редакторе (Edit ▸ Reload Resource). Очень удобно для отладки, тестирования и оптимизации. За подробностями обращайтесь к руководству по горячей перезагрузке.
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, чтобы вызвать список функций, соответствующих тому, что вы вводите.

Auto completion