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

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

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

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

И нет, не запуска мода. Весь цикл начинается с запуска лаунчера. Как только мы нажимаем на кнопку Играть, 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. Загляните в статью Построение и сборка для получения подробностей. Существует еще несколько вариантов этого свойства, они уже не входят в жизненный цикл движка, но могут появиться в нем при необходимости.