Рисуем, кодим под libGDX и другие маленькие радости из жизни инди-разработчика

657cf884b2db49178941e1577423795a.png

Привет, Хабр! В этом топике, я хотел бы поделиться впечатлениями от игрового движка libGDX, рассказать о буднях обычного инди-разработчика и приоткрыть завесу тайны над игрой, которую я делаю последние несколько месяцев в свободное от офисного рабства время. Надеюсь, эти мои записки будут полезны тем кто только начинает что-то делать на libGDX или тем, кто выбирает движок для «игры своей мечты».

И извините за котов. Они совершенно никакого отношения не имеют к игрострою. Я тут параллельно учусь (пытаюсь учиться) рисовать и теперь эти мои тренировочные коты просто повсюду! Требуют чтобы их, бездельников, кому-нибудь показывали.

Рисование


Кстати, о рисовании. Меня конечно, закидают тапками матерые CG-художники (и будут абсолютно правы), но как же я тащусь от процесса! Я и не думал, что рисование — это так круто! У меня давным-давно, среди всякого хлама, валялся кем-то подаренный графический планшет. Bamboo что-то там, я думаю все покупают такие же «на побаловаться». И вот, как-то раз, мне на глаза попалась книжка Марка Кистлера «Вы сможете рисовать через 30 дней». Нет, естественно, научится рисовать за 30 дней — это все-равно что выучить С++ за тоже время. Все это понимают. Но оказывается, рисование — это не какой-то там неосязаемый талант, не эльфы пасущие единорогов на лугах неизведанных островов. Это просто набор правил, такой же простой как синтаксис языка программирования. Плюс опыт, конечно! Набивание руки. Но ведь и хороший программист развивается аналогичным образом, решая задачи, изучая паттерны, технологии и следуя всяким best practice.
b5f155e3fb7b4f48ba7a7f89ba10f355.png
Так вот, в этой книге (точнее книжке, она несерьезная совсем) есть все базовые правила, чтобы начать рисовать что-то большее чем палка-палка-огуречек. И когда собственноручно нарисованные закорючки перестают у тебя вызывать ужас хотя бы, это классно! Процесс сам по себе начинает приносить удовольствие. Лично для меня, это было открытием.
2510e94f3a134e4ca4b6b557f5242839.png
Еще, пользуясь случаем, хочу посоветовать отличный редактор — SketchBook Pro. В нем рисуют и профи, но он именно такой, какой нужен не профессионалу. Юзерфрендли, не перегруженный функционалом. Лицензия стоит относительно недорого, в отличие от того же Photoshop. Хотя здесь и применяется эта отвратительная схема «ПО по подписке», базовые функции доступны бесплатно.

Я использую всего 3 инструмента: карандаш, шариковая ручка, кисть. Здесь есть, не знаю как правильно называется, функция выправления на ходу ваших кривуль — мега полезная вещь! Берем в одну руку перо, вторую на пробел (поворот и масштабирование холста) и веред! Творить творчество.

Почему не Unity?


Но я отвлекся. Когда разговор заходит о выборе игрового движка, первый и единственный вопрос, который теперь возникает — это «а почему не Юнити, чувак?». А потому! Потому что я привык к Android Studio (писал корпоративное приложение в последнее время), знаю Java более-менее, Android SDK. И использование такого многофункционального монстра как Unity для простых 2D игр — это немного «из пушки по воробьям», вы не находите? Незнакомая IDE, другой подход, опять же. А размер пустого проекта в 20 Мб? Да у меня на libGDX вся игра вместе с графикой (которой тут не так уж и мало) весит меньше! Я понимаю, мы живем в век быстрого Интернета, но все же, зависимость между размером приложения и количеством установок — существует.

Если копать глубже, то суть в том, что предупреждение «Размер приложения слишком велик, вы уверены что хотите скачать его не через Wi-Fi?» (дословно я не помню, но суть такая) — отсекает какое-то количество импульсивных установок.
32cbf30f09fa4ae592cc41a8ee1fa1d1.png
В общем, если вы раньше имели дело с 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. Как в обычном приложении. То есть никаких библиотек, сторонних расширений и так далее!

Минусы


  • Я не нашел актуального, толкового туториала. Информации очень много, даже здесь, на Хабре, но обычно она сводится к тому как вывести спрайт на экран. На практике это не нужно, зачем? Разумнее использовать абстракции более высокого уровня: Актер, Сцена (об этом ниже). При знакомстве с новым движком, в квикстарт-гайде, мне хотелось бы увидеть следующее: корректное масштабирование игры под различные экраны, фоновая загрузка ресурсов (с этим все хорошо), управление экранами/сценами и объектами на них.

0c7793c805df4939a45a4466f11544e3.png
Продолжая разговор о минусах, меня не покидает чувство легкой недосказанности в 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) куда обязаны попадать все объекты с которыми взаимодействует игрок, остальное — декорации и могут быть обрезаны в зависимости от конкретного устройства. Проще всего этот принцип проиллюстрировать картинкой:

90a581ba261c43da9b5d5ff3dc11f10f.png

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).
07ab4a3ae9d14b76a7a577515d9245e6.png
В 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 и прочие маркетинговые штучки. Да геймдиз любой коммерческой студии застрелится, увидев процент игроков застревающих на первом уровне! А я могу просто делать такую игру, в которую мне бы хотелось поиграть самому.
ed2779048b8549018bb9d914e2893c08.png
В общем, в августе этого года я приступил к строительству Коробки. Порисовав немного, я понял что мне не вытянуть такие объемы, да и мой арт не устраивал меня по качеству. Он, мягко говоря, своеобразный. В результате, на gamedev.ru был найден художник, согласившийся ввязаться в эту авантюру. Удивительно, оказалось что мы с Владимиром живем не только в одном городе, но и на соседних улицах. Он-то и придал Механической Коробке соответсвующий внешний вид. Я даже сохранил на память «как было» и «как стало». Разница, я думаю, очевидна.

69a4f192c4824dd5b1eb9bb8a0f38435.pngb63601acf6b846a3a1cfe29f5a3c0035.png

Я пока продолжаю работать над Механической Коробкой, у меня есть кое-какие мысли по развитию этой идеи. Если вы решите испытать свои силы, игра уже опубликована на Google Play. Ссылку не даю, чтобы не привлекать внимание НЛО лишний раз. И хотя Коробку довольно быстро разобрали на запчасти на одном популярном ресурсе, для отдельно взятого человека, эта головоломка — довольно серьезный вызов. Что, меня лично, радует. Я даже могу предсказать где вы застрянете. Это либо уровень с краном (как сдвинуть вправо эту чертову клешню?), либо три разноцветных квадрата (эта загадка мне кажется легкой, но статистика упрямая штука). Либо, добро пожаловать в «клуб потерянного нуля» — ну это уже элита.
147195f560b542f899812f57c74b5e7e.png

Спасибо за прочтение, задавайте вопросы, пишите замечания!
Делайте добро и бросайте его в воду.

Комментарии (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

    Я не нашел актуального, толкового туториала.

    Но ведь как-то вы учились?
    Ссылки на ВМЕНЯЕМЫЕ ресурсы по обучению или хитростям есть?

© Habrahabr.ru