从 flame 0.29.0 到 flame 1.0.0 和从 box2d_flame: ^0.4.6 到 flame_forge2d 0.11.0
From flame 0.29.0 to flame 1.0.0 and from box2d_flame: ^0.4.6 to flame_forge2d 0.11.0
我正在从 Flame v0.29.4 迁移到 Flame 1.1.1,但找不到合适的路线图。
如何有效地替换 Box2DComponent ?例如组件和视口等属性,我无法理解在哪里以及如何替换它们。
用 Flame 游戏替换 BaseGame 是否正确?用 Forge2DGame 替换 Box2DComponent 是否正确?
下面是我的 3 类。我知道这是一个难题,但我真的需要一些帮助。
谢谢
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:artista_app/features/tastes/model/presentation/tastes_vm.dart';
import 'package:box2d_flame/box2d.dart';
import 'package:flame/box2d/box2d_component.dart';
import 'package:flame/box2d/viewport.dart' as box2d_viewport;
import 'package:flame/components/mixins/tapable.dart';
import 'package:flame/game/base_game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/text_config.dart';
import 'package:flutter/material.dart';
class BubblePicker extends BaseGame with TapDetector {
PickerWorld _pickerWorld;
@override
ui.Color backgroundColor() {
return Colors.transparent;
}
final void Function(TastesVM) onTastesChange;
BubblePicker(TastesVM tastes, {this.onTastesChange}) : super() {
_pickerWorld = PickerWorld(tastes);
_pickerWorld.initializeWorld();
onTastesChange?.call(TastesVM(
tastes: (tastes.tastes.where((taste) => taste.checked).toList())));
}
@override
void onTapUp(TapUpDetails details) {
_pickerWorld.handleTap(details);
onTastesChange?.call(TastesVM(tastes: _pickerWorld.checkedTastes));
super.onTapUp(details);
}
@override
bool debugMode() => true;
@override
void render(Canvas canvas) {
super.render(canvas);
_pickerWorld.render(canvas);
}
@override
void resize(Size size) {
super.resize(size);
_pickerWorld.resize(size);
}
@override
void update(double t) {
super.update(t);
_pickerWorld.update(t);
}
}
class PickerWorld extends Box2DComponent {
final TastesVM tastes;
PickerWorld(this.tastes) : super(gravity: 0);
@override
void initializeWorld() {}
@override
void render(Canvas canvas) {
super.render(canvas);
}
Offset screenOffsetToWorldOffset(Offset position) {
return Offset(position.dx - (viewport.size.width / 2),
position.dy - (viewport.size.height / 2));
}
List<TasteVM> get checkedTastes => [
for (final component in components)
if (component is Ball && component.checked) component.model
];
void handleTap(TapUpDetails details) {
for (final component in components) {
if (component is Ball) {
final worldOffset = screenOffsetToWorldOffset(details.localPosition);
if (component.checkTapOverlap(worldOffset)) {
component.onTapUp(details);
}
}
}
}
@override
void resize(Size size) {
dimensions = Size(size.width, size.height);
viewport = box2d_viewport.Viewport(size, 1);
if (components.isEmpty) {
var tastesList = tastes.tastes;
tastesList.forEach((element) {
var ballPosOffset = Vector2(
math.Random().nextDouble() - 0.5, math.Random().nextDouble() - 0.5);
var x = ballPosOffset.x * 150;
var y = ballPosOffset.y * 150;
add(Ball(Vector2.array([x, y]), this, element));
});
}
}
}
class Ball extends BodyComponent with Tapable {
static const transitionSeconds = 0.5;
var transforming = false;
var kNormalRadius;
static const kExpandedRadius = 50.0;
var currentRadius;
var lastTapStamp = DateTime.utc(0);
final TasteVM model;
final TextConfig smallTextConfig = TextConfig(
fontSize: 12.0,
fontFamily: 'Arial',
color: Colors.white,
textAlign: TextAlign.center,
);
final TextConfig bigTextConfig = TextConfig(
fontSize: 24.0,
fontFamily: 'Arial',
color: Colors.white,
textAlign: TextAlign.center,
);
Size screenSize;
ui.Image ballImage;
bool get checked => model.checked;
Ball(
Vector2 position,
Box2DComponent box2dComponent,
this.model,
) : super(box2dComponent) {
ballImage = model.tasteimageResource;
final shape = CircleShape();
kNormalRadius = model.initialRadius;
currentRadius = (model.checked) ? kExpandedRadius : model.initialRadius;
shape.radius = currentRadius;
shape.p.x = 0.0;
// checked = model.checked;
final fixtureDef = FixtureDef();
fixtureDef.shape = shape;
fixtureDef.restitution = 0.1;
fixtureDef.density = 1;
fixtureDef.friction = 1;
fixtureDef.userData = model;
final bodyDef = BodyDef();
bodyDef.linearVelocity = Vector2(0.0, 0.0);
bodyDef.position = position;
bodyDef.type = BodyType.DYNAMIC;
bodyDef.userData = model;
body = world.createBody(bodyDef)..createFixtureFromFixtureDef(fixtureDef);
}
@override
void renderCircle(Canvas canvas, Offset center, double radius) async {
final rectFromCircle = Rect.fromCircle(center: center, radius: radius);
final ballDiameter = radius * 2;
if (ballImage == null) {
return;
}
final image = checked ? ballImage : null;
final paint = Paint()..color = const Color.fromARGB(255, 101, 101, 101);
final elapsed =
DateTime.now().difference(lastTapStamp).inMicroseconds / 1000000;
final transforming = elapsed < transitionSeconds;
if (transforming) {
_resizeBall(elapsed);
}
canvas.drawCircle(center, radius, paint);
if (image != null) {
//from:
canvas.saveLayer(rectFromCircle, Paint());
//draw the mask
canvas.drawCircle(
center,
radius,
Paint()..color = Colors.black,
);
//fit the image into the ball size
final inputSize = Size(image.width.toDouble(), image.height.toDouble());
final fittedSizes = applyBoxFit(
BoxFit.cover,
inputSize,
Size(ballDiameter, ballDiameter),
);
final sourceSize = fittedSizes.source;
final sourceRect =
Alignment.center.inscribe(sourceSize, Offset.zero & inputSize);
canvas.drawImageRect(
image,
sourceRect,
rectFromCircle,
Paint()..blendMode = BlendMode.srcIn,
);
canvas.restore();
}
final span = TextSpan(
style: TextStyle(color: Colors.white, fontSize: 10),
text: model.tasteDisplayName);
final tp = TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
tp.layout(minWidth: ballDiameter, maxWidth: ballDiameter);
tp.paint(canvas, Offset(center.dx - radius, center.dy - (tp.height / 2)));
}
@override
void update(double t) {
final center = Vector2.copy(box.world.center);
final ball = body.position;
center.sub(ball);
var distance = center.distanceTo(ball);
body.applyForceToCenter(center..scale(1000000 / (distance)));
}
@override
ui.Rect toRect() {
var rect = Rect.fromCircle(
center: Offset(body.position.x, -body.position.y),
radius: currentRadius,
);
return rect;
}
@override
void onTapUp(TapUpDetails details) {
lastTapStamp = DateTime.now();
model.checked = !checked;
if (checked) {
currentRadius = kExpandedRadius;
} else {
currentRadius = kNormalRadius;
}
}
void _resizeBall(elapsed) {
var progress = elapsed / transitionSeconds;
final fixture = body.getFixtureList();
var sourceRadius = (checked) ? kNormalRadius : kExpandedRadius;
var targetRadius = (checked) ? kExpandedRadius : kNormalRadius;
var progressRad = ui.lerpDouble(0, math.pi / 2, progress);
var nonLinearProgress = math.sin(progressRad);
var actualRadius =
ui.lerpDouble(sourceRadius, targetRadius, nonLinearProgress);
fixture.getShape().radius = actualRadius;
}
}
这个问题对于 Whosebug 来说有点太宽泛了,但我会尽力回答它。
要在 Flame 中使用 Forge2D(以前是 box2d.dart),您必须添加 flame_forge2d
作为依赖项。从 flame_forge2d
你会得到一个 Forge2DGame
,你应该使用它来代替 FlameGame
(而不是你正在使用的古老的 BaseGame
class)。
之后,为要添加到 Forge2DGame
的每个主体扩展 BodyComponent
s。
class Ball extends BodyComponent {
final double radius;
final Vector2 _position;
Ball(this._position, {this.radius = 2});
@override
Body createBody() {
final shape = CircleShape();
shape.radius = radius;
final fixtureDef = FixtureDef(
shape,
restitution: 0.8,
density: 1.0,
friction: 0.4,
);
final bodyDef = BodyDef(
userData: this,
angularDamping: 0.8,
position: _position,
type: BodyType.dynamic,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
在 createBody()
方法中,您必须创建 Forge2D 实体,在本例中创建了一个圆。如果你不想让它直接渲染圆你可以设置renderBody = false
。要在 BodyComponent
之上渲染其他内容,您可以覆盖 render
方法,或者将普通的 Flame 组件作为 child 添加到它,例如 SpriteComponent
或SpriteAnimationComponent
.
要添加 child,只需在 onLoad
方法中调用 add
(或在其他合适的地方):
class Ball extends BodyComponent {
...
@override
Future<void> onLoad() async {
await super.onLoad();
add(SpriteComponent(...));
}
...
}
由于您正在使用 Tappable
mixin,因此您还应该将 HasTappables
mixin 添加到您的 Forge2D 游戏中 class。
您可以在此处找到一些示例:
https://examples.flame-engine.org/#/flame_forge2d_Blob%20example
(按右上角的< >
获取代码)
我正在从 Flame v0.29.4 迁移到 Flame 1.1.1,但找不到合适的路线图。
如何有效地替换 Box2DComponent ?例如组件和视口等属性,我无法理解在哪里以及如何替换它们。
用 Flame 游戏替换 BaseGame 是否正确?用 Forge2DGame 替换 Box2DComponent 是否正确?
下面是我的 3 类。我知道这是一个难题,但我真的需要一些帮助。 谢谢
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:artista_app/features/tastes/model/presentation/tastes_vm.dart';
import 'package:box2d_flame/box2d.dart';
import 'package:flame/box2d/box2d_component.dart';
import 'package:flame/box2d/viewport.dart' as box2d_viewport;
import 'package:flame/components/mixins/tapable.dart';
import 'package:flame/game/base_game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/text_config.dart';
import 'package:flutter/material.dart';
class BubblePicker extends BaseGame with TapDetector {
PickerWorld _pickerWorld;
@override
ui.Color backgroundColor() {
return Colors.transparent;
}
final void Function(TastesVM) onTastesChange;
BubblePicker(TastesVM tastes, {this.onTastesChange}) : super() {
_pickerWorld = PickerWorld(tastes);
_pickerWorld.initializeWorld();
onTastesChange?.call(TastesVM(
tastes: (tastes.tastes.where((taste) => taste.checked).toList())));
}
@override
void onTapUp(TapUpDetails details) {
_pickerWorld.handleTap(details);
onTastesChange?.call(TastesVM(tastes: _pickerWorld.checkedTastes));
super.onTapUp(details);
}
@override
bool debugMode() => true;
@override
void render(Canvas canvas) {
super.render(canvas);
_pickerWorld.render(canvas);
}
@override
void resize(Size size) {
super.resize(size);
_pickerWorld.resize(size);
}
@override
void update(double t) {
super.update(t);
_pickerWorld.update(t);
}
}
class PickerWorld extends Box2DComponent {
final TastesVM tastes;
PickerWorld(this.tastes) : super(gravity: 0);
@override
void initializeWorld() {}
@override
void render(Canvas canvas) {
super.render(canvas);
}
Offset screenOffsetToWorldOffset(Offset position) {
return Offset(position.dx - (viewport.size.width / 2),
position.dy - (viewport.size.height / 2));
}
List<TasteVM> get checkedTastes => [
for (final component in components)
if (component is Ball && component.checked) component.model
];
void handleTap(TapUpDetails details) {
for (final component in components) {
if (component is Ball) {
final worldOffset = screenOffsetToWorldOffset(details.localPosition);
if (component.checkTapOverlap(worldOffset)) {
component.onTapUp(details);
}
}
}
}
@override
void resize(Size size) {
dimensions = Size(size.width, size.height);
viewport = box2d_viewport.Viewport(size, 1);
if (components.isEmpty) {
var tastesList = tastes.tastes;
tastesList.forEach((element) {
var ballPosOffset = Vector2(
math.Random().nextDouble() - 0.5, math.Random().nextDouble() - 0.5);
var x = ballPosOffset.x * 150;
var y = ballPosOffset.y * 150;
add(Ball(Vector2.array([x, y]), this, element));
});
}
}
}
class Ball extends BodyComponent with Tapable {
static const transitionSeconds = 0.5;
var transforming = false;
var kNormalRadius;
static const kExpandedRadius = 50.0;
var currentRadius;
var lastTapStamp = DateTime.utc(0);
final TasteVM model;
final TextConfig smallTextConfig = TextConfig(
fontSize: 12.0,
fontFamily: 'Arial',
color: Colors.white,
textAlign: TextAlign.center,
);
final TextConfig bigTextConfig = TextConfig(
fontSize: 24.0,
fontFamily: 'Arial',
color: Colors.white,
textAlign: TextAlign.center,
);
Size screenSize;
ui.Image ballImage;
bool get checked => model.checked;
Ball(
Vector2 position,
Box2DComponent box2dComponent,
this.model,
) : super(box2dComponent) {
ballImage = model.tasteimageResource;
final shape = CircleShape();
kNormalRadius = model.initialRadius;
currentRadius = (model.checked) ? kExpandedRadius : model.initialRadius;
shape.radius = currentRadius;
shape.p.x = 0.0;
// checked = model.checked;
final fixtureDef = FixtureDef();
fixtureDef.shape = shape;
fixtureDef.restitution = 0.1;
fixtureDef.density = 1;
fixtureDef.friction = 1;
fixtureDef.userData = model;
final bodyDef = BodyDef();
bodyDef.linearVelocity = Vector2(0.0, 0.0);
bodyDef.position = position;
bodyDef.type = BodyType.DYNAMIC;
bodyDef.userData = model;
body = world.createBody(bodyDef)..createFixtureFromFixtureDef(fixtureDef);
}
@override
void renderCircle(Canvas canvas, Offset center, double radius) async {
final rectFromCircle = Rect.fromCircle(center: center, radius: radius);
final ballDiameter = radius * 2;
if (ballImage == null) {
return;
}
final image = checked ? ballImage : null;
final paint = Paint()..color = const Color.fromARGB(255, 101, 101, 101);
final elapsed =
DateTime.now().difference(lastTapStamp).inMicroseconds / 1000000;
final transforming = elapsed < transitionSeconds;
if (transforming) {
_resizeBall(elapsed);
}
canvas.drawCircle(center, radius, paint);
if (image != null) {
//from:
canvas.saveLayer(rectFromCircle, Paint());
//draw the mask
canvas.drawCircle(
center,
radius,
Paint()..color = Colors.black,
);
//fit the image into the ball size
final inputSize = Size(image.width.toDouble(), image.height.toDouble());
final fittedSizes = applyBoxFit(
BoxFit.cover,
inputSize,
Size(ballDiameter, ballDiameter),
);
final sourceSize = fittedSizes.source;
final sourceRect =
Alignment.center.inscribe(sourceSize, Offset.zero & inputSize);
canvas.drawImageRect(
image,
sourceRect,
rectFromCircle,
Paint()..blendMode = BlendMode.srcIn,
);
canvas.restore();
}
final span = TextSpan(
style: TextStyle(color: Colors.white, fontSize: 10),
text: model.tasteDisplayName);
final tp = TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
tp.layout(minWidth: ballDiameter, maxWidth: ballDiameter);
tp.paint(canvas, Offset(center.dx - radius, center.dy - (tp.height / 2)));
}
@override
void update(double t) {
final center = Vector2.copy(box.world.center);
final ball = body.position;
center.sub(ball);
var distance = center.distanceTo(ball);
body.applyForceToCenter(center..scale(1000000 / (distance)));
}
@override
ui.Rect toRect() {
var rect = Rect.fromCircle(
center: Offset(body.position.x, -body.position.y),
radius: currentRadius,
);
return rect;
}
@override
void onTapUp(TapUpDetails details) {
lastTapStamp = DateTime.now();
model.checked = !checked;
if (checked) {
currentRadius = kExpandedRadius;
} else {
currentRadius = kNormalRadius;
}
}
void _resizeBall(elapsed) {
var progress = elapsed / transitionSeconds;
final fixture = body.getFixtureList();
var sourceRadius = (checked) ? kNormalRadius : kExpandedRadius;
var targetRadius = (checked) ? kExpandedRadius : kNormalRadius;
var progressRad = ui.lerpDouble(0, math.pi / 2, progress);
var nonLinearProgress = math.sin(progressRad);
var actualRadius =
ui.lerpDouble(sourceRadius, targetRadius, nonLinearProgress);
fixture.getShape().radius = actualRadius;
}
}
这个问题对于 Whosebug 来说有点太宽泛了,但我会尽力回答它。
要在 Flame 中使用 Forge2D(以前是 box2d.dart),您必须添加 flame_forge2d
作为依赖项。从 flame_forge2d
你会得到一个 Forge2DGame
,你应该使用它来代替 FlameGame
(而不是你正在使用的古老的 BaseGame
class)。
之后,为要添加到 Forge2DGame
的每个主体扩展 BodyComponent
s。
class Ball extends BodyComponent {
final double radius;
final Vector2 _position;
Ball(this._position, {this.radius = 2});
@override
Body createBody() {
final shape = CircleShape();
shape.radius = radius;
final fixtureDef = FixtureDef(
shape,
restitution: 0.8,
density: 1.0,
friction: 0.4,
);
final bodyDef = BodyDef(
userData: this,
angularDamping: 0.8,
position: _position,
type: BodyType.dynamic,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
在 createBody()
方法中,您必须创建 Forge2D 实体,在本例中创建了一个圆。如果你不想让它直接渲染圆你可以设置renderBody = false
。要在 BodyComponent
之上渲染其他内容,您可以覆盖 render
方法,或者将普通的 Flame 组件作为 child 添加到它,例如 SpriteComponent
或SpriteAnimationComponent
.
要添加 child,只需在 onLoad
方法中调用 add
(或在其他合适的地方):
class Ball extends BodyComponent {
...
@override
Future<void> onLoad() async {
await super.onLoad();
add(SpriteComponent(...));
}
...
}
由于您正在使用 Tappable
mixin,因此您还应该将 HasTappables
mixin 添加到您的 Forge2D 游戏中 class。
您可以在此处找到一些示例:
https://examples.flame-engine.org/#/flame_forge2d_Blob%20example
(按右上角的< >
获取代码)