libGDX: Часть 8. Создание меню и переход между экранами

Меню игры

В любой игре присутствует меню. Довольно сложно придумать иной способ навигации. В этой статье рассмотрим как создать свой экран с меню средствами libGDX. Попутно немного о проблеме соотношения сторон на различных устройствах.

Проблема с разрешением экрана

Основная проблема, с которой сталкиваются разработчики игр для платформы Android – неправильное отображение сцены на устройствах с разным соотношением сторон. Если вы посмотрите разрешения на различных устройствах, то обнаружите, что их диапазон довольно большой.

Ладно ещё 720×1280 можно без проблем масштабировать до 360×640. Но простым скалированием не всегда можно решить проблему. Каждый выкручивается по своему. Можно сцену размещать по центру и добавлять фоновые картинки по краям. Или по краю выравнивать, а с другого края фоновое что-то. В моих играх такой проблемы не было, просто видимая часть объектов на различных устройства различалась. На геймплей это не особо сказывалось.

Переключение между экранами

Переключение между экранами в LibGDX довольно простое. Надо вызвать метод setScreen(Screen screen);. То есть в классе игры будут ссылки на все возможные экраны:

public class MyGame extends   Game  {
  public GameScreen game;
  public IntroScreen intro;
	
  @Override
  public void create() {
    game = new GameScreen(this);
    intro = new IntroScreen(this);
    //при запуске игры открываем экран с меню
    setScreen(intro);
}

По сути, создание меню не особо отличается от экрана самой игры. Так же обрабатываем нажатия, рендерим и т.д. Создадим класс IntroScreen, реализовав Screen, InputProcessor. Возьмём за основу решение, которое используется в игре Defender 2. Фон 512×1024 размером. Сама пикча на этом спрайте 480×800. Если экран больше этой картинки, то справа и снизу будет чёрный фон.

Создание меню

Размеры камеры зададим для актуальной картинки фоновой. Всё остальное схоже с игровым экраном.

float CAMERA_WIDTH = 800F;
float CAMERA_HEIGHT = 480F;

Необходимо нарисовать сам фон.

public void showBG(){
  spriteBatch.draw(bgTexture,0, -32, 1024 , 512);
		
}

Поверх него вывести кнопку.

public void showMenu(){
  if(downBtn)
    spriteBatch.draw(textures.get("cover_button_start_down"),653, 183, 256 , 128);
  else
    spriteBatch.draw(textures.get("cover_button_start_up"),653, 183, 256 , 128);
		
}

downBtn показывает нажата ли кнопка или нет. Для красоты анимации, так сказать (: Теперь надо только обработать нажатия.


//обработка нажатия
@Override
public boolean touchDown(int x, int y, int pointer, int button) {

//если нажали на кнопку
  if((height-y)/ppuY >= 213 && (height-y)/ppuY <= 283 && x/ppuX>=660 && x/ppuX<=780)
    //то устанвоим флаг, чтобы при рендеринге нарисовать другой спрайт
    downBtn = true;
		
  return true;
}

//убрали палец с экрана
@Override
public boolean touchUp(int x, int y, int pointer, int button) {
  if (!Gdx.app.getType().equals(ApplicationType.Android))
    return false;
  //если до этого нажали на кнопку
  if(downBtn){
    //чистим ресурсы
    dispose();
    //переходим на экран игры
    game.setScreen(game.game);
  }
			
  downBtn = false;
  return true;
}

В целом работа с меню ничем не отличается от обработки нажатий и отрисовка на игровом экране. Многие меню делают не средствами OpenGL, а не уровне Вьюх. То есть, при запуске игры открывает Activity с лейаутами. С таким меню работать в принципе проще. А уже потом при нажатии на кнопку открывать другое Activity и загружать в нём OpenGL.

Можете скачать исходники урока Libgdxtutorial-lesson8.rar.

  Категории: Android, libgdx, Коддинг
  • Леха

    Большое спасибо. Давайте дальше выкладывайте уроки. Можно Вам сделать допустим игру как мутировавшие организмы с других планет-демоны планеты Укоа летают вокруг Земли и похищают космических гномов-космонавтов.

  • 1nt3g3r

    А почему для этих целей не использовать Button из scene2d? или на крайний случай не сделать отдельного актера для кнопки? Плюс код из жестким указанием координат кнопки – это вообще нонсенс) Простите, но нельзя этого показывать новичкам, вы показываете им очень плохой пример

    • http://suvitruf.ru Suvitruf

      В этом примере архитектура без scene2d. В scene2d то да, удобно было б актёров юзать.

      Плюс код из жестким указанием координат кнопки – это вообще нонсенс)

      В целом да, надо работать с относительными координатами.

      В идеале все настройки для кнопок и прочее надо выносить в файлы для удобства.

      Этим примером показал просто, что работа с меню в принципе не особо отличается от работы с самой игрой.

  • http://apachai.ru Юрий

    Я в этой игре – Deffender II – сейчас на 250 уровне подстрял)))))

    ЗЫ какой клевый мистический шаблон у блога;)

    • http://suvitruf.ru Suvitruf

      Я тож где-то в районе 200-300 остановился. Слишком однообразная игра)

  • http://winners-games.com Vitalik

    Во многих играх почему то мало уделяют меню а зря. Если игра плохая то надо хоть как то смягчить удар.

    • http://suvitruf.ru Suvitruf

      Ну не знаю. Какой смысл делать красочное HD меню, если сама игра не очень? )

      • http://winners-games.com/ Vitalik

        Можно привести в пример теже казуалки что не игра то калл, но почти у всех их есть отменное меню.

        • http://suvitruf.ru Suvitruf

          Угу)

          Мне ещё вспоминается игра (вроде в Эпл сторе была), которая себя позиционирует как аналог ВоВ. Меню там супер, трейлер супер. А когда входишь в игру, то :-!

  • http://hotelpamira.com/ Viktoria Zlata

    Сейчас много людей посвящают свое время играм )

  • rainnogame

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

    • http://suvitruf.ru Suvitruf

      Не совсем понимаю, о чём вы)
      Это не Unity, где скрипты для всего надо писать. Для работы с LibGDX используется только Java.

      • rainnogame

        http://code.google.com/p/libgdx-users/wiki/LuaTutorial – наткнулся случайно, вот и стало интересно.
        А так же, в демо ( https://github.com/libgdx/libgdx/tree/master/demos/pax-britannica ) есть пример, с использованием скриптов.

        • http://suvitruf.ru Suvitruf

          Удивили, честно говоря.

          Если судить по статье, то там вызов Lua скриптов всё равно из Java приходится делать, как и с ndk. То есть, без Java всё равно никак. Не особо представляю, зачем оно надо. Хотя, если вы много лет работали с Lua, то может и имеет смысл.

          Я вряд ли могу статьи по этому поводу написать, ибо с Lua никогда не работал =/

  • Alex Zaiats

    Добрый день. Спасибо за отличный цикл статей, очень легко и доходчиво написано.
    Было б интересно почитать и про Memory Management в libgdx в вашем блоге :)

    • http://suvitruf.ru Suvitruf

      А что именно про Memory Management хотелось бы почитать? )

      • Alex Zaiats

        Например, не ясно как очистить Texture , TextureRegion ( так как именно эти моменты дают хорошую утечку памяти в моей игре). А вообще, было б интересно пообщаться с вами в скайпе,так как нету знакомых знающих libgdx. Если будет желание – добавляйтесь : alex.zaiats

        • http://suvitruf.ru Suvitruf

          У этих классов есть метод dispose(), который чистит ресурсы. Я его вызываю при переходах между экрана и(-или) в дестркуторе.
          Просто не думаю, что это на отдельную статью тянет.

          А как вы об утечке памяти узнаёте?

          • Alex Zaiats

            самый простой способ – скачать программку типа Ram Booster для андроида, играть в игру и проверять потребление оперативы, если растет – утечка есть. Можно использовать MAT для еклипса,в нем чуть удобнее это прослеживается.
            У меня игра достаточно простая, ( если интересно – https://play.google.com/store/apps/details?id=com.platinumgame.catchthecat), там приходится после каждой игры пересоздавать объексты поля( делать new Texture ), иначе после нескольких игр ячейки поля затираются неясными черными пикселями. Вот и получается что после каждой новой игры утекает около 2-3мб памяти за счет пересоздания ячеек поля. :)
            И еще 1 вопрос : У вас не было жалоб что игра вылетает с ошибкой на девайся типа Galaxy Gio , Galaxy Duo с ошибкой :
            java.lang.RuntimeException: eglSwapBuffers failed: EGL_BAD_ATTRIBUTE
            Пост на эту тему на стек оверфлоу :
            http://stackoverflow.com/questions/10459357/java-lang-runtimeexception-eglswapbuffers-failed-egl-success

            • http://suvitruf.ru Suvitruf

              Я проверял потребление памяти тремя способами:
              1) В Eclipse чекал сколько памяти ест в общем.
              2) В SDK для 4.1+ Андройдов появился Tracer for OpenGL.
              3) Программа от создателя чипсета. У меня Adreno на HTC One S. Adreno Profiler показывает сколько памяти ест игра, можно посмотреть какие текстуры в конкретный момент времени в памяти и т.д.

              У вас не было жалоб что игра вылетает с ошибкой на девайся типа Galaxy Gio , Galaxy Duo с ошибкой

              Не, таких крашей ни в одной игре ни разу не было. Но пишут, что из-за размеров атласа проблема может быть. У меня все атласы не больше 1024×1024.

  • Игорь

    Спасибо. В своих играх часто наталкивался на ошибку исключения когда что ни будь обнулял, например когда рисуется Bitmap и в этот момент вызываешь метод Bitmap =null; то приложение вылетает с ошибкой java.lang.NullPointerException, и для того чтобы этого избежать, перед этим методом я рисовал что то другое или заполнял цветом весь экран, а потом обнулял. В вашем методе dispose(); вы не чего не рисуете, но обнуляете текстуры @Override
    public void dispose() {
    Gdx.input.setInputProcessor(null);
    try{
    spriteBatch.dispose();bgTexture.dispose();textures.clear();
    }
    catch(Exception e){}
    }. То есть текстуры обнуляются в тот момент, когда рисуются. Почему не возникает ошибка, чем это достигается? Это не опасно?

    • http://suvitruf.ru Suvitruf

      У меня после вызова dispose сразу переключение на другой экран всегда идёт, поэтому после очистки новой отрисовки на том экране уже нет, поэтому и не выпадает NullPointerException.

  • Игорь

    Просто на Bitmap есть ограничение по разрешению http://developer.android.com/training/displaying-bitmaps/index.html и превышение этого разрешения может вызвать ошибку java.lang.OutofMemoryError: bitmap size exceeds VM budget, потому необходимо периодически обнулять и экономить память. А на текстуры папки asset такое же ограничение, не подскажите, при вызове методов bgTexture.dispose();textures.clear(); освобождается память для загрузки новых изображений ?

    • http://suvitruf.ru Suvitruf

      Именно для очистки эти методы и вызываются. В принципе, даже если вы просто null присвоите, то GC должен будет сам потом очистить ресурсы теоретически.

  • Игорь

    цитата Suvitruf,
    “У меня после вызова dispose сразу переключение на другой экран всегда идёт, поэтому после очистки новой отрисовки на том экране уже нет, ” а если перед переходом придется загрузить большое количество атласов и на загрузку потребуется время, то что будет отображаться на экране? Если я не ошибаюсь, переключение на другой экран не может произойти пака не загрузятся нужные для него атласы? Что будет на дисплее, пака загружается новый экран методом game.setScreen(game.game);? Стоит ли делать загрузку в фоновом потоке с помощью AsyncTask или у libGDX есть для этого свой класс?
    И если не много, хотел спросить по поводу ограничения на разрешение. Загрузка суммарного разрешения Bitmap 2592×1936 из ресурсов может вызвать java.lang.OutofMemoryError (то есть при загрузке 6 Bitmap изображений разрешением 1024×1024 на шестой текстуре приложение вылетит). При загрузке текстур из asset методом bgTexture = new Texture(Gdx.files.internal(“images/bg.png”)); все произойдет точно так же, или тут другой подход к загрузке?

  • Dmitrij

    а можно ли сделать рандомное переключение экранов?

    • http://suvitruf.ru Suvitruf

      Это как? )

      • Dmitrij

        ну это типо нажал на кнопку, и открылся случайный экран

        • http://suvitruf.ru Suvitruf

          Можно.

          • Dmitrij

            а как?(хотябы немного намекните))

            • http://suvitruf.ru Suvitruf

              В обработчике нажатия на кнопку, используя Random(), устанавливать экран.

  • Игорь

    Подскажите пожалуйста, как можно выйти из игры на сайт? Через активити я раньше делал с помощью интент
    public boolean Exittouch(MotionEvent event){
    if(event.getAction()==MotionEvent.ACTION_DOWN){
    Intent inte = new Intent(android.content.Intent.ACTION_VIEW,Uri.parse(“https://play.google.com/….”));
    startActivity(inte);
    }
    Но тут расширение не от Activity, а от com.badlogic.gdx.backends.android.AndroidApplication. Как вы думаете, через какой класс это возможно сделать?