Статьи

Разработка игр для Android с помощью libgdx — прыжки, гравитация и улучшенные движения, часть 3

Это третья часть серии «Построение игры с помощью LibGdx». Обязательно прочитайте предыдущие статьи, чтобы создать контекст для этого.

В предыдущей статье мы анимировали движение Боба, но оно довольно роботизированное. В этой статье я попытаюсь заставить Боба прыгать, а также двигаться более естественным образом. Я достигну этого, используя немного физики. Я также немного приведу в порядок код и исправлю некоторые проблемы, которые возникли в коде в предыдущих статьях.

Прыжки — физика

Прыжки — это действия, выполняемые сущностью (в нашем случае Бобом), которая поднимается в воздух и приземляется обратно на землю (субстрат). Это достигается применением силы, достаточно большой против силы, действующей на землю (гравитация) на объект. Идентификация объектов у нас есть:

  • Боб — сущность
  • Земля — субстрат
  • Гравитация (G) — постоянная сила тяжести, которая действует на все сущности в мире

Для реализации реалистичного прыжка нам просто нужно применить законы движения Ньютона . Если мы добавим необходимые атрибуты (масса, гравитация, трение) к Бобу и миру, у нас будет все, что нам нужно для осуществления прыжков. Посмотрите на следующую диаграмму и изучите ее компоненты. Левая сторона — это когда мы удерживаем кнопку «прыжок», а правая показывает Боба в прыжке.

Силы в прыжке

Давайте рассмотрим силы в разных штатах Боба.

1. Боб бездействует и лежит на земле. В этом случае на Боба действует только гравитация . Это означает, что Боб сбрасывают с постоянной силой. Формула для расчета силы, которая тянет объект на землю F=m*a где mмасса (думаю, что вес — это не вес), а — ускорение . Мы упрощаем вещи и считаем, что у Боба масса 1, поэтому сила равна ускорению. Если мы прикладываем постоянную силу к объекту, его скорость увеличивается бесконечно. Формула для расчета скорости объекта: v=u+a*t где

  • v — конечная скорость
  • u — начальная скорость (скорость, которая t секунд назад)
  • а — это ускорение
  • t — время, прошедшее с момента применения ускорения

Если мы поместим Боба в воздух, это означает, что начальная скорость равна 0. Если мы считаем, что гравитационное ускорение Земли составляет 9,8, а вес (масса) Боба равен 1, то через секунду легко вычислить его скорость падения.

v = 0 + 9.8 * 1 = 9.8m/s

Таким образом, после секунды в свободном падении Боб ускорился с 0 до 9,8 метра в секунду, что составляет 35,28 км / ч или 21,92 км / ч. Это очень быстро. Если мы хотим узнать его скорость еще через секунду, мы будем использовать ту же формулу.
v = 9.8 + 9.8 * 1 = 19.6m/s

Это 70,56 км / ч или 43,84 миль / ч, что очень быстро. Мы уже видим, что ускорение является линейным и что при постоянной силе объект будет бесконечно ускоряться. Это в идеальной среде, где нет трения и сопротивления. Поскольку воздух имеет трение, и он также прикладывает некоторые силы к падающему объекту, падающий объект достигнет предельной скорости в некоторой точке, после которой он не будет ускоряться. Это зависит от многих факторов, которые мы будем игнорировать. Как только падающий объект упадет на землю, он остановится, гравитация на него больше не повлияет. Однако это не так, но мы строим не полный симулятор физики, а игру, в которой Боба не убьют, если он упадет на землю с предельной скоростью. Переформулируя его, мы проверяем, ударил ли Боб землю, и если так, то мы будем игнорировать гравитацию.

Заставить Боба прыгнуть

Чтобы заставить Боба прыгнуть, нам нужна сила, направленная против силы тяжести (вверх), которая не только устраняет эффект гравитации, но и подбрасывает Боба в воздух. Если вы проверите диаграмму, эта сила ( F ) будет намного сильнее (ее величина или длина намного больше, чем у вектора гравитации). Сложив вместе 2 вектора (G и F), мы получим последнюю силу, которая будет действовать на Боба.
Чтобы упростить вещи, мы можем избавиться от векторов и работать только с их Y-компонентами.
На земле, G = 9.8m/s^2 , Потому что это указывает вниз, мы это на самом деле -9.8 m/s^2 , Когда Боб прыгает, он не делает ничего больше, чем генерирует достаточно силы, чтобы произвести достаточное ускорение, которое приведет его к высоте ( h ), прежде чем гравитация ( G ) вернет его на землю. Поскольку Боб такой же человек, как и мы, он не может поддерживать ускорение, когда он в воздухе, по крайней мере, без реактивного ранца. Чтобы смоделировать это, мы могли бы создать огромную силу, когда нажимаем клавишу «прыжок». Применяя приведенные выше формулы, начальная скорость будет достаточно высокой, поэтому даже если гравитация будет действовать на Боба, он все равно поднимется до точки, после которой он начнет последовательность свободного падения. Если мы реализуем этот метод, у нас будет очень красивый реалистичный прыжок.

Если мы тщательно проверим оригинальную игру Star Guard, герой может прыгать на разные высоты в зависимости от того, как долго мы нажимаем кнопку прыжка. С этим легко справиться, если мы продолжаем прикладывать усилие наведения вверх, пока удерживаем нажатой клавишу прыжка, и через определенное время отключаем его, чтобы убедиться, что Боб не начнет летать.

Реализовать прыжок

Думаю, физики было достаточно, посмотрим, как мы реализуем прыжок. Мы также выполним небольшую хозяйственную задачу и реорганизуем код. Я хочу изолировать прыжки и движения, поэтому я буду игнорировать остальной мир. Чтобы увидеть, что было изменено в коде, прокрутите вниз до раздела « Рефакторинг ».

Откройте BobController.java . Это старый WorldController.java но он был переименован. Это имело смысл, так как мы контролируем Боба этим.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
public class BobController {
 
    enum Keys {
        LEFT, RIGHT, JUMP, FIRE
    }
 
    private static final long LONG_JUMP_PRESS   = 150l;
    private static final float ACCELERATION     = 20f;
    private static final float GRAVITY          = -20f;
    private static final float MAX_JUMP_SPEED   = 7f;
    private static final float DAMP             = 0.90f;
    private static final float MAX_VEL          = 4f;
 
    // these are temporary
    private static final float WIDTH = 10f;
 
    private World   world;
    private Bob     bob;
    private long    jumpPressedTime;
    private boolean jumpingPressed;
 
    // ... code omitted ... //
 
    public void jumpReleased() {
        keys.get(keys.put(Keys.JUMP, false));
        jumpingPressed = false;
    }
 
    // ... code omitted ... //
    /** The main update method **/
    public void update(float delta) {
        processInput();
 
        bob.getAcceleration().y = GRAVITY;
        bob.getAcceleration().mul(delta);
        bob.getVelocity().add(bob.getAcceleration().x, bob.getAcceleration().y);
        if (bob.getAcceleration().x == 0) bob.getVelocity().x *= DAMP;
        if (bob.getVelocity().x > MAX_VEL) {
            bob.getVelocity().x = MAX_VEL;
        }
        if (bob.getVelocity().x < -MAX_VEL) {
            bob.getVelocity().x = -MAX_VEL;
        }
 
        bob.update(delta);
        if (bob.getPosition().y < 0) {
            bob.getPosition().y = 0f;
            bob.setPosition(bob.getPosition());
            if (bob.getState().equals(State.JUMPING)) {
                    bob.setState(State.IDLE);
            }
        }
        if (bob.getPosition().x < 0) {
            bob.getPosition().x = 0;
            bob.setPosition(bob.getPosition());
            if (!bob.getState().equals(State.JUMPING)) {
                bob.setState(State.IDLE);
            }
        }
        if (bob.getPosition().x > WIDTH - bob.getBounds().width ) {
            bob.getPosition().x = WIDTH - bob.getBounds().width;
            bob.setPosition(bob.getPosition());
            if (!bob.getState().equals(State.JUMPING)) {
                bob.setState(State.IDLE);
            }
        }
    }
 
    /** Change Bob's state and parameters based on input controls **/
    private boolean processInput() {
        if (keys.get(Keys.JUMP)) {
            if (!bob.getState().equals(State.JUMPING)) {
                jumpingPressed = true;
                jumpPressedTime = System.currentTimeMillis();
                bob.setState(State.JUMPING);
                bob.getVelocity().y = MAX_JUMP_SPEED;
            } else {
                if (jumpingPressed && ((System.currentTimeMillis() - jumpPressedTime) >= LONG_JUMP_PRESS)) {
                    jumpingPressed = false;
                } else {
                    if (jumpingPressed) {
                        bob.getVelocity().y = MAX_JUMP_SPEED;
                    }
                }
            }
        }
        if (keys.get(Keys.LEFT)) {
            // left is pressed
            bob.setFacingLeft(true);
            if (!bob.getState().equals(State.JUMPING)) {
                bob.setState(State.WALKING);
            }
            bob.getAcceleration().x = -ACCELERATION;
        } else if (keys.get(Keys.RIGHT)) {
            // left is pressed
            bob.setFacingLeft(false);
            if (!bob.getState().equals(State.JUMPING)) {
                bob.setState(State.WALKING);
            }
            bob.getAcceleration().x = ACCELERATION;
        } else {
            if (!bob.getState().equals(State.JUMPING)) {
                bob.setState(State.IDLE);
            }
            bob.getAcceleration().x = 0;
 
        }
        return false;
    }
}

Потратьте немного времени, чтобы проанализировать, что мы добавили в этот класс. Объясняются следующие строки: # 07 — # 12 — константы, содержащие значения, которые влияют на мир, и Боб

  • LONG_JUMP_PRESS — время в миллисекундах до того, как усилие, примененное к прыжку, будет отключено. Помните, что мы делаем высокие прыжки, и чем дольше игрок нажимает кнопку, тем выше Боб прыгает. Для предотвращения полета мы отключим скачкообразную тягу через 150 мс.
  • УСКОРЕНИЕ — это на самом деле используется для ходьбы / бега. Это тот же принцип, что и прыжки, но по горизонтальной оси X
  • ГРАВИТАЦИЯ — это ускорение силы тяжести (G указывает вниз на диаграмме)
  • MAX_JUMP_SPEED — это предельная скорость, которую мы никогда не превысим при прыжке
  • DAMP — это для сглаживания движения, когда Боб останавливается. Он не остановит это внезапно. Подробнее об этом позже, игнорируйте это для прыжка
  • MAX_VEL — такой же, как MAX_JUMP_SPEED, но для движения по горизонтальной оси

# 15 — это временная константа, и это ширина мира в мировых единицах. Используется для ограничения движения Боба на экране.
# 19 — jumpPressedTime — это переменная, которая накапливает время нажатия кнопки перехода
# 20 — логическое значение, которое истинно, если была нажата кнопка перехода
# 26jumpReleased() должен установить для переменной jumpingReleased значение false. Это просто простая переменная состояния. После основного метода обновления, который делает большую часть работы для нас.
# 32 — вызывает processInput как обычно, чтобы проверить, были ли нажаты какие-либо клавиши
Переход к processInput
# 71 — проверяет, нажата ли кнопка JUMP
# 72 — # 76 — если Боб не находится в состоянии JUMPING (то есть он на земле), начинается прыжок. Боб находится в состоянии прыжка, и он готов к взлету. Здесь мы немного обманываем, и вместо того, чтобы прикладывать силу, направленную вверх, мы устанавливаем вертикальную скорость Боба на максимальную скорость, с которой он может прыгать (строка № 76 ). Мы также храним время в миллисекундах, когда начался скачок.
# 77 — # 85 — это выполняется, когда Боб находится в воздухе. В случае, если мы все еще нажимаем кнопку прыжка, мы проверяем, больше ли время, прошедшее с начала прыжка, чем пороговое значение, которое мы установили, и если мы все еще находимся во времени отсечения (в настоящее время 150 мс), мы поддерживаем вертикальную скорость Боба.
Игнорируйте линии № 87-107, так как они предназначены для горизонтальной ходьбы. Возвращаясь к методу update мы имеем:
# 34 — Ускорение Боба установлено в GRAVITY. Это потому, что гравитация постоянна, и мы начинаем отсюда:
# 35 — мы рассчитываем ускорение за время, проведенное в этом цикле. Наши начальные значения выражены в единицах / секундах, поэтому мы должны соответствующим образом скорректировать значения. Если у нас будет 60 обновлений в секунду, то дельта будет 1/60. Все это обрабатывается для вас libgdx.
# 36 — Текущая скорость Боба обновляется с его ускорением на обеих осях. Помните, что мы работаем с векторами в евклидовом пространстве.
# 37 — Это сгладит остановку Боба. Если у нас нет ускорения на оси X, то мы уменьшаем его скорость на 10% каждый цикл. Имея много циклов в секунду, Бобо остановится очень быстро, но очень плавно.
# 38 — # 43 — убедившись, что Боб не превысит максимально допустимую скорость (конечную скорость). Это охраняет закон, который гласит, что объект будет бесконечно ускоряться, если на него действует постоянная сила.
# 45 — вызывает метод update Боба, который делает только обновление позиции Боба в соответствии с его скоростью.
# 46 — # 66 — Это очень простое обнаружение столкновений, которое не дает Бобу покинуть экран. Мы просто проверяем, находится ли позиция Боба за пределами экрана (используя мировые координаты), и если это так, то мы просто помещаем Боба обратно к краю. Стоит отметить, что всякий раз, когда Боб ударяет по земле или достигает края света (экрана), мы устанавливаем его статус в режим Idle . Это позволяет нам снова прыгать.

Если мы запустим приложение с вышеуказанными изменениями, мы получим следующий эффект:

Домашнее хозяйство — рефакторинг

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

  • Переименован в WorldController в BobController . Это имело смысл, так как мы контролируем Боба этим.
  • drawBlocks() в WorldRenderer' render() WorldRenderer' . Мы не визуализируем плитки сейчас, потому что мы их игнорируем.
  • Добавлен метод setDebug() для WorldRendered и вспомогательная функция переключения в GameScreen.java . Отладочный рендеринг теперь можно переключать, нажимая D на клавиатуре в режиме рабочего стола.
  • В WorldRenderer есть новые текстурные области, представляющие прыгающего и падающего Боба. Мы все еще поддерживаем только одно государство все же. Как мировой рендерер знает, когда отображать что, происходит путем проверки вертикальной скорости Боба (по оси Y). Если он положительный, Боб прыгает, если отрицательный, Боб падает.
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class WorldRenderer {
     
        // ... omitted ... //
     
        private TextureRegion bobJumpLeft;
        private TextureRegion bobFallLeft;
        private TextureRegion bobJumpRight;
        private TextureRegion bobFallRight;
     
        private void loadTextures() {
            TextureAtlas atlas = new TextureAtlas(Gdx.files.internal('images/textures/textures.pack'));
     
            // ... omitted ... //
     
            bobJumpLeft = atlas.findRegion('bob-up');
            bobJumpRight = new TextureRegion(bobJumpLeft);
            bobJumpRight.flip(true, false);
            bobFallLeft = atlas.findRegion('bob-down');
            bobFallRight = new TextureRegion(bobFallLeft);
            bobFallRight.flip(true, false);
        }
     
        private void drawBob() {
            Bob bob = world.getBob();
            bobFrame = bob.isFacingLeft() ? bobIdleLeft : bobIdleRight;
            if(bob.getState().equals(State.WALKING)) {
                bobFrame = bob.isFacingLeft() ? walkLeftAnimation.getKeyFrame(bob.getStateTime(), true) : walkRightAnimation.getKeyFrame(bob.getStateTime(), true);
            } else if (bob.getState().equals(State.JUMPING)) {
                if (bob.getVelocity().y > 0) {
                    bobFrame = bob.isFacingLeft() ? bobJumpLeft : bobJumpRight;
                } else {
                    bobFrame = bob.isFacingLeft() ? bobFallLeft : bobFallRight;
                }
            }
            spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
        }
    }

    Приведенный выше фрагмент кода показывает важные дополнения.
    # 5- # 8 — Новые текстурные области для прыжков. Нам нужен один слева и один справа.
    № 15- № 20 — Подготовка активов. Нам нужно добавить еще несколько изображений PNG в проект. Проверьте каталог star-assault-android/images/ и там вы увидите bob-down.png и bob-up.png . Они были добавлены, а также текстурный атлас воссоздан с ImagePacker2 инструмента ImagePacker2 . Смотрите часть 2 о том, как его создать.
    # 28- # 33 — это часть, где мы определяем, какую область текстуры рисовать, когда Боб находится в воздухе.

  • В Bob.java было исправлено несколько ошибок. Ограничительная рамка теперь имеет ту же позицию, что и bob, и обновление позаботится об этом. Также метод setPosition обновляет положение ограничивающих рамок. Это оказало влияние на метод drawDebug() внутри WorldRenderer . Теперь нам не нужно беспокоиться о расчете ограничивающих рамок на основе положения плиток, поскольку теперь они имеют ту же позицию, что и объект. Это была глупая ошибка, в которую я попал. Это будет очень важно при обнаружении столкновений.

Этот список в значительной степени суммирует все изменения, но его должно быть очень легко выполнить. Исходный код этого проекта можно найти здесь: https://github.com/obviam/star-assault. Вам нужно оформить заказ на ветку part3. Чтобы проверить это с помощью git: git clone -b part3 [email protected]:obviam/star-assault.git Вы также можете скачать его в виде zip-файла .

Ссылка: Разработка игр для Android с помощью libgdx — Прыжки, гравитация и улучшенное движение, часть 3 от нашего партнера JCG Impaler в блоге Against the Grain .