--- title: Компонент Script brief: В этом руководстве описано, как добавить игровую логику с помощью компонента Script. --- # Компонент Script Компонент Script позволяет создавать игровую логику, используя [язык программирования Lua](/manuals/lua). Скрипты добавляются к игровым объектам точно так же, как и любой другой [компонент](/manuals/components), при этом Defold будет выполнять код Lua как часть функций жизненного цикла движка. ## Типы скриптов В Defold существует три типа Lua-скриптов, для каждого из которых доступны различные библиотеки. Скрипты логики : Расширение _.script_. Запускаются компонентами скриптов в игровых объектах. Как правило, используются для управления игровыми объектами и логикой, которая связывает игру с загрузкой уровней, правилами игры и так далее. Скрипты логики имеют доступ ко всем функциям библиотеки Defold, кроме функций [GUI](/ref/gui) и [Render](/ref/render). GUI-скрипты : Расширение _.gui_script_. Запускаются компонентами GUI и обычно содержат логику, необходимую для отображения элементов GUI, таких как HUD, меню и т.д. GUI-скрипты имеют доступ к функциям библиотеки [GUI](/ref/gui). Render-скрипты : Расширение _.render_script_. Запускаются конвейером рендеринга и содержит логику, необходимую для рендеринга графики приложения/игры в каждом кадре. Рендер-скрипты имеют доступ к функциям библиотеки. ## Script execution, callbacks and self Defold выполняет Lua-скрипты как часть жизненного цикла движка и раскрывает этот жизненный цикл через набор предопределенных функций обратного вызова. При добавлении компонента скрипта к игровому объекту скрипт становится частью жизненного цикла игрового объекта и его компонента(ов). Скрипт оценивается в Lua-контексте при его загрузке, затем движок выполняет следующие функции и передает ссылку на текущий экземпляр компонента скрипта в качестве параметра. Эту ссылку `self` можно использовать для хранения состояния в экземпляре компонента. ::: important `self` --- это объект типа userdata, который действует как Lua-таблица, однако его нельзя итерировать с помощью `pairs()` или `ipairs()` и нельзя распечатать с помощью `pprint()`. ::: `init(self)` : Вызывается при инициализации компонента. ```lua function init(self) -- Эти переменные доступны в течение всего времени существования экземпляра компонента self.my_var = "something" self.age = 0 end ``` `final(self)` : Вызывается при удалении компонента. Это полезно для очистки, например, если были порождены игровые объекты, которые должны быть удалены при удалении компонента. ```lua function final(self) if self.my_var == "something" then -- провести чистку end end ``` `update(self, dt)` : Вызывается один раз в каждом кадре. `dt` содержит дельту времени с момента последнего кадра. ```lua 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()`](/ref/msg#msg.post) движок вызывает эту функцию компонента-приемника. За подробностями обращайтесь к [руководству по передаче сообщений](/manuals/message-passing). ```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`](/ref/go/#acquire_input_focus)), движок вызывает эту функцию когда ввод зарегистрирован. За подробностями обращайтесь к [руководству по вводу](/manuals/input). ```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). Очень удобно для отладки, тестирования и оптимизации. За подробностями обращайтесь к [руководству по горячей перезагрузке](/manuals/hot-reload). ```lua function on_reload(self) print(self.age) -- вывести возраст данного игрового объекта end ``` ## Реактивная логика Игровой объект с компонентом Script реализует некоторую логику. Часто эта логика зависит от какого-либо внешнего фактора. Вражеский AI может реагировать на то, что игрок находится в определенном радиусе от него; дверь может отомкнуться и открыться в результате взаимодействия с игроком и т.д. и т.п. Функция `update()` позволяет реализовать комплексное поведение, определяемое как механизм состояний, запускаемый каждый кадр --- иногда это вполне адекватный подход. Однако каждый вызов `update()` сопряжен с определенными затратами. Если функция в действительности не нужна, ее следует удалить и вместо этого попытаться построить логику _реактивно_. Выгоднее пассивно ждать какого-либо сообщения с последующей реакцией, чем активно исследовать игровой мир в поисках данных для ответа. Более того, проектирование реактивным способом также часто приводит к более чистому и надежному дизайну и реализации. Давайте рассмотрим конкретный пример. Предположим, вы хотите, чтобы скрипт отправил сообщение через 2 секунды после того, как он был инициирован. Затем он должен дождаться определенного ответного сообщения и после получения ответа отправить еще одно сообщение через 5 секунд. Нереактивный код для этого будет выглядеть примерно так: ```lua 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 ``` Даже в этом довольно простом случае мы получаем довольно запутанную логику. Это можно улучшить с помощью горутин в модуле (см. ниже), но давайте вместо этого попробуем сделать это реактивным и использовать встроенный механизм синхронизации. ```lua 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](images/script/completion.png){srcset="images/script/completion@2x.png 2x"}