Найти тему

I/O Pinball на базе Flutter и Firebase

Оглавление

Повысьте уровень разработки игр на Flutter

Специально для Google I/O мы объединились с командой Flutter, чтобы вдохнуть новую жизнь в классическую игру "пинбол", созданную с помощью Flutter и Firebase. Вот, как мы оживили I/O Pinball в сети с помощью игрового движка Flame.

Основы разработки игр

The Framework Flutter - отличный выбор для создания игр, основанных на взаимодействии с пользователем, таких как головоломки и словесные игры. Когда речь идет об играх, использующих игровой цикл, полезным инструментом может стать Flame - 2D — игровой движок, построенный на базе Flutter.

I/O Pinball использует готовые функции от Flame: анимация, физика, обнаружение столкновений и многое другое, а также использует инфраструктуру фреймворка Flutter. Если вы можете создавать приложения на Flutter, это значит, что у вас уже есть база, необходимая для создания игр на Flame.

-2

Игровой цикл

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

Game Class занимается реализацией игровых компонентов и логикой, которая передается игровому виджету "GameWidget" в специальном "дереве виджетов" (Wiget Tree). В I/O Pinball игровой цикл реагирует на положение и состояние шарика на игровом поле и применяет необходимые эффекты, если шарик сталкивается с объектом или выпадает из игры.

@override
void update(double dt) {
super.update(dt);
final direction = -parent.body.linearVelocity.normalized();
angle = math.atan2(direction.x, -direction.y);
size = (_textureSize / 45) *
parent.body.fixtures.first.shape.radius;
}

Рендеринг 3D-пространства при помощи 2D-компонентов

Одна из трудностей при создании I/O Pinball заключалась в том, чтобы понять, как создать 3D-эффект, используя только 2D-элементы. Компоненты упорядочиваются, чтобы определить, как они отображаются на экране. Например, когда шарик запускается вверх по рампе, его местоположение меняется, так что он оказывается на вершине рампы.

-3

Мяч, поршень, обе лопасти и Динозавр Гугл Хром (The Chrome Dino) - это элементы с динамическим телом, на которые влияет физика. Мяч также меняет размер в зависимости от своего положения на доске. По мере продвижения к вершине доски шарик уменьшается в размерах и, с точки зрения пользователя, кажется, что он далеко. Кроме того, сила тяжести на шарик регулируется с учетом угла наклона пинбольного автомата, так что под наклоном шарик падает быстрее.

/// Scales the ball's body and sprite according to its position on the board.
class ParentIsA<Ball> {
@override
void update(double dt) {
super.update(dt);
final boardHeight = BoardDimensions.bounds.height;
const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor;
final standardizedYPosition = parent.body.position.y + (boardHeight / 2);
final scaleFactor = maxShrinkValue +
((standardizedYPosition / boardHeight) * (1 - maxShrinkValue));
parent.body.fixtures.first.shape.radius = (Ball.size.x / 2) * scaleFactor;
final ballSprite = parent.descendants().whereType<SpriteComponent>();
if (ballSprite.isNotEmpty) {
ballSprite.single.scale.setValues(
scaleFactor,
scaleFactor,
);
}
}
}

Физика с Forge 2D

I/O Pinball в значительной степени использует пакет forge2d от команды Flame. Этот пакет переносит физический движок Box2D с открытым исходным кодом в Dart, чтобы его можно было легко подключить к Flutter. Мы использовали forge2d для обеспечения физики игры, например, для распознавания столкновений между объектами (Fixtures) на игровом поле.

Благодаря forge2D мы можем услышать, когда происходят столкновения между объектами. Затем мы добавляем ContactCallbacks к фикстурам (Fixtures), чтобы получать уведомления, когда происходит контакт между двумя элементами.

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

@override
Body createBody() {
final shape = CircleShape()..radius = size.x / 2;
final bodyDef = BodyDef(
position: initialPosition,
type: BodyType.dynamic,
userData: this,
);
return world.createBody(bodyDef)
..createFixtureFromShape(shape, 1);
}

Анимация спрайт-листа

На игровом поле пинбола есть несколько анимированных элементов, таких как Android, Dash, Sparky и Chrome Dino. Для них мы использовали спрайт-листы, которые включены в движок Flame с компонентом SpriteAnimationComponent. Для каждого элемента у нас был файл с изображением в различных положениях, количество кадров в файле и время между кадрами. Используя эти данные, компонент SpriteAnimationComponent в Flame собирает все изображения вместе в цикле, чтобы элемент выглядел анимированным.

Пример спрайт-листа
Пример спрайт-листа
final spriteSheet = gameRef.images.fromCache(
Assets.images.android.spaceship.animatronic.keyName,
);
const amountPerRow = 18;
const amountPerColumn = 4;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
),
);

Посмотрим ближе на кодовую базу I/O Pinball

Таблица лидеров с результатами в режиме реального времени из Firebase

Таблица лидеров I/O Pinball отображает лучшие результаты игроков по всему миру в режиме реального времени. Пользователи также могут делиться своими результатами в Twitter и Facebook. Мы отслеживаем десять лучших результатов и отображаем их на доске лидеров при помощи облачного хранилища Firebase Cloud Firestore. Когда в таблицу лидеров записывается новый результат, облачная функция сортирует десять лучших результатов в порядке убывания и удаляет остальные.

-5

/// Acquires top 10 [LeaderboardEntryData]s.
Future<List<LeaderboardEntryData>> fetchTop10Leaderboard() async {
try {
final querySnapshot = await _firebaseFirestore
.collection(_leaderboardCollectionName)
.orderBy(_scoreFieldName, descending: true)
.limit(_leaderboardLimit)
.get();
final documents = querySnapshot.docs;
return documents.toLeaderboard();
} on LeaderboardDeserializationException {
rethrow;
} on Exception catch (error, stackTrace) {
throw FetchTop10LeaderboardException(error, stackTrace);
}
}

Создание онлайн игры

Создание сетевой игры может быть проще создания обычного приложения. Игровое поле пинбола просто должно масштабироваться в соответствии с размером устройства. В I/O Pinball мы создаем поле в зависимости от размера вашего устройства по фиксированному коэффициенту. Это гарантия того, что система координат всегда одна и та же, независимо от размера дисплея, что важно для обеспечения последовательного отображения и взаимодействия компонентов на разных устройствах.

I/O Pinball также адаптируется к мобильному или компьютерному браузеру. В мобильном браузере пользователи могут нажать на кнопку запуска, чтобы начать игру, а также нажать на левую и правую стороны экрана для управления соответствующими рычагами. В компьютерном браузере пользователи могут использовать клавиатуру для запуска шарика и управления лопастями.

Архитектура кодовой базы

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

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

-6
/// {@template character_theme}
/// Base class for creating character themes.
///
/// Character specific game components should have a getter specified here to
/// load their corresponding assets for the game.
/// {@endtemplate}
abstract class CharacterTheme extends Equatable {
/// {@macro character_theme}
const CharacterTheme();///
Name of character.
String get name;
/// Asset for the ball.
AssetGenImage get ball;
/// Asset for the background.
AssetGenImage get background;
/// Icon asset.
AssetGenImage get icon;
/// Icon asset for the leaderboard.
AssetGenImage get leaderboardIcon;
/// Asset for the the idle character animation.
AssetGenImage get animation;
@override
List<Object> get props => [
name,
ball,
background,
icon,
leaderboardIcon,
animation,
];
}


Управление игрой I/O Pinball осуществляется с помощью
flame_bloc, пакета, который соединяет между собой блоки с компонентами Flame. Например, мы используем flame_bloc для отслеживания количества раундов, оставшихся до конца игры, любых бонусов, полученных в процессе игры, а также текущего игрового счета.

Кроме того, в верхней части "дерева виджетов" находится виджет, который отвечает за страницу загрузки, включая инструкции по игре. Мы также следуем "pattern behaviour" (модели поведения), чтобы внедрять и изолировать определенные элементы игровой функции на основе ее компонентов. Например, чтобы бамперы воспроизводили звук при ударе мяча, мы внедрили BumperNoiseBehavior.

class BumperNoiseBehavior extends ContactBehavior {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
readProvider<PinballPlayer>().play(PinballAudio.bumper);
}
}

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

Песочница компонентов

Этот проект в значительной степени использует компоненты Flame, чтобы оживить игру в пинбол. Кодовая база поставляется с песочницей компонентов, которая похожа на UI component gallery (галерею компонентов пользовательского интерфейса). Это полезный инструмент при разработке игр, поскольку позволяет разрабатывать игровые компоненты отдельно друг от друга. Перед тем как интегрировать их в игру, Вы можете убедиться, что они выглядят и работают так, как Вы задумывали.

-7

Что дальше

Проверьте, сможете ли вы получить высокий результат в I/O Pinball! Код с открытым исходным кодом находится в этом хранилище GitHub. Следите за таблицей лидеров и не забудьте поделиться своими результатами в социальных сетях!