пятница, 13 мая 2016 г.

Unity – проба пера.

Давно хотел посмотреть на этого зверя, но не как не доходили руки, но в один прекрасный день я решился! Почему именно Unity?! Все довольно просто:

  • он использует в качестве скриптового языка c#;
  • он имеет довольно хороший редактор, свои велосипеды хорошо, но уж очень медленно новые проекты выходят в свет;
  • он кроссплатформенный, пора потрогать и другие платформы;

Начинать я решил с видео уроков на intuite 
Курс как всегда написан в лучших традициях - неплохое содержания, но странные вопросы в конце (возможно просто я не формат). Этих знаний вполне достаточно, чтобы приступить к делу, но тк ковыряться в примерах не так интересно, как делать игру – я решил взяться за новую игру, совсем маленькую, но залипательную.
В голову сразу пришла игровая механика из Flappy Bird, но двигающегося не в бесконечность, а запертого в клетке - с отражением от стен и выдвигающимися препятствиями. Разработка шла довольно гладко и довольно быстро получился прототип. Игру я решил выпустить сперва на android и на WP, до яблочной продукции я дойду как нить потом =) Написав прототип, я первым делам запустил на интересующих меня платформах и о чудо – все заработало. Ну осталось дело за малым, добавить рекламу и аналитику.
Аналитику я решил добавлять гугловую – тк использую её для текущих проектов, заодно решил разобраться с тем, как добавляются нативные элементы в Unity, в результате спустя неделю я имел свою сборку аналитики для Unity под WP8.
С рекламой я решил тоже немного поэкспериментировать – решил добавить видео рекламу. Для WP сборке под SL видео рекламы я не нашел и решил использовать admob, а в андройд версию Unity ads.
В один прекрасный момент у меня перестал запускаться проект под WP8. Git штука хорошая и я стал откатываться комит за комитом назад, долбясь лицом о клавитуру и с фразой «работало ведь», дойдя до места где точно работа, а оно не запускается я совсем загрустил и стал вспоминать, что менялось. Опытным путем я откатил на пару версий Unity и все заработало – жизнь меня к такому не готовила…

И так, оба приложение в обоих магазинах, результаты правда не радуют:
В андройд стор приложение было отправлено в свободное плавание, а вот с WP не задалось, приложение не появляется в общем поиске, пришлось общаться с тех. поддержкой очень долго, да и с добавлением в разделы дела обстоят не очень.
Ну как говориться, первый блин вышел комом, ну… бывает :-)

Жалко, что не попробовал Unity Analytics, слишком поздно узнал про него. Не сделал сборку под IOS, лень матушка…
Из хорошего UnityAds принес целых 3,3$ за 208 установок и это при том, что возврат в игру наблюдается только у 8% на следующий день, а через 7 дней только у 2%, да и время сессии очень короткое.
Взвесив все за и против, я решил продолжить изучать Unity и взяться за перенос гонок на Unity!
Но это уже в следующей статье =)

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

С Новым 2016 годом!

Год милой овечки выдался крайне тяжёлым и мало результативным, хотя трудились как лошади, что даже не было времени писать статьи в блог.
За 2015 год, мы выпустили две новых игры - IQTools и LockedRobot.

Locked Robot – проба сил в Unity, небольшая игра навеянная мотивами flappy bird. Игра вышла в конце года для Android и WP8. Пока количество пользователей меньше 500 суммарно, но это только пока =)
IQTools – сборник мини-игр с красивой графикой, игра вышла для WP7 и WP8, за 2015 год в игру поиграли 210 тыс. пользователей.
Trucking Mania – выпущено несколько обновлений с новыми картами и машинами, за 2015 год в игру поиграло 395 тыс. пользователей.
Слова любимым – выпущено несколько технических обновлений с новым рекламным ротатором, обновление аналитики, ну и новые поздравления с 2016 годом, приложением за 2015 год воспользовалось 360 тыс. пользователей.

Мы в плотную приблизились к планке 1 миллион пользователей в год, осталось совсем чуть - чуть! Но вот доходы с рекламы упали очень сильно, особенно это касается российского трафика. К примеру прибыль с begun упала в 3 раза, plus1 в 10 раз.

Но мы не унываем и надеемся трудолюбие принесет свои плоды в этом году!  Основной план на 2016 год - это развитие игры Trucking Mania, а также работы над новой игрой.

Хочу всех поздравить с Наступающим 2016 годом! И пожелать успехов и достижения новых вершин!


С НАСТУПАЮЩИМ!!!

понедельник, 14 сентября 2015 г.

Игре «Trucking mania» исполнился один год!

Вчера, 13 сентября, игра «Trucking mania» отметила свой день рождения – ей исполнился год!
Это мой самый успешный проект из запущенных на текущее время, и я очень рад, что он нравиться пользователям, о чем говорят высокие оценки.
А количество заездов и полученные очки у некоторых пользователей вообще поражают, приятно поражают =)
Текущий топ игроков:




Я хочу немного поделиться статистикой по игре за этот год:

===========Общая статистика=============
Количество игроков: 451 007
Количество заездов на сервере: 12 553 441
Суммарное время заездов: 1 030 757 614 сек
Суммарное количество заработанных денег: 3 116 592 606

Максимальное количество заездов совершенных одним игроком: 3 054
Среднее количество заездов: 27,68
Медианное количество заездов: 9

Максимальное количество времени проведенных в заездах, совершенных одним игроком : 518 531 сек
Среднее количество времени: 2 272 сек
Медианное количество времени: 536 сек

Среднее время одного заезда: 82,11 сек
Медианное время одного заезда: 76 сек
Среднее количество монет за заезд: 248,27
Медианное количество монет за заезд: 87
========================================

===========Статистика по картам============
Количество игроков, сделавших хотя-бы один заезд: 320 328
Количество заездов: 3 718 070
Суммарное время заездов: 218 952 638 сек
Суммарное количество заработанных денег: 178 329 181
Среднее время прохождения: 58,89 сек
Медианное время прохождения: 55 сек
Среднее количество заработанных денег: 47,96
Медианное количество заработанных денег: 46
Лучший заезд: playetId_524156, дата заезда 29.08.2015 03:37:54, время 39 сек, зона выгрузки 1, машина Id 5, деньги 240

Количество игроков, сделавших хотя-бы один заезд: 201 325
Количество заездов: 3 193 672
Суммарное время заездов: 302 607 883 сек
Суммарное количество заработанных денег: 413 839 172
Среднее время прохождения: 94,75 сек
Медианное время прохождения: 89 сек
Среднее количество заработанных денег: 129,58
Медианное количество заработанных денег: 112
Лучший заезд: playetId_3254, дата заезда 19.09.2014 10:31:59, время 129 сек, зона выгрузки 1, машина Id 5, деньги 8 841

Количество игроков, сделавших хотя-бы один заезд: 108 839
Количество заездов: 2 639 841
Суммарное время заездов: 254 557 381 сек
Суммарное количество заработанных денег: 590 287 322
Среднее время прохождения: 96,43 сек
Медианное время прохождения: 88 сек
Среднее количество заработанных денег: 223,61
Медианное количество заработанных денег: 178
Лучший заезд: playetId_10603, дата заезда 11.10.2014 13:16:51, время 116 сек, зона выгрузки 3, машина Id 6, деньги 3800

Количество игроков, сделавших хотя-бы один заезд: 36 757
Количество заездов: 1 450 143
Суммарное время заездов: 147 252 879 сек
Суммарное количество заработанных денег: 960 044 288
Среднее время прохождения: 101,54 сек
Медианное время прохождения: 94 сек
Среднее количество заработанных денег: 662,03
Медианное количество заработанных денег: 620
Лучший заезд: playetId_172435, дата заезда 06.12.2014 21:20:06, время 187 сек, зона выгрузки 4, машина Id 6, деньги 12 580

Количество игроков, сделавших хотя-бы один заезд: 11 443
Количество заездов: 442 539
Суммарное время заездов: 49 228 393 сек
Суммарное количество заработанных денег: 767 232 527
Среднее время прохождения: 111,24 сек
Медианное время прохождения: 105 сек
Среднее количество заработанных денег: 1 733,71
Медианное количество заработанных денег: 1 275
Лучший заезд: playetId_325986, дата заезда 02.03.2015 20:26:34, время 257 сек, зона выгрузки 5, машина Id 6, деньги 25 740

Количество игроков, сделавших хотя-бы один заезд: 1 923
Количество заездов: 74 491
Суммарное время заездов: 9 963 707 сек
Суммарное количество заработанных денег: 194 575 272
Среднее время прохождения: 133,76 сек
Медианное время прохождения: 123 сек
Среднее количество заработанных денег: 2 612,06
Медианное количество заработанных денег: 1 950
Лучший заезд: playetId_352933, дата заезда 30.07.2015 15:57:18, время 350 сек, зона выгрузки 6, машина Id 6, деньги 39 220
========================================

===========Статистика по машинам===========
Количество игроков, сделавших хотя-бы один заезд: 429 714
Количество заездов: 2 995 462
Суммарное время заездов: 176 735 430 сек
Суммарное количество заработанных денег: 59 114 073
Среднее время использования: 59 сек
Медианное время использования: 55 сек
Среднее количество заработанных денег: 19,74
Медианное количество заработанных денег: 19
Лучший заезд: playetId_99510, дата заезда 19.10.2014 11:12:41, время 109 сек, карта Id 5, зона выгрузки 1, деньги 630

Количество игроков, сделавших хотя-бы один заезд: 200 870
Количество заездов: 2 692 742
Суммарное время заездов: 209 425 765 сек
Суммарное количество заработанных денег: 253 195 600
Среднее время использования: 77,77 сек
Медианное время использования: 71 сек
Среднее количество заработанных денег: 94,03
Медианное количество заработанных денег: 84
Лучший заезд: playetId_3254, дата заезда 19.09.2014 10:29:50, время 258 сек, карта Id 8, зона выгрузки 3, деньги 8 096

Количество игроков, сделавших хотя-бы один заезд: 154 576
Количество заездов: 3 056 443
Суммарное время заездов: 266 781 322 сек
Суммарное количество заработанных денег: 356516626
Среднее время использования: 87,2849001273703 сек
Медианное время использования: 80 сек
Среднее количество заработанных денег: 116,64
Медианное количество заработанных денег: 104
Лучший заезд: playetId_3254, дата заезда 19.09.2014 10:19:09, время 899 сек, карта Id 4, зона выгрузки 3, деньги 8 497

Количество игроков, сделавших хотя-бы один заезд: 62 236
Количество заездов: 1 772 321
Суммарное время заездов: 161 820 438 сек
Суммарное количество заработанных денег: 504 175 300
Среднее время использования: 91,30 сек
Медианное время использования: 86 сек
Среднее количество заработанных денег: 284,47
Медианное количество заработанных денег: 243
Лучший заезд: playetId_165792, дата заезда 31.10.2014 21:32:55, время 200 сек, карта Id 4, зона выгрузки 4, деньги 4 080

Количество игроков, сделавших хотя-бы один заезд: 32 756
Количество заездов: 1 096 135
Суммарное время заездов: 112 484 254 сек
Суммарное количество заработанных денег: 593 363 572
Среднее время использования: 102,62 сек
Медианное время использования: 95 сек
Среднее количество заработанных денег: 541,32
Медианное количество заработанных денег: 570
Лучший заезд: playetId_406896, дата заезда 02.05.2015 00:53:07, время 282 сек, карта Id 5, зона выгрузки 5, деньги 11 700

Количество игроков, сделавших хотя-бы один заезд: 17 054
Количество заездов: 837 912
Суммарное время заездов: 92 515 849 сек
Суммарное количество заработанных денег: 1 242 364 481
Среднее время использования: 110,41 сек
Медианное время использования: 106 сек
Среднее количество заработанных денег: 1 482,69
Медианное количество заработанных денег: 1 250
Лучший заезд: playetId_352933, дата заезда 30.07.2015 15:57:18, время 350 сек, карта Id 6, зона выгрузки 6, деньги 39 220

Количество игроков, сделавших хотя-бы один заезд: 6 818
Количество заездов: 65 993
Суммарное время заездов: 6 393 953 сек
Суммарное количество заработанных денег: 46 233 061
Среднее время использования: 96,89 сек
Медианное время использования: 91 сек
Среднее количество заработанных денег: 700,58
Медианное количество заработанных денег: 600
Лучший заезд: playetId_313839, дата заезда 08.03.2015 10:03:17, время 270 сек, карта Id 5, зона выгрузки 5, деньги 14 820

Количество игроков, сделавших хотя-бы один заезд: 1 966
Количество заездов: 32 719
Суммарное время заездов: 4 175 900 сек
Суммарное количество заработанных денег: 58 321 635
Среднее время использования: 127,63 сек
Медианное время использования: 117 сек
Среднее количество заработанных денег: 1782,50
Медианное количество заработанных денег: 1 170
Лучший заезд: playetId_429424, дата заезда 04.06.2015 17:59:08, время 298 сек, карта Id 5, зона выгрузки 5, деньги 19 500

Количество игроков, сделавших хотя-бы один заезд: 83
Количество заездов: 1 366
Суммарное время заездов: 146 320 сек
Суммарное количество заработанных денег: 603 574
Среднее время использования: 107,12 сек
Медианное время использования: 94 сек
Среднее количество заработанных денег: 441,86
Медианное количество заработанных денег: 407
Лучший заезд: playetId_376578, дата заезда 22.04.2015 01:38:23, время 256 сек, карта Id 5, зона выгрузки 5, деньги 8 580

Количество игроков, сделавших хотя-бы один заезд: 89
Количество заездов: 2 348
Суммарное время заездов: 278 383 сек
Суммарное количество заработанных денег: 2 704 684
Среднее время использования: 118,56 сек
Медианное время использования: 115 сек
Среднее количество заработанных денег: 1 151,91
Медианное количество заработанных денег: 700
Лучший заезд: playetId_288404, дата заезда 18.02.2015 10:54:50, время 191 сек, карта Id 6, зона выгрузки 4, деньги 15 130
========================================

вторник, 14 июля 2015 г.

Записки программиста #17. Лето...

Всем привет!
Что-то давно я ничего не писал в свой блог…
Если честно совсем замотался... работа, проекты, кучу всего нужно сделать… да и в редкие солнечные деньки хочется позагорать… А вообще ужас и кошмар, что это такое и как так можно…
Надо исправляться!
А в целом произошло много всего, пойдем по списку:
  • Я выпустил новое приложение «IQTools», получил, как всегда, бесценный опыт… (сарказм)
  • «Слова любимым» теперь доступна и на андройде.
  • Небольшой пример с продвижением, что по чем.

Продвигать будем через AdDuplex, имеем 1200$ - это примерно 1 515 150 показов. Желаемую страну для раскрутки – США, давно хотел улучшить свои позиции в топе для данного рынка. Не новое, но и не сильно плохое приложение, но которое находиться довольно далеко в топе. Ставим ограничение на количество показов в день не больше 120 000, по факту один раз только к 100 000 приблизилась. Рекламная компания продержалась с 16 мая по 6 июня, те в среднем было в районе 70 000 показов в день. График показов и кликов внизу:

Имеем 300-400 кликов в день, те ожидаем в районе 30-100 новых пользователей.
А на самом деле имеем:

Эффект вроде виден, но он какой-то маленький, практически статистическая погрешность, я бы сказал в районе 10-30 пользователей. Те из кликнувших только в районе 5-10% скачали приложение.
Но смысл был в том, чтобы понять, как это повлияет на рост в топе магазина:

А вот эффект роста в топе в категории явно имеется и удалось подняться с 210 позиции до 129, а потом снова падение. Дальше думаем сами…
  • Совсем давным-давно обещал рекламный баннер plus1 выложить, руки не доходили… да и сейчас толком не дошли, выкладываю пока как .dll , код не выкладываю, тк не вижу смысла - он работает на старом API plus1, как подружусь с новым API выложу в open source, но замечу – работает.

Работает по принципу молотка. Размещаем на странице или через код. Устанавливаем AppId, если очень хочется, используем AdvLoaded; AdvLoadingError; AdvClick. При загрузке контрола, начинается его работа.
  • Серьезно перепилил AdRotator, он теперь представляет собой «межстраничный контрол», если можно так выразиться – поглядеть его работу можно в приложении «Словах любимым» версия 1.8.2.

Список поддерживаемых провайдеров рекламы:
PubCenter; AdMob; AdDuplex; InnerActive; MobFox; Smaato; Inmobi; Plus1; Begun; MMedia; Vserv; Leadbolt.
Возможность расширения этого списка без перекомпиляции AdRotator'a.
Поддержка сетевых настроек.
Поддержка мульти культуры при выборе рекламы.
В общем хороший велосипед вышел, простой и надежный, вот думаю о его дальнейшей судьбе…
  • Проект сервера для игры «Trucking mania» перерос в нечто большее. А точнее было написано ядро сервера с базовой логикой, которую можно применять для других игр. Так появился новый проект «AlienStorm.Server» состоящий из:

«AlienStorm.Server.Core» - ядро серверной логики
«AlienStorm.ClientServer.Core» - ядро клиентской логики
«AlienStorm.CommonServer.Core» - ядро общей клиент-серверной логики
«TruckingMania.Server» стало расширением этого ядра и получилось очень удобно и гибко. В течении июля планирую обновление боевого сервера – надеюсь все пройдет хорошо =)
  • Загорелся идеей создания новой игры под кодовым названием «Проект юный маг. Защита крепости.». Суть игры сводиться к сражением с другими игроками онлайн и прокачкой своего героя оффлайн, по средствам использования магии.

Оффлайн прокачка героя. Прокачка представляет собой разновидность известного жанра «tower defence», но в отличии от классических представителей данного жанра не нужно строить башни на пути монстров. Вам нужно ударами магией не дать монстрам подойти к стене и не дать выбить ворота. Имеется возможность улучшать стену, возводить укрепления, а также строить башню магов, для усиления магии. Постройка и улучшение зданий занимает некоторое время. Для уничтожения монстров используется магия, при использовании которой тратиться энергия. По мимо простых заклинаний, которые активируются нажатием на монстра, имеются и сложные, для активации которых необходимо начертить необходимую руну. Для изучения новых заклинаний, а также прокачке существующих, необходимы очки магии, которые даются за использовании магии.
Онлайн бои между игроками. Для боев используется сетевой профиль, который обновляется при каждом заходе в сеть, путем анализа текущих действий в офлайне. Бои бывают двух типов: случайных противник и турнир, но все они проходят 1 на 1. 
В этом проекте планирую использовать текущие наработки - «AlienStorm.Server» и «AlienStorm.Engine».
P.S. ищу единомышленников


 Вот так весело проходит моё лето =)

вторник, 10 марта 2015 г.

Локализация приложений.

Разрабатывая приложения очень часто встает вопрос с локализацией его. Кто-то делает только английскую версию, кто-то добавляет русский язык, а кто-то делает сразу под множество языков.
Рассказывать, что нужно сразу все строковые данные выносить в файл ресурсов, если подразумевается в дальнейшем локализация на другие языки, думаю не кому не нужно. А вот с шрифтами в XNA возникает небольшая проблема. Стандартный файл шрифта, в XNA с расширением .spritefont имеет

<CharacterRegion>
    <Start>&#32;</Start>
    <End>&#126;</End>
</CharacterRegion>

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

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

        private IEnumerable<string> GetStringsValue(CultureInfo culture)
        {
            var resourceSet = AppResources.ResourceManager.GetResourceSet(culture, true, true);
            
            var value = new List<string>();
            foreach (DictionaryEntry entry in resourceSet)
            {
                var resource = (string) entry.Value;
                value.Add(resource);

                value.Add(resource.ToLower());

                value.Add(resource.ToUpper());
            }

            value.Add("1234567890-=_+)(*&^%$#@!:;\"'/,.<>№[]{}~`");
            
            return value;

        }

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

        public void CreateRangeCharacters()
        {
            Debug.WriteLine("Start create range characters:");
            foreach (var cultureItem in _supportedCultures)
            {
                Debug.WriteLine("Culture start: " + (string.IsNullOrEmpty(cultureItem) ?
                    "default" : cultureItem));
                var culture = new CultureInfo("en");
                if (!string.IsNullOrEmpty(cultureItem))
                {
                    culture = new CultureInfo(cultureItem);
                } 
                
                var characters = new List<char>();
                
                var resources = GetStringsValue(culture);
                foreach (var resource in resources)
                {
                    foreach (var item in resource)
                    {
                        if (!characters.Contains(item))
                        {
                            characters.Add(item);
                        }
                    }
                }

                characters = characters.OrderBy(s => s).ToList();
                var array = string.Join(" ", characters.Select(PrintChar));
                Debug.WriteLine("Characters: ");
                Debug.WriteLine(array);

                /*Объединение*/
                Debug.WriteLine("    <CharacterRegions>");

                var oldValue = characters[0];

                var value = PrintChar(oldValue);

                var isFurther = false;

                for (var i = 1; i < characters.Count; i++)
                {
                    if (characters[i] - oldValue <= 1)
                    {
                        if (!isFurther)
                        {
                            Debug.WriteLine("      <CharacterRegion>");
                            Debug.WriteLine("        <Start>&#" + PrintChar(oldValue) + ";</Start>");
                            isFurther = true;
                        }
                    }
                    else
                    {
                        if (isFurther)
                        {
                            isFurther = false;
                            Debug.WriteLine("        <End>&#" + PrintChar(oldValue) + ";</End>");
                            Debug.WriteLine("      </CharacterRegion>");
                        }
                        else
                        {
                            if (!string.IsNullOrEmpty(value))
                            {
                                Debug.WriteLine("      <CharacterRegion>");
                                Debug.WriteLine("        <Start>&#" + value + ";</Start>");
                                Debug.WriteLine("        <End>&#" + value + ";</End>");
                                Debug.WriteLine("      </CharacterRegion>");
                            }
                        }
                        value = PrintChar(characters[i]);
                    }
                    oldValue = characters[i];
                }

                if (!string.IsNullOrEmpty(value))
                {
                    Debug.WriteLine("      <CharacterRegion>");
                    Debug.WriteLine("        <Start>&#" + value + ";</Start>");
                    Debug.WriteLine("        <End>&#" + value + ";</End>");
                    Debug.WriteLine("      </CharacterRegion>");
                }

                Debug.WriteLine("    </CharacterRegions>");

                Debug.WriteLine("Culture end!");
            }
            Debug.WriteLine("End create range characters!");
        }

        private static string PrintChar(char c)
        {
            var value = (int)c;
            return value.ToString(CultureInfo.InvariantCulture);

        }

А вот проверка уже получившихся файлов шрифтов, её следует запускать если были изменения.

        public void TestAllFonts(ContentManager contentManager)
        {
            Debug.WriteLine("Start test fonts: ");
            foreach (var cultureItem in _supportedCultures)
            {
                Debug.WriteLine("Culture start: " + (string.IsNullOrEmpty(cultureItem) ? "default" : cultureItem));

                var count = 0;
                foreach (var fontItem in _fonts)
                {
                    var fontPath = "Fonts/" + fontItem;

                    var culture = new CultureInfo("en");
                    if (!string.IsNullOrEmpty(cultureItem))
                    {
                        fontPath += "." + cultureItem;
                        culture = new CultureInfo(cultureItem);
                    }

                    var font = contentManager.Load<SpriteFont>(fontPath);

                    var resources = GetStringsValue(culture);
                    foreach (var resource in resources)
                    {
                        foreach (var item in resource)
                        {
                            if (!font.Characters.Contains(item))
                            {
                                Debug.WriteLine("Error!!! " + PrintChar(item));
                            }
                        }
                        //var size = font.MeasureString(resource);

                        count++;
                    }
                }
                Debug.WriteLine("Culture end count font: " + _fonts.Count + " resource count: " + count/_fonts.Count);
            }
            Debug.WriteLine("End test fonts!");

        }