Привет, Хабр! В этом топике, я хотел бы поделиться впечатлениями от игрового движка 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<TextureRegion> 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. Ссылку не даю, чтобы не привлекать внимание НЛО лишний раз. И хотя Коробку довольно быстро разобрали на запчасти на одном популярном ресурсе, для отдельно взятого человека, эта головоломка — довольно серьезный вызов. Что, меня лично, радует. Я даже могу предсказать где вы застрянете. Это либо уровень с краном (как сдвинуть вправо эту чертову клешню?), либо три разноцветных квадрата (эта загадка мне кажется легкой, но статистика упрямая штука). Либо, добро пожаловать в «клуб потерянного нуля» — ну это уже элита.


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

Комментарии (26)


  1. Suvitruf
    07.11.2016 08:10

    Я не нашел актуального, толкового туториала.
    Да не сочтите за рекламу, но в своё время делал туториалы небольшие по LibGDX (все сорсы тут), правда, по совсем древней версии и код с запашком. Так же, на 4pda тред стартовал по движку.
    А потому! Потому что я привык к Android Studio (писал корпоративное приложение в последнее время), знаю Java более-менее, Android SDK.
    В общем-то, при работе с LibGDX особо Android SDK знать то и не надо. Делаем одно Activity и всё, остальное — обычная Java. Разве что специфичные вещи из SDK только если нужны: реклама, инапы, гейм центр и т.п.
    Unity3d, конечно, оверхед по размеру билда, может медленнее работать, чем тоже самое на LibGDX, но, по крайней мере, скорость разработки на порядок выше.


  1. cry_san
    07.11.2016 08:23

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

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


    1. coder1cv8
      07.11.2016 09:28

      Я начинал с LibGDX Wiki и потом уже гуглил возникающие вопросы (как обычно, очень помогает stackoverflow)


  1. i360u
    07.11.2016 08:52
    +1

    Для тех кто учится рисовать или уже активно это делает, есть отличный СПО-вариант: https://krita.org/, очень рекомендую.


  1. suharik
    07.11.2016 09:45
    +3

    Классные коты! В мире, где власть захватывают совы, смотрятся здорово и стильно.


  1. tehnolog
    07.11.2016 10:20

    Кстати, я прошел вашу «Механическую коробку», когда вы ее выложили на Пикабу. Последний уровень с отсутствующим символом 0 на клавиатуре для ввода пароля — это что-то крышосносящее. Нельзя так издеваться над пользователями.


    1. coder1cv8
      07.11.2016 11:10

      Я не со зла… В основном, судя по отзывам, игроки соскучились по не казуальным играм. Поэтому, стараюсь соответствовать, делать крышесносящие и мозговзрывающие уровни :)


      1. suharik
        08.11.2016 16:27

        Reserve power снес уже ) Полдня вместо работы размышлял, что там и как.


        1. coder1cv8
          08.11.2016 16:35

          Эм, хочу предупредить, что дальше будет по настоящему сложно ))


          1. suharik
            08.11.2016 16:38
            +1

            А, то есть это я простую задачу не могу решить. Класс, отличный квест ) Стимулирует )


  1. Lasenokk
    07.11.2016 10:29

    Ну как можно извиняться за котиков?! :3


  1. polar11beer
    07.11.2016 10:50
    +1

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

    Каюсь, дальше не читал, полез гуглить эту Вашу книжку про рисование


  1. vvgladoun
    07.11.2016 11:55
    +5

    На картинке «было — стало» вариант слева глазу гораздо приятнее (разве что горящий диод поярче сделать, да кнопки с иконками чуть по контрастней сделать). Гораздо более «целостная», теплая и ламповая рисовка слева IMHO лучше, чем псевдо-натуральные элементы поверх рисованного фона и блестящие псевдо-3d кнопки прямиком из начала нулевых. В общем, зря вы так самокритично о своей рисовке — развивайте навыки, будьте труЪ-инди!


    1. Fen1kz
      07.11.2016 14:22

      Мне тоже больше понравился вариант было. Единственно, чем он проигрывает — количеством деталей, и отзеркаленным элементом с царапинами. Если это поправить, то куда лучше чем "стало"


  1. Prosto_Bro
    07.11.2016 14:18

    Я сам очень люблю Libgdx и много проектов на нем написал, но его кроссплатформенность пошатнулась. Собирать под Mac и iOS ему позволял RoboVM а разработчики закрыли проект. Так что, теперь, он уж не кажется таким хорошим(


    1. fogone
      07.11.2016 15:07

      Насколько я понимаю, под mac он работает на java. А вот под ios — да, он работал при помощи RoboVM. Который был коммерческим проектом последний год (или около того), но для libGdx его разрешали использовать бесплатно, потому что у них был общий разработчик. Проект заклылся, да, но, насколько я понимаю, его купил xamarin (читай майкрософт) — что бы с ним ни собирались делать, но RoboVM как проект существовать перестал. Однако, как жизнеспособная альтернатива сборки под ios сейчас внедряется intel multi-os-engine — разработчик libGdx пишет, что это наиболее близкий по возможностям к RoboVM проект. Так что есть надежда, что libGdx продолжит своё существование в виде мультиплатформенной системы. Так же хотел сказать, что при работе с libGdx-ом ощущается желание некоторого обновления, поэтому я потихоньку пытаюсь сделать надстройку над libGdx-ом на котлине с компонентным и реактивно-событийным подходом в нутрях. И обязательно сделаю статью, когда её можно будет назвать хотя бы альфой.


      1. Prosto_Bro
        07.11.2016 15:23

        Да, точно. Что-то я загнул когда и Mac сюда затащил -_-
        Знаю что разрешали, я его и использовал, да и сейчас еще пользуюсь.
        Тем не менее, Xamarin это уже шарп. Ребятки просто подмяли конкурентов.
        На счет планов внедрения я видел, но пока это не реализовано, все же минусик.
        Сейчас получается ситуация что если хочешь мультиплатформу, то иди в Xamarin, MonoGame, а это печалька так как «Java one love» и все дела.

        я потихоньку пытаюсь сделать надстройку над libGdx-ом на котлине с компонентным и реактивно-событийным подходом в нутрях.

        Годно, надеюсь не пропущу статью.


      1. Nudnyj
        18.02.2017 12:46
        +1

        Когда крутишь педали, кровь отливает от головы и думать сложнее.


        1. fogone
          08.11.2016 11:55

          Скорее всего они развивают версию RoboVM почти двухгодичной давности, когда та еще была в опенсорсе. С тех пор много воды утекло.


      1. Frimko
        19.02.2017 01:37
        +2

        Статья для мазохистов мне кажется.


  1. volume4
    07.11.2016 16:04
    +1

    А мне старый дизайн нравится больше


  1. Cobolorum
    17.02.2017 22:07
    +2

    За первую картинку «идеальная позиция для печатания» надо расстреливать


  1. m0sk1t
    10.11.2016 12:44
    +1

    так где всё-таки ноль?! ;) игра адски хороша, не останавливайтесь!


    1. coder1cv8
      10.11.2016 15:12

      Спасибо! Стараюсь :)
      Подсказка на дисплее.


  1. 3h4k
    10.11.2016 13:53
    +2

    Как же я согласен на счёт «отвратительная схема «ПО по подписке»» и «первый и единственный вопрос, который теперь возникает — это «а почему не Юнити, чувак?». А потому! Потому что я привык к Android Studio ..., знаю Java более-менее, Android SDK»!

    Ваш «Double Gun» на каком движке?


    1. coder1cv8
      10.11.2016 15:13

      На AndEngine, но с этим я завязал…