Статьи

Разработка игр для Android с помощью libgdx — анимация, часть 2

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

Анимация персонажей

Чтобы оживить Боба, мы будем использовать простейшую технику, называемую спрайтовой анимацией . Анимация — это не что иное, как последовательность изображений, показанных с заданным интервалом для создания

иллюзия движения. Следующая последовательность изображений используется для создания бегущей анимации.

Боб работает спрайт лист

, Я использовал Gimp для создания бегущего персонажа, много играя в Star Guard и анализируя его последовательность бега. Создать анимацию довольно просто. Мы хотим отображать каждый кадр в течение определенного промежутка времени, а затем переключаться на следующее изображение. Когда мы достигли конца последовательности, мы начинаем снова. Это называется зацикливанием . Нам нужно определить длительность кадра, в течение которого будет отображаться рамка. Допустим, мы рендерим игру со скоростью 60 FPS, что означает, что мы рендерим кадр каждые 1/60 = 0,016 с. У нас всего 5 кадров для анимации полного шага. Учитывая типичную частоту вращения спортсмена 180, мы можем определить, сколько времени мы показываем каждый кадр, чтобы сделать бег реалистичным.

Математика бега

Частота 180 означает 180 шагов в минуту. Чтобы рассчитать количество шагов в секунду, мы имеем 180/60 = 3. Таким образом, каждую секунду нам нужно делать 3 шага. Наши 5 кадров составляют один полный шаг, поэтому нам нужно показывать 3 * 5 = 15 кадров каждую секунду, чтобы имитировать бег профессионального спортсмена. Это не спринт, кстати. Наша длительность кадра будет 1/15 = 0,066 секунды. Это 66 мс.

Оптимизация изображений

Прежде чем добавить его в игру, мы оптимизируем изображения. В настоящее время проект содержит изображения в виде отдельных файлов png в каталоге assets/images в проекте star-assault-android . В настоящее время мы используем block.png и bob_01.png . Есть еще несколько изображений, а именно bob_02 - 06.png . Эти изображения составляют последовательность анимации. Поскольку libgdx использует OpenGL под капотом, не оптимально предоставлять каркасу много изображений в качестве текстур для работы. Что мы будем делать, так это создать так называемый Texture Atlas . Текстурный атлас — это просто изображение, которое достаточно велико, чтобы вместить все изображения на нем, и имеет дескриптор, содержащий имя каждого отдельного изображения, положение в атласе и размер. Отдельные изображения называются регионами в атласе. Если изображений много, атлас может иметь несколько страниц . Каждая страница загружается в память как отдельное изображение, а регионы используются как отдельные изображения. Не нужно знать все это, но это делает приложение более оптимальным, загружается быстрее и работает более плавно. В Libgdx есть утилита TexturePacker2 для создания этих атласов. Его можно запустить из командной строки или использовать программно. Чтобы запустить его из Java, используйте следующую программу:

01
02
03
04
05
06
07
08
09
10
package net.obviam.starassault.utils;
 
import com.badlogic.gdx.tools.imagepacker.TexturePacker2;
 
public class TextureSetup {
 
    public static void main(String[] args) {
        TexturePacker2.process('/path-to-star-guard-assets-images/', 'path-to-star-guard-assets-images', 'textures.pack');
    }
}

Убедитесь, что вы добавили gdx-tools.jar в каталог libs . Измените атрибуты метода process чтобы он указывал на каталог, в котором расположены активы.

Замечания:

Также переименуйте файлы, которые содержат символ подчеркивания «_», потому что TexturePacker2 использует его как разделитель, и в настоящее время нам это не нужно. Замените знак подчеркивания на дефис «-». При обработке изображений в нашем каталоге должны появиться 2 файла: textures.png и textures.pack . Текстурный атлас должен выглядеть примерно так:

текстурный атлас

Структура каталогов, которую я использую, следующая: Структура каталогов активов

Теперь, когда мы разработали анимацию, продолжительность кадра и оптимизировали ресурсы, давайте добавим это в игру. Bob.java мы Bob.java как это самый маленький бит

01
02
03
04
05
06
07
08
09
10
11
12
13
public class Bob {
 
    // ... omitted ... //
 
    float       stateTime = 0;
 
    // ... omitted ... //
 
    public void update(float delta) {
        stateTime += delta;
        position.add(velocity.tmp().mul(delta));
    }
}

Мы добавили атрибут с именем stateTime . Это позволит отслеживать время Боба в определенном состоянии. Мы будем использовать его, чтобы предоставить время, проведенное Бобом в игре. Для анимации важно определить, какой кадр показывать. Не беспокойся об этом сейчас. Если вы действительно хотите понять, думайте о каждом кадре анимации как о состоянии. Боб проходит через state_frame_1, state_frame_2 и так далее. Каждое из этих состояний длится 0,066 секунды. Как только время состояния превысило 0,066 секунды, Боб переходит в следующее состояние. Класс анимации знает, какое изображение предоставить для отображения в текущем состоянии. Это также называется ключевой кадр . WorldRenderer.java страдает больше всего изменений. Следующий фрагмент содержит все изменения.

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
38
39
40
41
42
43
44
45
46
47
48
49
public class WorldRenderer {
 
    // ... omitted ... //
 
    private static final float RUNNING_FRAME_DURATION = 0.06f;
 
    /** Textures **/
    private TextureRegion bobIdleLeft;
    private TextureRegion bobIdleRight;
    private TextureRegion blockTexture;
    private TextureRegion bobFrame;
 
    /** Animations **/
    private Animation walkLeftAnimation;
    private Animation walkRightAnimation;
 
    // ... omitted ... //
 
    private void loadTextures() {
        TextureAtlas atlas = new TextureAtlas(Gdx.files.internal('images/textures/textures.pack'));
        bobIdleLeft = atlas.findRegion('bob-01');
        bobIdleRight = new TextureRegion(bobIdleLeft);
        bobIdleRight.flip(true, false);
        blockTexture = atlas.findRegion('block');
        TextureRegion[] walkLeftFrames = new TextureRegion[5];
        for (int i = 0; i < 5; i++) {
            walkLeftFrames[i] = atlas.findRegion('bob-0' + (i + 2));
        }
        walkLeftAnimation = new Animation(RUNNING_FRAME_DURATION, walkLeftFrames);
 
        TextureRegion[] walkRightFrames = new TextureRegion[5];
 
        for (int i = 0; i < 5; i++) {
            walkRightFrames[i] = new TextureRegion(walkLeftFrames[i]);
            walkRightFrames[i].flip(true, false);
        }
        walkRightAnimation = new Animation(RUNNING_FRAME_DURATION, walkRightFrames);
    }
 
    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);
        }
        spriteBatch.draw(bobFrame, bob.getPosition().x * ppuX, bob.getPosition().y * ppuY, Bob.SIZE * ppuX, Bob.SIZE * ppuY);
    }
    // ... omitted ... //
}

# 05 — объявление константы RUNNING_FRAME_DURATION, которая контролирует, как долго будет отображаться кадр в цикле бега / ходьбы
# 08 — # 11TextureRegion для разных состояний Боба. bobFrame — будет содержать регион, который будет отображаться в текущем цикле.
# 14 — # 15 — два объекта Animation , которые используются для анимации Боба при ходьбе / беге.

Загрузка изображений из TextureAtlas

# 19 — новый loadTextures()
# 20 — загрузка TextureAtlas из внутреннего файла. Это файл .pack полученный из TexturePacker2 .
№ 21 — присвоение региона с именем «боб-01? (это фактическое имя png без расширения — см. TexturePacker2) для переменной bobIdleLeft .
# 22 — # 23 — создаем новый TextureRegion (обратите внимание на использование конструктора копирования, нам нужна копия, а не ссылка) и переворачиваем его по оси X, чтобы у нас было то же изображение, но зеркальное отражение для состояния простоя Боба, но при взгляде вправо , Переворот очень полезен, так как нам не нужно загружать дополнительное изображение, мы создаем его из существующего.
# 24 — назначить соответствующий регион блоку
# 25 — # 28 — мы создаем массив объектов TextureRegion которые будут составлять анимацию. Мы знаем, что существует 5 кадров и их названия: bob-02, bob-03, bob-04, bob-05 и bob-06 . Для удобства мы используем цикл for.
# 29 — Здесь определяется анимация для состояния левой прогулки. Первый параметр — это продолжительность каждого кадра из последовательности, выраженной в секундах (0,06), а второй параметр принимает упорядоченный список кадров, составляющих анимацию.
# 31 — # 38 — Создание анимации для правильного состояния ходьбы. Это копия анимации для состояния ходьбы влево, но каждый кадр переворачивается. Важно сделать копию кадров, а не переворачивать их, так как оригиналы тоже переворачиваются.
# 40 — измененный drawBob() .
# 42 — установка активного кадра на один из незанятых кадров в зависимости от облика Боба
# 44 — если Боб находится в состоянии ходьбы, мы извлекаем соответствующий кадр для одной из последовательностей ходьбы на основе текущего времени состояния Боба и назначаем его bobFrame, который будет отображаться на экране. StarAssaultDesktop приложение StarAssaultDesktop , мы должны увидеть анимацию Боба в действии. Это должно выглядеть примерно так:

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

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