mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 03:15:43 +08:00
Replaces the switch cases that can be replaces with switch expressions and removes `break;` where it isn't needed. https://dart.dev/language/branches#switch-statements
221 lines
6.2 KiB
Dart
221 lines
6.2 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:flame/components.dart' hide World;
|
|
import 'package:flame/effects.dart';
|
|
import 'package:flame/extensions.dart';
|
|
import 'package:flame/game.dart';
|
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
/// Since a pure BodyComponent doesn't have anything drawn on top of it,
|
|
/// it is a good idea to turn on [debugMode] for it so that the bodies can be
|
|
/// seen
|
|
///
|
|
/// You can use the optional [bodyDef] and [fixtureDefs] arguments to create
|
|
/// the [BodyComponent]'s body without having to create the definitions within
|
|
/// the component.
|
|
class BodyComponent<T extends Forge2DGame> extends Component
|
|
with HasGameReference<T>, HasPaint
|
|
implements
|
|
CoordinateTransform,
|
|
ReadOnlyPositionProvider,
|
|
ReadOnlyAngleProvider {
|
|
BodyComponent({
|
|
Paint? paint,
|
|
super.children,
|
|
super.priority,
|
|
this.renderBody = true,
|
|
this.bodyDef,
|
|
this.fixtureDefs,
|
|
super.key,
|
|
}) {
|
|
this.paint = paint ?? (Paint()..color = defaultColor);
|
|
}
|
|
|
|
static const defaultColor = Color.fromARGB(255, 255, 255, 255);
|
|
late Body body;
|
|
|
|
/// The default implementation of [createBody] will use this value to create
|
|
/// the [Body], if it is provided.
|
|
///
|
|
/// If you do not provide a [BodyDef] here, you must override [createBody].
|
|
BodyDef? bodyDef;
|
|
|
|
/// The default implementation of [createBody] will add these fixtures to the
|
|
/// [Body] that it creates from [bodyDef].
|
|
List<FixtureDef>? fixtureDefs;
|
|
|
|
@override
|
|
Vector2 get position => body.position;
|
|
|
|
/// Specifies if the body's fixtures should be rendered.
|
|
///
|
|
/// [renderBody] is true by default for [BodyComponent], if set to false
|
|
/// the body's fixtures wont be rendered.
|
|
///
|
|
/// If you render something on top of the [BodyComponent], or doesn't want it
|
|
/// to be seen, you probably want to set it to false.
|
|
bool renderBody;
|
|
|
|
/// You should create the Forge2D [Body] in this method when you extend
|
|
/// the BodyComponent.
|
|
Body createBody() {
|
|
assert(
|
|
bodyDef != null,
|
|
'Ensure this.bodyDef is not null or override createBody',
|
|
);
|
|
final body = world.createBody(bodyDef!);
|
|
fixtureDefs?.forEach(body.createFixture);
|
|
return body;
|
|
}
|
|
|
|
@mustCallSuper
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await super.onLoad();
|
|
body = createBody();
|
|
}
|
|
|
|
Forge2DWorld get world => game.world;
|
|
CameraComponent get camera => game.camera;
|
|
Vector2 get center => body.worldCenter;
|
|
|
|
@override
|
|
double get angle => body.angle;
|
|
|
|
/// The matrix used for preparing the canvas
|
|
final Transform2D _transform = Transform2D();
|
|
Matrix4 get _transformMatrix => _transform.transformMatrix;
|
|
double? _lastAngle;
|
|
|
|
@mustCallSuper
|
|
@override
|
|
void renderTree(Canvas canvas) {
|
|
final matrix = _transformMatrix;
|
|
if (matrix.m14 != body.position.x ||
|
|
matrix.m24 != body.position.y ||
|
|
_lastAngle != angle) {
|
|
matrix.setIdentity();
|
|
matrix.translate(body.position.x, body.position.y);
|
|
matrix.rotateZ(angle);
|
|
_lastAngle = angle;
|
|
}
|
|
canvas.save();
|
|
canvas.transform(matrix.storage);
|
|
super.renderTree(canvas);
|
|
canvas.restore();
|
|
}
|
|
|
|
@override
|
|
void render(Canvas canvas) {
|
|
if (renderBody) {
|
|
for (final fixture in body.fixtures) {
|
|
renderFixture(canvas, fixture);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void renderDebugMode(Canvas canvas) {
|
|
body.fixtures.forEach(
|
|
(fixture) => renderFixture(canvas, fixture),
|
|
);
|
|
}
|
|
|
|
/// Renders a [Fixture] in a [Canvas].
|
|
///
|
|
/// Called for each fixture in [body] when [render]ing. Override this method
|
|
/// to customize how fixtures are rendered. For example, you can filter out
|
|
/// fixtures that you don't want to render.
|
|
///
|
|
/// **NOTE**: If [renderBody] is false, no fixtures will be rendered. Hence,
|
|
/// [renderFixture] is not called when [render]ing.
|
|
void renderFixture(Canvas canvas, Fixture fixture) {
|
|
canvas.save();
|
|
switch (fixture.type) {
|
|
case ShapeType.chain:
|
|
_renderChain(canvas, fixture);
|
|
case ShapeType.circle:
|
|
_renderCircle(canvas, fixture);
|
|
case ShapeType.edge:
|
|
_renderEdge(canvas, fixture);
|
|
case ShapeType.polygon:
|
|
_renderPolygon(canvas, fixture);
|
|
}
|
|
canvas.restore();
|
|
}
|
|
|
|
void _renderChain(Canvas canvas, Fixture fixture) {
|
|
final chainShape = fixture.shape as ChainShape;
|
|
renderChain(
|
|
canvas,
|
|
chainShape.vertices.map((v) => v.toOffset()).toList(growable: false),
|
|
);
|
|
}
|
|
|
|
void renderChain(Canvas canvas, List<Offset> points) {
|
|
canvas.drawPoints(PointMode.polygon, points, paint);
|
|
}
|
|
|
|
void _renderCircle(Canvas canvas, Fixture fixture) {
|
|
final circle = fixture.shape as CircleShape;
|
|
renderCircle(canvas, circle.position.toOffset(), circle.radius);
|
|
}
|
|
|
|
void renderCircle(Canvas canvas, Offset center, double radius) {
|
|
canvas.drawCircle(center, radius, paint);
|
|
}
|
|
|
|
void _renderPolygon(Canvas canvas, Fixture fixture) {
|
|
final polygon = fixture.shape as PolygonShape;
|
|
renderPolygon(
|
|
canvas,
|
|
polygon.vertices.map((v) => v.toOffset()).toList(growable: false),
|
|
);
|
|
}
|
|
|
|
late final Path _path = Path();
|
|
|
|
void renderPolygon(Canvas canvas, List<Offset> points) {
|
|
final path = _path
|
|
..reset()
|
|
..addPolygon(points, true);
|
|
// TODO(Spydon): Use drawVertices instead.
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
void _renderEdge(Canvas canvas, Fixture fixture) {
|
|
final edge = fixture.shape as EdgeShape;
|
|
renderEdge(canvas, edge.vertex1.toOffset(), edge.vertex2.toOffset());
|
|
}
|
|
|
|
void renderEdge(Canvas canvas, Offset p1, Offset p2) {
|
|
canvas.drawLine(p1, p2, paint);
|
|
}
|
|
|
|
@override
|
|
Vector2 parentToLocal(Vector2 point) => _transform.globalToLocal(point);
|
|
|
|
@override
|
|
Vector2 localToParent(Vector2 point) => _transform.localToGlobal(point);
|
|
|
|
late final Vector2 _hitTestPoint = Vector2.zero();
|
|
|
|
@override
|
|
bool containsLocalPoint(Vector2 point) {
|
|
_transform.localToGlobal(point, output: _hitTestPoint);
|
|
return body.fixtures.any((fixture) => fixture.testPoint(_hitTestPoint));
|
|
}
|
|
|
|
@override
|
|
bool containsPoint(Vector2 point) {
|
|
return body.fixtures.any((fixture) => fixture.testPoint(point));
|
|
}
|
|
|
|
@override
|
|
void onRemove() {
|
|
world.destroyBody(body);
|
|
super.onRemove();
|
|
}
|
|
}
|