Как добавить HUD в игру Flame

Как добавить HUD в игру Flame

20 марта 2023 г.

Это последняя часть моей серии из 4 частей, в которой я узнаю, как создать простую игру-платформер на движке Flame. Мы уже знаем, как добавить анимированного игрового персонажа, которого я назвал The Boy, как создать прокручиваемый игровой уровень с помощью редактора Tiled и как добавить гравитацию и прыжки с помощью обнаружения столкновений (части 1, 2, 3).

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

Добавление монет

Нам нужно указать игре, где создавать монеты (или любые другие игровые объекты, если на то пошло). Как вы уже догадались, мы будем использовать редактор Tiled, чтобы добавить еще один слой объектов, аналогично тому, как мы добавляли платформы, но с двумя отличиями:

  1. Что касается платформ, мы запекали изображения спрайтов в данные уровня. Этот метод не очень подходит для монет, потому что мы хотим удалить объект, как только игрок его соберет. Поэтому мы просто добавим точки возрождения, чтобы знать, где в игре должны появляться монеты, а рендеринг будет выполняться с использованием компонентов Flame.

2. Для платформ мы использовали прямоугольники любого размера, но для монет мы добавим точки возрождения размером с 1 плитку. Однако, если вы хотите добавить ряд монет одну за другой (привет, Марио), вы можете легко сделать это, изменив код игры и учитывая размер точки появления при добавлении объектов-монет. Но для целей этой серии мы предполагаем, что точки появления монет равны 1x1.

Откройте наш уровень в редакторе плиток и создайте новый объектный слой под названием «Монеты». Затем с помощью инструмента «Прямоугольник» добавьте несколько точек появления на карте, чтобы мы могли добавить компоненты монеты с помощью игрового движка. Мой уровень теперь выглядит так:

Spawn points for the coins

Я должен добавить, что наша игра довольно проста, и мы знаем, что эти пустые прямоугольники будут превращаться в монеты во время выполнения игры. Но если мы захотим добавить больше типов объектов, их станет трудно различить. К счастью, в Tiled есть для этого инструмент под названием «Вставить плитку», который может добавлять визуальные подсказки для каждого объекта, но эти изображения не будут отображаться в игре.

Хорошо, сохраните уровень и вернитесь в IDE. Давайте добавим наш класс Coin в папку /objects/.

class Coin extends SpriteAnimationComponent with HasGameRef<PlatformerGame> {

  late final SpriteAnimation spinAnimation;
  late final SpriteAnimation collectAnimation;

  Coin(Vector2 position) : super(position: position, size: Vector2.all(48));

  @override
  Future<void> onLoad() async {
    spinAnimation = SpriteAnimation.fromFrameData(
      game.images.fromCache(Assets.COIN),
      SpriteAnimationData.sequenced(
        amount: 4,
        textureSize: Vector2.all(16),
        stepTime: 0.12,
      ),
    );

    collectAnimation = SpriteAnimation.fromFrameData(
      game.images.fromCache(Assets.COIN),
      SpriteAnimationData.range(
        start: 4,
        end: 7,
        amount: 8,
        textureSize: Vector2.all(16),
        stepTimes: List.filled(4, 0.12),
        loop: false
      ),
    );

    animation = spinAnimation;

    final hitbox = RectangleHitbox()
      ..collisionType = CollisionType.passive;
    add(hitbox);

    return super.onLoad();
  }
}

У нас есть 2 разные анимации для вращения и сбора и RectangleHitbox для проверки столкновений с игроком позже.

Затем вернитесь к game.dart и измените spawnObjects метод для создания наших монет:

final coins = tileMap.getLayer<ObjectGroup>("Coins");

    for (final coin in coins!.objects) {
      add(Coin(Vector2(coin.x, coin.y)));
    }

Запустите игру и увидите добавленные монеты:

The game added CoinComponent for each spawn point

Теперь давайте заставим их исчезать, когда игрок их собирает.

Вернуться к coin.dart и добавить собирать метод:

void collect() {
    animation = collectAnimation;
    collectAnimation.onComplete = () => {
      removeFromParent()
    };
  }

При вызове этого метода мы переключим анимацию вращения на анимацию сбора, а когда она завершится, удалим этот компонент из игры.

Затем перейдите к theboy.dart классу и переопределитеonCollisionStart метод:

@override
  void onCollisionStart(Set<Vector2> intersectionPoints, PositionComponent other) {
    if (other is Coin) {
      other.collect();
    }
    super.onCollisionStart(intersectionPoints, other);
  }

Причина, по которой мы используем onCollisionStart вместо onCollision, заключается в том, что мы хотим, чтобы обратный вызов столкновения срабатывал только один раз.

Монеты теперь исчезают при столкновении с Мальчиком. Давайте добавим пользовательский интерфейс для отслеживания количества собранных монет.

Добавление HUD

HUD – это просто строка состояния, в которой отображается любая информация об игре: очки здоровья, боеприпасы и т. д. Мы будем отображать значок монеты для каждой собранной монеты.

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

Добавьте новый класс, который будет содержать логику HUD: lib/hud.dart

class Hud extends PositionComponent with HasGameRef<PlatformerGame> {

  Hud() {
    positionType = PositionType.viewport;
  }

  void onCoinsNumberUpdated(int total) {
    final coin = SpriteComponent.fromImage(
        game.images.fromCache(Assets.HUD),
        position: Vector2((50 * total).toDouble(), 50),
        size: Vector2.all(48));
    add(coin);
  }
}

Две интересные вещи здесь:

  1. Мы устанавливаем для positionType значение PositionType.viewport, чтобы прикрепить наш HUD к углу экрана. Если мы этого не сделаем, из-за движения камеры HUD будет двигаться вместе с уровнем.
  2. Метод onCoinsNumberUpdated будет вызываться каждый раз, когда игрок собирает монету. Он использует параметр total для вычисления смещения значка следующей монеты, а затем добавляет новый спрайт монеты к вычисленной позиции.

Затем вернитесь к файлу game.dart и добавьте новые переменные класса:

int _coins = 0; // Keeps track of collected coins
late final Hud hud; // Reference to the HUD, to update it when the player collects a coin

Затем добавьте Hud компонент в конец onLoad метода:

hud = Hud();
add(hud);

И добавьте новый метод:

void onCoinCollected() {
    _coins++;
    hud.onCoinsNumberUpdated(_coins);
}

Наконец, вызовите его из collect метода Coin:

void collect() {
    game.onCoinCollected();
    animation = collectAnimation;
    collectAnimation.onComplete = () => {
      removeFromParent()
    };
  }

Отлично, наш HUD теперь показывает, сколько монет мы собрали!

The HUD in top left corner displays collected coins

Экран победы

Последнее, что я хочу добавить, — это экран выигрыша, который будет отображаться после того, как игрок соберет все монеты.

Добавить новую константу в PlatformerGame класс:

late int _totalCoins;

И назначаем ему количество монет, которое у нас есть на уровне. Добавьте эту строку в конец метода spawnObjects :

_totalCoins = coins.objects.length;

И добавьте это в конец метода onCoinCollected.

Обратите внимание, что вам может понадобиться добавить import 'package:flutter/material.dart'; вручную.

if (_coins == _totalCoins) {
      final text = TextComponent(
        text: 'U WIN!',
        textRenderer: TextPaint(
          style: TextStyle(
            fontSize: 200,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
        ),
        anchor: Anchor.center,
        position: camera.viewport.effectiveSize / 2,
      )..positionType = PositionType.viewport;
      add(text);
      Future.delayed(Duration(milliseconds: 200), () => { pauseEngine() });
    }

Здесь я проверяю, равен ли счетчик монет количеству монет на уровне, и если да, то добавляю метку Win в верхней части игрового экрана. Затем приостановите игру, чтобы остановить движение игрока. Я также добавил задержку в 200 мс, чтобы метка отображалась перед паузой.

Это должно выглядеть так:

The Win screen is displayed upon collecting the last coin

И это игра! Конечно, сейчас это не похоже на законченную игру, но со всем, что я объяснил, будет довольно легко добавить дополнительные уровни, врагов или другие коллекционные предметы.

Обзор

Эта часть завершает серию.

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

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

Другие истории серии:

Полный код этого руководства вы можете найти в мой github

Ресурсы

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


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE