Как я писал Bomberman’а на Android

в 3:34, , рубрики: android, bomberman, game development, Gamedev, GameDevelop, java, libgdx, mvc, Разработка под android, метки: , , , , ,

День добрый, уважаемые читатели.
Писать игры хотел ещё с того момента, когда только начал программировать. И вот, решил всё-таки попробовать себя в написании игр на Android.
Игру осенью сделал ещё и выложил в маркет. Правда её удалили, так как права на Bomberman'а у Konami. Но статья, естественно, не об этом.

Параллельно с разработкой игры писал туториалы по LibGDX, и постоянно люди просили выложить исходники. Решил всё-таки поделиться ими и немного рассказать про разработку. Может кому-нибудь и поможет в изучении LibGDX. Ссылка на репозитарий с исходниками внизу статьи.
Как я писал Bomberman’а на Android

Небольшое отступление

До этого не имел опыта работы с Java, а уж тем более с Android. Решил и то, и другое изучать на практической задаче. Писать на чистом OpenGL свой движок не очень хотелось. Особой разницы не было на каком движке игру разрабатывать. Чем-то приглянулся LibGDX, решил с него и начать. Так что, не судите слишком строго. Особого плана или ТЗ не было. По мере того, как разбирался в либе, потихоньку писал и игру.

Архитектура

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

В целом всё довольно просто и понятно. Основной игровой класс BomberMan наследуется от Game. В нём содержаться ссылки на все возможные экраны игровые: сама игра, главное меню, окно при победе и т.д. Нам необходимо только переключаться между этими классами с помощью метода setScreen().

В качестве View у нас классы, которые реализуют интерфейсы Screen и InputProcessor (между ними и происходит переключение в классе BomberMan). В классе мы обрабатываем действия пользователя и рендерим объекты. Изменение объектов и последующая отрисовка происходит в одном методе этого класса. Сначала изменяем состояние объектов, если это необходимо (грубо говоря, работаем с физикой), а потом рендерим:

@Override
public void render(float delta) {
  controller.update(delta); 
  renderer.render();
}

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

public void update(float delta) {
		processInput();
		checkBombsTimer();
		if(!world.bonus) destroyBricks();
		removeDeadNpc();
		killByBoom();
		removeHiddenObjects();
		bomberman.update(delta);
		for(NpcBase npc : world.getNpcs()){
			npc.update(delta);
		}
		removeBooms();
	}

Для объяснения модели, думаю, проще диаграмму предоставить:
Как я писал Bomberman’а на Android

World является контейнером, который содержит в себе все объекты: блоки, бонусы, игрока, npc. Никакой логики в нём нет. Лишь в самих объектах часть логики по ним есть (как уже выше писал). Не стал полную диаграмму классов приводить.
Все типы npc наследуются от NpcBase. Все объекты (дверь, бонусы) от HiddenObject.

Прототип

Как я писал Bomberman’а на Android
Изначально разбирался с текстурами и атласом. Первоначально все спрайты были в отдельных файлах. После чего объединил всё в один атлас, что ускорило работу. И тут были проблемы. Тестировал на своём телефоне всё (HTC One S) с Android 4.0. Когда пробовал на более ранних версиях, то игра крашилась. Как оказалось, более старые устройства падают из-за ограничений на размер текстуры. К примеру, какие-то не поддерживали спрайты более 1024x1024. К тому же, изначально размеры у атласы были не степенью двойки, что тоже приводило к падениям на многих устройствах.

Так что:
1) Не делать слишком большие атласы.
2) Размеры должны быть степенью двойки.

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

Большинство объектов имеют целочисленные координаты, да и меняются не так часто. Определил массив public int[][] map и обращался к нему, чтобы постоянно не прогонять списки объектов. Суть в том, что все блоки находятся на целочисленных координатах, поэтому использование массива помогло ускорить работу. Когда обновляем физику, просто смотрим расстояние от игрока/npc до окружающих его объектов.
Что-то вроде:

if(around[0][1] ==0 && Math.abs(getPosition().x-x1)<SIZE/2 && Math.abs(getPosition().x-x1)>0.05){
				if(getPosition().x-x1<0){
					getVelocity().x = +speed; 
					getVelocity().y = +speed; 
				}
				if(getPosition().x-x1>0){
					getVelocity().x = -speed; 
					getVelocity().y = +speed; 
				}
			}	

Ооочень кривое решение, да. В следующих своих играх вся обработка коллизий основана на delta, которая показывает сколько времени прошло с прошлой отрисовки. Но здесь всё вот так печально =/

NPC

После того, как разобрался с текстурами и обработкой коллизий, добавил в игре и npc. Стратегий поведения, в сущности, у них всего три:
1) Двигаться в случайном направлении. По прошествии определённого времени меняют направление на рэндомное.
2) Как и первая в целом. Но, если npc видит игрока, то начинает двигаться к нему. Если игрок пропадает из поля зрения, то npc меняет направление движения на случайное.
3) В целом как вторая стратегия. Вот только, если теряет из вида игрока, всё равно идёт в ту сторону, где последний раз видел игрока.

Все виды npc наследуются от NpcBase и должны реализовать методы:

public abstract void changeDirection(float delta);
public abstract void update(float delta) ;

GUI

GUI тоже реализовал средствами LibGDX. Все элементы интерфейса представлены в виде классов, экземпляры которых содержаться в классе World. У каждого элемента координаты и размеры есть в свойствах, что позволяет нам определять при тапах по экрану, на какой, собственно, элемент пользователь нажал.

Реализовал два метода управления:
1) Стрелками.
Как я писал Bomberman’а на Android
2) Джойстиком.
Как я писал Bomberman’а на Android

В настройках можно задать положение объектов, какое хотим:
Как я писал Bomberman’а на Android

Итог

Весь процесс разработки в один пост не уместить. Подумал, может люди сами предложат тему для статьи, если им интересен этот движок. Рассказал бы вам о каких-то наиболее интересных аспектах разработки или наиболее интересных моментах при работе с LibGDX.

Исходники выложил на github.
Там же можно посмотреть и мои уроки по LibGDX, если кто-то хочет с этим движком работать.

Автор: Suvitruf

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js