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.

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

  1. Леха

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

  2. 1nt3g3r

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

    1. Suvitruf Автор записи

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

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

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

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

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

  3. Юрий

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

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

  4. Vitalik

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

      1. Vitalik

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

        1. Suvitruf Автор записи

          Угу)

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

  5. rainnogame

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

    1. Suvitruf Автор записи

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

        1. Suvitruf Автор записи

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

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

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

  6. Alex Zaiats

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

      1. Alex Zaiats

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

        1. Suvitruf Автор записи

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

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

          1. 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

            1. Suvitruf Автор записи

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

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

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

              1. uragan

                Где можно андроид прорутировать пожалуйста подскажите?

  7. Игорь

    Спасибо. В своих играх часто наталкивался на ошибку исключения когда что ни будь обнулял, например когда рисуется 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){}
    }. То есть текстуры обнуляются в тот момент, когда рисуются. Почему не возникает ошибка, чем это достигается? Это не опасно?

    1. Suvitruf Автор записи

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

  8. Игорь

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

    1. Suvitruf Автор записи

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

  9. Игорь

    цитата 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»)); все произойдет точно так же, или тут другой подход к загрузке?

  10. Игорь

    Подскажите пожалуйста, как можно выйти из игры на сайт? Через активити я раньше делал с помощью интент
    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. Как вы думаете, через какой класс это возможно сделать?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *