This translation is community contributed and may not be up to date. We only maintain the English version of the documentation. 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

OSZAR »