title: Компонент Script
Компонент Script позволяет создавать игровую логику, используя язык программирования Lua.
В Defold существует три типа Lua-скриптов, и для каждого из них доступны разные библиотеки Defold.
Скрипты игровых объектов : Расширение .script. Эти скрипты добавляются к игровым объектам точно так же, как и любой другой компонент, и Defold будет выполнять Lua-код как часть функций жизненного цикла движка. Скрипты игровых объектов обычно используются для управления игровыми объектами и логикой, объединяющей игру воедино: загрузкой уровней, игровыми правилами и так далее. Скрипты имеют доступ к функциям GO и ко всем библиотечным функциям Defold, за исключением GUI и Render.
Скрипты GUI : Расширение _.guiscript. Выполняются компонентами GUI и обычно содержат логику, необходимую для отображения элементов GUI, таких как интерфейс, меню и т.п. Defold выполняет Lua-код как часть функций жизненного цикла движка. Скрипты GUI имеют доступ к функциям GUI и ко всем библиотечным функциям Defold, за исключением GO и Render.
Скрипты рендера : Расширение _.renderscript. Выполняются в рамках пайплайна рендеринга и содержат логику, необходимую для отрисовки всей графики приложения/игры в каждом кадре. Скрипт рендера занимает особое место в жизненном цикле вашей игры. Подробнее см. в документации по жизненному циклу приложения. Скрипты рендера имеют доступ к функциям Render и ко всем библиотечным функциям Defold, за исключением GO и GUI.
Defold выполняет Lua-скрипты как часть жизненного цикла движка и раскрывает этот жизненный цикл через набор предопределенных функций обратного вызова. При добавлении компонента скрипта к игровому объекту скрипт становится частью жизненного цикла игрового объекта и его компонента(ов). Скрипт оценивается в Lua-контексте при его загрузке, затем движок выполняет следующие функции и передает ссылку на текущий экземпляр компонента скрипта в качестве параметра. Эту ссылку self
можно использовать для хранения состояния в экземпляре компонента.
::: important
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
fixed_update(self, dt)
: Обновление, не зависящее от частоты кадров. dt
содержит дельту времени с момента последнего обновления. Эта функция вызывается, когда включен параметр engine.fixed_update_frequency
(!= 0). Полезна, когда вы хотите управлять физическими объектами с регулярными интервалами для достижения стабильной симуляции физики при включенном параметре physics.use_fixed_timestep
в game.project.
function fixed_update(self, dt)
msg.post("#co", "apply_force", {force = vmath.vector3(1, 0, 0), position = go.get_world_position()})
end
on_message(self, message_id, message, sender)
: При отправке сообщений компоненту Script через msg.post()
движок вызывает эту функцию компонента-приемника. За подробностями обращайтесь к руководству по передаче сообщений.
```lua
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
), движок вызывает эту функцию когда ввод зарегистрирован. За подробностями обращайтесь к руководству по вводу.
```lua
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 раз в секунду, даже если он простаивает.
Можно использовать Lua-препроцессор и специальную разметку для условного включения кода в зависимости от варианта сборки. Пример:
-- Используйте одно из следующих ключевых слов: RELEASE, DEBUG или HEADLESS
--#IF DEBUG
local lives_num = 999
--#ELSE
local lives_num = 3
--#ENDIF
Препроцессор доступен как расширение сборки. Подробнее о его установке и использовании см. на странице расширения на GitHub.
Редактор Defold поддерживает редактирование Lua-скриптов с подсветкой синтаксиса и автодополнением. Чтобы автоматически подставить имена функций Defold, нажмите Ctrl+Space — появится список функций, соответствующих набираемому тексту.