import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/extensions.dart';
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: SafeArea(
child: Center(
child: GameWidget.controlled(gameFactory: MyGame.new),
),
),
),
);
}
}
class MyGame extends Forge2DGame {
MyGame()
: super(
world: MyGameWorld(),
gravity: Vector2.zero(),
zoom: 20,
cameraComponent:
CameraComponent.withFixedResolution(width: 400, height: 800),
);
}
class MyGameWorld extends Forge2DWorld
with HasGameReference<Forge2DGame>, TapCallbacks {
late final Ball ball;
late final TextComponent elapsedTime;
@override
FutureOr<void> onLoad() async {
super.onLoad();
final background = RectangleComponent.fromRect(
game.camera.visibleWorldRect,
paint: Paint()..color = Colors.purple,
);
add(background);
final boundaries = createBoundaries(game);
addAll(boundaries);
final center = Vector2.zero();
ball = Ball(center, 0.5);
add(ball);
elapsedTime = TextComponent(
position: game.camera.visibleWorldRect.topLeft.toVector2(),
textRenderer: TextPaint(
style: const TextStyle(fontSize: 1),
));
add(elapsedTime);
await Future.wait([
background.loaded,
for (final wall in boundaries) wall.loaded,
ball.loaded,
elapsedTime.loaded,
]);
game.loaded.then((value) => ball.body.applyLinearImpulse(Vector2(0, 200)));
}
@override
void onTapUp(TapUpEvent event) {
super.onTapUp(event);
if (game.paused) {
game.resumeEngine();
} else {
game.pauseEngine();
}
}
}
List<Wall> createBoundaries(Forge2DGame game) {
final visibleRect = game.camera.visibleWorldRect;
final topLeft = visibleRect.topLeft.toVector2();
final topRight = visibleRect.topRight.toVector2();
final bottomRight = visibleRect.bottomRight.toVector2();
final bottomLeft = visibleRect.bottomLeft.toVector2();
return [
Wall(topLeft, topRight),
Wall(topRight, bottomRight),
Wall(bottomLeft, bottomRight),
Wall(topLeft, bottomLeft),
];
}
class Wall extends BodyComponent {
final Vector2 start;
final Vector2 end;
Wall(this.start, this.end);
@override
Body createBody() {
paint.color = Colors.transparent;
final shape = EdgeShape()..set(start, end);
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
userData: this, // To be able to determine object in collision
position: Vector2.zero(),
type: BodyType.static,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class Ball extends BodyComponent
with ContactCallbacks, HasWorldReference<MyGameWorld> {
final double radius;
final Vector2 _position;
int? lastContact;
Ball(this._position, this.radius);
@override
Body createBody() {
final shape = CircleShape()..radius = radius;
final fixtureDef = FixtureDef(
shape,
restitution: 1,
density: 10,
);
final bodyDef = BodyDef(
userData: this,
position: _position,
type: BodyType.dynamic,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (lastContact == null) {
lastContact = DateTime.timestamp().millisecondsSinceEpoch;
return;
}
final now = DateTime.timestamp().millisecondsSinceEpoch;
final diff = now - lastContact!;
world.elapsedTime.text = '$diff ms';
lastContact = now;
}
}