libGDX: Часть 2. Архитектура игры

После рассмотрения жизненного цикла игры сразу стоит рассмотреть архитектуру (каркас). Вообще Роллингс и Моррис (Rollings and Dave Morris) в своей книге «Game Architecture and Design» подробно описывают создание игр с точки зрения архитектуры. В своё время я правда не особо проникся этой книгой, но вам может понравится. Я же опишу архитектуру, которую стараюсь использовать сам.

Разбиение приложения на компоненты со слабым связыванием — это не просто какой-то идеологических ход, такой подход действительно очень упрощает разработку. В частности, я предлагаю использовать заезженный паттерн — MVC. Часть материала брал с занятного сайта http://obviam.net/. Там вообще очень много полезной информации для разработчиков игр.

MVC

Довольно-таки удобный образец архитектуры для разработки игр. Главным его преимуществом, как по мне, является то, что можно вносить изменения в какую-то часть игры, при этом не затрагивая остальные компоненты приложения.

MVC

Примерно как всё в играх происходит? Игрок производит какую-то манипуляцию:

  1. Игрок нажимает на экран (или на клавиатуру).
  2. В controller обрабатывается нажатие. Здесь же по сути вся логика реализована: проверка на препятствия, отслеживание состояний объектов, изменение их состояний и т.д.
  3. То есть, controller изменяет состояние объектов (model‘s).
  4. После чего объекты отрисовываются (view).

MVC очень удачно подходит. Если ещё не поняли, поясню кое-какие моменты. Объекты (Model) абсолютно ничего не знают про рендеринг. Многие пишут, что объекты не должны и состояние менять сами, а за них это должен делать контроллер. Я к этому вопросы подошёл практически. Возьмите, к примеру, вашего персонажа, которому надо как-то описать логику обхода препятствий. Большинство скажет, что в контроллере сие дело надо реализовывать. Но почему? Ведь, когда вы идёте по улице, то обходите препятствие после собственных расчётов, а не мир или контроллер просчитывает это дело. Так что, часть логики взаимодействия с миром я бы посоветовал именно в сами объекты добавлять.

Я имею ввиду именно живые объекты (если можно так сказать про виртуальных персонажей (: ). Логику неодушевлённых предметов можно и в контроллере делать. Перейдём к практической части. За основу берём проект из введения.

Игровые компоненты

В данной статье покажу как разбить на части приложение. Немного про объекты мира расскажу.

Создание мира и его объектов

Добавьте к проекту package suvitruf.libgdxtutorial.model. Здесь у нас будут объекты мира. Добавьте в этот пакет класс Brick и зададим базовые свойства:

01.package suvitruf.libgdxtutorial.model;
02. 
03.//импорт нужных либ
04.import com.badlogic.gdx.math.Rectangle;
05.import com.badlogic.gdx.math.Vector2;
06. 
07.public class Brick {
08.  //размер объекта
09.  static final float SIZE = 1f;
10.  //координаты
11.  Vector2 position = new Vector2();
12.  Rectangle bounds = new Rectangle();
13.     
14.  public Brick(Vector2 pos) {
15.    this.position = pos;
16.    this.bounds.width = SIZE;
17.    this.bounds.height = SIZE;
18.  }
19.   
20.  public Rectangle getBounds() {
21.    return bounds;
22.  }
23. 
24.  public Vector2 getPosition() {
25.    return position;
26.  }
27.}

У блока нет никакой логики, он представляет собой…ммм…просто кирпич. Он ни с чем не взаимодействует, но с ним могут взаимодействовать живые объекты. Мы используем тип Vector2 от libgdx. Это позволяет нам работать лишь с Евклидовыми векторами. Мы будем использовать векторы для позиционирования, вычисления скорости и для движения (ну да, кирпич не двигается…но наш персонаж будет).

Далее добавим класс (добавьте класс Player к пакету suvitruf.libgdxtutorial.model), который будет являть собой нашего персонажа.

01.package suvitruf.libgdxtutorial.model;
02. 
03.import com.badlogic.gdx.math.Rectangle;
04.import com.badlogic.gdx.math.Vector2;
05. 
06.public class Player {
07. 
08.  //состояние
09.  public enum State {
10.    NONE, WALKING, DEAD
11.  }
12. 
13.  
14.  //скорость движения
15.  public static final float SPEED = 2f;
16.  //размер 
17.  public static final float SIZE = 0.7f;
18.     
19.  //позиция в мире
20.  Vector2 position = new Vector2();
21.  //используется для вычисления движения
22.  Vector2 velocity = new Vector2();
23.  //прямоугольник, в который вписан игрок
24.  //будет использоваться в будущем для нахождения коллизий (столкновение со стенкой и т.д.
25.  Rectangle     bounds = new Rectangle();
26.  //текущее состояние
27.  State     state = State.NONE;
28. 
29.  public Player(Vector2 position) {    
30.    this.position = position;
31.    this.bounds.height = SIZE;
32.    this.bounds.width = SIZE;
33.  }
34. 
35.  public Rectangle getBounds() {
36.    return bounds;
37.  }
38. 
39.  public Vector2 getVelocity() {
40.    return velocity;
41.  }
42. 
43.  public Vector2 getPosition() {
44.    return position;
45.  }
46. 
47.  //обновления движения
48.  public void update(float delta) {
49.    position.add(velocity.tmp().mul(delta));
50.  }
51.}

Теперь нам нужно создать мир, в котором будут все эти объекты. Добавляем в пакет suvitruf.libgdxtutorial.model класс World. Мир условно делится на клетки. К примеру создадим мир 8×5.

01.package suvitruf.libgdxtutorial.model;
02. 
03. 
04.import com.badlogic.gdx.math.Vector2;
05.import com.badlogic.gdx.utils.Array;
06. 
07.public class World {
08.  //массив блоков
09.  Array<Brick> bricks = new Array<Brick>();
10.  //наш персонаж
11.  public Player player;
12.     
13.  //ширина мира
14.  public int width;
15.  //высота мира
16.  public int height;
17.     
18.  //получить массив блоков
19.  public Array<Brick> getBricks() {
20.    return bricks;
21.  }
22.  //получить игрока
23.  public Player getPlayer() {
24.    return player;
25.  }
26.     
27.  public World() {
28.    width = 8;
29.    height = 5;
30.    createWorld();
31.  }
32. 
33.  //создадим тестовый мир какой-нибудь
34.  public void createWorld() {
35.    player = new Player(new Vector2(6,2));
36.    bricks.add(new Brick(new Vector2(0, 0)));
37.    bricks.add(new Brick(new Vector2(1, 0)));
38.    bricks.add(new Brick(new Vector2(2, 0)));
39.    bricks.add(new Brick(new Vector2(3, 0)));
40.    bricks.add(new Brick(new Vector2(4, 0)));
41.    bricks.add(new Brick(new Vector2(5, 0)));
42.    bricks.add(new Brick(new Vector2(6, 0)));
43.    bricks.add(new Brick(new Vector2(7, 0)));
44.   
45.         
46.  }
47.}

World — модель мира. По сути он является контейнером для объектов, что логично (:

Контроллер

Создадим пакет suvitruf.libgdxtutorial.controller и добавим в него класс WorldController. В этом классе как раз и будет реализована логика вся. По идеи в контроллере производятся изменения состояний объектов мира. И главное, в зависимости от действий юзера будут манипуляции с объектом Player.

001.package suvitruf.libgdxtutorial.controller;
002. 
003.import java.util.HashMap;
004.import java.util.Map;
005.import suvitruf.libgdxtutorial.model.*;
006. 
007.public class WorldController {
008. 
009.  //направление движения
010.  enum Keys {
011.    LEFT, RIGHT, UP, DOWN
012.  }
013.  //игрок
014.  public Player player;
015.     
016.  //куда движемся...игрок может двигаться одновременно по 2-м направлениям
017.  static Map<Keys, Boolean> keys = new HashMap<WorldController.Keys, Boolean>();
018. 
019.  //первоначально стоим
020.  static {
021.    keys.put(Keys.LEFT, false);
022.    keys.put(Keys.RIGHT, false);
023.    keys.put(Keys.UP, false);
024.    keys.put(Keys.DOWN, false);
025.  };
026. 
027.  public WorldController(World world) {
028.    this.player = world.getPlayer();
029.  }
030.   
031.  //флаг устанавливаем, что движемся влево
032.  public void leftPressed() {
033.    keys.get(keys.put(Keys.LEFT, true));
034.  }
035. 
036.  //флаг устанавливаем, что движемся вправо
037.  public void rightPressed() {
038.    keys.get(keys.put(Keys.RIGHT, true));
039.  }
040.   
041.  //флаг устанавливаем, что движемся вверх 
042.  public void upPressed() {
043.    keys.get(keys.put(Keys.UP, true));
044.  }
045.     
046.  //флаг устанавливаем, что движемся вниз
047.  public void downPressed() {
048.    keys.get(keys.put(Keys.DOWN, true));
049.  }
050.     
051.  //освобождаем флаги
052.  public void leftReleased() {
053.    keys.get(keys.put(Keys.LEFT, false));
054.  }
055.  public void rightReleased() {
056.    keys.get(keys.put(Keys.RIGHT, false));
057.  }
058.  public void upReleased() {
059.    keys.get(keys.put(Keys.UP, false));
060.  }
061.  public void downReleased() {
062.    keys.get(keys.put(Keys.DOWN, false));
063.  }
064.     
065.  //главный метод класса...обновляем состояния объектов здесь
066.  public void update(float delta) {
067.    processInput();
068.    player.update(delta);
069.  }
070. 
071.  public void resetWay(){
072.    rightReleased();
073.    leftReleased();
074.    downReleased();
075.    upReleased();
076.  }
077. 
078.  //в зависимости от выбранного направления движения выставляем новое направление движения для персонажа
079.  private void processInput() {
080.    if (keys.get(Keys.LEFT))
081.      player.getVelocity().x = -Player.SPEED;
082. 
083.    if (keys.get(Keys.RIGHT))
084.      player.getVelocity().x =Player.SPEED;
085.         
086.    if (keys.get(Keys.UP))
087.      player.getVelocity().y = Player.SPEED;
088.         
089.         
090.    if (keys.get(Keys.DOWN))
091.      player.getVelocity().y = -Player.SPEED;
092. 
093.    //если не выбрано направление, то стоим на месте
094.    if ((keys.get(Keys.LEFT) && keys.get(Keys.RIGHT)) || (!keys.get(Keys.LEFT) && (!keys.get(Keys.RIGHT))))
095.      player.getVelocity().x = 0;                  
096.    if ((keys.get(Keys.UP) && keys.get(Keys.DOWN)) || (!keys.get(Keys.UP) && (!keys.get(Keys.DOWN))))
097.      player.getVelocity().y = 0;          
098.         
099.  }
100.}

Рендеринг

И так, про контроллер и объекты мира поговорили. Теперь нужно отрисовать объекты наши. Для этого создадим пакет suvitruf.libgdxtutorial.view, а в нём класс WorldRenderer.

01.package suvitruf.libgdxtutorial.view;
02. 
03.import suvitruf.libgdxtutorial.model.*;
04.import com.badlogic.gdx.graphics.Color;
05.import com.badlogic.gdx.graphics.OrthographicCamera;
06.import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
07.import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
08.import com.badlogic.gdx.math.Rectangle;
09. 
10.public class WorldRenderer {
11.  public static float CAMERA_WIDTH = 8f;
12.  public static  float CAMERA_HEIGHT = 5f;
13.     
14.  private World world;
15.  public OrthographicCamera cam;
16.  ShapeRenderer renderer = new ShapeRenderer();
17. 
18.     
19.  public int width;
20.  public int height;
21.  public float ppuX;    // пикселей на точку мира по X
22.  public float ppuY;    // пикселей на точку мира по Y
23.     
24.  public void setSize (int w, int h) {
25.    this.width = w;
26.    this.height = h; 
27.    ppuX = (float)width / CAMERA_WIDTH;
28.    ppuY = (float)height / CAMERA_HEIGHT;
29.  }
30.  //установка камеры
31.  public void SetCamera(float x, float y){
32.    this.cam.position.set(x, y,0); 
33.    this.cam.update();
34.  }
35.     
36.  public WorldRenderer(World world) {
37.         
38.    this.world = world;
39.    this.cam = new OrthographicCamera(CAMERA_WIDTH, CAMERA_HEIGHT);
40.    //устанавливаем камеру по центру
41.    SetCamera(CAMERA_WIDTH / 2f, CAMERA_HEIGHT / 2f);
42. 
43.  }
44.   
45.  //основной метод, здесь мы отрисовываем все объекты мира
46.  public void render() {
47.    drawBricks();
48.    drawPlayer() ;
49.         
50.  }
51. 
52.  //отрисовка кирпичей
53.  private void drawBricks() {
54.    renderer.setProjectionMatrix(cam.combined);
55.    //тип устанавливаем...а данном случае с заливкой
56.    renderer.begin(ShapeType.FilledRectangle);
57.    //прогоняем блоки
58.    for (Brick brick : world.getBricks()) {
59.      Rectangle rect =  brick.getBounds();
60.      float x1 =  brick.getPosition().x + rect.x;
61.      float y1 =  brick.getPosition().y + rect.y;
62.      renderer.setColor(new Color(0, 0, 0, 1));
63.      //и рисуем блоки
64.      renderer.filledRect(x1, y1, rect.width, rect.height);
65.    }
66.         
67.    renderer.end();
68.  }
69. 
70.  //отрисовка персонажа по аналогии
71.  private void drawPlayer() {
72.    renderer.setProjectionMatrix(cam.combined);
73.    Player player = world.getPlayer();
74.    renderer.begin(ShapeType.Rectangle);
75.         
76.    Rectangle rect = player.getBounds();
77.    float x1 = player.getPosition().x + rect.x;
78.    float y1 = player.getPosition().y + rect.y;
79.    renderer.setColor(new Color(1, 0, 0, 1));
80.    renderer.rect(x1, y1, rect.width, rect.height);
81.    renderer.end();
82.  }
83.     
84.}

ppuX и ppuY очень важны…Ведь мир у нас 8на5, а экран телефона в пикслеях не соответствует этим размерам. Поэтому нужны эти переменные, которые при рендеринге будут корректировать координаты объектов для вывода на реальный экран телефона.

OrthographicCamera cam — камера, которая используется для того, чтобы «посмотреть» на мир. В текущем примере мир очень маленький, и он влезает в камеру, но когда у нас будет большой уровень, и персонаж будет перемещается в нем, мы должны будем менять положение камеры. Собственно, там и расчёт координат изменится…В будущих статьях остановлюсь на этом.

GameScreen

Теперь осталось лишь связать все наши компоненты вместе. Для этого создадим пакет suvitruf.libgdxtutorial.screens, а в нём класс GameScreen.

GameScreen реализует интерфейс Screen, который очень походит на ApplicationListener, но у этого есть 2 важных ключевых отличия (два метода). show() – вызывается, когда становится активным. hide()– вызывается, когда активным становится другой экран.

001.package suvitruf.libgdxtutorial.screens;
002.import suvitruf.libgdxtutorial.model.*;
003.import suvitruf.libgdxtutorial.controller.*;
004.import suvitruf.libgdxtutorial.view.*;
005. 
006.import com.badlogic.gdx.Gdx;
007.import com.badlogic.gdx.InputProcessor;
008.import com.badlogic.gdx.Screen;
009.import com.badlogic.gdx.Application.ApplicationType;
010. 
011.import com.badlogic.gdx.graphics.GL10;
012. 
013.public class GameScreen implements Screen, InputProcessor {
014.    private World           world;
015.    private WorldRenderer   renderer;
016.    private WorldController controller;
017. 
018.    private int width, height;
019.     
020.    @Override
021.    public void show() {
022. 
023.        world = new World();
024.        renderer = new WorldRenderer(world);
025.        controller = new WorldController(world);
026.        Gdx.input.setInputProcessor(this);
027. 
028.    }
029.    @Override
030.    public boolean touchDragged(int x, int y, int pointer) {
031.        ChangeNavigation(x,y);
032.        return false;
033.    }
034. 
035.     
036.    public boolean touchMoved(int x, int y) {
037.        return false;
038.    }
039. 
040.    @Override
041.    public boolean mouseMoved(int x, int y) {
042.        return false;
043.    }
044.     
045.    @Override
046.    public boolean keyTyped(char character) {
047.        return false;
048.    }
049.     
050.    @Override
051.    public void resize(int width, int height) {
052.        renderer.setSize(width, height);
053.        this.width = width;
054.        this.height = height;
055.    }
056. 
057.    @Override
058.    public void hide() {
059.        Gdx.input.setInputProcessor(null);
060.    }
061. 
062.    @Override
063.    public void pause() {
064.    }
065. 
066.    @Override
067.    public void resume() {
068.    }
069. 
070.    @Override
071.    public void dispose() {    
072.        Gdx.input.setInputProcessor(null);
073.    }
074. 
075. 
076.    @Override
077.    public boolean keyDown(int keycode) {
078.         
079.        return true;
080.    }
081. 
082.    @Override
083.    public void render(float delta) {
084. 
085.        Gdx.gl.glClearColor(1, 1, 1, 1);
086.        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
087. 
088.        controller.update(delta);
089.        renderer.render();
090.    }
091.    @Override
092.    public boolean keyUp(int keycode) {
093. 
094.        return true;
095.    }
096.     
097.    private void ChangeNavigation(int x, int y){
098.        controller.resetWay();
099.        if(height-y >  controller.player.getPosition().y * renderer.ppuY)
100.            controller.upPressed();
101.         
102.        if(height-y <  controller.player.getPosition().y * renderer.ppuY)
103.            controller.downPressed();
104.         
105.        if ( x< controller.player.getPosition().x * renderer.ppuX)
106.            controller.leftPressed();
107.             
108.        if (x> (controller.player.getPosition().x +Player.SIZE)* renderer.ppuX)
109.            controller.rightPressed();
110.             
111.    }
112.     
113.    @Override
114.    public boolean touchDown(int x, int y, int pointer, int button) {
115. 
116.        if (!Gdx.app.getType().equals(ApplicationType.Android))
117.            return false;
118.        ChangeNavigation(x,y);
119.        return true;
120.    }
121.     
122.    @Override
123.    public boolean touchUp(int x, int y, int pointer, int button) {
124.        if (!Gdx.app.getType().equals(ApplicationType.Android))
125.            return false;
126.        controller.resetWay();
127.         
128.        return true;
129.    }
130.     
131.    @Override
132.    public boolean scrolled(int amount) {
133.        return false;
134.    }
135.}

Этот класс отзывается на действия юзера. По названиям методов, думаю, понятно всё. В ChangeNavigation() мы определяем и устанавливаем направление движения персонажа. В данном примере, если вы кликаете левее игрока, то двигаетесь влево, если вверх то вверх и т.д.

Если запустите игру, то увидите что-то такое. Персонаж (красный квадратик) будет двигаться в зависимости от того, куда вы кликните относительно него.

libgdxlesson2

Собственно всё. Можете скачать исходники урока Libgdxtutorial-lesson2.rar.

libGDX: Часть 2. Архитектура игры: 71 комментарий

  1. Уведомление: libGDX: Часть 3. Обзор модулей | Suvitruf's Blog

  2. Уведомление: libGDX: Часть 4. Спрайты, Текстуры, Регионы, Атлас | Suvitruf's Blog

  3. Валик

    И зачем такие костыли? если уж пользуетесь движком так пользуйтесь есть Stage, Actor,Group и не нужно заново выдумывать велосипед.

  4. Уведомление: libGDX: Часть 6. Работа с Box2D | Suvitruf's Blog

  5. Женя

    Сделал все по инструкции, при запуске приложения у меня только черный экран. Что это может быть?

  6. Женя

    Просмотрел ваши исходники, в классе «MyGame» у вас появились несколько новых строк, которые не были указаны ранее. Спасибо вам за статью, очень интерестно =) Читаю дальше ;)

  7. Леха

    O-o-o. MVC-старая добрая ЯВА и C++ ! А где AWT -) ? Спасибо за рекомендацию-книгу- “Game Architecture and Design” !

  8. Лёха

    Сделал все по инструкции, но при запуске вижу только черный экран. Делал вручную, так как описывалось в статье. исходники смотрел, но там достаточно существенные отличия, а хотелось бы сделать как в статье.

        1. Леха

          Да в том то и дело, что свои изменения я отменил а оно все равно работает. Видимо а процессе написания когда я запускал еще не дописав до конца, какие-то файлы остались и из-за этого не работало. А при моих изменениях старые файлы просто перетерлись и с новыми все заработало.

  9. Анон

    Для тех у кого не работает, проверьте в классе MyGame что бы сам класс был public, т.е. имел вид public class MyGame extends Game.

  10. Игорь

    Добрый день. У меня не запускается исходники не с этого примера не с 6го урока…
    Среда Android Developer Tools
    Build: v21.1.0-569685
    This product includes Eclipse Platform, JDT, CDT, EMF, GEF and WTP,
    all of which are Copyright (c) Eclipse contributors and others.
    Лог:
    [2013-02-15 17:45:20 — MainActivity] ——————————
    [2013-02-15 17:45:20 — MainActivity] Android Launch!
    [2013-02-15 17:45:20 — MainActivity] adb is running normally.
    [2013-02-15 17:45:20 — MainActivity] Performing suvitruf.libgdxtutorial.MainActivity activity launch
    [2013-02-15 17:45:20 — MainActivity] Automatic Target Mode: launching new emulator with compatible AVD ‘AVD_for_Nexus_S_by_Google’
    [2013-02-15 17:45:20 — MainActivity] Launching a new emulator with Virtual Device ‘AVD_for_Nexus_S_by_Google’
    [2013-02-15 17:45:32 — Emulator] Warning: No DNS servers found
    [2013-02-15 17:45:32 — Emulator] emulator: WARNING: Requested RAM size of 343MB is too large for your environment, and is reduced to 257MB.
    [2013-02-15 17:45:34 — MainActivity] New emulator found: emulator-5554
    [2013-02-15 17:45:34 — MainActivity] Waiting for HOME (‘android.process.acore’) to be launched…
    Запускается через эмулятор AVD_for_Nexus_S_by_Google, android 4.2

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

      Вы сначала запускаете эмулятор, а потом пытаетесь проект запустить или вместе?

  11. Philip

    Не запускается, черный экран и больше ничего. Пробовал и на эмуляторе и на телефоне напрямую. Сделал все по уроку.Только была ошибка.
    Тут renderer.filledRect(x1, y1, rect.width, rect.height);
    исправил на renderer.rect(x1, y1, rect.width, rect.height);
    и тут renderer.begin(ShapeType.FilledRectangle);
    просило поменять FilledRectangle на Filled поменял -ошибки исчезли.
    Почему возникли эти ошибки? мб из-за того что исправил неправильно и не запускается?

    1. Philip

      Извиняюсь, плохо читал предыдущие комменты. Проблема была в том что
      в класс MyGame нужно было добавить строки.Все же выложите лучше сюда и код MyGame.java.
      И что насчет той ошибки с renderer.filledRect(x1, y1, rect.width, rect.height); ?

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

            Это просто как пример)

            Хотите плавное движение, меняйте метод processInput() в зависимости от того, чего хотите добиться.

  12. volos

    Извините… Я правильно понял что теперь в Vector2 не рекомендуют использовать tmp() и mul(float)? И строчка:
    position.add(velocity.tmp().mul(delta));
    теперь должна выглядеть так:
    position.add(velocity.scl(delta));
    ?

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

      Вместо velocity.tmp().mul(delta) сейчас velocity.scl(delta) вроде. Или velocity.cpy().scl(delta). Не разбирался в новом билде.

    2. Рома

      спасибо за комментарий, а то никак не мог найти описание метода tmp() в документации к libgdx.

  13. technick

    public void rightReleased() {
    keys.get(keys.put(Keys.RIGHT, false));
    }
    Разве нельзя просто использовать <> ????

  14. Уведомление: Как я писал Bomberman'а на Android | Suvitruf's Blog

  15. Berkut

    Почему не показано как запустить не под андроид? Причем вопрос был такой задан ранее, ответ: «Java мультиплатформенная». Но в итоге, совершенно непонятно как запустить этот код прямо на компьютере. Ломаю голову до сих пор, при чем был неприятно удивлен, когда дописал все по уроку, а в итоге ничего не работает.

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

      На эмуляторе. Либо использовать эмуляторы, которые входят в SDK (тогда можно будет дебажить), либо упаковывать проект в .apk и запускать уже на независимом эмуляторе вроде BlueStack.

      1. Berkut

        Меня как таковая разработка под мобильные устройства не интересовала, в том-то и дело. Я разобрался — там идет обвес под каждую из платформ, по сути надо создать Main класс, где проконфигурировать под десктоп (также как под андройд) и потом уже из этого класса создать инстанс LwjglApplication с экземпляром MyGame (и экземпляром настроек) качестве параметра. Только меня все это сбило столку — генератор проектов, который идет с LibGdx делает тоже самое, но выглядит это эпичнее.

      2. Berkut

        Кстати, спасибо большое за уроки. От себя бы посоветовал делать код более чистый. В паре мест заметил такие штуки, как методы с заглавной буквы, ну и прочая мелочевка.

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

          Рад, что полезны уроки.

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

          Что есть, то есть)

          1. Berkut

            Для людей страдающих грамматическим нацизмом это как серпом по яйцам, я думаю)) но в целом, порядок в голове — порядок в коде. Так что рефакторинг уроков может быть вполне хорошей затеей.

  16. Хелпер

    Решение проблем с «ЧЕРНЫМ ЭКРАНОМ»
    В myGame добавить импорт:
    import suvitruf.libgdxtutorial.screens.*;

  17. shubniggurath

    Во- первых — спасибо автору за такие уроки с разъяснениями и выкладыванием сорцов.

    У кого черный экран и чтобы не копаться в сорцах, надо просто в MyGame прописать следующее:
    package suvitruf.libgdxtutorial;
    import com.badlogic.gdx.Game;
    import suvitruf.libgdxtutorial.screen.*;
    public class MyGame extends Game {
    public GameScreen game;
    public void create() {
    game = new GameScreen();
    setScreen(game);
    }
    }

  18. Dmitry

    Не понял данную строку
    по идее ширина и высота камеры должна быть равна точкам экрана, а тут на сколько я понял идет установка в размерах клеток игры
    SetCamera(CAMERA_WIDTH / 2f, CAMERA_HEIGHT / 2f);

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

      Мы просто работаем с относительными координатами. При отрисовке то всё в абсолютные трансформируем.

      1. Dmitry

        а в чем тогда смысл камере устанавливать такой диапазон? Или класс OrthographicCamera это понимает?

        static final int WIDTH = 480;
        static final int HEIGHT = 320;
        cam = new OrthographicCamera(WIDTH, HEIGHT);
        cam.position.set(WIDTH / 2, HEIGHT / 2, 0);
        это из других примеров здесь все понятно

      2. Dmitry

        как мне кажется после установки камеры в координаты 8,5 ее центр будет именно в точке 8,5 абсолютных координат

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

          У нас весь мир в относительных координатах:
          width = 8;
          height = 5;

          И камера:
          public static float CAMERA_WIDTH = 8f;
          public static float CAMERA_HEIGHT = 5f;

          Не пойму, в чём проблема то)

  19. Joe Black

    Пример запускаю в Eclipse, всё нормально, но одно но — плейер и кирпичики все в виде прямоугольников, вытянуты в высоту. Из за чего может быть нарушена пропорция? Сам код как и здесь.

  20. YuryKlmchuk

    Добрый день! Не могли бы вы пояснить строки, в методе drawBricks():

    float x1 = brick.getPosition().x + rect.x;
    float y1 = brick.getPosition().y + rect.y;

    Для чего нужно + rect.x и + rect.y ???

  21. roma

    Привет! Спасибо за урок, многое стало понятнее.
    Появился вопрос: делал по твоему примеру, всё заработало, решил на основе этого примера начать делать что-то похожее, и в какой-то момент заметил что у меня игра каждую секунду сжирает по паре метров оперативки. Подскажи пожалуйста, как мне отследить где у меня ошибка и найти место где у меня «утечка памяти»
    Спасибо!

  22. Олег

    При попытке запуска выдает ошибку:
    07-18 13:50:39.892: W/GL2JNIView(1832): creating OpenGL ES 2.0 context
    07-18 13:50:39.936: W/dalvikvm(1832): Exception Ljava/lang/UnsatisfiedLinkError; thrown while initializing Lcom/badlogic/gdx/backends/android/AndroidGL20;
    07-18 13:50:39.936: W/dalvikvm(1832): threadid=11: thread exiting with uncaught exception (group=0xa4d34b20)
    07-18 13:50:39.940: E/AndroidRuntime(1832): FATAL EXCEPTION: GLThread 129
    07-18 13:50:39.940: E/AndroidRuntime(1832): Process: suvitruf.libgdxtutorial, PID: 1832
    07-18 13:50:39.940: E/AndroidRuntime(1832): java.lang.UnsatisfiedLinkError: Couldn’t load androidgl20 from loader dalvik.system.PathClassLoader[DexPathList[[zip file «/data/app/suvitruf.libgdxtutorial-1.apk»],nativeLibraryDirectories=[/data/app-lib/suvitruf.libgdxtutorial-1, /system/lib]]]: findLibrary returned null
    07-18 13:50:39.940: E/AndroidRuntime(1832): at java.lang.Runtime.loadLibrary(Runtime.java:358)
    07-18 13:50:39.940: E/AndroidRuntime(1832): at java.lang.System.loadLibrary(System.java:526)
    07-18 13:50:39.940: E/AndroidRuntime(1832): at com.badlogic.gdx.backends.android.AndroidGL20.(AndroidGL20.java:27)
    07-18 13:50:39.940: E/AndroidRuntime(1832): at com.badlogic.gdx.backends.android.AndroidGraphics.setupGL(AndroidGraphics.java:251)
    07-18 13:50:39.940: E/AndroidRuntime(1832): at com.badlogic.gdx.backends.android.AndroidGraphics.onSurfaceCreated(AndroidGraphics.java:302)
    07-18 13:50:39.940: E/AndroidRuntime(1832): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1501)
    07-18 13:50:39.940: E/AndroidRuntime(1832): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1240)

  23. прохожий

    Мягко говоря, исходники ужасны. А ведь это будет растащено новичками и надолго засядет у них в головах.

    1. Антон Грозный

      исходники рил написаны по mvc ,хотя и есть пару косяков… Так что норм

  24. Александр Матюхин

    «Создадим пакет suvitruf.libgdxtutorial.controller и добавим в него классWorldController. В этом классе как раз и будет реализована логика вся.» — вся логика должна быть в моделе, а не в контроллере. В общем, вышеуказанный код — извращение над паттерном MVC.

  25. Никита Громов

    Хех, ознакомился с уроком ,очень понравился патерн MVC, удобная штука (теперь хоть меньше быдлокодить буду),но проблема в том ,что данный урок достаточно сильно устарел и приходилось много гуглить чтобы пофиксить баги ,в этоги получилось как-то так http://prntscr.com/ahd9sq =) , а так все отлично ,спс, буду дальше осваивать движок)

  26. Паша Гребенщиков

    не могу понять, почему мы не можем писать логику и отрисовку обьекта в model.
    Я например пишу на OpenGl графику и через шейдерную программу движется визуальную часть обьекта.

  27. Влад Ямковой

    for (Block brick : world.getBlocks()) {
    * * *
    }
    Can only iterate over an array or an instance of java.lang.Iterable

  28. Axe Dallas

    а зачем нам World Renderer? Не лучше ли Screen использовать как View ?И что если в игре несколько окон , как это спроектировать под MVC

    1. Suvitruf

      В текущих версиях LibGDX куча вспомогательных классов есть нынче. Статье то более 4-х лет уже )

  29. Дмитрий К

    Обновите, пожалуйста, уроки. Position.add(velocity.tmp().mul(delta)) Idea ругается, что нет метода tmp() и mul() принимает только Matrix3

    1. Anton Bolotnikoff

      Поддержу, статьи не актуальны, больше вводят в ступор чем учат…

  30. Serhij Nebesnyj

    Спасибо за статью, очень жаль что уже не все актуально. Есть ли похожие уроки только с актуальной инфой?

Добавить комментарий для Dmitry Отменить ответ

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