icon

Создание модуля

Есть много классов, которыми можно воспользоваться. Необязательно использовать все. Если создать папку в директории Modules, то PluginYG2 уже определит папку как модуль. В каких то случаях и этого может быть достаточно.

Основное

  1. Сразу придумайте хорошее название для модуля. Оно будет использоваться в коде.
  2. Создайте папку модуля в PluginYourGames/Modules. Теперь PluginYG2 определил созданную папку как модуль. Он будет отображаться в инструменте контроля версий. И для него создастся дефайн с подписью _yg.
  3. Внутри модуля создайте текстовый документ Version.txt и запишите в него номер версии следующего формата: v1.0. (По желанию)

Скрипты

Мы будем использовать уже существующие partial скрипты. Мы их будем расширять. Название файлов может быть любым, но я называю скрипты модулей так — сначала название модуля, потом приписка используемого класса. Получается примерно так: MyModule_yg.

Атрибуты

  1. HeaderYG — заголовок. Принимает параметр текста для заголовка. Остальные параметры опциональны: можно сменить цвет, принимает строку названия цвета или 3-4 float значения (RGBA); Еще есть параметр indent, чтобы поменять отступ.
  2. NestedYG — вложение. Передайте в него строку названия поля, от которого будет зависеть активность текущего поля. Таких полей можно передавать хоть сколько. Если хоть одно поле = false, то текущее поле скроется. Также у него есть параметы для смещения текста и линий. И bool параметр для отключения линий.
  3. ColorYG — изменяет цвет поля. Принимает параметры 3-4 float значения (RGBA).
  4. LabelYG — ползволяет с небольшими костылями отобразить надпись в инспекторе. Пример использования: [SerializeField, LabelYG("MyLabel")] private bool label;

Создание опций в настройках

Создим скрипт MyModule_info. Внутри partial класса InfoYG создайте новый класс с именем модуля, добавив к нему слово Settings. Примените к классу атрибут сериализации. Можно сделать новый скрипт также partial.
Cоздайте поле экземпляр нового класса c точно таким же названием как назвали модуль.

namespace YG
{
    public partial class InfoYG
    {
        public MyModuleSettings MyModule = new MyModuleSettings();

        [System.Serializable]
        public partial class MyModuleSettings
        {
			// Параметр для нового модуля
			public bool myOption;
        }
    }
}
Если имя экземпляра класса совпадает с именем папки модуля, то в настройках PluginYG2 появится раздел нового модуля и его опции.
Теперь к этим опциям можно обращаться следующим образом:
YG2.infoYG.MyModule.myOption

В настройках можно отобразить иконку модуля. Для этого поместите иконку внутри модуля в такой путь Scripts/Editor/Icons. Иконка должна иметь расширение png и называться также как модуль.

Изменение билда

PluginYG2 обладает инструментарием для изменения index.html и style.css файлов после билда.
Можно удобно упаковать js, html или css код в отдельные файлы, прописать в какое место его нужно добавить, прописать условия для добавляения, установить параметры явно настраиваемые через интерфейс Unity.
Как это сделать описывается в разделе Template.

Добавление настроек платформы

Можно создать опции, которые будут настраиваться отдельно для каждой платформы. Смотрите в разделе Платформы.
icon

Класс YG2

В классе YG2 содержится весь основной функционал. В нём мы прописываем основную логику модулей, которая будет одна для всех платформ. Но и в нём же выполняем методы интерфейса с реализациями для разных платформ.
Это статичный класс. Все поля и методы должны быть с модификатором static. Файл с классом YG2 я нызваю с припиской yg (MyModule_yg). Пример:

using UnityEngine;
namespace YG
{
    public static partial class YG2
    {
		// Метод для примера
        private static void GameplayStart()
        {
#if UNITY_EDITOR // Код скомпилируется только для Unity Editor
            // Создаём симуляцию. Можно брать опции симуляции из настроек модуля.
            Debug.Log("Gameplay Start");
#else // Код скомпилируется только в сборке игры
			// Выполняем реализацию метода для платформы, которая выбрана в настройках плагина
            iPlatform.GameplayStart();
#endif
        }
    }
}
Здесь вы можете прописать публичные методы и поля, которые будут находиться в классе YG2. Также здесь выполняются методы инициализации модуля. Подробнее ниже.

Интерфейс платформы

Чтобы выполнять разные реализации одного метода для разных платформ, нужно создать его в интерфейсе. Файл интерфейса я называю с припиской interface. Пример:

namespace YG
{
    public partial interface IPlatformsYG2
    {
        void GameplayStart() { }
    }
}
Стандартная реализация обязательно должна быть. Либо пустая, либо создайте её в фигурных скобках. Если стандартной реализации не будет, то если в какой-либо платформе не будет реализован метод, то возникнет ошибка. А при пустой реализации метод будет просто в таком случае проигнорирован при его выполнении.

Реализация метода от конкретной платформы

В PluginYG2 вместе с модулем идёт всегда реализация стандартной платформы YandexGames. Файл реализации для Яндекс Игр я нызваю с припиской yandexPlatform. Пример:

#if YandexGamesPlatform_yg // Определяем платформу, чтобы обёрнутый код компилировался только для необходимой платформы
using System.Runtime.InteropServices; // Подключаем библиотеку для контакта с jslib
namespace YG
{
    public partial class PlatformYG2 : IPlatformsYG2
    {
        // Выполняет метод в js через jslib
        [DllImport("__Internal")]
        private static extern string GameplayStart_js();

		// Метод должен быть публичный и того же имени что и в интерфейсе
        public void GameplayStart()
        {
            // Выполняем метод в jslib
            GameplayStart_js();
        }
    }
}

Посредник между C# и JS

Чтобы выполнить код из C# в js, нужнен посредник jslib. Для этого необходимо создать папку Plugins и поместить в неё скрипт с расширением jslib. Внутри должен быть примерно такой код:

mergeInto(LibraryManager.library,
{
	GameplayStart_js: function ()
	{
		// JS метод в index.html
		GameplayStart();
	}
});

Метод в js

Как поместить код в файлы index.html и style.css не изменяя шаблон — смотрите в разделе Template.
Пример метода из js скрипта в index.html файле:

function GameplayStart() {
	// Можно проверить на готовность инициализации SDK
	if (ysdk == null)
		return;

	ysdk.features.GameplayAPI.start();

	// Пример, как выполнить метод из js в Unity
	YG2Instance("MethodName", "param")
}
NO_DATA — это как бы null от PluginYG. Используется своё поле, потому что с передачей null между C# и js есть проблемы. В C# используется поле InfoYG.NO_DATA.
Для выполнения методов из js в Unity — используется функция YG2Instance. Она практически дублирует стандартную sendMessage. Принимает название метода, который нужно выполнить и опционально можно передать значение. Игнорирует выполнение метода, если игра ещё не инициализирована.
Есть ещё булевые поля: initYSDK, initGame, syncInit.
Это всё в WebGL шаблоне YandexGames.

Методы, которые будут выполняться в Unity из js

Класс YGSendMessage «принимает сообщения» из js. Он содержится в отдельном пространстве имён YG.Insides. Обычно этот класс я помещаю в файл с кодом реализации платформы.

namespace YG.Insides
{
    public partial class YGSendMessage
    {
		// Этот метод выполнится из js
        public void MethodName(string data)
        {
            // Проверяем существуют ли данные
            if (data == InfoYG.NO_DATA || string.IsNullOrEmpty(data))
            {
                Debug.LogError("Error!");
                return;
            }

			// Лог от PluginYG2
            YG2.Message(data);
        }
    }
}
#endif

Скрытый класс

Статичный класс YGInsides содержится в пространстве имён YG.Insides. Он нужен чтобы просто что то скрыть — не хранить в классе YG2. Обычно я прописываю его в файле с классом YG2.

namespace YG.Insides
{
    public static partial class YGInsides
    {
		public static insideParam;
		public static InsideMethod() { ... }
	}
}

Класс опциональный для платформ

Класс OptionalPlatform — в него помещаются спецефические методы для рызных платформ. Он создан чтобы отделить нестандартные методы от общей массы и обозначить назначение методов внутри данного класса. Методы, которые находятся в данном классе можно найти так: YG2.optionalPlatform.FirstInterAdvShow();

namespace YG.Insides
{
    public partial class OptionalPlatform // Публичный метод
    {
		// Метод пропускающий первую рекламу для определённых платформ
        public void FirstInterAdvShow() => YG2.iPlatform.FirstInterAdvShow();

        public static void FirstInterAdvShow_RealizationSkip()
        {
			// Реализация пропуска рекламы
        }
    }
}

Инициализация модуля

Рассмотрим паттерн инициализации, который используется для плаформы YandexGame. Мы будем получать данные от SDK платформы из JS и передавать их в Unity при запуске игры. Рассмотрим пример получения данных игрока.
В index.html есть место, в которое вставляется код инициаллизации, подробнее читайте в разделе Template.
В это место можно помещать асинхронный метод получения данных, пример:

#if YandexGamesPlatform_yg // Определите платформу
namespace YG.EditorScr.BuildModify // Скрипт компилируется только в Unity Editor
{
    public partial class ModifyBuild
    {
        public static void EnvirData() // Метод должен называться в точности как имя модуля
        {
            // Вставляем асинхронную функцию в раздел инициализации

            // 1. Метод, в который нужно записать только название функции
            InitFunction("RequestingPlayerData", CodeType.Init);

            // Он сам допишет строку, пример итогового кода для js:
            await RequestingPlayerData();
            LogStyledMessage('Init ИмяМодуля ysdk');

            // 2. Мотод просто вставляет текст, который вы передаёте
            AddIndexCode("await RequestingEnvironmentData();", CodeType.Init);
        }
    }
}
#endif
Код в index.html будет выглядеть примерно так:
async function InitYSDK() {
	ysdk = await YaGames.init();

	// Additional init0 modules

	// Additional init1 modules

	// Additional init2 modules

	// Additional init modules
	await RequestingEnvironmentData();
    LogStyledMessage('Init ИмяМодуля ysdk');

	// Завершение инициализации SDK...
}
Код ждёт пока проинициализируются все модули.
Пример метода инициализации в js (получения данных игрока):
let playerData = NO_DATA; // Поле для сохранения данных

function RequestingPlayerData() {
    // Метод будет возвращать данные в строке json
    return new Promise((resolve) => {
        // Можно проверить на готовность инициализации SDK - чтобы небыло ошибок на локальном хосте, например
        if (ysdk == null) {
            Final(NO_DATA); // Возвращаем null, если что то пошло не так
            return;
        }
        // Оборачиваем в try-catch, чтобы избежать краша игры
        // Такое может произойти из-за ошибки в коде или если у SDK платформы изменится API
        try {
            ysdk.getPlayer({ scopes: _scopes })
                .then(player => {
                    // Создаём класс, в который записываем данные
                    let authJson = {
                        "playerName": player.getName(),
                        "playerPhoto": player.getPhoto()
                    };
                    // Передаём данные в json
                    Final(JSON.stringify(authJson));
                });
        }
        catch (e) {
            console.error('CRASH Requesting Environment Data: ', e.message);
            Final(NO_DATA);
        }

        // Функция передающая данные
        function Final(res) {
            playerData = res; // Сохраняем данные в отдельно поле
            YG2Instance('SetAuthEndAvatar', res); // Передаём данные в Unity
            resolve(res); // Метод RequestingPlayerData вернёт поле res
        }
    });
}
Такой метод подходит и для асинхронной загрузки игры и SDK, и для поочерёдной. За синхронность загрузки отвечает параметр Sync Init SDK в настройках PluginYG, раздел Basic Settings. Почитайте всплывающие подсказки опции и обратите внимание на вытекающие из него параметры.

  1. Если игра запустится первее инициализации SDK, то в Unity поступят данные через SendMessage (метод YG2Instance). Он отправляет данные в Unity только если игра уже запущена, чтобы избежать ошибок при отправке сообщений когда игра не инициализированна.
  2. Если SDK инициализируется первее загрузки игры, то данные будут сохранены в строку playerData. И при старте игры мы получим эти данные взяв строку playerData из js. А метод YG2Instance проигнорирует отправку сообщения в Unity.

Если к методу применить атрибут инициализации, то он выполнится при запуске игры в определённой последовательности с другими инициализациями плагина.
Есть пять атрибутов для инициализации модулей, они выполняются в таком порядке:

  1. InitYG_0 — для инициализации базовых модулей, от которых могут зависить другие.
  2. InitYG_1 — базовые модули №1.
  3. InitYG_2 — базовые модули №2.
  4. InitYG — стандартная инициализация.
  5. StartYG — выполняется в методе Start, но перед всеми другими методами Start.

Далее будет предоставлен C# код с использованиес всего функционала модулей с инициализацией. Классы желательно разделить по отдельным файлам, но для наглядности покажу всё в одном:

using System;
using System.Runtime.InteropServices;
using UnityEngine;

namespace YG
{
    // Создаём метод инициализации в интерфейсе
    public partial interface IPlatformsYG2
    {
        void InitPlayer() { }
    }

    // При запуске игры будет выполнен метод, который реализует метод инициализации модуля текущей платформы
    public partial class YG2
    {
        // Создаём публичное поле, чтобы потом обращаться к нему для получения различных объектов
        public static Player playerData = new Player();

        [Serializable] // Сериализуем класс, чтобы json мог загрузить в него данные
        public class Player // Создаём класс содержащйи данные
        {
            public string name = "unauthorized";
            public string photo = string.Empty;
        }

        [InitYG] // С этим атрибутом, метод будет выполняться при запуске игры в правильной последовательности среди инициализаций плагина
        private static void InitPlayerData()
        {
#if UNITY_EDITOR // Код скомпилируется только для Unity Editor
            // Устанавливаем данные для симуляции. Можно взять их из раннее созданных опций в настройках модуля.
            playerData.name = "User test";
            playerData.photo = "demo image";
#else // Код скомпилируется только для сборки игры
            iPlatform.InitPlayer(); // Выполняем реализацию метода для платформы, которая выбрана в настройках плагина
#endif
        }
    }
}

// Реализуем метод инициализации для текущей платформы
#if YandexGamesPlatform_yg // Определяем платформу, чтобы обёрнутый код компилировался только для необходимой платформы
namespace YG
{
    public partial class PlatformYG2 : IPlatformsYG2
    {
        // Метод, который возвращает данные из js
        [DllImport("__Internal")]
        private static extern string InitPlayerData_js();

        // Метод реализации для текущей платформы, который выполняет интерфейс
        public void InitPlayerData()
        {
            // Получаем данные из js
            string playerData = InitPlayerData_js();
            // Обрабатываем данные
            YG2.sendMessage.SetPlayerData(playerData);
        }
    }
}

namespace YG.Insides
{
    // Получение данных из js и их обработка
    public partial class YGSendMessage
    {
        // Метод обработки данных и записи в поля класса YG2 для дальнейшего использования
        public void SetPlayerData(string data)
        {
            // Проверяем существуют ли данные
            if (data == InfoYG.NO_DATA || string.IsNullOrEmpty(data))
            {
                Debug.LogError("Error!");
                return;
            }

            // Записываем данные преобразуя json в экземпляр класса
            YG2.playerData = JsonUtility.FromJson<YG2.Player>(data);

            // Вызываем событие YG2.onGetSDKData
            YG2.GetDataInvoke();
        }
    }
}
#endif
Метод посердник в jslib возвращающий поле с данными, которое заранее заполнялось в js:
mergeInto(LibraryManager.library,
{
	InitPlayerData_js: function ()
	{
		var returnStr = playerData;
		var bufferSize = lengthBytesUTF8(returnStr) + 1;
		var buffer = _malloc(bufferSize);
		stringToUTF8(returnStr, buffer, bufferSize);
		return buffer;
	}
});

Создали модуль? Напишите мне 😀 mbornysov@mail.ru