我迁移到火焰1.1.1,从火焰v0.29.4,我找不到一个好的路线图。
如何有效地替换Box2DComponent?比如组件和视图,我不知道在哪里以及如何替换它们。
用火焰游戏代替BaseGame正确吗?用Forge2DGame代替Forge2DGame是否正确?
在这里我的三节课。我知道这是个困难的问题,但我真的需要一些帮助。谢谢
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: https://stackoverflow.com/questions/60468768/masking-two-images-in-flutter-using-a-custom-painter/60470034#60470034
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;
}
}发布于 2022-05-16 20:37:01
这个问题对StackOverflow来说有点太宽泛了,但是我会尽我所能来回答它。
要在火焰中使用Forge2D (以前是box2d.dart),您必须添加flame_forge2d作为依赖项。在flame_forge2d中,您将得到一个应该使用的Forge2DGame,而不是FlameGame (而不是您正在使用的古老的BaseGame类)。
在此之后,为要添加到BodyComponent的每个主体扩展Forge2DGame 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组件作为子组件添加到其中,例如SpriteComponent或SpriteAnimationComponent。
要添加一个子元素,只需在add方法(或在另一个合适的地方)中调用onLoad:
class Ball extends BodyComponent {
...
@override
Future<void> onLoad() async {
await super.onLoad();
add(SpriteComponent(...));
}
...
}由于您使用的是Tappable混音,所以您还应该将HasTappables混音添加到Forge2D游戏类中。
您可以在这里找到一些示例:https://examples.flame-engine.org/#/flame_forge2d_Blob%20example (按右上角的< >以获取代码)。
https://stackoverflow.com/questions/72261720
复制相似问题