- PVSM.RU - https://www.pvsm.ru -
У меня было давнее желание разработать полноценную игру для социальных сетей, но не было интересных идей. Однажды я увидел в аппсторе игру в слова, которая была только на английском языке. Мне захотелось сделать аналогичную с русским словарем.
Смысл игры – составлять слова из соприкосающихся гексагонов. В игре 3 режима:
У оригинальной игры монетизация заключалась в том, что пользователю доступен 1 тип игры из трех. Остальные разблокируются после 50 и 100 сыгранных игр.
Я решил делать аналогичную монетизацию. Но в последствии добавил еще функционал подсказок слов. Подсказки можно докупать.
Дополнительная реклама:
Разработка велась с нуля (включая изучение фреймворка) в свободное от основной работы время, в первый месяц очень активно.
График затрат чистого времени на разработку (не включает время на документацию и форумы):
В первую очередь перед разработкой игры мне пришлось изучить особенности математики с гексагонами. Отличная статья, которая покрывает всё что мне надо было знать www.redblobgames.com/grids/hexagons/ [1]
Разработка была разбита на 2 части – на 2 автономных проекта:
Изначально была задумка используя кроссплатформенный фреймворк разработать игру для браузера и затем портировать в ios и android. Поэтому выбор пал на фреймворк cocos2d версию html5.
К основному недостатку данного фреймворка можно отнести плохую документацию, некоторые вещи приходилось искать в исходниках. Но подкупило использование этого фреймворка некоторая его популярность, а также участие компании zynga в его разработке. В целом я в нем не разочаровался.
Особенностью данного фреймворка является то, что на экране всё разделено на слои и объекты-спрайты. Которые по умолчанию отрисовываются с частотой 60 кадров/сек. Можно также активировать принудительно или автоматический выбор webgl режима, который задействует аппаратное ускорение для графики.
Всю графику я заказывал на фриланс сайте после того, как был готов прототип. Отдельно пришлось заказать иконку, которая стала более выигрышной.
Если посмотреть на основной экран игры, то можно увидеть множество объектов – это гексагоны и буквы. В двух играх их общее количество насчитывает порядка 5 тыс. Это вызвало первую проблему – фреймворк все их перерисовывает 60 раз в секунду и жутко тормозит.
Для решения подобной проблемы нашлось одно решение:
var size = cc.director.getWinSize();
var x0 = size.width / 2 - (3 / 4 * this.game.tileWidth * this.game.size) / 2;
var y0 = size.height / 2 + this.game.tileHeight * this.game.size / 2;
var hex = cc.textureCache.addImage(res.Game_png);
this._hexBatch = new cc.SpriteBatchNode(hex, 50);
this._tableLayer.addChild(this._hexBatch);
for (var i = 0; i < this.game.size; i++) {
for (var j = 0; j < this.game.size; j++) {
var layer = this.game.table[i][j];
if (!layer || layer == '*') {
continue;
}
var height = Math.sqrt(3) / 2 * layer.width - 2;
var width = layer.width - 2;
var x = x0 + i * 3 / 4 * width;
var y = y0 - j * height / 2 - (j + 1) * height / 2 - (i % 2) * height / 2;
var border = new cc.Sprite('#' + layer.hexagon_border);
border.x = x;
border.y = y;
this._hexBatch.addChild(border);
layer.attr({
x: x,
y: y
});
this._hexBatch.addChild(layer);
}
}
this.addChild(this._tableLayer);
this.wordPreviewLayer = WordPreviewLayer.create();
this.addChild(this.wordPreviewLayer);
Единственная проблема – это имеет смысл только при активном WebGL у пользователя, без наличия аппаратного ускорения, оно не будет работать аналогичным образом.
Весь проект поделен на отдельные компоненты, каждый из которых лежит в отдельном классе и файле. Напрямую они друг с другом не контактируют. Взаимодействие реализовано с помощью глобальных событий. Cocos2d содержит класс EventManager для работы с событиями.
Было создано некоторое количество собственных событий.
var Timer = cc.LabelBMFont.extend({
started: false,
paused: false,
lastTime: null,
timerLength: 0,
alert: 20,
listeners: [],
init: function () {
this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_START, function (data) {
this.start(data.getUserData());
}.bind(this)));
this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_STOP, function () {
this.stop();
}.bind(this)));
this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_PAUSE, function () {
this.pause();
}.bind(this)));
this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_CONTINUE, function () {
this.resume();
}.bind(this)));
this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_ADD, function (data) {
var seconds = data.getUserData();
this.add(seconds);
cc.eventManager.dispatchCustomEvent(EVENT_TIMER, this.timerLength);
}.bind(this)));
this.listeners.push(cc.eventManager.addCustomListener(EVENT_TIMER_END, function (data) {
this.timerLength = 0;
this.stop();
}.bind(this)));
return true;
},
cleanup: function() {
this._super();
for (var i in this.listeners) {
cc.eventManager.removeListener(this.listeners[i]);
}
},
Пример вызова события:
cc.eventManager.dispatchCustomEvent(EVENT_TIMER_START, this.timer_start);
При работе с кастомными событиями тоже есть некоторые особенности: после того как объект будет уничтожен, eventManager все равно будет его считать подписавшимся. Для решения этой проблемы для каждого объекта заполняется локальный массив listeners, и при уничтожении объекта всегда вызывается метод-деструктор cleanup, в котором я удаляю добавленные кастомные методы.
С мобильной версией игры так и не сложилось.
Во-первых, игра запускается в мобильной среде в эмуляторе SpiderMonkey, в котором по какой то причине не заработало кеширование. Из-за чего игра ужасно тормозила на айпаде.
Во-вторых, некоторые написанные мной конструкции вызвали ошибку, код необходимо местами дорабатывать для совместимости.
Затею портирования для ios и андроид в данный момент отложил.
Позже в игре были добавлены 50 ачивок. Работа с ними оказалось не такой сложной. Для реализации ачивок создаем массив, который содержит параметры и метод init для инициализации обработчика, который будет фиксировать выполнение условия ачивки:
var achievements = [
{
id: 'the_graduate',
name: t('The Graduate'),
description: t('You completed Tutorial'),
game: 'Tutorial',
icon: 'graduate.png',
pts: 1,
init: function () {
var event = cc.eventManager.addCustomListener(EVENT_TUTORIAL_END, function () {
sendAchieve(this, event);
}.bind(this));
}
}, {
…
}];
И добавляем инициализацию всех ачивок, которые пользователь еще не заработал.
cc.eventManager.addCustomListener(EVENT_GAME_LOADED, function () {
for (var i in achievements) {
if (!cc.UserData.achievements || cc.UserData.achievements.indexOf(achievements[i].id) == -1) {
achievements[i].init();
}
}
cc.eventManager.addCustomListener(EVENT_SEND_ACHIEVEMENT, function(data) {
var achieveData = data.getUserData();
if (!cc.UserData.achievements) {
cc.UserData.achievements = [];
}
cc.UserData.achievements.push(achieveData.id);
})
});
В данном примере при окончании обучающего уровня пользователю выводится всплывающее сообщение о заработанной ачивке, отправляется запрос на сервер и ачивку добавляем в массив информации о пользователе.
При загрузке игры с сервера подгружается словарь слов, по которому осуществляется поиск при выделении каждой буквы. Первоначально использовался простой массив со словами, что сказывалось на общей производительности, т.к словарь содержит порядка 150 тыс. слов. Занявшись поиском решения этой проблемы, я наткнулся на понятие Trie или Префиксное дерево. В результате исходный массив был сконвертирован в файл в формате trie данных. Данное решение позволило заметно уменьшить тормоза при выделении каждой из буквы слова.
Перед тем, как передать js исходники во второй проект, необходима сборка и минификация всех файлов в один js файл. Для этого в cocos2d уже есть готовый build.xml файл, который был дополнен. И в результате сборка производится одной командой в консоли – ant. Готовый файл просто копируем во второй проект.
В качестве бекэнда выбор пал на symfony 2 и бд mongo только потому, что для меня это быстрый и простой способ реализовать бекэнд часть.
Здесь можно выделить только одну особенность – для каждой соц сети был создан отдельный файл интеграции и build.xml создавал 3 минифицированных файла для каждой социальной сети.
Весь проект размещается на одном
Для оптимизации трафика была включена gzip оптимизация. Но это увеличило нагрузку на процессор в момент, когда был большой трафик в одноклассниках. В результате было реализовано такое решение:
gzip_static on;
Для каждого js файла скрипт build.xml создавал сжатую версию с расширением .gz В результате nginx просто брал эти файлы и отдавал клиенту без какой либо нагрузки на процессор.
Игра в первую очередь была запущена в ВК. На следующий день после одобрения игра была добавлена в раздел новые.
Отдельный всплеск был через 2 недели, когда я заказал новую иконку, которая смотрелась более логично.
С Фейсбуком не очень понятная ситуация. Все же мне кажется, туда без рекламы бессмысленно лезть — в каталоге много мобильных игр.
Статистика следующая. В начале января виден явный всплеск, который резко ушел в ноль.
Для перевода заработанных средств необходимо предоставить документы. Здесь тоже я не понял – можно зарегистрироваться как частное лицо, но они просят еще ОГРН.
Для запуска игры в ОК необходимо иметь ИП или юрлицо. Но можно найти людей, которые уже имеют свои игры в ОК и через них опубликоваться, договорившись на сумму от 10% перечисленных на р/с счет (ОК забирает себе чуть больше 50%).
В ОК статистика выглядит интереснее:
Общая статистика гугла с момента запуска:
Также у Гугла есть возможность сохранять события. Например, есть такая статистика по событиям в игре:
Статистика нагрузки на CPU:
ВК:
ОК:
ФБ:
Можно сделать вывод, что для какого-то заработка необходима игра с обязательным донатом. Также все виды монетизации и рекламу необходимо подключать сразу же. Так как я не был знаком с популярными рекламными площадками и фактическим количеством трафика, не подключил их позже, когда первая волна трафика прошла.
Также необходимо производить нагрузочное тестирование, поскольку заранее неизвестно, как пойдет игра.
Автор: galmi
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/vkontakte/81549
Ссылки в тексте:
[1] www.redblobgames.com/grids/hexagons/: http://www.redblobgames.com/grids/hexagons/
[2] vps: https://www.reg.ru/?rlink=reflink-717
[3] Источник: http://habrahabr.ru/post/249631/
Нажмите здесь для печати.