вторник, 29 декабря 2015 г.

Менеджер проектов для Torque 2D

Юрий “yurembo” Язев
независимый игродел

Чтобы создать независимую от SandBox’а игру на движке Torque 2D нужно проделать утомительные операции по созданию папок, копированию файлов и их изменению для соответствия имени нового проекта (я уже описывал эту последовательность операций). Для создания каждого нового проекта эти действия приходится выполнять снова и снова. Выполнение одних и тех же операций оказывает на программиста демотивирующий эффект. Поэтому, то, что можно автоматизировать, надо автоматизировать. Сознанием новых проектов должен заниматься менеджер проектов. Так как, в Torque 2D таковой отсутствует, я решил разработать его. В первую очередь для своих собственных нужд. А, также, для участников торковского сообщества и всех остальных пользователей движка. Кроме того, я выложил на GitHub все исходники, поэтому любой программист может скачать код и модифицировать его под свои цели. Для ускорения разработки я написал Менеджер проектов на C#, воспользовавшись dotNet 4.5.

Я очень надеюсь, что менеджер проектов снизит порог вхождения для новых пользователей Torque 2D, которым, сходу мало, что понятно, в том числе, как создать новый проект. Менеджер проектов создает минимальное торковское приложение, в котором, кроме инициализации канвы содержатся объекты: Scene, SceneWindow, объект управления – InputManager и, для примера, спрайт с натянутой текстурой, размещенный в центре экрана. Дополнительно, менеджер проектов может создать Torsion-проект для данной конкретной игры, который, впоследствии, можно открыть с помощью Torsion IDE и удобно редактировать код на Torque Script.

Создание проекта



Менеджер проектов представляет минимальное Windows-приложение с минималистическим интерфейсом:

Менеджер проектов

Ничто так не вдохновляет, как релиз новой версии Торка, что отражено в заголовке окна, но, на самом деле, менеджер проектов будет работать с любой версией Торка 2D.
Итак, для того, чтобы создать новую игру на Torque 2D с помощью менеджера проектов, надо:
1)      в поле “Project name” ввести имя будущей игры;
2)      в поле “Company name” ввести имя компании-разработчика;
3)      в поле “Torque 2D root folder” ввести путь к папке, куда установлен движок Torque 2D, так же, можно нажать кнопку “Select folder” и выбрать нужную папку в диалоге;
4)      в поле “Your project home folder” ввести путь к папке, в которой будет создан проект пользовательской игры, кроме того, можно нажать кнопку “Select folder”, располагающейся рядом с этим полем, и выбрать нужную папку с помощью диалога; в ней будет создана подпапка с именем проекта – “Project Name”;
5)      ниже, можно отметить или снять (по умолчанию, отмечен) флажок “Create Torsion Project”, в случае, если он отмечен, тогда будет создан Torsion проект, который можно открыть с помощью Torsion IDE и редактировать скриптовый код на Torque Script с помощью последней;
6)      после нажатия кнопки Create Project, проект новой минимальной игры на движке Torque 2D будет создан в выбранном каталоге, о чем известит появившееся внизу окна надпись;

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

За место выбора директории с Торком, пользователь может поместить исполняемый файл менеджера проектов в один каталог с движком. Тогда, при запуске менеджера, он сам определит свое месторасположение и заполнит соответствующее текстовое поле.

В результате создания проекта с помощью менеджера, получается довольно незамысловатая игра, представляющая собой фундамент для дальнейшего развития:

Проект Racing создан

Содержимое папки проекта

Минимальная Torque 2D игра

Скачать менеджер проектов для Torque 2D можно с моей страницы GitHub

воскресенье, 20 декабря 2015 г.

Разработка лаунчера для MMORPG Project Genom, часть 1 

Юрий “yurembo” Язев 
независимый игродел 

Лаунчер 



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

Платформа и инструментарий



Давным-давно, Геном разрабатывался на движке Torque 3D… Время шло, движки умирали и появлялись. Нынешняя итерация Генома зиждется на движке Unreal Engine 4. Тем не менее, в сегодняшнем разговоре нам от этого ни холодно, ни жарко. Игра ориентирована на платформу Windows NT, следовательно, лаунчер, в идеале, должен поддерживать все версии этой операционки, начиная от Windows XP. При этом для нас, в данном случае, неважно будет ли работать игра на этом парке систем. Ибо для нас важен лаунчер.
Итак, чем я руководствовался при выборе языка и инструментария. Конечно, наиболее предпочтительный вариант – это воспользоваться языком C# и WinForms. C# позволяет не беспокоиться о куче, а WinForms включает широкий ассортимент визуальных компонентов. Между тем, чтобы все это богатство работало, на компе конечного пользователя должен быть установлен .NET. По сути, это не проблема, так как начина с Windows XP SP 3, .NET третьей версии пред устанавливается. Однако код, в таком случае, по определению не нативный.
Немного подумав, взвесив все «за» и «против», я решил выбрать нативный вариант, чтобы мое приложение наверняка работало на всех Windows-системах, включая Windows 2000 и 9x. В таком случае, мое решение остановилось на старой, заросшей мхом, посыпанной пеплом тяжелых боев библиотеке MFC.

MFC все еще жива?



Когда-то очень давно, будучи очень молодым, я купил книгу «Программирование на Visual C++ 6.0» 5-е издание серии «Для профессионалов» издательства Питер. Уже тогда авторы задавались вопросом «MFC, ATL, WFCMFC мертва?» Тем не менее, MFC жива и по сей день: тому подтверждение компании, которые построили свой бизнес на разработке MFC-компонентов! MFC-элементы и по сей день прекрасно служат для построения пользовательских интерфейсов, как в унаследованных, так и в новых приложениях. С каждой новой версией Visual Studio так же обновляется MFC, Microsoft добавляет в нее новые элементы, оптимизирует и подгоняет под новые версии операционной системы старые (унаследованные) компоненты.

Проектирование



Описанное в начале тех. задание на разработку лаунчера очень поверхностное. Но, и в начале разработки ничего более подробного у меня не было. Будем разрабатывать по шагам: сделав один, будем смотреть, чего не хватает, и шагать дальше.
Инструменты и средства разработки уже выбраны, теперь, надо перейти к определению списка задач. Для удобства пронумеруем все шаги. Сначала, рассмотрим пожелания к пользовательскому интерфейсу.
1)      Лаунчер должен представлять оконное Win32-приложение.
2)      Фоновая картинка должна растягиваться на всю область окна определенных размеров.
3)      Окно появляется в центре экрана.
4)      При запуске приложения, оно должно скачать с сервера изображение, отображаемое в левой части окна, и текст – описание, отображаемое в правой части окна.
5)      Кнопки свертывания и закрытия окна должны быть кастомными.
6)      Перетаскивание окна должно осуществляться за верхнюю панель (заголовок) окна.
7)      При первом запуске лаунчера, он должен предложить пользователю выбрать папку, куда будет закачан клиент.
8)      Диалог выбора папки должен отобразиться по нажатию на кнопку «Изменить».
9)      Путь к выбранному каталогу должен отображать в окне лаунчера.
10)   Скачивание клиента должно начаться после нажатия на кнопку «Установить».
11)   Перед началом скачивания внизу окна должны присутствовать 2 надписи:
1)      Требуется места: (размер клиента в МБ)
2)      Свободное место: (оставшееся свободное место в МБ на выбранном диске)
12)   После запуска программы, она подключается к жестко заданному серверу. Этот сервер может быть, как FTP, так и HTTP. В процессе разработки мы рассмотрим плюсы и минусы каждого, а пока будет иметь в виду, что у нас есть выбор. FTP-сервер может (и должен) пускать в свои владения только авторизированного пользователя; у HTTP-сервера есть открытая для чтения директория, где, обычно, хранится веб-сайт, к которому имеет доступ любой пользователь сети. В эту же директорию мы можем положить данные для скачивания – игровой клиент.
13)   Нам предстоит выбрать механизм и/или фреймворк для подключения и сетевого взаимодействия.
14)   После подключения и/или успешной авторизации, из определенной подпапки лаунчер должен сравнить все файлы, имеющиеся на компе юзера и находящиеся на сервере. В случае, отсутствия каких-то либо файлов лаунчер скачивает их.
15)   В случае различия версий файлов, лаунчер заменяет на компе пользователя эти файлы. Условная версия файла может вычислять следующими способами: различие в размерах файлов с одинаковыми именами и относительными расположениями, даты модификации, контрольные суммы. Во время разработки мы выберем подходящий вариант.
16)   В случае неудачного скачивания какого-либо файл, его закачка должна повториться. Успешность скачивания файла определяется по тем же параметрам, что условная версия файла.
17)   После успешного скачивания всех файлов, лаунчер должен предоставить способ запуска клиента игры.
18)   Прогресс скачивания файлов должен отображаться на шкале, в процентах показывающей количественное соотношение скачанных данных к общему их количеству.

Создание списка файлов



Будем разбирать наш список не по порядку. Оставим внешний вид на потом. Разберемся с начала с другим насущным вопросом – с файлами. Поэтому начнем с пункта №14. Список файлов. В первом приближение, список не нужен, лаунчер может воспользоваться функциями из библиотеки WinInet для обхода всех файлов в заданном каталоге на удаленном сервере: после установки соединения, с помощью функции FtpFindFirstFile получить список всех файлов и подпапок, а затем, с помощью функции InternetFindNextFile переходить к следующему файлу и читать его свойства. Таким образом, мы просмотрим все имеющиеся в заданном каталоге файлы. Если эту операцию проведет один удаленный пользователь, то все ОК, однако, у нас же сервер сетевой игры, и к нему может одновременно  подключиться сотни игроков, желающие скачать или запустить игру. В обоих случаях нам нужно сверить все файлы. Тогда, серверу надо будет осуществить столько проверок всех файлов, сколько пользователей подключилось к игровому серверу. Это вызовет замедление работы сервера, а, возможно, станет причиной отказа в обслуживании. Поэтому, очевидно, что этот вариант не подходит для высоконагруженных игровых серверов.
Вот, поэтому нам нужен заранее приготовленный список файлов. Кроме имен файлов список должен содержать сведения сравнения для каждого файла. Во время подключения клиента к серверу, лаунчер запрашивает с сервера этот файл, и, так как последний содержит самые новые сведения, лаунчер по этому файлу может проверить свои файлы. Если какого-то файла он у себя не нашел, то докачивает его с сервера, а, если обнаружил расхождение в значениях свойств, то заменяет у себя на скаченный с сервера.
В итоге, нам нужна программа, которая будет сканировать определенный каталог на сервере (где находится клиент игры для скачивания), включая подкаталоги, и собирать информацию обо всех файлах. Она будет выполняться локально, так как её будет запускать администратор сервера. В этом случае, подготавливать новый файл – запускать приложение надо будет только при обновлении файлов клиента на сервере.

Разработка приложения FileData



Задача поставлена. Я назвал это приложение FileData, к лаунчеру оно имеет второстепенное отношение, поскольку оно предназначено для выполнения на сервере. Для упрощения задачи, это консольное приложение, так как никакие параметры для его работы настраивать не надо, в том числе, не нужно передавать никакие параметры командной строки.
Для разработки приложения я воспользовался Visual Studio 2013 Community Update 5. Для начала я создал обычное консольное приложение. Начнем с функции main(), с которой, как считается, начинается выполнение программы. Конечно, это не так, прежде чем заработает функция main() выполняется куча кода, заботливо вставленного компилятором, для выделения памяти, загрузки системных функций и т.д., но мы в данном случае притворимся желторотыми прикладными программистами и будем считать начало с main(). Итак, в функции main() создаются 3 переменные: для файлового вывода (ofstream fout), для записи количества файлов (int fc) и для подсчета размера всех файлов (DWORDLONG filesSize). После этого вызывается функция fileSearch, которая принимает 4 параметра: маску для поиска файлов (так как, нам нужны все файлы, то маска представляет собой “*”) и все 3 выше созданные переменные. Забегая вперед, они передаются по ссылке, поэтому после выполнения функции fileSearch в них содержатся значения соответственно, количество и размер всех файлов, эти значения выводятся в консоль и записываются в файл. В конце открытый для записи файл закрывается.

void main()
{
   ofstream fout(_fileName, ios::trunc);
   if (!fout) return;

   int fc = 0;//files count
   DWORDLONG filesSize = 0;
   fileSearch(LPCWSTR("*"), fc, filesSize, fout);

   cout << "Files_count: " << fc << " size " << filesSize << endl;
   fout << "Files_count: " << fc << " size " << filesSize << endl;
   fout.close();

   system("PAUSE");
}

Перейдем к разбору функции fileSearch. В ней выполняется основная задача нашей утилиты: поиск файлов. 


void fileSearch(LPCWSTR dir, int& fc, DWORDLONG& filesSize, ofstream& fout)
{
WIN32_FIND_DATA FindFileData;
HANDLE hf;

hf = FindFirstFile(dir, &FindFileData);
if (hf != INVALID_HANDLE_VALUE)
{
do
{
string fileName = narrow(FindFileData.cFileName);
if ((fileName != ".") && (fileName != "..") && (fileName != _fileName) && (fileName != _programName)) {
string d = narrow(dir);
d = deleteAsterix(d);
string filePath = d + fileName + "\\*";
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
wstring stemp = wstring(filePath.begin(), filePath.end());
LPCWSTR sw = stemp.c_str();
fileSearch(sw, fc, filesSize, fout);
}
else {
// использование структуры ULARGE_INTEGER 
//для нахождения 64-х битных значений, состоящих из двух 32-х 
//битных частей
ULARGE_INTEGER ul;
ul.HighPart = FindFileData.nFileSizeHigh;
ul.LowPart = FindFileData.nFileSizeLow;
ULONGLONG fileSize = ul.QuadPart;
filePath.erase(filePath.size() - 2, 2);
UINT32 crc = calcCtrlSum(filePath);
fout << "\"" << filePath << "\"" << "   " << fileSize << "   " << crc << endl;
cout << "\"" << filePath << "\"" << "   " << fileSize << "   " << crc << endl;
fc++;
filesSize += fileSize;
}
}
} while (FindNextFile(hf, &FindFileData) != 0);
FindClose(hf);
}

}

Программа начинает сканировать каталоги с того, где находится исполняемый файл, зарываясь вглубь файловой системы. Аргументы, принимаемые функцией fileSearch, мы уже рассмотрели, перейдем внутрь. Вначале, объявляются переменные типа WIN32_FIND_DATA (WIN32_FIND_DATA FindFileData) и HANDLE (HANDLE hf). Первый представляет собой структуру, содержащую информацию по найденному с помощью функции FindFirstFile файлу. А, второй – дескриптор файла. Далее, вызываем функцию FindFirstFile, чтобы найти заданный файл. Функция принимает имя файла, который надо найти, так как нам надо найти все файлы, а первый найденный файл может быть любым, мы передаем символ астериск (*). Вторым параметром функция по ссылке принимает переменную типа WIN32_FIND_DATA, в которой возвращает сведения о найденном файле. Вдобавок, функция возвращает дескриптор найденного файла. В случае если ничего не найдено функция возвращает константу INVALID_HANDLE_VALUE. В зависимости от возвращенного значения мы определяем: продолжать выполнение функции или нет. И, если ничего не найдено – вернулось значение INVALID_HANDLE_VALUE, тогда продолжать выполнять функцию не имеет смысла. В ином случае, мы запускаем цикл. Сначала, нам надо взять имя найденного файла. Оно хранится в структуре WIN32_FIND_DATA в поле cFileName:

string fileName = narrow(FindFileData.cFileName);

Имя файла в структуре WIN32_FIND_DATA хранится в формате wstring (широкая строка), поэтому перед ее присвоением переменной типа string, ее надо сузить – преобразовать. Эта операция осуществляется в функции narrow:

string narrow(wstring const& text)
{
   locale const loc("");
   wchar_t const* from = text.c_str();
   size_t const len = text.size();
   vector<char> buffer(len + 1);
   use_facet<ctype<wchar_t> >(loc).narrow(from, from + len, '_', &buffer[0]);
   string s = string(&buffer[0], &buffer[len]);;
   return s;
}

Когда имя получено, мы проверяем, чтобы оно не было равно: «.» - скрытый файл – ссылка на текущий каталог, «..» - ссылка на каталог верхнего уровня, имя файла вывода (info.txt), имя исполняемого файла программы (FileData.exe). Если имя файла прошло такую проверку, то имя директории, переданное в параметре, сужается (посредством функции narrow); далее, посредством функции deleteAsterix проверяем всю полученную строку и удаляем из нее все символы *, которые не могут присутствовать в имени файла. Функция deleteAsterix выглядит следующим образом:

string deleteAsterix(string s)
{
   string::iterator it = s.begin();
   while (it != s.end()) {
                   if (*it == '*')
                                  it = s.erase(it);
                   else
                                  it++;
   }
   return s;
}

Затем, строим новую строку и присваиваем ее переменной filePath:

string filePath = d + fileName + "\\*";

То есть, filePath содержит: имя материнского каталога + ‘\’ + имя текущего файла/каталога + ‘\*’.
После этого проверяем, что найденный функцией FindFirstFile объект является каталогом:

if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {

Для этого смотрим: присутствует ли в атрибутах объекта элемент FILE_ATTRIBUTE_DIRECTORY, если ответ положительный, значит, объект – каталог. В таком случае, значение filePath есть путь к вложенному каталогу, и мы его расширяем до wstring, затем конвертируем в тип – указатель на строку широких символов:

wstring stemp = wstring(filePath.begin(), filePath.end());
LPCWSTR sw = stemp.c_str();

Когда путь к вложенному каталогу будет в нужном формате, мы передаем его вместе с полученными аргументами рекурсивно вызываемой функции fileSearch, которая выполнит такой же поиск во вложенном каталоге, а, найдя подкаталоги, так же войдет в каждый из них:
fileSearch(sw, fc, filesSize, fout);
В том случае, если найденный с помощью функции FindFirstFile объект является файлом, тогда из заполненной структуры WIN32_FIND_DATA надо выбрать интересующие нас свойства файла.
Первым делом, нас интересует размер файла. Так как, размер файла может превышать максимальное значение, помещаемое в 32-х разрядные регистры, значение размера хранится в 2-х полях структуры WIN32_FIND_DATA: nFileSizeLow и nFileSizeHigh. 32-х битные системы уже давно сошли со сцены, уступив 64-х разрядным. Однако остался вот такой рудимент. Но мы можем из двух 32-битных частей собрать одно 64-х битное. Для этого можно воспользоваться объединением ULARGE_INTEGER, основанном на структуре, состоящей из двух полей:

DWORD LowPart; - содержит младшую часть числа;
DWORD HighPart; - содержит старшую часть числа;

Присвоив этим полям 32-х битные значения младшей и старшей частей числа, из поля QuadPart можно получить 64-битное число.

ULARGE_INTEGER ul;
ul.HighPart = FindFileData.nFileSizeHigh;
ul.LowPart = FindFileData.nFileSizeLow;
ULONGLONG fileSize = ul.QuadPart;

Тип ULONGLONG представляет 64-битное число.
Таким образом, мы получили размер файла, какой бы большой он не был.
Как мы помним, в переменной filePath содержится относительный путь к файлу + его имя + ‘\*’. Очевидно, для дальнейшей работы с файлом нам надо избавиться от последних двух символов:

filePath.erase(filePath.size() - 2, 2);


Контрольная сумма



Нужен еще один атрибут файла, по которому можно было бы сделать проверку или сравнить. Сначала я реализовал этот атрибут в виде последней модификации файла. Но целостность файла, таким образом, не проверялась. Тогда, я решил сделать проверку контрольных сумм. При этом текстовые файлы, в отличие от двоичных, не поддаются такой проверке. Поэтому, для них проверка осуществляется только по размеру. Мы еще вернемся к этому вопросу. Вычисление контрольных сумм для небольших файлов проходит очень быстро, но если файл большой по размеру, тогда контрольная сумма для него вычисляется очень долго. Поскольку, программе надо считать файл целиком в оперативную и/или виртуальную память, а это при больших файлах занимает длительное время. Тогда, я решил читать не весь файл, а только его часть, а именно 4194304 бита или 4 мегабайта. По моим вычислениям и тестам это достаточное число для двоичных файлов, а для текстовых, как я сказал выше, сравнение по контрольным суммам не подходит.
Для подсчета контрольных сумм я решил воспользоваться средствами библиотеки Boost. Поскольку, они очень хорошо оптимизированы. Итак, объявляем переменную типа boost::crc_32_type для работы с контрольной суммой, открываем бинарный файл для чтения, читаем в подготовленный буфер размером 4 мб данные, затем, методу process_bytes объекта  типа crc_32_type передаем считанный буфер и количество считанных символов. В результате, метод на основе переданных данных вычисляет контрольную сумму и сохраняет ее внутри объекта. Чтобы получить ее, достаточно вызвать метод checksum(). Контрольная сумма представляет собой целочисленное без знаковое 32-х битное значение – UINT32.

UINT32 calcCtrlSum(string filePath)
{
   //вычисление контрольной суммы
   boost::crc_32_type crc_sum;
   ifstream ifs(filePath, ios_base::binary);
   if (ifs.is_open()) {
                   char *buffer = new char[buffer_size];
                   ifs.read(buffer, buffer_size);
                   crc_sum.process_bytes(buffer, ifs.gcount());
                   delete[] buffer;
   }
   //***
   UINT32 crc = crc_sum.checksum();

   return crc;
}

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

fout << "\"" << filePath << "\"" << "   " << fileSize << "   " << crc << endl;

Те же данные выведем в консоль:

cout << "\"" << filePath << "\"" << "   " << fileSize << "   " << crc << endl;

Увеличим счетчик файлов и суммарный объем на размер текущего файла:

fc++;
filesSize += fileSize;

После тела цикла переходим к следующему файлу, в случае его отсутствия прекращаем выполнение:

} while (FindNextFile(hf, &FindFileData) != 0);

В конце, закрываем дескриптор файлового поиска.
Тем самым, функция соберет информацию обо всех файлах, находящихся в текущем с исполняемым файлом программы каталоге, а так же во всех вложенных папках и сохранит эти сведения в файл.



Во второй части обзора читай обзор исходника самого лаунчера.

вторник, 15 декабря 2015 г.

Встраивание рекламы в торковские игры 

Юрий “yurembo” Язев
независимый игродел

После того, как я разместил в Google Play свои игры, мне стали приходить разного рода предложения о повышении продаж: через рекламу за определенную плату, за определенный процент от продаж, etc. Одно из этих предложений мне понравилось, показавшись вполне реальным. Поэтому, сегодня, я хочу рассказать как реализовать рекламу в игре на движке Torque 2D.

Итак, я решил воспользоваться услугами сервиса AdBuddiz
Дальнейшее повествование будет вестись на основе моей игры MagicMancala, уже выложенной в Google Play, не беда, загрузим 2-ю версию. 

Чтобы зарегистрировать нашу Android-игру, надо сначала зарегистрироваться на сайте http://www.adbuddiz.com/. Затем, перейти в раздел Dashboard (открывается автоматически) и нажать кнопку Add app. Тебе предложат выбрать платформу: Google Play, Apple Store. На следующей странице надо ввести имя пакета и нажать Next.

Дальше, если приложение уже успешно зарегистрировано в Google Play, загрузиться новая страница, на которой отобразится иконка приложения и будет предложено выбрать стандартную ориентацию. 
Жмем кнопку “Create App”. На следующей - финальной странице, сообщающей об успешном добавлении приложения, будет выведен Publisher Key для твоей игры. 
Этот же ключ будет находиться в письме, которое придет на ящик, указанный при регистрации на http://www.adbuddiz.com/. На этом, добавление приложения завершено. Итак, Publisher Key у нас уже есть, совсем скоро он нам понадобится.

Откроем проект игры в Android Studio. Заодно, я заменил титульную картинку, а так же иконку для запуска игры.

Новая титульная картинка

Далее, откроем файл build.dradle (Module: app), в начало файла, после описания параметров signingConfigs { … } добавим следующий код:

dependencies {
    compile 'com.purplebrain.adbuddiz.sdk:AdBuddiz-Java:3.+'
}
repositories {
    maven {
        url 'http://repository.adbuddiz.com/maven'
    }
}

Затем на инструментальной панели Android Studio надо нажать кнопку «Sync Project with Gradle Files». После это откроем AndroidManifest.xml. В начало файла после описания манифеста добавим следующий код:

<!-- Mandatory permission -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- Highly recommended permission to get more ads and revenue -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Кроме этого, в раздел тэга <application …> перед его закрытием вставим такие строчки:

<activity android:name="com.purplebrain.adbuddiz.sdk.AdBuddizActivity"
          android:theme="@android:style/Theme.Translucent" />

Тем самым, мы дополняем тэг <activity …>, поскольку он уже написан. 
Еще в этом файле надо увеличить номер версии приложения - строчка: android:versionCode="2" в манифесте. Потому что, при загрузки в Google Play приложения с одинаковым именем и той же версией, магазин сообщит о нарушении и откажет размещать.

Следующим шагом надо настроить SDK и включить объект рекламы. В обычных android-приложениях есть java-метод protected void onCreate() { … }, в нем и осуществляется это действо, однако, игра на движке Torque 2D не совсем обычное android-приложение, оно полностью написано на C/C++, и, хотя в нем имеется код инициализации на java, метод onCreate() там отсутствует. Но есть аналогичный метод, в котором можно произвести настройку рекламы. Откроем файл MyNativeActivity.java и перейдем в метод onWindowFocusChanged. Он выглядит следующим образом:

@Override
  public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        getWindow().getDecorView().setSystemUiVisibility(
              View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                      | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                      | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                      | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                      | View.SYSTEM_UI_FLAG_FULLSCREEN
                      | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}
  }
}

После установки свойств окна добавим такие строчки:
AdBuddiz.setLogLevel(AdBuddizLogLevel.Info);
Тем самым, мы включили средства логирования данного SDK.
В случае отсутствия регистрации приложения, для проведения проверки на успешность настройки SDK, надо включить тестовый режим (даже если приложение уже зарегистрировано, как в нашем случае, эту проверку лучше провести). Это делается такой строчкой:

AdBuddiz.setTestModeActive();

Затем, надо инициализировать  SDK для получения рекламы:

AdBuddiz.setPublisherKey("полученный Publisher Key");

С помощью этого метода мы передаем полученный при регистрации приложения Publisher Key для инициализации ключа, по которому будет определяться приложение в маркете, и в соответствии с его свойствами (как то: возрастные ограничения, etc) будет подбираться и показываться реклама. Между тем, если приложение не зарегистрировано (отсутствует Publisher Key), и/или нам надо осуществить проверку, тогда в качестве параметра методу надо передать строку: TEST_PUBLISHER_KEY. В этом случае, будет выведена стандартная реклама от AdBuddiz.
Следующим методом, наше приложение получает рекламу и сохраняет ее в кэше:

AdBuddiz.cacheAds(this);

Наконец, отображаем рекламу:

AdBuddiz.showAd(this);

В итоге, метод onWindowFocusChanged принял следующий вид:

@Override
  public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        getWindow().getDecorView().setSystemUiVisibility(
              View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                      | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                      | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                      | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                      | View.SYSTEM_UI_FLAG_FULLSCREEN
                      | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);}

      AdBuddiz.setLogLevel(AdBuddizLogLevel.Info);
      AdBuddiz.setTestModeActive();
      AdBuddiz.setPublisherKey("TEST_PUBLISHER_KEY");
      AdBuddiz.cacheAds(this);
      AdBuddiz.showAd(this);
  }
}

Стандартная реклама от AdBuddiz выглядит так:




Когда добавленный код будет протестирован, и ты убедишься в том, что все работает, как надо, удали строчку: AdBuddiz.setTestModeActive(); А в вызове метода setPublisherKey параметр “TEST_PUBLISHER_KEY” замени на полученный в результате регистрации приложения Publisher Key.

После регистрации приложения и задания ему выданного Publisher Key, в него стала приходить реальная реклама: