Pull to refresh

Way of Tanks. Путь от идеи к игре

Reading time 9 min
Views 39K


Я всегда затрудняюсь ответить на вопрос: откуда берутся идеи для игр? Но в этот раз, я более-менее точно могу сказать, что эта идея родилась у меня когда я увидел баннер «World of Tanks» на каком-то из сайтов. Знаете, бывают такие баннеры, которые привлекают внимание пользователя микро-играми, прежде чем перенаправить его на сайт рекламодателя? Так вот, на этом баннере был танк, который по клику мог проезжать то или иное расстояние, зачем-то пробивая при этом кирпичные стены. Вот именно с этого момента я стал размышлять, по дороге на работу, о раннере с танковой тематикой. Тут же родилась и отсылка к нашумевшему хиту от Wargaming в названии. Она показалась мне забавной, учитывая, что суть моей игры — это движение по некой дороге, пути, с целью пройти максимальное расстояние. Под хабракатом вас ждет рассказ об игре, разработке, технические детали и все-все-все, что должно быть в классической «gamedev story».

Об игре


Итак, что же у нас здесь имеется? Ну во-первых, у нас есть вот такой танчик:

дыр-дыр-дыр

Который едет по дороге, виртуально разделенной на 3 полосы (привет, Subway Surfers!). Свайпом влево-вправо, можно перестраиваться в соседние полосы, чтобы избежать столкновений с препятствиями. В отличие от других раннеров, маневры здесь не мгновенные (это все-таки танк), что тоже нужно учитывать. Согласен, звучит странно, но наш танк умеет прыгать (свайп вверх). Какой же раннер может быть без прыжков? К тому же это фаново, прыгать через яму-ловушку и в прыжке расстреливать противотанковые ежи, коварно расставленные сразу за ней. Разумеется, танк умеет и стрелять (свайп вниз), правда снаряды в игре ограничены. На мой взгляд, стрельба — это аналог скейтов в том же Subway Surfers, она позволяет пройти «силой» тот участок, где нет времени на маневр.


Одна из действительно коварных ловушек

Вообще, я часто подсматривал в топовые раннеры, когда делал «Way of Tanks». Конечно, у меня нет многих из модных нынче фишек, типа рулетки, ежедневных соревнований, друзей из фейсбука, push-уведомлений и прочего-прочего, что должно наращивать ARPU, MAU, DAU и все вот эти вот не понятные мне штуки. Но, хочется верить, что в игре есть главное — это сделанный с душой геймплей! Впрочем, я отвлекся.

Во-вторых, путь танка усеян препятствиями, расставленными невидимым противником. Все препятствия можно разделить на 2 типа: те, которые можно перепрыгнуть, они находятся на уровне земли и ниже (ямы-ловушки, минные поля) и те которые перепрыгнуть нельзя, а можно только объехать или уничтожить (стены, противотанковые ежи, доты). Причем, просто уничтожать все на своем пути — не получится. Орудие танка имеет ограниченную скорость перезарядки, которая не позволяет стрелять слишком часто. Поэтому, для успешной игры понадобятся не только реакция и везение, но и умение быстро принимать решение, когда маневрировать, а когда стрелять.

Изначально, я задумывал добавить в игру несколько мини-боссов, но как обычно, жизнь и ограниченные ресурсы вносят свои коррективы. Поэтому из мини-боссов, в «Way of Tanks» пока есть самолет, который пытается уничтожить танк игрока, сбрасывая на него бомбы.


AI самолета устроен довольно просто, можно даже сказать, примитивно. Есть две основные характеристики: скорость принятия решений и время подготовки следующей бомбы к бомбометанию, проще говоря, «перезарядка». Так вот, когда бомба готова, бомбардировщик стремится занять одну полосу с игроком, а когда идет перезарядка, наоборот избежать нахождения на линии ответного огня. После тестов, я добавил пару усложнений: самолет фиксирует начало поворота танка и дальше алгоритм работает так, как если бы танк УЖЕ перестроился в новую полосу. Таким образом, бомбардировщик научился «предсказывать» ответные действия игрока и работать по цели с упреждением. Плюс постепенное улучшение характеристик для соблюдения баланса в игре (на максимальную сложность самолет выходит только с пятого-шестого захода).

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

Удваивает собираемые монеты
«Рывок» — позволяет танку пробивать любые препятствия, но только в прыжке
Увеличивает маневренность танка
Бронебойные заряды — стреляя, танк уничтожает всю линию препятствий перед собой (а также +1 к зарядам)

Ах да, еще была мысль сделать летящие навстречу ракеты, как в Jetpack Joyride. Но посовещавшись и подумав, решили, что ракеты либо будут лететь слишком быстро, либо будут странно смотреться (если их замедлить). В результате, ракеты превратились вот в такие броневики-камикадзе, которые будут таранить наш танк:



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

Техническая часть


Это уже пятая игра, которую я делаю на AndEngine. Да, я знаю про Unity, но не гонюсь за кроссплатформенностью. По моему мнению, лучше быстрее и качественнее сделать эксклюзив для одной платформы. Да и игра на знакомом поле, так сказать, у меня более-менее получается, в отличии от попыток хоть как-то заявить о себе в App Store. Хотя я и посматриваю в последнее время на LibGDX.

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

У нас есть четыре варианта бэкграунда, размером с камеру (1280х720), которые бесшовно переходят из любого в любой. Одновременно, игрок видит не более двух из них. Поэтому, на первом шаге, мы устанавливаем SpriteGroup с бэкграундами в позицию (0, -CAMERA_HEIGHT), а два спрайта внутри нее в (0, 0) и (0, CAMERA_HEIGHT), соответственно. Остальные два — скрываем. Далее, мы двигаем SpriteGroup вниз со скоростью танка. Когда она оказывается в координатах (0, 0), мы возвращаем ее на изначальную позицию, при этом, случайным образом выбирая следующий участок бэкграунда и скрывая пройденный. Все просто!



Но тут я столкнулся с неприятным багом: спрайты внутри SpriteGroup могут «мигать» при изменении позиции. На stackoverflow, я нашел вот такое решение этой проблемы. Для SpriteGroup переопределяем onManagedUpdate и пишем:

@Override
protected void onManagedUpdate(float pSecondsElapsed) {
	final SmartList<IEntity> children = this.mChildren;

	if (children != null) {
		final int childCount = children.size();

		for (int i = 0; i < childCount; i++) {
			this.drawWithoutChecks((Sprite) children.get(i));
		}

		submit();
	}
}

Вместе с движением фона, нам нужно создавать и препятствия. Делать это полностью случайно, было бы не правильно, потому что в таком случае, возможно появление непроходимых участков. Все ловушки у меня тоже разделены на секции и подготовлены заранее. Они хранятся в JSON-файле примерно такой структуры:
{
«root»:{
«sections»:[
[
[«CELL_EMPTY»,«CELL_EMPTY»,«CELL_EMPTY»],
[«CELL_COIN»,«CELL_EMPTY»,«CELL_EMPTY»],
[«CELL_COIN»,«CELL_EMPTY»,«CELL_EMPTY»],
[«CELL_COIN»,«CELL_EMPTY»,«CELL_HEDGEHOG»],
[«CELL_PIT»,«CELL_WALL»,«CELL_WALL»],
[«CELL_EMPTY»,«CELL_EMPTY»,«CELL_EMPTY»]
],

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

Как и в любой современной Android-игре, у нас есть интеграция с Google Play Game Services (ачивки и лидерборды). Тут все довольно банально и подробно описано в руководстве на офф. сайте: developers.google.com/games/services/android/quickstart. Я просто использую один раз написанный код в каждой новой игре, практически без изменений.



Оптимизация


Хочу отдельно сказать несколько слов об оптимизации в AndEngine. Для всех объектов, которые не являются анимированными спрайтами, для которых мы не переопределяем onManagedUpdate и не используем entity modifiers, нужно не забывать устанавливать setIgnoreUpdate(true) сразу при создании. Это положительно влияет на производительность за счет уменьшения количества вызовов onUpdate на каждом такте движка. Так же, нужно делать setIgnoreUpdate(true), когда мы прячем какой-либо объект (устанавливаем setVisible(false)).

Далее, все что создается на сцене динамически и не в единичном количестве, должно помещаться в пулы (специальный класс для повторного использования объектов). У меня это ловушки, взрывы, следы на земле от траков танка и т.д. Вот так, например, выглядит пул ловушек:

public class TrapPool extends GenericPool<Trap> {

	@Override
	protected Trap onAllocatePoolItem() {
		return new Trap(0, 0, Assets.trapRegion, ContextHelper.getVBOM());
	}

	@Override
	public synchronized Trap obtainPoolItem() {
		return super.obtainPoolItem();
	}

	@Override
	protected void onHandleRecycleItem(Trap pItem) {
		pItem.setVisible(false);
		pItem.detachSelf();

		super.onHandleRecycleItem(pItem);
	}

}

Теперь, при необходимости появления на сцене нового препятствия, делаем:

Trap trap = trapPool.obtainPoolItem();
trap.setPosition(nX, nY);
trapLayer.attachChild(trap);

А когда ловушка больше не нужна (вышла за пределы видимости камеры):

trapPool.recyclePoolItem(trap);

Для ускорения отрисовки, я помещаю все бэкграунды в один SpriteGroup. Единственный нюанс тут заключается в том, что для SpriteGroup может использоваться только один атлас. Поэтому, все спрайты бэкграундов уменьшены на 20%, чтобы уложиться в максимальный размер текстуры 2048х2048. Не знаю правда, насколько это актуально сейчас, прогресс все-таки не стоит на месте и все мои тестовые устройства поддерживают 4096х4096, но раз на качестве изображения это особо не сказывается, то лишним не будет уж точно. Так же, имеет смысл устанавливать формат изображения RGBA4444 вместо RGBA8888. Для такой «мультяшной» графики картинка не изменится, а объем используемой памяти сокращается вдвое (если вы используете TexturePacker, то достаточно выбрать соответствующую опцию при публикации атласа).

А вот трюк, который я подсмотрел в «AndEngine for Android Game Development Cookbook» — отключение отрисовки фона Activity, он нигде не виден и не используется, а на FPS влияет заметно. Для этого создаем свою тему в res/values:

<resources>
	<style name="Theme.NoBackground" parent="android:Theme">
    	<item name="android:windowBackground">@null</item>
	</style>
</resources>

И в манифесте устанавливаем ее для нашей Activity:

android:theme="@style/Theme.NoBackground"

Фрагментация


Еще одна вещь, о которой интересно рассказать — борьба с различными разрешениями экранов устройств на Android. Изначально, игра разработана под соотношение сторон 16:9. Я рассматривал 3 варианта масштабирования изображения под другие экраны, условно назовем их:

1. Original size — правильное соотношение сторон, картинка полностью заполняет экран по вертикали, но за счет этого, появляются не используемые области по бокам
2. Zoom screen — то же самое, но картинка заполняет экран по горизонтали, «лишняя» часть для фонов сверху и снизу обрезается, а элементы интерфейса сдвигаются к центру
3. Stretch screen — просто растягиваем игру по размерам экрана, без соблюдения соотношения сторон

Покажу, как это выглядит на примере максимально не соответствующего соотношения сторон — 4:3


Слева-направо: original size, zoom screen, stretch screen

Очевидно, напрашивается и четвертый вариант: перерисовать графику под 4:3, но использовать для игровых элементов также область 16:9. То есть получится вариант 1, но с декорациями уровня вместо рамок по бокам. Отличный вариант, на самом деле, жаль мне подсказали его поздно, все-таки перерисовывание графики — это не так просто и быстро. Но так как внутренний перфекционист никак не хотел успокоиться, в результате, графику все же перерисовали (не всю, в основном фоны) и теперь на планшетах игра выглядит даже лучше чем на смартфонах!


Скриншоты с планшета (4:3) и смартфона (16:9)

Кто-то скажет: «Пффф! Открыл Америку!», но не всегда удается научиться на чужих ошибках, иногда приходится изобретать свои «велосипеды», чтобы понять как правильнее и лучше. В частности, в AndEngine «из коробки» нет такого типа Камеры, на котором я в результате остановился. И в туториалах, особо о таких подходах не пишут.

Расходы и благодарности


Так как о доходах мне рассказать нечего, расскажу о расходах. Время создания игры от первого commit-a до публикации в Google Play, составило почти три месяца. Свое чистое время, как программиста, я бы оценил где-то в полтора месяца (игра делалась в свободное время и иногда надо было ждать результата от других участников команды, естественно). Пусть это будет ~3000$ по ставке. С профессиональным и очень талантливым художником, Егором, мы работаем уже над второй игрой на условиях разделения возможной прибыли, поэтому абсолютную стоимость создания арта к игре, я не знаю как учесть (разве что по времени тоже). Локализация страницы Google Play на основные языки, обошлась мне примерно в 60$ (короткое описание и подписи к скриншотам).



За звуковое оформление хочу поблагодарить ребят из Gamingearz. С ними я тоже сотрудничаю не первый раз, все как всегда на высшем уровне и в кратчайшие сроки. Ребята с большим вниманием и любовью относятся к своему делу и что не менее важно, для таких инди-разработчиков как я, готовы предложить варианты в рамках ограниченного бюджета. Для «Way of Tanks» они записали просто потрясный саундтрек (плюс несколько игровых звуков) за символические 200$. А всякие интерфейсные звуки я сделал сам с помощью сервиса diforb.com.

Промо-ролик для игры создал профессиональный фотограф и просто хороший человек — Сергей Муратов из Минска. И он тоже не первый раз делает видео к моим играм. Я, как и все, склонен обращаться к людям с которыми уже был успешный опыт работы, ранее. Это плюс еще 100$. Вот практически, и все затраты. У меня, конечно, есть еще пару идей по маркетинговым вложениям, но это опционально. Сначала хочется понять, понравится ли «Way of Tanks» игрокам так же, как он нравится мне?

Суммируя все расходы, получается, что собственный несложный раннер на Google Play обойдется примерно в 3500 долларов. Много это или мало — решайте сами. По мне, так это справедливая цена за удовольствие от интересной и хорошо выполненной работы, за нажатие заветной кнопки «Опубликовать» в Developer Console, за возможность сыграть в «лотерею сторов», в которую играют все инди. И может быть даже что-то выиграть, кто знает…
Tags:
Hubs:
+48
Comments 43
Comments Comments 43

Articles