Содержание
Создание модуля
Есть много классов, которыми можно воспользоваться. Необязательно использовать все. Если создать папку в директории Modules, то PluginYG2 уже определит папку как модуль. В каких то случаях и этого может быть достаточно.
Основное
- Сразу придумайте хорошее название для модуля. Оно будет использоваться в коде.
- Создайте папку модуля в PluginYourGames/Modules. Теперь PluginYG2 определил созданную папку как модуль. Он будет отображаться в инструменте контроля версий. И для него создастся дефайн с подписью
_yg
. - Внутри модуля создайте текстовый документ
Version.txt
и запишите в него номер версии следующего формата:v1.0
. (По желанию)
Скрипты
Мы будем использовать уже существующие partial скрипты. Мы их будем расширять. Название файлов может быть любым, но я называю скрипты модулей так — сначала название модуля, потом приписка используемого класса. Получается примерно так: MyModule_yg
.
Атрибуты
HeaderYG
— заголовок. Принимает параметр текста для заголовка. Остальные параметры опциональны: можно сменить цвет, принимает строку названия цвета или 3-4 float значения (RGBA); Еще есть параметр indent, чтобы поменять отступ.NestedYG
— вложение. Передайте в него строку названия поля, от которого будет зависеть активность текущего поля. Таких полей можно передавать хоть сколько. Если хоть одно поле = false, то текущее поле скроется. Также у него есть параметы для смещения текста и линий. И bool параметр для отключения линий.ColorYG
— изменяет цвет поля. Принимает параметры 3-4 float значения (RGBA).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;
}
}
}
Теперь к этим опциям можно обращаться следующим образом:
YG2.infoYG.MyModule.myOption
В настройках можно отобразить иконку модуля. Для этого поместите иконку внутри модуля в такой путь Scripts/Editor/Icons. Иконка должна иметь расширение png и называться также как модуль.
Изменение билда
PluginYG2 обладает инструментарием для изменения index.html и style.css файлов после билда.
Можно удобно упаковать js, html или css код в отдельные файлы, прописать в какое место его нужно добавить, прописать условия для добавляения, установить параметры явно настраиваемые через интерфейс Unity.
Как это сделать описывается в разделе Template.
Добавление настроек платформы
Можно создать опции, которые будут настраиваться отдельно для каждой платформы. Смотрите в разделе Платформы.
Класс 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
}
}
}
Интерфейс платформы
Чтобы выполнять разные реализации одного метода для разных платформ, нужно создать его в интерфейсе. Файл интерфейса я называю с припиской 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
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, то в Unity поступят данные через
SendMessage
(методYG2Instance
). Он отправляет данные в Unity только если игра уже запущена, чтобы избежать ошибок при отправке сообщений когда игра не инициализированна. - Если SDK инициализируется первее загрузки игры, то данные будут сохранены в строку
playerData
. И при старте игры мы получим эти данные взяв строкуplayerData
из js. А методYG2Instance
проигнорирует отправку сообщения в Unity.
Если к методу применить атрибут инициализации, то он выполнится при запуске игры в определённой последовательности с другими инициализациями плагина.
Есть пять атрибутов для инициализации модулей, они выполняются в таком порядке:
InitYG_0
— для инициализации базовых модулей, от которых могут зависить другие.InitYG_1
— базовые модули №1.InitYG_2
— базовые модули №2.InitYG
— стандартная инициализация.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
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