title: Компонент Script
Компонент Script позволяет создавать игровую логику, используя язык программирования Lua. Скрипты добавляются к игровым объектам точно так же, как и любой другой компонент, при этом Defold будет выполнять код Lua как часть функций жизненного цикла движка.
В Defold существует три типа Lua-скриптов, для каждого из которых доступны различные библиотеки.
Скрипты логики : Расширение .script. Запускаются компонентами скриптов в игровых объектах. Как правило, используются для управления игровыми объектами и логикой, которая связывает игру с загрузкой уровней, правилами игры и так далее. Скрипты логики имеют доступ ко всем функциям библиотеки Defold, кроме функций GUI и Render.
GUI-скрипты : Расширение _.guiscript. Запускаются компонентами GUI и обычно содержат логику, необходимую для отображения элементов GUI, таких как HUD, меню и т.д. GUI-скрипты имеют доступ к функциям библиотеки GUI.
Render-скрипты : Расширение _.renderscript. Запускаются конвейером рендеринга и содержит логику, необходимую для рендеринга графики приложения/игры в каждом кадре. Рендер-скрипты имеют доступ к функциям библиотеки.
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
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 раз в секунду, даже если он простаивает.
Редактор Defold поддерживает редактирование Lua-скриптов с подсветкой синтаксиса и автозаполнением. Чтобы заполнить имена функций Defold, нажмите Ctrl+Space, чтобы вызвать список функций, соответствующих тому, что вы вводите.
{srcset="images/script/[email protected] 2x"}