Перейти к основному содержанию

Жизненный цикл мода

Обычно начинается перед запуском игры, в этот момент мод объявляет лаунчеру о своем существовании и производит список необходимых для пользователя процедур. Сейчас мы рассмотрим полный цикл от этого момента до закрытия игры, что происходит в моде во время различных событий и как он может повлиять на игровой процесс.

Начнем с запуска​

И нет, не запуска мода. Весь цикл начинается с запуска лаунчера. Как только мы нажимаем на кнопку Играть, Inner Core осуществляет поиск модов, собирает их скрипты и определяет список ресурсов к загрузке. На этот момент игра еще не знает о том, что ее будут запускать, а моды и вовсе ни коим образом не взаимодействуют с пользователем.

Но тогда, чем интересна эта стадия? Мы можем определить список ресурсов, которые будут загружены. Это желательно сделать именно до запуска игры, чтобы все корректно загрузилось во время ее запуска. И самое классное, что этот процесс полностью нам подвластен.

Нужно ли запускать мод?​

Движок способен игнорировать запуск модов при необходимости, это происходит в случае если свойство enabled их конфигурации сейчас false. Ресурсы, однако, будут загружены раньше запуска, хоть и скрипты презагрузчика не будут выполнены. Учтите это при проектировании модов, зависимых от других (их еще называют аддонами, не путать с пакетами поведения).

Презагрузчик​

Позволяет совершать любые манипуляции с ресурсами вашего мода до загрузки игры, выполняется под скриптами с одноименным типом preloader. Следующий пример создан на основе библиотеки BetterQuesting:

const recolorBitmap = (function() {
let canvas = new android.graphics.Canvas();
let paint = new android.graphics.Paint();
return function(bitmap, color) {
let source = android.graphics.Bitmap.createBitmap(
bitmap.getWidth(),
bitmap.getHeight(),
android.graphics.Bitmap.Config.ARGB_8888
);
canvas.setBitmap(source);
paint.setColorFilter(new android.graphics.PorterDuffColorFilter
(color, android.graphics.PorterDuff.Mode.SRC_ATOP)
);
canvas.drawBitmap(bitmap, 0, 0, paint);
return source;
};
})();

let bitmap = android.graphics.BitmapFactory.decodeFile(
new java.io.File(__dir__, "template/leaves.png")
);
bitmap = recolorBitmap(bitmap, android.graphics.Color.BLUE);
bitmap.compress(android.graphics.Bitmap.CompressFormat.PNG, 100, new java.io.FileOutputStream(
new java.io.File(__dir__, "assets/resources/leaves_blue_0.png")
));

Изображение будет перекрашено в синий цвет, используя маску; в целом пример довольно комплексный, не стоит заранее пугаться размерам кода здесь. Скажем, что основной функцией этих скриптов является преобразование ресурсов, например, перекрашивание листвы или большого количество однотипных текстур, а также изменение размера существующих элементов интерфейса.

Кешируйте текстуры единожды

И обновляйте их лишь когда требуется, не заставляйте пользователей ожидать сборки вашего мода, если этого на самом деле не требуется.

Игра загрузилась​

Это следующий шаг, который мы можем контролировать. Между переходом от экрана хорайзона до полной загрузки игры лишь загружаются ресурсы модов и происходит нативная инициализация возможностей нашего движка. Этого вполне достаточно. Отображается экран загрузки если он включен, и основное происходящее с модами — запуск скриптов launcher.

Лаунчер​

И под лаунчером имеется ввиду одноименный тип скрипта. На этом моменте мы должны определиться что делать с модом: можно как запустить основной скрипт, так и ничего не делать, например, в случае если необходимая для мода библиотека не была загружена. Здесь же определяются настройки для мультиплеера, моды бывают как клиентскими так и серверными.

В большинстве случаев, следующего содержания launcher.js будет вполне достаточно:

launcher.js
ConfigureMultiplayer({
name: "Назови меня",
version: "auto",
isClientOnly: false
});

Launch();

Перед запуском остальной части мода, мы заранее определили информацию о нем в мультиплеере. Так, Inner Core и другие моды смогут идентифицировать ваш мод. Не изменяйте свойство name, если однажды оно уже было задано! Свойство version содержит значение "auto", это значит что значение будет получено напрямую из mod.info. В случае, если isClientOnly: true, определение остальных свойств не требуется.

Лаунчер еще и отличное место для проведения ряда оптимизаций, рассмотрим несколько. Воспользуемся событием тика для обеспечения стабильной работы мира и его обновлений:

Callback.addCallback("LocalTick", function() {
while (true) {
// производим оптимизацию
}
});

Конструкция while (true) обеспечивает очистку операционной памяти, сокращая нагрузку на устройство. Более продвинутым вариантом будет очистить еще и кеш, но тогда нужно воспользоваться сборщиком мусора:

Callback.addCallback("LevelDisplayed", function() {
runOnClientThread(function() {
while (true) {
java.lang.Thread.yield();
}
});
})
Можем упростить

В целом, не будет ничего страшного если не использовать свойства name и version здесь. По умолчанию они получают значения из информационного файла, то есть находятся в значении "auto". Следующий код эквивалентен коду выше, если name в mod.info определен как "Назови меня":

launcher.js
ConfigureMultiplayer({
isClientOnly: false
});

Launch();

Однако, если вы планируете часто обновлять свойства в файле mod.info, рекомендуется все же изменять этот скрипт напрямую. Так мы можем отделить, например, основные версии от частых обновлений в репозиториях.

Не забывайте, что каждый мод может быть запущен лишь единожды. Повторные вызовы Launch непременно приведут к ошибке.

Основные скрипты​

Метод Launch() лаунчера запускает скрипты main, мы дошли до основной логики мода. Здесь определяются и задаются новые блоки и предметы, регистрируются рецепты и интерфейсы для них, события ожидают своего исполнения, а следующие взаимодействия уже происходят непосредственно в игре.

Именно в контексте этих скриптов мы будем работать на протяжении всей документации. Здесь нельзя выделить отдельный пример, ведь код каждого мода по своему уникален!

Мы уже в мире​

Все скрипты жизненного цикла были запущены; события игры обрабатываются модами, а моды определяют последующий цикл действий для игрового окружения. И здесь нам на помощь может прийти еще один тип скриптов — независимые custom. У них самих по себе нет условий запуска, мы же можем вызывать их когда это необходимо. Для этого используется функция runCustomSource(имяСкрипта[, дополнительныйКонтекст]), она доступна из любого скрипта вашего мода:

alert(
runCustomSource("custom.js", {
THUNDER_MULTIPLIER: 0.4,
RAIN_MULTIPLIER: 0.6,
WEATHER: World.getWeather()
})
);

Мы еще рассмотрим этот пример подробнее, но а сейчас достаточно того, что эти скрипты готовы к работе в любое время.

Заключение​

Возможно, незаметно для себя, но мы определились с предназначением большинства вариантов свойства sourceType вашего build.config. Загляните в статью Построение и сборка для получения подробностей. Существует еще несколько вариантов этого свойства, они уже не входят в жизненный цикл движка, но могут появиться в нем при необходимости.