Жизненный цикл мода
Обычно начинается пер ед запуском игры, в этот момент мод объявляет лаунчеру о своем существовании и производит список необходимых для пользователя процедур. Сейчас мы рассмотрим полный цикл от этого момента до закрытия игры, что происходит в моде во время различных событий и как он может повлиять на игровой процесс.
Начнем с запуска
И нет, не запуска мода. Весь цикл начинается с запуска лаунчера. Как только мы нажимаем на кнопку Играть, 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 будет вполне достаточно:
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 определен как "Назови меня":
ConfigureMultiplayer({
isClientOnly: false
});
Launch();
Однако, если вы планируете часто обновлять свойства в файле mod.info, рекомендуется все же изменять этот скрипт напрямую. Так мы можем отделить, например, основные версии от частых обновлений в репозиториях.
Не забывайте, что каждый мод может быть запущен лишь единожды. Повторные вызовы Launch непременно приведут к ошибке.