День добрый, уважаемые хабражители.
Писать игры хотел ещё с того момента, когда только начал программировать. И вот, решил всё-таки попробовать себя в написании игр на Android.
Игру осенью сделал ещё и выложил в маркет. Правда её удалили, так как права на Bomberman'а у Konami. Но статья, естественно, не об этом.
Параллельно с разработкой игры писал туториалы по LibGDX, и постоянно люди просили выложить исходники. Решил всё-таки поделиться ими и немного рассказать про разработку. Может кому-нибудь и поможет в изучении LibGDX. Ссылка на репозиторий с исходниками внизу статьи.
![](https://habrastorage.org/r/w1560/storage2/08a/e7c/cdf/08ae7ccdffb84470860da419075daaec.png)
До этого не имел опыта работы с Java, а уж тем более с Android. Решил и то, и другое изучать на практической задаче. Писать на чистом OpenGL свой движок не очень хотелось. Особой разницы не было на каком движке игру разрабатывать. Чем-то приглянулся LibGDX, решил с него и начать. Так что, не судите слишком строго. Особого плана или ТЗ не было. По мере того, как разбирался в либе, потихоньку писал и игру.
Для архитектуры, как мне показалось, лучше всего подходит MVC. Я об этом уже рассказывал в статье про архитектуру.
В целом всё довольно просто и понятно. Основной игровой класс
В качестве View у нас классы, которые реализуют интерфейсы Screen и InputProcessor (между ними и происходит переключение в классе
Контроллер, думаю понятно, работает с моделью. Большая часть логики как раз в нём. Не совсем был уверен в том, куда всё-таки логику запихнуть. В итоге, часть логики перенёс в сами объекты (обработка коллизий), а логику по обработке взрывов, убийств (т.е. когда объектам надо влиять на другие объекты) в контроллере. Была идея реализовать подобное с помощью сообщений. То есть, в каждом объекте определить хэндлер, который будет обрабатывать сообщения и как-то реагировать. Но тогда проблема с синхронизацией возникла. Поэтому решил всё синхронно обрабатывать.
В итоге в контроллере просто последовательно работаем со всеми объектами мира:
Для объяснения модели, думаю, проще диаграмму предоставить:
![](https://habrastorage.org/r/w1560/storage2/9ee/2e7/e4e/9ee2e7e4e5910b6533aedd99be07e11d.png)
Все типы npc наследуются от NpcBase. Все объекты (дверь, бонусы) от HiddenObject.
![](https://habrastorage.org/r/w1560/storage2/412/6ba/ce8/4126bace8ced53328c0ae4a7ada4ebf7.png)
Изначально разбирался с текстурами и атласом. Первоначально все спрайты были в отдельных файлах. После чего объединил всё в один атлас, что ускорило работу. И тут были проблемы. Тестировал на своём телефоне всё (HTC One S) с Android 4.0. Когда пробовал на более ранних версиях, то игра крашилась. Как оказалось, более старые устройства падают из-за ограничений на размер текстуры. К примеру, какие-то не поддерживали спрайты более 1024x1024. К тому же, изначально размеры у атласы были не степенью двойки, что тоже приводило к падениям на многих устройствах.
Так что:
1) Не делать слишком большие атласы.
2) Размеры должны быть степенью двойки.
Вещи довольно очевидные, но бродя по develop форумам, многие от подобных ошибок страдали). После того, как все собрал в одном атласе и разобрался, как выделять регионы и настроить анимацию, начал обрабатывать коллизии.
Большинство объектов имеют целочисленные координаты, да и меняются не так часто. Определил массив
Что-то вроде:
Ооочень кривое решение, да. В следующих своих играх вся обработка коллизий основана на delta, которая показывает сколько времени прошло с прошлой отрисовки. Но здесь всё вот так печально =/
После того, как разобрался с текстурами и обработкой коллизий, добавил в игре и npc. Стратегий поведения, в сущности, у них всего три:
1) Двигаться в случайном направлении. По прошествии определённого времени меняют направление на рэндомное.
2) Как и первая в целом. Но, если npc видит игрока, то начинает двигаться к нему. Если игрок пропадает из поля зрения, то npc меняет направление движения на случайное.
3) В целом как вторая стратегия. Вот только, если теряет из вида игрока, всё равно идёт в ту сторону, где последний раз видел игрока.
Все виды npc наследуются от
GUI тоже реализовал средствами LibGDX. Все элементы интерфейса представлены в виде классов, экземпляры которых содержаться в классе
Реализовал два метода управления:
1) Стрелками.
![](https://habrastorage.org/r/w1560/storage2/a1e/858/ba2/a1e858ba2b4882d84edcbaf534a2d3bc.png)
2) Джойстиком.
![](https://habrastorage.org/r/w1560/storage2/d21/7e8/444/d217e84449f43e0250a4e68debeeef93.png)
В настройках можно задать положение объектов, какое хотим:
![](https://habrastorage.org/r/w1560/storage2/00a/3ed/8e1/00a3ed8e199e582f3df9d521b9e7701a.png)
Весь процесс разработки в один пост не уместить. Подумал, может люди сами предложат тему для статьи, если им интересен этот движок. Рассказал бы вам о каких-то наиболее интересных аспектах разработки или наиболее интересных моментах при работе с LibGDX.
Исходники выложил на github.
Там же можно посмотреть и мои уроки по LibGDX, если кто-то хочет с этим движком работать.
Писать игры хотел ещё с того момента, когда только начал программировать. И вот, решил всё-таки попробовать себя в написании игр на Android.
Игру осенью сделал ещё и выложил в маркет. Правда её удалили, так как права на Bomberman'а у Konami. Но статья, естественно, не об этом.
Параллельно с разработкой игры писал туториалы по LibGDX, и постоянно люди просили выложить исходники. Решил всё-таки поделиться ими и немного рассказать про разработку. Может кому-нибудь и поможет в изучении LibGDX. Ссылка на репозиторий с исходниками внизу статьи.
![](https://habrastorage.org/storage2/08a/e7c/cdf/08ae7ccdffb84470860da419075daaec.png)
Небольшое отступление
До этого не имел опыта работы с 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();
}
Для объяснения модели, думаю, проще диаграмму предоставить:
![](https://habrastorage.org/storage2/9ee/2e7/e4e/9ee2e7e4e5910b6533aedd99be07e11d.png)
World
является контейнером, который содержит в себе все объекты: блоки, бонусы, игрока, npc. Никакой логики в нём нет. Лишь в самих объектах часть логики по ним есть (как уже выше писал). Не стал полную диаграмму классов приводить. Все типы npc наследуются от NpcBase. Все объекты (дверь, бонусы) от HiddenObject.
Прототип
![](https://habrastorage.org/storage2/412/6ba/ce8/4126bace8ced53328c0ae4a7ada4ebf7.png)
Изначально разбирался с текстурами и атласом. Первоначально все спрайты были в отдельных файлах. После чего объединил всё в один атлас, что ускорило работу. И тут были проблемы. Тестировал на своём телефоне всё (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) Стрелками.
![](https://habrastorage.org/storage2/a1e/858/ba2/a1e858ba2b4882d84edcbaf534a2d3bc.png)
2) Джойстиком.
![](https://habrastorage.org/storage2/d21/7e8/444/d217e84449f43e0250a4e68debeeef93.png)
В настройках можно задать положение объектов, какое хотим:
![](https://habrastorage.org/storage2/00a/3ed/8e1/00a3ed8e199e582f3df9d521b9e7701a.png)
Итог
Весь процесс разработки в один пост не уместить. Подумал, может люди сами предложат тему для статьи, если им интересен этот движок. Рассказал бы вам о каких-то наиболее интересных аспектах разработки или наиболее интересных моментах при работе с LibGDX.
Исходники выложил на github.
Там же можно посмотреть и мои уроки по LibGDX, если кто-то хочет с этим движком работать.