Как самому создать свою собственную игру в ВКонтакте

Что происходит в игре вообще. механика.

Да нечего тут сказать. Нажимаешь кнопку — самолёт летит вверх/вниз. Только вот лететь в нужном направлении у играющего он начинает сразу, а у соперника не сразу, ибо долго ожидание злосчастного мэссаджа о том, что оппонент уже давно нажал кнопочку вверх и летит себе… Собственно это и есть проблема с пингом.

Чем больше пинг — тем быстрее картина становится разной у двух клиентов. Решил проблему гениально — каждый N промежуток времени (1.5 — 3сек) делается синхронизация о реальном положении дел. Появились конечно незначительные скачки самолётов во время этой синхронизации (в радиусе 2-3px), но это не так заметно, как то, что вот стреляешь ты в соперника, видишь как ракета пролетает его насквозь, а не попал — потому что на самом деле игрок уже может быть вообще в другом углу и палить оттуда в тебя. И так туда-сюда.

Вся система была построена в духе клиент-(сервер-проверка)-клиент(ы).

Вступление. (это можно пропустить)

Путь до идеи полученной игры был очень долог. Всё началось с того, что я познал JQuery. Да. Это было золотое время. С помощью тупых анимаций я делал всякую хренотень вроде движения смайлика по полю в место клика мыши. Когда этот уровень достиг совершенства, мне захотелось добавить в эту «игру» онлайновости. И тут понеслась…

Читайте также  Стикеры Telegram

Первое что мне пришло в голову, это отправка каждые N секунд запроса на сервер, не сделал ли что-то второй игрок. Всё было сделано на PHP MySQL. Данные о месте и времени клика сохранялись в базе и выдавались игрокам по запросу. Исходников чуда к сожалению не осталось, ну и ладно.

Следующим жизненно важным этапом стала производительность. Вариант с постоянными запросами к серверу естественно отпал, и была необходимость искать другие решения. Я перебирал всякие Comet-ы, флэши… но у меня это всё не получалось даже просто поставить на сервер.

Это всё было для меня как открыть Америку, я не спал ночами и писал код, делал тесты… как вдруг заметил что моя игра застряла на месте. Немного поразмыслив я стал делать своё первое творение, похожее на игру. Опять же… весть клиент был на JQuery, те же анимации, те же движения смайликов.

Всё понемногу усовершенствовалось, добавился чат, несколько видов оружия (ага, смайлики стреляли). Большая карта была обычной картинкой размером 10к х 10к пикселей. Тогда же я узнал о Chrome Dev Tools. Увидев то, как моё детище жрёт память я стал снова углубляться в недры гугла.

Узнал о том что уже существует HTML5 со своим Canvas-ом. Это стало еще одним поводом порадоваться. Опять бессонные ночи изучения API (слово ночи во множественном числе потому что JS я в общем то знал крайне поверхностно, и вот пришло время заняться и им вплотную).

Ох уж это 3D. Тупые попытки создания в браузере 3D фигур в основном ни к чему не приводили. То есть их получалось создать на странице, даже двигаться между красными шарами и серыми кракозябрами, слепленными в блендере. На удивление, при написании статьи обнаружил что на серваке эта ночь под названием «жена в ночной смене» еще жива.

Ссылка для особо любопытных. — можно даже вращать мышкой и двигаться стрелками. TreeJS показался мне очень сложным и опять гугл пришёл мне на помощь. Там я узнал о Unity3D. Вообще у меня имеется дурацкая привычка делать тупые тесты производительности.

При первом запуске юнити я был удивлён на сколько удобно сделан интерфейс, и даже не имея навыков работы с такого рода программами я сумел сделать карту с горами, тропинками и водопадами. С водопадами как то не срослось, случайно добавил их на карту несколько десятков тысяч и при рендере у меня отказала видеокарта. Ну что поделать… сделал отличную кладку кирпичей.

Взлёт

Дело осталось за малым. Друг, занимающийся группами, сделал несколько рекламных постов, и тут приложение как с цепи сорвалось:

За неделю посещаемость сама по себе подскочила до 125 тысяч в день. Признаться, такой нагрузки я не ожидал, поэтому пришлось даже арендовать сервер по мощнее. Воодушевленный таким успехом, я сделал еще несколько тестов на готовой платформе. они набрали 1’300’00, 300’000 и 100’000 тысяч установок на сегодняшний день. Рассказывать о них не вижу смысла, они аналогичные.

Первыми граблями, на которые я наступил, был лог файл php, разросшийся аж до 800гб. Произошло это из-за нескольких ошибок уровня notice, которые логировались при каждом прохождении теста пользователем.

Интересно, что в первый месяц существования, тест пользовался популярностью у старшей аудитории (25 ), однако месяцем позже школьники перехватили инициативу.

Любопытства ради, я добавил следующий вопрос «Как вы думаете, доллар завтра подорожает?» и начал вести по нему статистику, чтобы предугадывать курс валют по настроениям населения. Но до дела так и не дошло, так что, если кому интересно, могу предоставить базу данных для анализа.

Второй запуск

Когда я позакрывал дыры, понаставил try…catch-ей, научился проверять на баше запущенность сервера (скрипт ниже), припилил VK API на уровне получения фотки соперника, и выдачи ТОП-30 игроков (тоже с фотками между прочим, и даже с именами), стал опять тратить деньги на рекламу.

Скрипт проверки, запущена ли нода

Говнодизайн

Всё время разработки считал, что дизайн это просто. С этой идиотской мыслю я попрощался только после 3 версии релиза. Об этом позже… В общем, чтобы было понятно, выкладываю первый вариант «дизайна» «игры»:


Как видно, контрастные цвета, круглые уголки, отсутствие рамок немного напоминают деревню. «Как умею так и брею» — так что технари отнеситесь с пониманием, а дизайнерам интерфейсов надеюсь эта картинка музу не спугнула.

Делаем стимул играть

Из новых добавленных возможностей, основным достижением я считаю получение медалей и бонусов за какие-то заслуги. Сыграть 100 раз, добить пулемётом и т.д. естественно с предложением разместить красивую картинку медальки у себя на стене. Двух зайцев одним ударом. Социальная перделка была выполнена. Кстати, вот код для загрузки фотографии на сервер Вконтакте:

И снова немножко о говнодизайне

Тем летом мне посчастливилось купить жене айфон а себе забрать HTC на WP 7.5. Мне почему то жутко понравился плиточный интерфейс. Он простой и понятный. Реально простой и понятный. И захотелось мне начать «переписывать» игру с дизайна. Для себя решил, что это должно быть просто и понятно… короче плагиат с плиточками. Короче говоря, то что у меня получилось куда-то делось, так что

вам посчастливилось сейчас не закрывать рукой лицо в ужасе

даже не смейте думать об этом. Но поверьте на слово, это выглядело гораздо лучше, чем предыдущая версия.

Искусственный интеллект

О возможности игры с компьютером я уже думал, но как то не хотел лезть в эту кашу, т.к. думал что это как всегда на долго у меня затянется. Но так как выше я уже говорил, что код игры получился модульным, за первый день я сделал возможность убивать болванку компьютера, за второй день сделал компьютеру мозги и припилил ему 30 уровней.

Код ИИ заключается в немногом: самолёт противника просто постоянно следует за твоим самолётом, и в случае, когда угол атаки становится приемлемым — стреляет из всего оружия. Тут опять тригонометрия 7 класса и не более того. Код:

Кодим, кодим, снова кодим

Взбредило же мне делать эти плитки, решил выпендриться (перед кем?) и сделать каждую плиточку на отдельном канвасе. Сделал конечно… но, простите,

плохое слово, обозначающее в русском языке запятую

, одни плиточки заняли у меня почему то половину всего клиентского кода.

После переписывания всего всего, пришло время припиливать VK api. Вот тут я бы зачеркнул весь текст далее но… постараюсь выразить кратко и прилично: функционала много — документации мало. В документации коды ошибок даны не все, половины параметров не описано, результат возвращается в виде ничего тогда для меня не значащего непонятного

массива объектов объектов

. К игре припиливал всё эти запросы к API по 1 в день, ибо опять же методом научного тыка. Дело привычки конечно. Тут больше нечего сказать. Ребят, пишите документацию как для своей бабушки. Новички теряются).

Концепция

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

И заявляю я это не просто так. Есть огромное количество хороших программистов, которые смогут реализовать любые задумки. Рекламщики и дизайнеры позаботятся о внешнем виде, который заинтересует ваших будущих игроков, и заставит их, по крайней, мере попробовать то, что вы им предлагаете.

Все это нюансы. А вот концепция, или если хотите, идея — вот что самое главное в разработке игры.

Вы должны сделать игровой процесс действительно интересным и увлекательным. Без этого успеха не добиться. Попробуйте представить себя на месте игрока. Вам самому было бы интересно проводить время за этой игрой?

Логика и говнокод

И вот, в один прекрасный свободный день, сел на работе, загрузил практикантов и стал снова экспериментировать с canvas-ом. Первые эксперименты были незатейливыми — заставить квадратик двигаться хотябы прямо (после того как в гонках квадратик ехал, я уже забыл как я это сделал и решил переписать всё заново уже с новыми знаниями).

Конечно, он начал двигаться спустя минут 10… потом решил добавить управление клавишами — вверх и вниз. И тут всё застопорилось. Этот канвас со своими матриксами и транслейтами, будь они неладны… расчетов было столько, что я уже не мог ориентироваться в коде.

Хоть и вся «физика» основывалась на геометрии 5 класса, а точнее геометрии различных треугольников =) Тригонометрию в школе ненавидел, о чем впоследствии сильно пожалел. Расчеты треугольников были слишком масштабными и тяжелыми, но изучив основы тригонометрии я понял какой был дурак. Но даже с тригонометрией

расчеты

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

else if(player[pid].key.up == false){
	if(player[pid].key.down == true){
		if(player[pid].alfa >= -359){
			player[pid].alfa -= (1.4*(time/player[pid].speed));
		}
		else{
			player[pid].alfa = 0;
		}
		player[pid].x  = k2*Math.cos(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed);
		player[pid].y  = k2*Math.sin(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed);
		player[pid].x = rezak(pid,real,player,k1,k2);
		if(pid == 'green'){
			real['green'].x = player['green'].x   k2*(player[pid].rad * Math.sin(player['green'].alfa*Math.PI/180));
			real['green'].y = player['green'].y-player[pid].rad   k2*(player[pid].rad * Math.cos(player['green'].alfa*Math.PI/180));
		}
		else if(pid == 'red'){
			real['red'].x = player['red'].x   k2*(player[pid].rad * Math.sin(player['red'].alfa*Math.PI/180));
			real['red'].y = player['red'].y-player[pid].rad   k1*(player[pid].rad * Math.cos(player['red'].alfa*Math.PI/180));
		}
	}
	player[pid].x  = k2*Math.cos(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed);
	player[pid].y  = k2*Math.sin(k1*player[pid].alfa*Math.PI/180)*2*(time/player[pid].speed);
	player[pid].x = rezak(pid,real,player,k1,k2);
	if(pid == 'green'){
		real['green'].x = player['green'].x   k2*(player[pid].rad * Math.sin(player['green'].alfa*Math.PI/180));
		real['green'].y = player['green'].y-player[pid].rad   k2*(player[pid].rad * Math.cos(player['green'].alfa*Math.PI/180));
	}
	else if(pid == 'red'){
		real['red'].x = player['red'].x   k2*(player[pid].rad * Math.sin(player['red'].alfa*Math.PI/180));
		real['red'].y = player['red'].y-player[pid].rad   k1*(player[pid].rad * Math.cos(player['red'].alfa*Math.PI/180));
	}
	ctx.save();
	ctx.translate(player[pid].x, player[pid].y-player[pid].rad);
	ctx.rotate(k1*player[pid].alfa*Math.PI/180);
	ctx.drawImage(player[pid].img, -22, (player[pid].rad-8));
	ctx.restore();
}

Меняем курс

При всей вирусности тестов, у них есть один большой недостаток — малый возврат аудитории. По второму разу заходить в такое приложение будут единицы. Поэтому я решил сделать игру. Точнее, я взял за основу игру 2048 Gabriele Cirulli, тогда о ней еще никто не знал. Не смотря на то, что игра opensource, я всё-таки спросил разрешения лично у

Получив положительный ответ, я принялся за дело. Спустя несколько дней игра под названием “Стаккер 2048” прошла модерацию.

Внезапно появились и вторые грабли. На следующее же утро приложение взломали простейшей XSS атакой. О том, что пользовательским данным нельзя доверять, я тогда попросту не знал. Школьник подставил скрипт вместо своего имени в списке «Топ 100», в результате чего матерные алерты не давали игрокам ничего сделать.

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

Были и SQL инъекции и CSRF уязвимости. Один раз мне снесли всю базу данных. Кто-то даже пытался положить сервер генерируемыми картинками, подавая на вход файлу-генератору случайную строку 10 раз в секунду. Теперь, набив шишек, я использую токен и сессии для каждого пользователя, передаю все данные POST запросом, прикрепляя хэш от всех передаваемых данных, который проверяется на сервере. С тех пор ничего страшного, к счастью, больше не происходило.

Граблями номер три стали генерируемые картинки. Если для 2048 число набранных очков для многих пользователей совпадает и проще хранить картинки с результатами, чем каждый раз генерировать их снова, то для тестов исход всегда разный и картинок накапливается очень много. Поэтому приходится при помощи планировщика Cron чистить папку с картинками каждые 10 минут.

На данный момент в «Стаккер» играют 180’000 человек, причем возвращаемость пользователей очень высокая

Для игр Вконтакте предусмотрен специальный стартовый влив трафика — добавление приложения в категорию новые, на самый верх. Стоит это 1000 голосов, при этом нужно выждать полтора месяца в очереди и обязательно иметь в приложении встроенные покупки

Модерация

Когда приложение создано и включено в настройках, оно может быть запущено по прямой ссылке любым пользователем. Для того, чтобы попасть в поиск и каталог, оно должно пройти модерацию. Для этого нужно оставить в качестве залога 10 голосов (внутренняя валюта вк) и ждать. Очень часто при первой модерации ответ от техподдержки выглядит так:

Внимание: уточнить, что именно модераторам не понравилось при таком ответе не получится. Вам вежливо скажут, что они не дают таких справок

В редких случаях техподдержка отклоняет заявку и сразу говорит Вам, что нужно поправить и даже присылает скриншот. Вероятнее всего, у них есть какой-то чеклист при помощи которого они проверяют приложения. Мою ситуацию спасло добавление кнопки «о приложении», которая не несет абсолютно никакой смысловой нагрузки, однако, после ее появления приложение, наконец-то, одобрили. Залог, кстати, всегда возвращали.

Монетизация

Как только посещаемость пошла в гору, я всерьез задумался о монетизации своих приложений. Особого выбора не было, так как партнерских программ, работающих напрямую с Вконтакте не так много. Я остановился на прелоудере, который показывает короткий рекламный ролик каждый раз при входе в приложение, если пользователь попадает под целевую аудиторию.

Это примерно 60% всей аудитории. И рекламные объявления в стиле тех, что контакт показывает сам. При текущей посещаемости, доход с четырех моих приложений составляет в среднем 2000р в сутки. Для отслеживания посещаемости я сделал отдельную страницу, на которой при помощи HTML canvas вывожу графики активности пользователей.

Немножко статистики

В первые дни игру установило 6-7 тыс. человек, что не могло меня не радовать (конечно, сравнивать то несчем). Так, к слову, интересный (а может и нет) момент произошел вот тут

(обведено в кружок)

Там видно какая дата, так что о возрасте игроков не трудно догадаться. Будьте осторожны в выборе дат релиза.


Всё, после этого я совсем отчаялся, установки шли на спад, онлайн был максимум полтора «пилота». Это включая меня.

Единственный плюс, который заставил меня продолжать работу — это нехилые предложения о покупке игры. В первый день было много предложений об обмене, и цены до 80тыс.р. Во второй день мне предлагали до 7 тыс. $, теперь жалею что не согласился.

Оптимизация клиента

Итак, я всё-таки решился поплотнее почитать про синусы-косинусы и впервую очередь избавиться от отдельных расчетов для отрисовки и реальных координат. Тут рассказывать особо нечего, но код стал явно получше.

this.draw = function(t){
	this.x2 = this.x;
	this.y2 = this.y;
	this.s2 = this.s;
	this.r2 = this.r;
	this.cosa2 = this.cosa;
	this.a2 = this.a;
	this.timeK = (t > 40) ? 1 ((t-40)/40) : 1;
	this.x  = this.timeK*this.koef*this.s*Math.sin((90-this.ra)*this.PI/180);
	if(isNaN(this.x)) this.x = this.x2 (2*(this.timeK*this.koef*this.s*Math.sin((90-this.ra)*this.PI/180)));
	this.y -= this.timeK*this.koef*this.s*Math.sin(this.ra*this.PI/180);
	if(isNaN(this.y)) this.y = this.y2-(2*(this.timeK*this.koef*this.s*Math.sin(this.ra*this.PI/180)));
	this.s -= this.timeK*(this.koef*Math.sin(this.ra*this.PI/180))*(this.wspeed/(this.y/this.wspeed))/4.5;
	if(isNaN(this.s)) this.s = this.s2-(2*(this.timeK*(this.koef*Math.sin(this.ra*this.PI/180))*(this.wspeed/(this.y/this.wspeed))/4.5)); 
	if(this.s <= 0) this.s = this.koef*0.0001;
	this.r = this.timeK*this.s*50/this.wradius;
	if(isNaN(this.r)) this.r = this.r2;
	if(this.r == 0) this.r = this.koef*0.000000001;
	this.cosa = this.timeK*(this.r*this.r this.r*this.r-this.s*this.s)/(2*this.r*this.r);
	if(isNaN(this.cosa)) this.cosa = this.cosa2;
	this.a = this.timeK*this.koef*Math.acos(this.cosa)*180/this.PI;
	if(isNaN(this.a)) this.a = this.a2;
	for(var i = 0; i < weapons.all.length; i  ){
		if(this.id && this.per[weapons.all[i]] <= this.tper[weapons.all[i]]){
			this.per[weapons.all[i]]  = (t > 40) ? t 40 : 40;		//время выстрела (наращивается с каждым кадром)
		}
	} 
	if(this.id){
		this.opacity = (this.per.M < this.tper.M) ? 0.6 : 1;
		if(this.debaff.M.per != 1) this.debaff.M.per  = (t > 40) ? t 40 : 40;
		if(this.debaff.E.per <= this.debaff.E.tper) this.debaff.E.per  = (t > 40) ? t 40 : 40;
	}
	if(this.x > 700 this.w) this.x = -this.w;
	if(this.x < -this.w) this.x = 700 this.w;
	if(this.key == 'up') this.ra  = this.a
	else if(this.key == 'down') this.ra -= this.a;
	if(isNaN(this.x) || isNaN(this.y) || isNaN(this.r) || isNaN(this.a) || isNaN(this.ra) || isNaN(this.s) || isNaN(this.cosa) || isNaN(this.timeK)){
		this.x = (par.side == 'left') ? 40 : 660;		
		this.y = 450;
		this.s = this.wspeed;
		this.r = this.s*50/this.wradius;
		this.cosa = (this.r*this.r this.r*this.r-this.s*this.s)/(2*this.r*this.r);
		this.a = 0;
		this.ra = this.a;
	}
	if(this.opacity >= 0.5){
		ctx.globalAlpha = this.opacity;
		ctx.save();
		ctx.translate(this.x, this.y);
		ctx.fillStyle = '#f00';
		ctx.strokeStyle = '#000';
		ctx.fillRect(-34,-40,(this.life/100)*67,4);
		ctx.strokeRect(-34,-40,67,4);
		ctx.rotate(-this.ra*this.PI/180);
		ctx.drawImage(this.image, -this.w/2, -this.h/2, this.w, this.h);
		ctx.restore();
	}
}

Тут вроде всё понятно кроме имён переменных, расскажу про проверки на NaN. Одним из игроков был обнаружен (и позже проверен мной) неприятный баг — самоёт резко оказывается в левом верхнем углу и всё, можно выходить из игры. Всё из-за того, что при просчете некоторые переменные с небольшой долей вероятности становились === 0, а потом на них приходилось делить, и получалась такая лажа. После вкропления в код проверки на NaN баг изчез и все довольны.

if(this.opacity >= 0.5){…} код именно отрисовки на канвасе выполняется только в случае, если самолёт не находится в режиме невидимости (да, есть и такой бонус)

Еще момент — функция принимает параметр t. Он по сути нужен для слабых компьютеров/планшетов. Тоесть в этом параметре содержится инфа о том, сколько по времени рисуется кадр, и если это больше 40мс (всё работает на скорости 25 кадров в секунду), то все передвижения самолёта необходимо пропорционально увеличить. Эти изменения сократили время отрисовки примерно на 2-3мс, что уже не плохо.

Следующим шагом стала оптимизация дыма от самолёта, т.к. больше всего памяти и процессора кушал именно он. О том как реализован дым, я писал в этой статье. Для оптимизации я сделал другую картинку — меньше в размере и которую не надо вращать (серый круглый градиент), и один раз отрисовал его на невидимом канвасе (буфер), а потом копировал с него.

Потом нашлась самая большая дырка — в отрисовке показателя перезарядки и количества для каждого оружия. В бою это выглядит так:

Стрелками показаны штуки, отрисовка которых была наиболее сложной (arc). Я решил, что т.к. большинство оружия бОльшую часть времени находится в полной перезарядке (или с отсутствием патронов (нижняя полосочка)), то можно просто нарисовать эти состояния на отдельной канве, и при необходимости просто рисовать готовое (не рисовать вообще). Самое интересное — что оптимизация этих 32 полукругов дала наибольший эффект — аш 5-6мс. И теперь миссия выполнена. При максимально возможном дыме у обоих игроков (чем меньше жизней тем больше дыма), кадр стал рисоваться 1-2мс, а без дыма вообще меньше 1мс (0.2-0.3 если брать среднее). Все цифры для Chrome 32.

Оптимизация сервера

Проверять каждую выпущенную ракету на сервере оказалось слишком затратно, а уж тем более отсылать ответ клиенту (socket.io не совершенен, как оказалось). По этому эту ответственность я перенес на клиента, но чтобы всё-таки уберечься от читеров, на сервере существует просто выборочная проверка сомнительных пользователей (такую штуку соорудил умную — у каждого пользователя на сервере накапливается определенное количество ошибок, после чего за ним устанавливается более тщательный контроль, вплоть до того что я лично проверяю логи игрока — ну это совсем если подозрительный попался).

И ответ от сервера в случае, например, отсутствия оружия не идёт — а что, я еще и читера должен уведомлять о том что у него не получится? В общем то это сняло колоссальную часть нагрузки и на ноду и на базу. Итог составил 5% CPU на 150чел. онлайна. Памяти кушает вообще копейки.

Опять переделки

По неуспеху в плане установок и гневных отзывов игроков я снова понял что это провал. Но в нашем деле главное не сдаваться! И опять что? Да, я опять стал всё переписывать. По второму запуску я сделал следующие выводы:

Начал как всегда опять с дизайна, и это уже было больше похоже на правду. Показал друзьям, сказали «ВАУ это гораздо лучше того дерьма!», но я этого ВАУ не почувствовал, и сделал наконец первый правильный шаг — пошёл на фриланс искать дизайнера интерфейса.

Это уже финальный вариант, который существует сейчас. О большем я и не мечтал.

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


Обучение делать оказалось для меня геморойным занятием, и я обошелся просто созданием подсказок на каждой кнопке.

Первые шаги в сервисе разработчиков

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

Первый пошёл

image

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

Но тестов было море, и нужно было придумать что-то лестное, что интриговало бы пользователей и подталкивало их делиться результатом на своей стене. И я решил сделать тест на психологическую зарплату. Вам нужно ответить на несколько простых вопросов, после чего приложение предлагает разместить запись с результатом на Вашей стене. Называется он “Какую зарплату ты заслуживаешь?”. На данный момент у него 1’700’000 установок.

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

Важно: не забудьте в настройках приложения выбрать, к какой персональной информации вам понадобится доступ. Иными словами то, что Вы будете запрашивать через api, в противном случае Вконтакте не выдаст вам эту информацию. image

Пост выглядит так:

Я решил добавить изюминку в приложение и в конце сделал подсчет, сколько и чего можно купить на Вашу «психологическую» зарплату

Переломный момент.

Думаю многим известна история успеха игры «Гонки на клавиатурах»? (ссылку на статью не нашёл, буду рад вставить). Когда я узнал, сколько зарабатывают разработчики УСПЕШНЫХ приложений в социальных сетях, мне, наверное, как и другим захотелось создать свою игру для социальной сети.

И я начал активную работу над этим. Узнал, что еще и юнити проект можно собрать под веб… Замысел игры был относительно простой — своя линейка с чем нибудь и куртизанками. Чтобы было чем страдать в юнити, я даже уже было начал переговоры с дизайнером, который сможет отрисовать для игры мобов, карты и т.д.

Конечно, на голом энтузиазме никто работать не захотел, и я принял решение сделать что нибудь «попроще», чтобы набить карман для первоначального бюджета более серьёзного проекта. Этим «попроще» стала идея сделать онлайн гонки 2d на HTML5, только не тот онлайн, в котором нажимаешь на соперника, ждешь, и «ты выиграл!/проиграл =(», а реальный онлайн с управлением автомобиля на трассе.

Чтобы еще больше облегчить себе жизнь, гонки решил делать драговыми =) Ну а что… никаких поворотов, меньше физики… Короче говоря, спустя пол года всё закончилось тестовой версией с 20 автомобилями. Я уже выдохся искать все параметры для машин, т.к. пытался сделать реалистичную (даже 2d) физику, нужны были такие параметры, как аэродинамическое сопротивление, графики крутящего момента и прочие характеристики, которые, как оказалось, не так легко найти.

Но эти гонки были для меня чем-то обучающим. Я изучал всё новые новые технологии, придумывал велосипеды из костылей, натыкался по нескольку раз на свои грабли… И зато за эти пол года сложился некоторый идеальный для меня скелет клиент-сервера. Если говорить конкретно, то на клиента canvas, на сервере nodejs socket.io.

Банально, криво, зато работало. А если работает, значит не трогай =) В основном проблемы я испытывал из-за незнания языка, и даже самые простые ф-ии, уже встроенные в JS я велосипедил сам, не зная об их существовании. Примерно так выглядел небольшой кусок серверной части:

io =  require('socket.io');
mysql = require('mysql');
players = []; //массив, содержащий игроков. Каждый игрок - объект.
races = []; //массив гонок, где каждый элемент - массив объектов 4 игроков
//тут какая-то инициализация чего-то
...
io.sockets.on('connection', function(socket){
   socket.on('message', function(msg){
      msg = JSON.pars(msg);
      switch(msg.type){
         case 1:
            ...
         break;
            ...
      }
   }
}

(Кому уже не интересно читать а хочется посмотреть — ссылка в конце статьи)

Внутри swith был код, отправляющий определённый ответ пользователю. Кстати о том, что в case можно писать не только цифры я еще не знал, по этому приходилось держать в голове все типы сообщений, которых было ни много ни мало — около 50. Среди них и авторизация, и начало гонки, и конец гонки, и чат, и разные разные внутриигровые события.

Я знал, что для сокета вместо использования switch конструкции можно сразу писать socket.on(‘тип сообщения’…), но такой подход создаёт лишнюю порцию спагетти, и не сильно большое удобство написания кода, по этому непосредственно серверная часть конечной игры написана именно так.

То-есть скелет такой, позже то я всё таки узнал что в case можно писать что вздумается =). На клиенте конструкция один в один, только нету массива всех игроков, есть только объект самого себя (причем глобальный), и огромный набор функций (тоже глобальных), берущих на себя роль обработчиков входящих сообщений.

Постепенно я узнавал возможности JS, NodeJS и socket.io. Помню первый успешно-провалившийся тест максимального количества подключений. При 10к – 250 коннектов socket.io терял отзывчивость и не принимал новые подключения. После долгой возни помогла вот эта статья. Там всё подробно описано так что повторяться не буду.

Как я уже говорил, с гонками я бодался пол года, и на большее сил не хватило. Про идею с 3d игрой я уже совсем забыл, и от неё остался лишь ватман на комнатной двери со структурой БД. И вот, так как гонки оказались для меня слишком сложными, я решил еще больше упростить себе задачу, и сделать самолётики, летающие в воздухе и просто стреляющие друг в друга.

Послесловие

Для меня лично, такой независимый заработок стал прорывом и отличным стимулом. Чем больше я занимаюсь приложениями, тем больше идей у меня появляется. Планирую в ближайшем будущем заняться разработкой чего-нибудь более серьезного. Надеюсь, этот пост был полезен и интересен.

Работа над ошибками

Пораскинув мозгами о том, что

кладут в шампунь для волос

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

Ошибка 1. Я думал что из меня выйдет неплохой дизайнер.Решил упростить ещё сильнее, сделал огромные кнопки с некрасивыми надписями — это чудо я тоже не хочу показывать из религиозных соображений.

Ошибка 2. Мало свистелок (оружия, обвеса, как хотите)Тут была проделана огромная работа (над ошибками, конечно). Кроме того, что я добавил 12 новых видов оружия/бонусов, я сделал для каждого из них «спецэффект» при попадании — кстати смотрелось ничего.

На этом этапе переписывание себя оправдало. Появилась некая модульность, благодаря которой я сейчас могу добавлять хоть по новому оружию в минуту, были бы идеи. Тут же была сделана суперзащита от кулхацкинга.Если раньше было так: клиент стреляет, клиент проверяет есть ли вообще оружие такое, всё он же (клиент) проверяет — не попала ли ракета в соперника, и если попала, то только тогда отправляем сообщение на сервер и он передает сообщение оппоненту о том что в него попали.

То стало вот так: клиент нажимает клавишу, на сервер передаётся код клавиши, сервер смотрит — на какое оружие назначена у игрока эта клавиша, проверяет перезаряжено ли оружие, есть ли оно вообще, можно ли им пользоваться на этом самолёте (самолётов тоже добавил, у каждого свои преимущества, в том числе какое оружие можно использовать), и ещё куча мелких условий.

В случае, если не было ни одного false, данные о выстреле отправлялись обоим игрокам. Если false все таки нашелся, то у стрелявшего вежливо (красными большими буквами) показывалось сообщение вроде «ПЕРЕЗАРЯДКА!….». Собственно, если интересно то вот так:

case 'shot':
	if(Date.now() < players[socket.id].per[msg.typeW]){
		console.log('перезарядка');
	}
	else if(players[socket.id].weapons[msg.typeW] <= 0){
		players[socket.id].weapons[msg.typeW] = 0;
		console.log('закончились боеприпасы');
	}
	else if(weapons[players[socket.id].plane].weapons.indexOf(msg.typeW) < 0 && weapons.weapons.indexOf(msg.typeW) >= 0){
		console.log('оружие недоступно');
	}
	else if(weapons[players[socket.id].plane].bonuses.indexOf(msg.typeW) < 0 && weapons.bonuses.indexOf(msg.typeW) >= 0){
		console.log('бонус недоступен');
	}
	else{
		if(players[socket.id].wrate != weapons[players[socket.id].plane].rate   players[socket.id].rate && Date.now() >= players[socket.id].per.P){
			players[socket.id].wrate = weapons[players[socket.id].plane].rate   players[socket.id].rate;
		}
		if(weapons.weapons.indexOf(msg.typeW) >= 0){
			msg.per = weapons[msg.typeW].rate*1000/players[socket.id].wrate;
			players[socket.id].per[msg.typeW] = Date.now()   msg.per;
			players[socket.id].weapons[msg.typeW] -= 1;
			players[socket.id].pot[msg.typeW]  = 1;
			msg.hit = weapons[msg.typeW].radius;
			msg.speed = weapons[msg.typeW].speed;
			msg.val = players[socket.id].weapons[msg.typeW];
			msg.side = players[socket.id].side;
		}
		else if(weapons.bonuses.indexOf(msg.typeW) >= 0){
			msg.per = weapons[msg.typeW].rate*1000*players[socket.id].wdamage;
			players[socket.id].per[msg.typeW] = Date.now()   msg.per; 
			players[socket.id].weapons[msg.typeW] -= 1;
			players[socket.id].pot[msg.typeW]  = 1;
			msg.val = players[socket.id].weapons[msg.typeW];
			msg.side = players[socket.id].side;
			switch(msg.typeW){
				case 'I':
					players[socket.id].life  = 10;
					msg.life = Math.ceil(players[socket.id].life);
					if(players[socket.id].life > 100){
						players[socket.id].life = 100;
						msg.life = 100;
					}
				break;
				case 'J':
					players[socket.id].wsuspension  = 2;
				break;
				case 'L':
					players[socket.id].kamikadze = true;
				break;
				case 'P':
					players[socket.id].wrate = 100000;
				break;
			};
		}
		socket.json.send(msg);
		io.sockets.socket(players[socket.id].oppSocket).json.send(msg);
	}
break;

Немного пояснения к коду. players — массив всех игроков и с идентификацией по socket.id. Каждый элемент массива — это объект игрока, который создается сразу при входе в игру, методом присваивания того, что вернула MySQL. На самом деле, между каждым else if стояла отправка ошибки юзеру, но я этот код не нашёл. weapons — это массив параметров оружий и бонусов.

Просчет попадания я сделал по хитрому: просчитывает оппонент. Как бы это описать дальше… после успешного выстрела 1 игрока, 2 игрок отрисовывает у себя ракету и смотрит, не попала ли она в него (в себя). Если попала, то отправляет мэссадж на сервер с содержанием «что попало и в каком месте на экране».

Сервер высчитывает урон, и отправляет обоим игрокам. При этом рисуется сказочный спецэффект и ракета исчезает. Заодно на сервере проверяется сколько там чего осталось, жизней и т.д. — и если жизней вдруг у игрока 2 стало меньше 0, то записываем всё в БД, отправляем игрокам инфу о том кто выиграл а кто проиграл и все счастливы.Если интересно, то вот клиентский код рассчета попадания:

for(var i = 0; i < weapons_1.length; i  ){
	if(weapons_1[i].draw({t:avatars[0].time2, x:avatars[0].x, y:avatars[0].y, ra:avatars[0].ra}) == 'coordLimit'){
		weapons_1.splice(i,1);
	}
	else if(weapons_1[i].s && Math.abs(avatars[0].x-weapons_1[i].x) < 80 && Math.abs(avatars[0].y-weapons_1[i].y) < 80){
		var dl = Math.sqrt(((avatars[0].x-weapons_1[i].x)*(avatars[0].x-weapons_1[i].x)) ((avatars[0].y-weapons_1[i].y)*(avatars[0].y-weapons_1[i].y)));
		if(dl < (avatars[0].w/4 weapons_1[i].w/4) weapons_1[i].hit){
			sock.send($.toJSON({type: 'hit', typeW: weapons_1[i].typeW, i: i}));
			weapons_1.splice(i,1);
		};
	}
}

weapons_1 — ничто иное как массив всех выпущенных оппонентом ракет. Как видно, чтобы не грузить память, когда ракета покидает пределы канваса, она удаляется из массива.

Второй кусочек — небольшая оптимизация. Я не знаю, на сколько это полезно, но вот чтобы не считать каждый кадр гипотенузу (в треугольнике ракета — самолёт), сначала идет небольшой расчет абсолютного положения на экране. И если ракета по приблизительным меркам слишком далеко, то и гипотенузу считать незачем.

avatars[0] и avatars[1] — это объекты игроков. После прочтения этой статьи, почему-то решил назвать это аватарами, так и повелось.

«Событие» попадания на клиенте выгладит так:

case 'hit':
	avatars[0].life = msg.life;
	if(avatars[0].life <= 0) avatars[0].life = 0;
	if(avatars[0].life > 100) avatars[0].life = 100;
	$('#life span, #life2 span').html(msg.life);
	if(msg.damage <= 0){
		particles.push(new Effect({type: 'message', text: ('ПРОМАХ!'), x: avatars[0].x, y: avatars[0].y, t: 1000}));
	}
	else{ 
		particles.push(new Effect({type: 'message', text: ('-' (Math.ceil(msg.damage))), x: avatars[0].x, y: avatars[0].y, t: 1000}));
	}
	switch(msg.typeW){
		case 'C':
			avatars[0].ra -= msg.damage*msg.damage*avatars[1].koef;
		break;
		case 'E':
			avatars[0].debaff.E.per = Date.now();
			avatars[0].debaff.E.tper = Date.now() (msg.damage*500);
			particles.push(new Effect({type: 'message', text: ('Управление заблокировано!'), x: 350, y: 350, t: 2000}));
		break;
	}
	particles.push(new Effect({type: msg.typeW, x: avatars[0].x, y: avatars[0].y, side: 0, damage: msg.damage}));
break;
case 'pop':
	avatars[1].life = msg.life; 
	if(avatars[1].life <= 0) avatars[1].life = 0;
	if(avatars[1].life > 100) avatars[1].life = 100;
	if(weapons_0.length > msg.i){
		weapons_0.splice(msg.i,1);
	}
	if(msg.damage <= 0){
		particles.push(new Effect({type: 'message', text: ('ПРОМАХ!'), x: avatars[1].x, y: avatars[1].y, t: 1000}));
	}
	else{
		particles.push(new Effect({type: 'message', text: ('-' (Math.ceil(msg.damage))), x: avatars[1].x, y: avatars[1].y, t: 1000}));
	}
	switch(msg.typeW){
		case 'C':
			avatars[1].ra  = msg.damage*msg.damage*avatars[1].koef;
		break;
		case 'E':
			particles.push(new Effect({type: 'message', text: ('Прямое попадание! Управление соперника заблокировано!'), x: 300, y: 220, t: 2000}));
		break;
	}
	if(msg.typeW == 'F') msg.damage = 1;
	particles.push(new Effect({type: msg.typeW, x: avatars[1].x, y: avatars[1].y, side: 1, damage: msg.damage}));
break;

Тут case ‘pop’ — ничто иное как перевод слова «попал», так что те кто не может похвастаться знанием языка может меня поймут. particles — массив частиц (спецэффектов). Тут и прокомментировать нечего. Событие выстрела происходит по аналогии, только вместо частиц используются массивы weapons_0 (мои ракеты) и weapons_1 (его ракеты), с напихиванием в них новеньких объектов.

Ошибка 3. Множество багов.Если не считать мелких падений сервера из за резкого отсутствия каких-либо переменных, то основной проблемой для меня стал пинг. Так случилось, что на работе с патч-корда слетел коннектор, а в запасе как то не оказалось, и пришлось сидеть по wifi.

На работе он гниленький, так что задержки были обеспечены качественные (150-200мс вместо 18-20 привычных). Тестируя очередное оружие, заметил что через некоторое время в двух окнах браузера картина расположения самолётов совсем не одинаковая. А как оказалось, виной тому wifi, а точнее пинг а то и вовсе потери пакетов. Как решил — читаем дальше если не устали.

Работа с вк api при создании игры для вконтакте

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

1. Введение в ВК API. Настройка работы тестовой среды и инструментов отладки. Подключение интерфейса ВК, инициализация и старт игры.

2. Получение данных о пользователях, запускающих игру, аватар, картинка. Отображение элементов, полученных от сервера ВКонтакте.

3. Ожидание загрузки ресурсов. Создание загрузчика с отображением текущего статуса загрузки.

4. Создание меню для игры, рисование и обработка объектов. Работа с курсором и движением объектов.

5. Начинаем делать геймплей игры, управление главным персонажем, стрельба, полет, пули и ёлки.

Исходник с проектом могу скинуть в ЛС.

Сессия, диплом, лето… лирическое отступление

Когда пришло лето со своими бонусами вроде сессии, а точнее гос. экзаменов, защитой неначатого диплома, проблем с армией — про игру пришлось ненадолго забыть, и в течении 2 месяцев (июнь, июль) был мозговой перерыв. И вдруг меня осенило, что как то я мало зарабатываю, и стал искать новую работу.

Собственно, первый запуск

Приближалась запланированная дата выпуска игры, и возникали мелкие проблемы которые необходимо было решать. Во время разработки я пользовался исключительно своим домашним сервером, но было и так понятно, что арендовать всё таки придётся. Денег у меня не было, так что идеальным на тот момент для меня решением было арендовать облачный сервер. Selectel подошел идеально.

ХХХ августа настал день первого провала игры. Приложение было одобрено спустя неделю после подачи заявки, но это даже хорошо. За это неделю я успел слить почти 10 тыс на таргет рекламу ВК — самая бесполезная моя трата денег. 10к улетели за пару часов.

Но эти пара часов оказались отличным опытом. С первыми же игроками по каждой фигне начал ныть и падать NodeJS, про try…catch я еще не знал, и пока игра не попала в каталог нужно было что-то решать. Успел закрыть много дыр, но далеко не все, из-за чего этот «релиз» провалился не успев начаться.

Технические моменты

Сразу будьте готовы к тому, что понадобятся знания вот из этих областей:

  • HTML
  • CSS
  • JavaScript
  • Adobe Flash
  • Action Script

Третий запуск

А точнее — недозапуск. Вконтакте есть возможность разместить своё приложение в топ 9 блока «Новые» за 1000 голосов (7тыс.р. не считая скидки). Закинул голосов, отправил заявку, выбрал день (прямо на тогдашнее завтра), и ждал. Ночью пришло уведомление, что мне вынуждены отказать в размещении, с предложением добавить в игру возможность игры с компьютером или обучение. Решил делать и то и другое.

Выводы

p.s. Ни в коем случае не рекомендую использовать написанный тут код. Уверен, что есть на много более элегантные решения, о которых буду рад услышать в комментариях. Спасибо за внимание!

Update С чистой совестью, ввиду большого кол-ва просьб показать ссылку, пишу её тут: ссылочка

Понравилась статья? Поделиться с друзьями:
ТВОЙ ВК
Добавить комментарий