Рисуем, кодим под libGDX и другие маленькие радости из жизни инди-разработчика
Привет, Хабр! В этом топике, я хотел бы поделиться впечатлениями от игрового движка libGDX, рассказать о буднях обычного инди-разработчика и приоткрыть завесу тайны над игрой, которую я делаю последние несколько месяцев в свободное от офисного рабства время. Надеюсь, эти мои записки будут полезны тем кто только начинает что-то делать на libGDX или тем, кто выбирает движок для «игры своей мечты».
И извините за котов. Они совершенно никакого отношения не имеют к игрострою. Я тут параллельно учусь (пытаюсь учиться) рисовать и теперь эти мои тренировочные коты просто повсюду! Требуют чтобы их, бездельников, кому-нибудь показывали.
Рисование
Кстати, о рисовании. Меня конечно, закидают тапками матерые CG-художники (и будут абсолютно правы), но как же я тащусь от процесса! Я и не думал, что рисование — это так круто! У меня давным-давно, среди всякого хлама, валялся кем-то подаренный графический планшет. Bamboo что-то там, я думаю все покупают такие же «на побаловаться». И вот, как-то раз, мне на глаза попалась книжка Марка Кистлера «Вы сможете рисовать через 30 дней». Нет, естественно, научится рисовать за 30 дней — это все-равно что выучить С++ за тоже время. Все это понимают. Но оказывается, рисование — это не какой-то там неосязаемый талант, не эльфы пасущие единорогов на лугах неизведанных островов. Это просто набор правил, такой же простой как синтаксис языка программирования. Плюс опыт, конечно! Набивание руки. Но ведь и хороший программист развивается аналогичным образом, решая задачи, изучая паттерны, технологии и следуя всяким best practice.
Так вот, в этой книге (точнее книжке, она несерьезная совсем) есть все базовые правила, чтобы начать рисовать что-то большее чем палка-палка-огуречек. И когда собственноручно нарисованные закорючки перестают у тебя вызывать ужас хотя бы, это классно! Процесс сам по себе начинает приносить удовольствие. Лично для меня, это было открытием.
Еще, пользуясь случаем, хочу посоветовать отличный редактор — SketchBook Pro. В нем рисуют и профи, но он именно такой, какой нужен не профессионалу. Юзерфрендли, не перегруженный функционалом. Лицензия стоит относительно недорого, в отличие от того же Photoshop. Хотя здесь и применяется эта отвратительная схема «ПО по подписке», базовые функции доступны бесплатно.
Я использую всего 3 инструмента: карандаш, шариковая ручка, кисть. Здесь есть, не знаю как правильно называется, функция выправления на ходу ваших кривуль — мега полезная вещь! Берем в одну руку перо, вторую на пробел (поворот и масштабирование холста) и веред! Творить творчество.
Почему не Unity?
Но я отвлекся. Когда разговор заходит о выборе игрового движка, первый и единственный вопрос, который теперь возникает — это «а почему не Юнити, чувак?». А потому! Потому что я привык к Android Studio (писал корпоративное приложение в последнее время), знаю Java более-менее, Android SDK. И использование такого многофункционального монстра как Unity для простых 2D игр — это немного «из пушки по воробьям», вы не находите? Незнакомая IDE, другой подход, опять же. А размер пустого проекта в 20 Мб? Да у меня на libGDX вся игра вместе с графикой (которой тут не так уж и мало) весит меньше! Я понимаю, мы живем в век быстрого Интернета, но все же, зависимость между размером приложения и количеством установок — существует.
Если копать глубже, то суть в том, что предупреждение «Размер приложения слишком велик, вы уверены что хотите скачать его не через Wi-Fi?» (дословно я не помню, но суть такая) — отсекает какое-то количество импульсивных установок.
В общем, если вы раньше имели дело с Android SDK, libGDX — это отличный выбор для плавного перехода на кроссплатформенную разработку. Постараюсь сформулировать все плюсы и минусы, которые я вынес для себя из первого знакомства с libGDX.
Плюсы
- Бесплатный
- Кроссплатформенный. Desktop-билд одинаково беспроблемно собирается и запускается под Windows и Mac (на Linux не было возможности проверить, но я уверен — аналогично). Для HTML столкнулся с небольшим нюансом: все пакеты, которые вы добавляете, должны быть прописаны в Core-проекте в gwt.xml, иначе не будет билдится. С Android вообще никаких проблем, iOS у меня пока только в планах.
- Низкий порог вхождения для Android-разработчика. Та же Studio, тот же Gradle. У нас есть Core-проект в котором мы описываем всю игровую логику и Android-проект — это, грубо говоря, MainActivity на которой лежит View, а в нем исполняется Core-проект. Из core-проекта мы можем легко вызывать платформозависимую часть, которую пишем в Android-проекте. И что очень приятно, платформенный код (реклама, аналитика, игровые сервисы) мы реализуем прямо по туториалам от Google. Как в обычном приложении. То есть никаких библиотек, сторонних расширений и так далее!
Минусы
- Я не нашел актуального, толкового туториала. Информации очень много, даже здесь, на Хабре, но обычно она сводится к тому как вывести спрайт на экран. На практике это не нужно, зачем? Разумнее использовать абстракции более высокого уровня: Актер, Сцена (об этом ниже). При знакомстве с новым движком, в квикстарт-гайде, мне хотелось бы увидеть следующее: корректное масштабирование игры под различные экраны, фоновая загрузка ресурсов (с этим все хорошо), управление экранами/сценами и объектами на них.
Продолжая разговор о минусах, меня не покидает чувство легкой недосказанности в libGDX. Здесь есть отличные абстракции: Screen — игровой экран, Stage — сцена или слой, Actor — любой объект на этой сцене. Я в своей игре решил сделать один Screen и множество Stage на все игровые экраны: уровни, меню и т.д. От Actor наследуем игровые объекты, потом просто делаем stage.addActor (ball) и все. Дальше Stage сам занимается отрисовкой, перемещениями и т.п. этого объекта. Так вот Actor, сам по себе, не делает ничего. Не выводит спрайтов (а 99% игровых объектов — спрайты), нельзя проверить коллизию между двумя актерами, в общем, ничего!
Приходится делать свой SimpleActor и от него уже наследовать игровые объекты:
public class SimpleActor extends Actor {
private final TextureRegion region;
public SimpleActor(TextureRegion region) {
this.region = region;
setSize(region.getRegionWidth(), region.getRegionHeight());
setBounds(0, 0, getWidth(), getHeight());
}
@Override
public void draw(Batch batch, float parentAlpha) {
batch.draw(region, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
}
}
Если вам нужны анимированные игровые объекты (ну, а куда же без них?) пишем SimpleAnimatedActor:
public class SimpleAnimatedActor extends Actor {
private Animation animation;
private TextureRegion currentFrame;
private float stateTime;
private boolean started;
public SimpleAnimatedActor(float frameDuration, Array frames) {
animation = new Animation(frameDuration, frames);
currentFrame = animation.getKeyFrame(stateTime);
setBorders(frames.get(0));
}
private void setBorders(TextureRegion sample) {
setSize(sample.getRegionWidth(), sample.getRegionHeight());
setBounds(0, 0, getWidth(), getHeight());
}
public void start() {
stateTime = 0;
started = true;
}
public boolean isFinished() {
return animation.isAnimationFinished(stateTime);
}
@Override
public void act(float delta) {
super.act(delta);
if (!started) {
return;
}
stateTime += delta;
currentFrame = animation.getKeyFrame(stateTime);
}
@Override
public void draw(Batch batch, float parentAlpha) {
batch.draw(currentFrame, getX(), getY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation());
}
}
Для реализации примитивной проверки коллизий, можно добавить Polygon к нашему SimpleActor:
Polygon polygon = new Polygon(new float[]{0, 0, getWidth(), 0, getWidth(), getHeight(), 0, getHeight()});
polygon.setPosition(getX(), getY());
polygon.setOrigin(getOriginX(), getOriginY());
polygon.setScale(getScaleX(), getScaleY());
polygon.setRotation(getRotation());
И так далее. Особых трудностей это конечно, не вызывает. Но все-таки, я не могу понять, почему бы не сделать что-то подобное в движке? А так придется таскать свой дописанный код из проекта в проект.
Фрагментация
Как сделать так, чтобы игра выглядела одинаково клево на всем зоопарке Android-устройств? Для меня это один из ключевых вопросов. Поэтому расскажу подробнее как я решал проблему масштабирования картинки в libGDX.
Мне нравится ставший уже классическим подход, когда мы на планшетах (4:3) показываем больше окружения, а на смартфонах (16:9) — меньше. При этом нигде ничего не растягивается, а игровые объекты, можно сказать, не изменяют своих размеров. Всю графику храним в одном разрешении, у меня это 960×1280 (4:3). В центре экрана у нас есть «игровая область» 720×1280 (16:9) куда обязаны попадать все объекты с которыми взаимодействует игрок, остальное — декорации и могут быть обрезаны в зависимости от конкретного устройства. Проще всего этот принцип проиллюстрировать картинкой:
LibGDX предоставляет все возможности для этого. Единственный нюанс — некоторые объекты может понадобиться прикрепить к краю экрана. Как кнопку Home на изображении выше. Для этого нам надо знать фактические левый и правый края экрана в игровых координатах (с верхом и низом проще — это всегда 1280 и 0, соответственно). Напишем немного кода:
public class MyGame extends Game {
public static final float SCREEN_WIDTH = 960f;
public static final float SCREEN_HEIGHT = 1280f;
public static float VIEWPORT_LEFT;
public static float VIEWPORT_RIGHT;
private GameScreen mGameScreen;
@Override
public void create() {
mGameScreen = new GameScreen();
setScreen(mGameScreen);
}
@Override
public void resize(int width, int height) {
super.resize(width, height);
float aspectRatio = (float) width / height;
float viewportWidth = SCREEN_HEIGHT * aspectRatio;
VIEWPORT_LEFT = (SCREEN_WIDTH - viewportWidth) / 2;
VIEWPORT_RIGHT = VIEWPORT_LEFT + viewportWidth;
}
@Override
public void dispose() {
super.dispose();
mGameScreen.dispose();
}
}
Что здесь происходит? Это главный класс игры, унаследованный от Game. Здесь мы создаем и устанавливаем Screen (экран, в моем случае — единственный) и переопределяем resize (). Это событие для Android вызывается при старте приложения и здесь мы можем рассчитать левый и правый края картинки (VIEWPORT_LEFT и VIEWPORT_RIGHT).
В GameScreen нам надо установить FillViewport, именно он отвечает за то чтобы изображение обрезалось если соотношение сторон не соответствует целевому. Вьюпорт — это наше окно в мир игры. Они бывают нескольких типов, документация об этом расскажет даже лучше чем я, а я просто приведу код:
public class GameScreen implements Screen {
private final OrthographicCamera mCamera;
private final Viewport mViewport;
private final AssetManager mAssetManager;
private Stage mActiveStage;
public GameScreen() {
mCamera = new OrthographicCamera();
mViewport = new FillViewport(MyGame.SCREEN_WIDTH, MyGame.SCREEN_HEIGHT, mCamera);
mAssetManager = new AssetManager();
mActiveStage = new MyStage(mViewport, mAssetManager);
}
@Override
public void render(float delta) {
mCamera.update();
Gdx.gl.glClearColor(65 / 255f, 65 / 255f, 65 / 255f, 1f); // просто цвет фона
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
mActiveStage.act(delta);
mActiveStage.draw();
}
@Override
public void resize(int width, int height) {
mViewport.update(width, height, true);
}
@Override
public void dispose() {
mActiveStage.dispose();
mAssetManager.dispose();
}
}
Здесь, в общем-то, все интуитивно должно быть понятно, не забывайте только делать mViewport.update (width, height, true) при resize ().
Механическая Коробка
Теперь, когда я рассказал вкратце о движке, мне бы хотелось сказать еще несколько слов о самой игре. Я где-то уже год не занимался играми, корпоративное болото почти засосало меня. Но это не мешало мне мечтать. Мечтать о квесте, головоломке настолько сложной, насколько это вообще возможно. Без подсказок за деньги и прочих фритуплейных глупостей.
Так появилась идея Механической Коробки — устройства, созданного с одной единственной целью: надежно защищать то, что находится внутри. Где каждый слой защиты — это отдельная, самостоятельная головоломка с новой игровой механикой.
Огромный плюс инди, мы можем позволить себе делать то, что нам вздумается. Мне плевать на Retention Rate и прочие маркетинговые штучки. Да геймдиз любой коммерческой студии застрелится, увидев процент игроков застревающих на первом уровне! А я могу просто делать такую игру, в которую мне бы хотелось поиграть самому.
В общем, в августе этого года я приступил к строительству Коробки. Порисовав немного, я понял что мне не вытянуть такие объемы, да и мой арт не устраивал меня по качеству. Он, мягко говоря, своеобразный. В результате, на gamedev.ru был найден художник, согласившийся ввязаться в эту авантюру. Удивительно, оказалось что мы с Владимиром живем не только в одном городе, но и на соседних улицах. Он-то и придал Механической Коробке соответсвующий внешний вид. Я даже сохранил на память «как было» и «как стало». Разница, я думаю, очевидна.
Я пока продолжаю работать над Механической Коробкой, у меня есть кое-какие мысли по развитию этой идеи. Если вы решите испытать свои силы, игра уже опубликована на Google Play. Ссылку не даю, чтобы не привлекать внимание НЛО лишний раз. И хотя Коробку довольно быстро разобрали на запчасти на одном популярном ресурсе, для отдельно взятого человека, эта головоломка — довольно серьезный вызов. Что, меня лично, радует. Я даже могу предсказать где вы застрянете. Это либо уровень с краном (как сдвинуть вправо эту чертову клешню?), либо три разноцветных квадрата (эта загадка мне кажется легкой, но статистика упрямая штука). Либо, добро пожаловать в «клуб потерянного нуля» — ну это уже элита.
Спасибо за прочтение, задавайте вопросы, пишите замечания!
Делайте добро и бросайте его в воду.
Комментарии (2)
7 ноября 2016 в 08:10
0↑
↓
Я не нашел актуального, толкового туториала.
Да не сочтите за рекламу, но в своё время делал туториалы небольшие по LibGDX (все сорсы тут), правда, по совсем древней версии и код с запашком. Так же, на 4pda тред стартовал по движку.А потому! Потому что я привык к Android Studio (писал корпоративное приложение в последнее время), знаю Java более-менее, Android SDK.
В общем-то, при работе с LibGDX особо Android SDK знать то и не надо. Делаем одно Activity и всё, остальное — обычная Java. Разве что специфичные вещи из SDK только если нужны: реклама, инапы, гейм центр и т.п.
Unity3d, конечно, оверхед по размеру билда, может медленнее работать, чем тоже самое на LibGDX, но, по крайней мере, скорость разработки на порядок выше.7 ноября 2016 в 08:23
0↑
↓
Я не нашел актуального, толкового туториала.
Но ведь как-то вы учились?
Ссылки на ВМЕНЯЕМЫЕ ресурсы по обучению или хитростям есть?