Создание своего уровня в Flame

Создание своего уровня в Flame

2 марта 2023 г.

Это вторая часть моей серии Platformer 101 with Flame Engine. В предыдущей части мы узнали, как создать проект игры Flame, загрузить ресурсы, и заставьте нашего персонажа, Мальчика, бежать. Однако на данный момент это пустой экран, поэтому давайте исправим это в этой части. Мы создадим игровой уровень, добавим движение камеры и фоновый эффект параллакса.

Создание игрового уровня

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

Это может быть так же просто, как массив целых чисел, в котором хранится положение сетки и тип плитки. Но обычно игры хранят информацию об уровне в отдельных файлах, используя JSON, XML или даже пользовательский формат — затем игра анализирует файл уровня и транслирует его в игровые объекты. Затем вы можете пойти еще дальше и создать редактор уровней, который понимает выбранный вами формат уровней, поэтому будет очень легко создавать новые уровни и изменять существующие.

К счастью, такие инструменты уже существуют, одним из наиболее известных является Tiled — редактор уровня пользовательского интерфейса с собственным форматом, основанным на XML. Он поддерживает множество игровых движков, в том числе Flame. Давайте загрузим Tiled и создадим нашу игровую карту.

После загрузки нажмите «Новая карта». Это настройки моей карты. Одно но: лучше использовать тайлы целевого размера, так как если вы хотите масштабировать карту в самом Пламени, она может получиться размытой.

Tile Map settings

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

Tile Set settings

Сохраните оба файла в папку assets/tiles вашего проекта.

Затем перетащите ground.png и platform.png в свой набор фрагментов, а затем откройте вкладку карты. Используя плитки из набора плиток, нарисуйте уровень так, как вам нравится. Мой выглядит так:

Game level in Tiled editor UI

Не беспокойтесь о сером фоне, мы добавим его позже в код игры. Сохраните изменения и вернитесь в IDE.

Теперь нам нужно указать игре загрузить только что созданный уровень. Сначала откройте файл pubspec.yaml. Здесь нам нужно сделать две вещи: включить зависимость flame_tiled и указать путь к нашему уровню в разделе assets. Затем нажмите кнопку pub get.

dependencies:
  flutter:
    sdk: flutter
  flame: ^1.6.0
  flame_tiled: ^1.9.1

flutter:
  assets:
    - assets/images/
    - assets/tiles/

Затем перейдите к файлу game.dart и загрузите файл нашего уровня в методе onLoad перед добавлением компонента TheBoy:

final level = await TiledComponent.load("level1.tmx", Vector2.all(64));

mapWidth = level.tileMap.map.width * level.tileMap.destTileSize.x;
mapHeight = level.tileMap.map.height * level.tileMap.destTileSize.y;

add(level);

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

Кроме того, создайте две переменные класса mapWidth и mapHeight, которые представляют размер нашего уровня. Мы будем использовать эти переменные позже.

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

Управление камерой

Представьте себе уровень Super Mario Bros. Когда персонаж достигает определенной точки, экран прокручивается, как будто камера следит за игроком. Вот именно: игровой уровень может быть любого размера, но нам нужно показать только небольшую его часть, а область просмотра отвечает за то, чтобы сообщить игре, какую часть нашего уровня мы хотим показать.

Итак, давайте добавим это в конец нашего метода onLoad:

camera.viewport = FixedResolutionViewport(Vector2(1920 , 1280));

Если вы запустите игру, вы должны увидеть это. Мы поставили это разрешение намеренно, чтобы пока увидеть весь уровень.

Zoomed out game level

Теперь давайте переместим позицию Мальчика в левый угол нашего уровня.

final theBoy = TheBoy(
      position: Vector2(128, mapHeight - 64),
    );
add(theBoy);

Далее давайте немного приблизим нашу камеру, чтобы игрок не видел сразу весь уровень:

camera.zoom = 2;

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

camera.followComponent(theBoy, worldBounds: Rect.fromLTWH(0, 0, mapWidth, mapHeight));

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

The camera stops moving when the player reaches the end of the level map

Последнее, что мы хотим сделать, это запретить мальчику отходить от экрана. Это легко сделать, установив для скорости x значение 0, если положение нашего компонента TheBoy находится у края экрана. Давайте добавим два метода, чтобы проверить это:

bool doesReachLeftEdge() {
    return position.x <= size.x / 2 && _horizontalDirection < 0;
  }

bool doesReachRightEdge() {
  return position.x >= game.mapWidth - size.x / 2 && _horizontalDirection > 0;
}

Достаточно просто: мы проверяем, смотрит ли игрок на край и совпадает ли его позиция x с координатой края. Во-первых, нам нужно учитывать размер спрайта, иначе игрок сможет отойти от экрана на расстояние, равное половине размера спрайта (поскольку точка привязки спрайта находится в центре).

Теперь просто ограничим движение, если игрок достиг одного из краев экрана:

@override
void update(double dt) {
  super.update(dt);
    if (doesReachLeftEdge() || doesReachRightEdge()) {
      _velocity.x = 0;
    } else {
      _velocity.x = _horizontalDirection * _moveSpeed;
    }

    ...

 }

Круто, наша игра выглядит намного лучше! Но мы можем сделать его еще лучше, добавив красивый фон.

Фон параллакса

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

Создадим новый файл background.dart в папке lib/objects:

class ParallaxBackground extends ParallaxComponent<PlatformerGame> {
  ParallaxBackground({required super.size});

  @override
  Future<void> onLoad() async {
    final clouds = await game.loadParallaxLayer(
      ParallaxImageData(Assets.CLOUDS),
      velocityMultiplier: Vector2(1, 0),
      fill: LayerFill.none,
      alignment: Alignment.topCenter,
    );

    final mist = await game.loadParallaxLayer(
      ParallaxImageData(Assets.MIST),
      velocityMultiplier: Vector2(2, 0),
      fill: LayerFill.none,
      alignment: Alignment.bottomCenter,
    );

    final hills = await game.loadParallaxLayer(
      ParallaxImageData(Assets.HILLS),
      velocityMultiplier: Vector2(3, 0),
      fill: LayerFill.none,
      alignment: Alignment.bottomCenter,
    );

    positionType = PositionType.viewport;
    parallax = Parallax(
      [clouds, mist, hills],
      baseVelocity: Vector2.all(10),
    );
  }
}

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

  • velocityMultiplier — насколько быстро будет двигаться слой по сравнению с baseVelocity ParallaxComponent. Чем дальше слой от переднего плана, тем выше должно быть значение.
  • fill — если мы хотим масштабировать спрайт, чтобы заполнить область просмотра
  • alignment - положение слоя на игровом экране. В текущей версии Flame у вас есть возможность прикрепить слой только к верхней, центральной или нижней части экрана. Если вам нужна более тонкая настройка, вам нужно добавить отступы к самому активу. Другим вариантом может быть несколько вложенных ParallaxComponents.

После этого давайте добавим наш компонент ParallaxBackground в дерево игры перед другими компонентами:

add(ParallaxBackground(size: Vector2(mapWidth, mapHeight)));

А также давайте переопределим метод backgroundColor в game.dart, чтобы получить более приятный цвет фона:

@override
Color backgroundColor() {
  return const Color.fromARGB(255, 69, 186, 230);
}

Auto-moving parallax background

Гораздо лучше, но в настоящее время фон движется независимо от движения игрока. Но мы хотим, чтобы фон параллакса двигался вместе с камерой. Давайте исправим это! Вернитесь к background.dart и добавьте новую переменную класса:

Vector2 _lastCameraPosition = Vector2.zero();

Затем установите значение параметра baseVelocity в конструкторе объекта Parallax на Vector2.zero()

Наконец, добавьте реализацию метода update:

@override
  void update(double dt) {
    final cameraPosition = gameRef.camera.position;
    final baseVelocity = (cameraPosition - _lastCameraPosition) * 10;
    parallax!.baseVelocity.setFrom(baseVelocity);
    _lastCameraPosition.setFrom(gameRef.camera.position);
    super.update(dt);
  }

Итак, вместо константы baseVelocity мы хотим вычислять ее динамически, основываясь на движении камеры. Мы вычисляем разницу в положении камеры с момента последнего игрового цикла и умножаем ее на базовую скорость (в данном случае 10). Затем мы обновляем baseVelocity вычисленным значением. Давайте проверим это.

The Parallax background is moving with the camera

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

Заключение

На этом часть 2 подошла к концу. Мы научились создавать карту уровня с помощью редактора Tiled, заставили камеру следовать за персонажем игрока и добавили потрясающий фон параллакса. Весь игровой код, который у нас есть на данный момент, можно найти на на моем GitHub, а первая статья из этой серии < a href="https://hackernoon.com/teaching-your-character-to-run-in-flame.">здесь.

Теперь наш проект выглядит как настоящая игра. Но в следующей главе мы сделаем его еще лучше, научим Мальчика прыгать.

Ресурсы

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


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