Все компоненты наследуются от класса Component
и могут иметь другие Component
в качестве дочерних элементов.
Дочерние элементы можно добавлять либо с помощью метода add(Component c)
, либо непосредственно в конструкторе компонента.
Пример:
void main() {
final component1 = Component(children: [Component(), Component()]);
final component2 = Component();
component2.add(Component());
component2.addAll([Component(), Component()]);
}
У каждого компонента есть несколько методов, которые вы можете дополнительно реализовать, которые используются классом FlameGame
.
Метод onGameResize
вызывается при каждом изменении размера экрана, а также при добавлении этого компонента в дерево компонентов, до вызова onMount. Он полезен для адаптации компонента к текущему размеру экрана.
Метод onParentResize
похож: он вызывается при монтировании компонента в дерево компонентов, а также при каждом изменении размера родительского компонента. Используйте его, когда компоненту нужно реагировать на изменения размера родителя.
Метод onRemove
можно переопределить, чтобы выполнить код непосредственно перед удалением компонента из игры. Он будет выполнен только один раз, даже если компонент удаляется и методом remove родителя, и собственным методом remove компонента. Здесь можно освободить ресурсы.
Метод onLoad
можно переопределить для выполнения асинхронного кода инициализации компонента, например, для загрузки изображений. Этот метод выполняется до onGameResize
и onMount
. Важно: onLoad
гарантировано выполнится только один раз за время жизни компонента. Рассматривайте его как "асинхронный конструктор".
Метод onMount
выполняется каждый раз, когда компонент монтируется в дерево игры. Это означает, что не следует инициализировать переменные, объявленые с ключевым словом late final
, в этом методе, поскольку он может быть вызван несколько раз в течение жизни компонента. Этот метод будет выполнен, только если родительский компонент уже смонтирован. Если родитель еще не смонтирован, выполнение onMount
ставится в очередь (это не повлияет на остальную часть игрового движка).
Метод onChildrenChanged
можно переопределить, если необходимо отслеживать изменения в дочерних элементах родительского компонента. Этот метод вызывается при каждом добавлении или удалении дочернего элемента у родителя (включая случаи, когда дочерний элемент меняет родителя). Параметры метода содержат целевой дочерний элемент и тип произошедшего изменения (добавлен или удален).
Состояние жизненного цикла компонента можно проверить с помощью следующих геттеров:
• isLoaded
: Возвращает bool
, указывающий текущее состояние загрузки.
• loaded
: Возвращает Future, который завершится после завершения загрузки компонента (после выполнения onLoad
).
• isMounted
: Возвращает bool
, указывающий текущее состояние монтирования.
• mounted
: Возвращает Future
, который завершится после монтирования компонента (после выполнения onMount).
• isRemoved
: Возвращает bool
, указывающий, был ли компонент удален.
• removed
: Возвращает Future
, который завершится после удаления компонента (после выполнения onRemove).
В Flame каждый компонент имеет свойство priority
типа int
, которое определяет порядок сортировки компонента среди дочерних элементов его родителя. В других языках и фреймворках это иногда называют z-index
. Чем выше значение priority
, тем ближе к пользователю будет казаться компонент на экране, поскольку он будет отрисован поверх любых компонентов с более низким приоритетом, которые были отрисованы ранее.
Если вы добавите два компонента и установите приоритет одного из них равным, например, 1, то этот компонент будет отрисован поверх другого компонента (если они перекрываются), поскольку приоритет по умолчанию равен 0.
Все компоненты принимают priority
в качестве именованного аргумента, поэтому, если вы знаете желаемый приоритет компонента во время компиляции, вы можете передать его в конструктор.
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
class BackgroundComponent extends PositionComponent {
BackgroundComponent() : super(priority: 0);
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), Paint()..color = Colors.blue);
}
}
class ForegroundComponent extends PositionComponent {
ForegroundComponent() : super(priority: 1);
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), Paint()..color = Colors.red.withValues(red: 0.7));
}
}
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
// Создаем компоненты
final background = BackgroundComponent()
..size = Vector2(200, 200)
..position = Vector2(50, 50);
final foreground = ForegroundComponent()
..size = Vector2(150, 150)
..position = Vector2(75, 75);
add(background);
add(foreground);
}
}
void main() {
runApp(GameWidget(game: MyGame()));
}
Иногда бывает полезно оборачивать другие компоненты внутри вашего компонента. Например, группируя визуальные компоненты посредством иерархии. Вы можете сделать это, добавляя дочерние компоненты к любому компоненту, например, PositionComponent
.
Когда у компонента есть дочерние компоненты, каждый раз, когда родительский компонент обновляется и отрисовывается, все дочерние компоненты также отрисовываются и обновляются с теми же условиями.
Пример использования, где видимость двух компонентов управляется оберткой:
class GameOverPanel extends PositionComponent {
bool visible = false;
final Image spriteImage;
GameOverPanel(this.spriteImage);
@override
void onLoad() {
final gameOverText = GameOverText(spriteImage); // GameOverText - это Component
final gameOverButton = GameOverButton(spriteImage); // GameOverRestart - это SpriteComponent
add(gameOverText);
add(gameOverButton);
}
@override
void render(Canvas canvas) {
if (visible) {
} // Если не visible, ни один из дочерних компонентов не будет отрисован
}
}
Есть два способа добавления дочерних компонентов к вашему компоненту. Во-первых, у вас есть методы add()
, addAll()
и addToParent()
, которые можно использовать в любое время во время игры. Традиционно дочерние компоненты создаются и добавляются из метода onLoad() компонента, но также обычно добавлять новые дочерние компоненты в течение игры.
Второй способ — использовать параметр children
: в конструкторе компонента. Этот подход больше похож на стандартный API Flutter:
class MyGame extends FlameGame {
@override
void onLoad() {
add(
PositionComponent(
position: Vector2(30, 0),
children: [
HighScoreDisplay(),
HitPointsDisplay(),
FpsComponent(),
],
),
);
}
}
Если компоненту, у которого есть World
в качестве предка и которому требуется доступ к этому объекту World
, можно использовать mixin HasWorldReference
.
class MyComponent extends Component with HasWorldReference<MyWorld>,
TapCallbacks {
@override
void onTapDown(TapDownEvent info) {
// world имеет тип MyWorld
world.add(AnotherComponent());
}
}
Если вы попытаетесь получить доступ к world из компонента, у которого нет предка World правильного типа, будет выдана ошибка утверждения (assertion error).
Наследуется от Component и добавляет понятия позиции, размера, угла поворота и точки привязки (anchor). Это один из самых часто используемых компонентов.
Используется для представления объектов на экране, у которых есть положение, размер и ориентация.
class MyPositionComponent extends PositionComponent {
MyPositionComponent() {
size = Vector2(50, 50); // Устанавливаем размер
position = Vector2(100, 100); // Устанавливаем позицию
anchor = Anchor.center; // Устанавливаем точку привязки в центр
}
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), Paint()..color = Colors.green); // Рисуем зеленый квадрат
}
}
Наследуется от PositionComponent и отображает изображение (спрайт). Это позволяет легко отображать графику на экране.
Требует загрузки изображения (например, из файла) и создания объекта Sprite.
class MySpriteComponent extends SpriteComponent {
MySpriteComponent({required super.sprite, required super.size, required super.position});
}
class SpriteExample extends FlameGame {
@override
Future<void> onLoad() async {
final sprite = await loadSprite('brick.png'); // Загружаем спрайт из файла
final mySprite = MySpriteComponent(sprite: sprite, size: Vector2(100, 100), position: Vector2(50, 50));
add(mySprite);
}
}
Как подготовить спрайт:
Добавьте изображение 'brick.png' в папку assets/images. Не забудьте обновить pubspec.yaml чтобы включить эту папку как asset.
Наследуется от PositionComponent и отображает текст на экране.
Позволяет настраивать шрифт, цвет, размер и выравнивание текста.
class MyTextComponent extends TextComponent {
MyTextComponent() : super(
text: 'Hello, Flame!',
textRenderer: TextPaint(
style: const TextStyle(
fontSize: 24,
color: Colors.white,
),
),
position: Vector2(20, 20),
);
}
ShapeComponent сам по себе не рисует ничего конкретного. Он служит базовым классом для компонентов, которые рисуют фигуры. Он определяет общие свойства, такие как цвет, стиль заливки (заливка или обводка) и позицию.
Главное отличие от PositionComponent в том, что ShapeComponent абстрагируется от растровой графики (как в случае SpriteComponent) и позволяет рисовать фигуры, которые масштабируются без потери качества (векторная графика).
Наследуется от ShapeComponent и рисует прямоугольник.
Имеет свойства для определения размера (size) и радиуса скругления углов (borderRadius).
class MyRectangle extends RectangleComponent {
MyRectangle({Vector2? position, Vector2? size})
: super(
position: position ?? Vector2(50, 50),
size: size ?? Vector2(50, 25),
paint: Paint()
..color = Colors.orange
..style = PaintingStyle.fill,
);
}
Наследуется от ShapeComponent и рисует круг.
Имеет свойство radius для определения радиуса круга.
class MyCircle extends CircleComponent {
MyCircle({Vector2? position, double? radius}) : super(
radius: radius ?? 30,
position: position ?? Vector2(200, 100),
paint: Paint()
..color = Colors.blue
..style = PaintingStyle.fill,
anchor: Anchor.center
);
}
Наследуется от ShapeComponent и рисует многоугольник, используя список точек, определяющих его вершины.
Требует указания списка точек (List) в конструкторе.
Позволяет создавать сложные фигуры, задавая координаты каждой вершины.
class MyPolygon extends PolygonComponent {
MyPolygon({Vector2? position}) : super(
[ // Список вершин многоугольника (в локальных координатах)
Vector2(0, 0), // Левая верхняя
Vector2(50, 0), // Правая верхняя
Vector2(75, 50), // Правая нижняя
Vector2(25, 100), // Левая нижняя
Vector2(0, 50), // Левая середина
],
position: position ?? Vector2(100, 200),
paint: Paint()
..color = Colors.green
..style = PaintingStyle.fill,
);
}
Наследуется от Component и позволяет запускать код через определенные промежутки времени.
Полезен для создания событий, которые должны происходить не каждый кадр, а, например, раз в секунду.
class MyTimerComponent extends TimerComponent {
MyTimerComponent() : super(period: 1, repeat: true); // Выполнять каждую секунду, бесконечно
@override
void onTick() {
print('Timer ticked!'); // Выводим сообщение в консоль каждую секунду
}
}
Позволяет периодически или по требованию создавать (порождать) другие компоненты в игре. Например, можно использовать для генерации врагов, частиц или других игровых объектов.
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'dart:math';
class Enemy extends PositionComponent {
Enemy({super.position}) : super(size: Vector2.all(25)) {
anchor = Anchor.center;
}
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), Paint()..color = Colors.red);
}
}
class EnemySpawner extends SpawnComponent {
EnemySpawner()
: super(
factory: (data) {
return Enemy();
},
period: 1,
area: Rectangle.fromLTWH(0, 0, 300, 150));
}
В этом примере EnemySpawner создает новых врагов (Enemy) каждую секунду (period: 1). area определяет область, в которой враги будут появляться случайным образом.
Используется для создания эффекта параллакса, когда разные слои фона движутся с разной скоростью, создавая ощущение глубины. Часто используется в 2D-играх для улучшения визуального восприятия.
class MyParallaxComponent extends ParallaxComponent {
@override
Future<void> onLoad() async {
parallax = await game.loadParallax(
[
ParallaxImageData('parallax/far_hills.png'), // Самый дальний слой
ParallaxImageData('parallax/mountains.png'), // Средний слой
ParallaxImageData('parallax/sky.png'), // Ближний слой
],
repeat: ImageRepeat.repeatX,
baseVelocity: Vector2(20, 0), // Скорость движения всех слоев
velocityMultiplierDelta: Vector2(1.2, 0), // На сколько каждый слой быстрее предыдущего
);
}
}
Позволяет отображать SVG-изображения (Scalable Vector Graphics) в игре. SVG - это векторный формат, который хорошо масштабируется без потери качества.
class MySVGComponent extends SvgComponent {
MySVGComponent({required super.svg, required super.size, required super.position});
}
class SVGExample extends FlameGame {
@override
Future<void> onLoad() async {
final svg = await loadSvg('flame.svg'); // Загружаем SVG из файла
final mySvg = MySVGComponent(svg: svg, size: Vector2(100, 100), position: Vector2(50, 50));
add(mySvg);
}
}
Сделать домик (единым компонентом - HomeComponent), используя компоненты RectangleComponent, PolygonComponent и CircleComponent. У домика должны быть крыша, стена и круглое окно. Каждый из элементов домика должен быть сделан отдельным компонентом и быть связан с родительским (HomeComponent) с помощью add в методе onLoad или иным способом.
Что такое Mixins?
Представьте, что у вас есть кукла. У этой куклы есть базовые свойства: имя, цвет волос, одежда. Но вы хотите, чтобы кукла умела танцевать, петь или рисовать. Вместо того, чтобы создавать новую куклу с этими умениями, можно добавить к существующей кукле специальные "волшебные добавки", которые научат её танцевать, петь или рисовать.
В Flame, Mixins — это как такие "волшебные добавки". Они позволяют добавлять новые возможности к компонентам, не изменяя сам компонент. С помощью Mixins можно легко добавить возможность реагировать на касания, нажатия клавиш, перетаскивание и другие действия пользователя.
Добавляет возможность реагировать на касания экрана.
Используется для создания кнопок, интерактивных объектов и управления персонажем.
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
class MyTappableComponent extends PositionComponent with TapCallbacks {
MyTappableComponent() {
size = Vector2.all(100);
anchor = Anchor.center;
}
Paint paint = Paint()..color = Colors.red;
bool isTapped = false;
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), paint);
}
@override
void onTapDown(TapDownEvent event) {
isTapped = true;
paint.color = Colors.green;
print('Tap down at ${event.localPosition}');
}
@override
void onTapUp(TapUpEvent event) {
isTapped = false;
paint.color = Colors.red;
print('Tap up at ${event.localPosition}');
}
@override
void onTapCancel(TapCancelEvent event) {
isTapped = false;
paint.color = Colors.yellow;
print('Tap cancelled');
}
}
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
add(MyTappableComponent()..position = size / 2);
}
}
void main() {
runApp(MaterialApp(
home: Scaffold(
body: GameWidget(game: MyGame()),
),
));
}
onTapDown(TapDownEvent event): Вызывается, когда пользователь касается экрана.
onTapCancel(TapCancelEvent event): Вызывается, если касание было прервано (например, если пользователь переместил палец за пределы компонента).
onTapUp(TapUpEvent event): Вызывается, когда пользователь отпускает палец.
Добавляет возможность реагировать на жесты панорамирования (перемещения пальцем по экрану).
Используется для перемещения камеры, прокрутки списков и других действий, связанных с перемещением контента.
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
void main() {
final myGame = MyGame();
runApp(GameWidget(game: myGame));
}
class MyGame extends FlameGame with PanDetector {
final player = Player();
@override
Future<void> onLoad() async {
add(player);
}
@override
void onPanStart(DragStartInfo info) {
player.onPanStart(info);
}
@override
void onPanUpdate(DragUpdateInfo info) {
player.onPanUpdate(info);
}
@override
void onPanEnd(DragEndInfo info) {
player.onPanEnd(info);
}
}
class Player extends PositionComponent with HasGameRef<MyGame> {
@override
Future<void> onLoad() async {
size = Vector2(100, 100);
position = gameRef.size / 2 - size / 2;
}
final Paint paint = Paint()..color = Colors.blue;
void onPanStart(DragStartInfo info) {
// Можно добавить логику при начале жеста
print('PanStarted');
}
void onPanUpdate(DragUpdateInfo info) {
// Перемещаем компонент в соответствии с движением пальца
position.add(info.delta.global);
print('PanUpdated');
}
void onPanEnd(DragEndInfo info) {
print('PanEnded');
}
@override
void render(Canvas canvas) {
super.render(canvas);
canvas.drawRect(size.toRect(), paint);
}
}
onPanStart(DragStartInfo info): Вызывается, когда начинается жест панорамирования.
onPanUpdate(DragUpdateInfo info): Вызывается при каждом перемещении пальца во время жеста панорамирования.
onPanEnd(DragEndInfo info): Вызывается, когда жест панорамирования заканчивается.
Добавляет возможность реагировать на перетаскивание объектов.
Используется для перемещения объектов по экрану, создания головоломок и других игр, где нужно перемещать предметы.
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
class MyDraggableComponent extends PositionComponent with DragCallbacks {
MyDraggableComponent() {
size = Vector2.all(100);
anchor = Anchor.center;
}
Paint paint = Paint()..color = Colors.green;
bool isDragging = false;
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), paint);
}
@override
void onDragStart(DragStartEvent event) {
super.onDragStart(event);
isDragging = true;
paint.color = Colors.yellow;
print('Drag started');
}
@override
void onDragUpdate(DragUpdateEvent event) {
position.add(event.canvasDelta);
print('Dragging: ${event.canvasDelta}');
}
@override
void onDragEnd(DragEndEvent event) {
super.onDragEnd(event);
isDragging = false;
paint.color = Colors.green;
print('Drag ended');
}
@override
void onDragCancel(DragCancelEvent event) {
super.onDragCancel(event);
isDragging = false;
paint.color = Colors.green;
print('Drag cancelled');
}
}
class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
add(MyDraggableComponent()..position = size / 2);
}
}
void main() {
runApp(MaterialApp(
home: Scaffold(
body: GameWidget(game: MyGame()),
),
));
}
onDragStart(DragStartInfo info): Вызывается, когда начинается перетаскивание.
onDragUpdate(DragUpdateInfo info): Вызывается при каждом перемещении пальца во время перетаскивания.
onDragEnd(DragEndInfo info): Вызывается, когда перетаскивание заканчивается.
onDragCancel(): Вызывается, если перетаскивание было прервано.
Сделать возможным,чтобы домик, реализованный в первом задании можно было подвигать по экрану. Можно использовать любой из способов управления, описанных выше. (Во время занятия большинство использовало DragCallbacks, используя его, не забудьте правильно задать размер HomeComponent, чтобы его можно было подвинуть).
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flame/src/experimental/geometry/shapes/rectangle.dart';
void main() {
runApp(GameWidget(game: MyGame()));
}
class MyGame extends FlameGame with TapCallbacks, HasCollisionDetection {
late Player player;
late MultiEnemySpawner multiEnemySpawner;
late EnemySpawner enemySpawner;
late TextComponent scoreText;
late TextComponent timerText;
late StartButton startButton;
late StopButton stopButton;
double timer = 0;
bool isTimerRunning = false;
@override
Future<void> onLoad() async {
player = Player()
..position = size / 2
..anchor = Anchor.center;
add(player);
enemySpawner = EnemySpawner();
add(enemySpawner);
multiEnemySpawner = MultiEnemySpawner();
add(multiEnemySpawner);
timerText = TextComponent(
text: 'Time: ${timer.toStringAsFixed(1)}',
textRenderer: TextPaint(
style: const TextStyle(fontSize: 20, color: Colors.white),
),
position: Vector2(10, 60),
anchor: Anchor.topLeft,
priority: 2,
);
add(timerText);
startButton = StartButton(
position: Vector2(size.x / 2, size.y - 50),
onPressed: startTimer,
);
add(startButton);
stopButton = StopButton(
position: Vector2(size.x / 2, size.y - 50),
onPressed: stopTimer,
);
stopButton.removeFromParent(); // Сначала кнопка остановки скрыта
}
@override
void update(double dt) {
super.update(dt);
if (isTimerRunning) {
timer += dt;
timerText.text = 'Time: ${timer.toStringAsFixed(1)}';
}
}
void startTimer() {
isTimerRunning = true;
timer = 0;
startButton.removeFromParent(); // Убираем кнопку старта
add(stopButton); // Добавляем кнопку остановки
}
void stopTimer() {
isTimerRunning = false;
stopButton.removeFromParent(); // Убираем кнопку остановки
add(startButton); // Добавляем кнопку старта
}
}
// Компонент игрока
class Player extends PositionComponent with DragCallbacks, HasGameRef<MyGame> {
Player() : super(size: Vector2.all(50));
final _paint = Paint();
bool _isDragged = false;
@override
void onDragStart(DragStartEvent event) {
super.onDragStart(event);
_isDragged = true;
}
@override
void onDragUpdate(DragUpdateEvent event) => position += event.localDelta;
@override
void onDragEnd(DragEndEvent event) {
super.onDragEnd(event);
_isDragged = false;
}
@override
void render(Canvas canvas) {
_paint.color = _isDragged ? Colors.green : Colors.white;
canvas.drawCircle(Offset(size.x / 2, size.y / 2), size.x / 2, _paint);
}
}
// Компонент врага
class Enemy extends PositionComponent with HasGameRef<MyGame> {
static const speed = 100.0;
late Vector2 velocity;
VoidCallback? onKilled; // Колбэк для обработки убийства врага
Enemy({super.position}) : super(size: Vector2.all(30)) {
anchor = Anchor.center;
// Начальное направление движения
velocity = Vector2(0, 1);
}
@override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), Paint()..color = Colors.red);
}
@override
void update(double dt) {
super.update(dt);
position.add(velocity * speed * dt);
// Удаление врага, когда он выходит за нижний край экрана
if (position.y > gameRef.size.y) {
removeFromParent();
}
}
void kill() {
onKilled?.call(); // Вызываем колбэк при уничтожении врага
removeFromParent();
}
}
class EnemySpawner extends SpawnComponent with HasGameRef<MyGame> {
EnemySpawner()
: super(
factory: (data) {
return Enemy();
},
period: 1,
);
@override
Future<void> onLoad() async {
area = Rectangle.fromLTWH(0, 0, gameRef.size.x, 150);
super.onLoad();
}
}
class MultiEnemySpawner extends SpawnComponent with HasGameRef<MyGame> {
MultiEnemySpawner()
: super(multiFactory: (amount) => [Enemy(), Enemy(), Enemy()], period: 5);
@override
Future<void> onLoad() async {
area = Rectangle.fromLTWH(
gameRef.size.x / 4,
0,
gameRef.size.x / 4 * 3,
50,
);
super.onLoad();
}
}
// Кнопка старта таймера
class StartButton extends PositionComponent with TapCallbacks {
final VoidCallback onPressed;
StartButton({required Vector2 position, required this.onPressed})
: super(
priority: 4,
position: position,
size: Vector2(150, 50),
anchor: Anchor.center,
);
@override
void render(Canvas canvas) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawRect(size.toRect(), paint);
final textPainter = TextPainter(
text: TextSpan(
text: 'Start Timer',
style: const TextStyle(fontSize: 20, color: Colors.white),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
size.x / 2 - textPainter.width / 2,
size.y / 2 - textPainter.height / 2,
),
);
}
@override
void onTapDown(TapDownEvent event) {
onPressed();
}
}
// Кнопка остановки таймера
class StopButton extends PositionComponent with TapCallbacks {
final VoidCallback onPressed;
StopButton({required Vector2 position, required this.onPressed})
: super(
priority: 4,
position: position,
size: Vector2(150, 50),
anchor: Anchor.center,
);
@override
void render(Canvas canvas) {
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
canvas.drawRect(size.toRect(), paint);
final textPainter = TextPainter(
text: TextSpan(
text: 'Stop Timer',
style: const TextStyle(fontSize: 20, color: Colors.white),
),
textDirection: TextDirection.ltr,
);
textPainter.layout();
textPainter.paint(
canvas,
Offset(
size.x / 2 - textPainter.width / 2,
size.y / 2 - textPainter.height / 2,
),
);
}
@override
void onTapDown(TapDownEvent event) {
onPressed();
}
}
P.S: Рассмотрел после занятия запрос на показ спавнера с несколькими типами объектов и отобразил его в финальном примере (MultiEnemySpawner).
Добавить еще одного противника и его спавнер для игры выше. У нового противника должны быть совершенно другие параметры (скорость, форма, цвет). У нового спавнера может быть другой период спавна объектов или другие размеры зоны для пояления противников. Результаты работы присылать по ссылке https://forms.yandex.com/u/67c28684eb61469df8a59744/ , а именно скриншоты кода и противников в игре.