--- title: Компонент Script brief: В этом руководстве описано, как добавить игровую логику с помощью компонента Script. --- # Компонент Script Компонент Script позволяет создавать игровую логику, используя [язык программирования Lua](/manuals/lua). ## Типы скриптов В Defold существует три типа Lua-скриптов, и для каждого из них доступны разные библиотеки Defold. Скрипты игровых объектов : Расширение _.script_. Эти скрипты добавляются к игровым объектам точно так же, как и любой другой [компонент](/manuals/components), и Defold будет выполнять Lua-код как часть функций жизненного цикла движка. Скрипты игровых объектов обычно используются для управления игровыми объектами и логикой, объединяющей игру воедино: загрузкой уровней, игровыми правилами и так далее. Скрипты имеют доступ к функциям [GO](/ref/go) и ко всем библиотечным функциям Defold, за исключением [GUI](/ref/gui) и [Render](/ref/render). Скрипты GUI : Расширение _.gui_script_. Выполняются компонентами GUI и обычно содержат логику, необходимую для отображения элементов GUI, таких как интерфейс, меню и т.п. Defold выполняет Lua-код как часть функций жизненного цикла движка. Скрипты GUI имеют доступ к функциям [GUI](/ref/gui) и ко всем библиотечным функциям Defold, за исключением [GO](/ref/go) и [Render](/ref/render). Скрипты рендера : Расширение _.render_script_. Выполняются в рамках пайплайна рендеринга и содержат логику, необходимую для отрисовки всей графики приложения/игры в каждом кадре. Скрипт рендера занимает особое место в жизненном цикле вашей игры. Подробнее см. в [документации по жизненному циклу приложения](/manuals/application-lifecycle). Скрипты рендера имеют доступ к функциям [Render](/ref/render) и ко всем библиотечным функциям Defold, за исключением [GO](/ref/go) и [GUI](/ref/gui). ## Выполнени Script, колбэки и 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 ``` #### `fixed_update(self, dt)` : Обновление, не зависящее от частоты кадров. `dt` содержит дельту времени с момента последнего обновления. Эта функция вызывается, когда включен параметр `engine.fixed_update_frequency` (!= 0). Полезна, когда вы хотите управлять физическими объектами с регулярными интервалами для достижения стабильной симуляции физики при включенном параметре `physics.use_fixed_timestep` в *game.project*. ```lua 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()`](/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 раз в секунду, даже если он простаивает. ## Препроцессинг Можно использовать Lua-препроцессор и специальную разметку для условного включения кода в зависимости от варианта сборки. Пример: ```lua -- Используйте одно из следующих ключевых слов: RELEASE, DEBUG или HEADLESS --#IF DEBUG local lives_num = 999 --#ELSE local lives_num = 3 --#ENDIF ``` Препроцессор доступен как расширение сборки. Подробнее о его установке и использовании см. на [странице расширения на GitHub](https://github.com/defold/extension-lua-preprocessor). ## Поддержка со стороны редактора Редактор Defold поддерживает редактирование Lua-скриптов с подсветкой синтаксиса и автодополнением. Чтобы автоматически подставить имена функций Defold, нажмите *Ctrl+Space* — появится список функций, соответствующих набираемому тексту. ![Auto completion](images/script/completion.png)