When using a FixedResolutionViewport the tap event on a PositionComponent is relative to the screen size and not to the resized viewport as expected.
If I remove FixedResolutionViewport the tap is registered correctly.
Am I missing something? What can I do to tap correctly on my Component (Circle)?
This is my code.
I'm using flame 1.1.0.
Vector2 calculateVector(
double x, double y, double fieldW, double fieldH, Vector2 size) {
var calcY = (y / fieldH) * size.y;
var calcX = (y / fieldW) * size.x;
return Vector2(calcX, calcY);
}
class TestGame extends FlameGame with HasTappableComponents, ScaleDetector {
TestGame();
#override
bool debugMode = true;
late double startZoom;
static double fieldW = 1080;
static double fieldH = 1717;
#override
backgroundColor() => const Color.fromARGB(255, 65, 129, 77);
#override
Future<void> onLoad() async {
double maxSide = max(size.x, size.y);
var side = maxSide * (fieldW / fieldH);
camera.viewport =
FixedResolutionViewport(Vector2(side, maxSide), clip: false);
final fieldSprite = await loadSprite('field_checkered.png');
final field = SpriteComponent(
sprite: fieldSprite,
size: Vector2(side, maxSide),
);
var ply = Circle(
position: calculateVector(500, 500, fieldH, fieldH, size),
size: Vector2(50, 50));
await add(field);
await add(ply);
}
}
class Circle extends PositionComponent with TapCallbacks, HasGameRef<TestGame> {
var isSelected = false;
var radius = 0.0;
var paint = Paint()..color = const Color(0xFF80C080);
Circle({required position, required size}) {
super.position = Vector2(
position.x,
position.y,
);
super.size = size;
super.positionType = PositionType.viewport;
radius = size.x / 2;
}
#override
void render(Canvas canvas) {
gameRef.camera.viewport.apply(canvas);
canvas.drawCircle(Offset(radius, radius), radius, paint);
super.render(canvas);
}
#override
void onTapDown(TapDownEvent event) {
isSelected = !isSelected;
if (isSelected) {
paint = Paint()..color = Color.fromARGB(255, 75, 0, 76);
priority = 2;
} else {
paint = Paint()..color = Color.fromARGB(255, 253, 147, 255);
priority = 1;
}
}
}
You should be able to use the Tappable and HasTappables mixins instead. The HasTappableComponents mixin is still in the experimental package and is to be used with the new CameraComponent and not the viewport+camera on the FlameGame.
Related
I'm Learning Custom Paint and I'm Confused About Them i referred Few Documentation but Still i'm Lacking on it.
Referrence:Curved Chart,
Paths
Some of the Code I have Mentioned Here ..Pls Help Me out of This....
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
class ChartPainter extends CustomPainter {
final List<String> x;
final List<double> y;
final double min, max;
ChartPainter(this.x, this.y, this.min, this.max);
final yLabelStyle = const TextStyle(color: Colors.white, fontSize: 14);
final xLabelStyle = const TextStyle(
color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold);
static double border = 10.0;
static double radius = 5.0;
final dotPaintFill = Paint()
..color = Colors.black
..style = PaintingStyle.fill
..strokeWidth = 2;
final linePaint = Paint()
..color = Colors.white
..style = PaintingStyle.stroke
..strokeWidth = 2.0;
#override
void paint(Canvas canvas, Size size) {
final clipRect = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.clipRect(clipRect);
canvas.drawPaint(Paint()..color = Colors.black);
final drawableHeight = size.height / 10.0 * border;
final drawableWidth = size.width / 15 * border;
final leftPadding = size.width / 6;
final rightPadding = size.width / 1000;
final hd = drawableHeight / 5.0;
final wd = drawableWidth / x.length.toDouble();
final height = hd * 3.0;
final width = drawableWidth;
if (height <= 0.0 || width <= 0.00) return;
if (max - min < 1.0e-6) return;
final hr = height / (max - min);
final left = border;
final top = border;
final c = Offset(left + wd / 2.0, top + height / 2.0);
_drawOutline(canvas, c, wd, height);
final points = _computePoints(c, wd, height, hr);
final fillpath = _computePath(
points, drawableHeight, width, true, leftPadding, rightPadding);
final labels = _computeLabels();
canvas.drawPath(fillpath, linePaint);
// drawvertical(canvas, c, wd, height, hr, wd);
_drawDataPoints(canvas, points, dotPaintFill, linePaint);
_drawYLabels(canvas, labels, points, wd, top);
// final c1 = Offset(c.dx, top + 4 * hd);
// _drawXLabels(canvas, c1, wd);
}
void _drawXLabels(Canvas canvas, Offset c, double wd) {
x.forEach((xp) {
drawTextCentered(canvas, c, xp, xLabelStyle, wd);
c += Offset(wd, 0);
});
}
void _drawYLabels(Canvas canvas, List<String> labels, List<Offset> points,
double wd, double top) {
var i = 0;
labels.forEach((label) {
final dp = points[i];
final dy = (dp.dy - 15.0) < top ? 15.0 : -15.0;
final ly = dp + Offset(0, dy);
drawTextCentered(canvas, ly, label, yLabelStyle, wd);
i++;
});
}
void _drawDataPoints(
Canvas canvas, List<Offset> points, Paint dotPaintFill, Paint linePaint) {
points.forEach((dp) {
canvas.drawCircle(dp, radius, dotPaintFill);
canvas.drawCircle(dp, radius, linePaint);
});
}
Path _computePath(List<Offset> points, double h, double w, bool cp,
double leftPadding, double rightPadding) {
Path path = Path();
for (var i = 0; i < points.length; i++) {
final p = points[i];
if (i == 0) {
path.moveTo(p.dx, p.dy);
path.lineTo(p.dx, p.dy);
} else {
path.lineTo(p.dx, p.dy);
}
}
return path;
}
List<Offset> _computePoints(
Offset c, double width, double height, double hr) {
List<Offset> points = [];
y.forEach((yp) {
final yy = height - (yp - min) * hr;
final dp = Offset(c.dx, c.dy - height / 2.0 + yy);
points.add(dp);
c += Offset(width, 0);
});
return points;
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
final Paint outlinePaint = Paint()
..strokeWidth = 1.0
..style = PaintingStyle.stroke
..color = Colors.white;
void _drawOutline(Canvas canvas, Offset c, double width, double height) {
y.forEach((p) {
final rect = Rect.fromCenter(center: c, width: width, height: height);
canvas.drawRect(rect, outlinePaint);
c += Offset(width, 0);
});
}
List<String> _computeLabels() {
return y.map((yp) => yp.toStringAsFixed(1)).toList();
}
TextPainter measureText(
String s, TextStyle style, double maxwidth, TextAlign align) {
final span = TextSpan(text: s, style: style);
final tp = TextPainter(
text: span, textAlign: align, textDirection: TextDirection.ltr);
tp.layout(minWidth: 0, maxWidth: maxwidth);
return tp;
}
Size drawTextCentered(
Canvas canvas, Offset c, String text, TextStyle style, double maxwidth) {
final tp = measureText(text, style, maxwidth, TextAlign.center);
final offset = c + Offset(-tp.width / 2.0, -tp.height / 2.0);
tp.paint(canvas, offset);
return tp.size;
}
}
In cubicTO() Method we assign 3 points one for control points to draw a curve . and One for Starting point and another for end point I'm confused about points and math
I'm trying to draw this kind of shape with flutter:
Expected result
So far I can draw an arc using drawArc():
class CurvePainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint();
paint.color = Colors.green;
paint.style = PaintingStyle.fill;
paint.strokeWidth = 5;
final rect = Rect.fromLTRB(50, 100, 130, 200);
final startAngle = -pi;
final sweepAngle = pi;
canvas.drawArc(rect, startAngle, sweepAngle, false, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
But is that the right way to do it or should I use quadraticBezierTo and drawPath?
I want to make a sparkling animation in flutter
How to make this in flutter??
I would suggest a custom paint approche. My awswer is highly customisable. I only change the innerOuterRadiusRatio and the velocity. You can change the color or Opacity, the number of edges of the star, the rotation(angleOffsetToCenterStar),and the beamLength.
import 'package:flutter/material.dart';
import 'dart:math' as math;
class Sparkling extends StatefulWidget {
const Sparkling({Key? key}) : super(key: key);
#override
_SparklingState createState() => _SparklingState();
}
class _SparklingState extends State<Sparkling>
with SingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation animation;
late List<MyStar> myStars;
#override
void initState() {
super.initState();
myStars = <MyStar>[];
animationController = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 250,
));
animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
for (final star in myStars) {
star.isEnable = math.Random().nextBool();
}
animationController.forward();
}
});
animation = Tween<double>(begin: 0, end: 8).animate(CurvedAnimation(
parent: animationController, curve: Curves.easeInOutSine));
animation.addListener(() {
setState(() {});
});
animationController.forward();
}
void postFrameCallback(_) {
if (!mounted) {
return;
}
final size = MediaQuery.of(context).size;
if (myStars.isEmpty) {
myStars = List.generate(60, (index) {
double velocityX = 2 * (math.Random().nextDouble());//max velocity 2
double velocityY = 2 * (math.Random().nextDouble());
velocityX = math.Random().nextBool() ? velocityX : -velocityX;
velocityY = math.Random().nextBool() ? velocityY : -velocityY;
return MyStar(
isEnable: math.Random().nextBool(),
innerCirclePoints: 4,
beamLength: math.Random().nextDouble() * (8 - 2) + 2,
innerOuterRadiusRatio: 0.0,
angleOffsetToCenterStar: 0,
center: Offset(size.width * (math.Random().nextDouble()),
size.height * (math.Random().nextDouble())),
velocity: Offset(velocityX, velocityY),
color: Colors.white);
});
} else {
for (final star in myStars) {
star.center = star.center + star.velocity;
if (star.isEnable) {
star.innerOuterRadiusRatio = animation.value;
if (star.center.dx >= size.width) {
if (star.velocity.dy > 0) {
star.velocity = const Offset(-1, 1);
} else {
star.velocity = const Offset(-1, -1);
}
star.center = Offset(size.width, star.center.dy);
} else if (star.center.dx <= 0) {
if (star.velocity.dy > 0) {
star.velocity = const Offset(1, 1);
} else {
star.velocity = const Offset(1, -1);
}
star.center = Offset(0, star.center.dy);
} else if (star.center.dy >= size.height) {
if (star.velocity.dx > 0) {
star.velocity = const Offset(1, -1);
} else {
star.velocity = const Offset(-1, -1);
}
star.center = Offset(star.center.dx, size.height);
} else if (star.center.dy <= 0) {
if (star.velocity.dx > 0) {
star.velocity = const Offset(1, 1);
} else {
star.velocity = const Offset(-1, 1);
}
star.center = Offset(star.center.dx, 0);
}
}
}
}
}
#override
Widget build(BuildContext context) {
WidgetsBinding.instance!.addPostFrameCallback(postFrameCallback);
return CustomPaint(
size: MediaQuery.of(context).size,
painter: StarPainter(
myStars: myStars,
));
}
}
The CustomPainter
class StarPainter extends CustomPainter {
List<MyStar> myStars;
StarPainter({required this.myStars});
List<Map> calcStarPoints(
{required double centerX,
required double centerY,
required int innerCirclePoints,
required double innerRadius,
required double outerRadius,
required double angleOffsetToCenterStar}) {
final angle = ((math.pi) / innerCirclePoints);
final totalPoints = innerCirclePoints * 2; // 10 in a 5-points star
List<Map> points = [];
for (int i = 0; i < totalPoints; i++) {
bool isEvenIndex = i % 2 == 0;
final r = isEvenIndex ? outerRadius : innerRadius;
var currY = centerY + math.cos(i * angle + angleOffsetToCenterStar) * r;
var currX = centerX + math.sin(i * angle + angleOffsetToCenterStar) * r;
points.add({'x': currX, 'y': currY});
}
return points;
}
#override
void paint(Canvas canvas, Size size) {
for (final myStar in myStars) {
final innerRadius = myStar.beamLength / myStar.innerCirclePoints;
final outerRadius = innerRadius * myStar.innerOuterRadiusRatio;
List<Map> points = calcStarPoints(
centerX: myStar.center.dx,
centerY: myStar.center.dy,
innerCirclePoints: myStar.innerCirclePoints,
innerRadius: innerRadius,
outerRadius: outerRadius,
angleOffsetToCenterStar: myStar.angleOffsetToCenterStar);
var star = Path()..moveTo(points[0]['x'], points[0]['y']);
for (var point in points) {
star.lineTo(point['x'], point['y']);
}
canvas.drawPath(
star,
Paint()..color = myStar.color,
);
}
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
And the class MyStar.
class MyStar {
bool isEnable;
int innerCirclePoints; //how many edges you need?
double beamLength;
double
innerOuterRadiusRatio; // outter circle is x2 the inner // set star sharpness/chubbiness
double angleOffsetToCenterStar;
Offset center;
Offset velocity;
Color color;
MyStar(
{required this.isEnable,
required this.innerCirclePoints,
required this.beamLength,
required this.innerOuterRadiusRatio,
required this.angleOffsetToCenterStar,
required this.center,
required this.velocity,
required this.color});
}
I would suggest using Lottie animations.
If you make a quick search you can find the one that matches your needs:
https://lottiefiles.com/search?q=star&category=animations
If you found the right now click on it and press download -> lottie.json and then install this package in flutter:
https://pub.dev/packages/lottie
Then you simply add the downloaded json animation in your asset folder and reference it like this:
Lottie.asset(
'assets/LottieLogo1.json',
width: 200,
height: 200,
fit: BoxFit.fill,
animate: true,
repeat: true
),
With this you have an beautiful repeating animation.
You can also use an controller to adapt everything even more.
Basically you can also make an animation in after effect and export it as an json animation with the bodymovin plugin
Use https://rive.app/ for creating animation for flutter applications. You can find many tutorials of creating animation in rive.app and integrating in flutter app.
I have been having trouble trying to implement a method that moves a Rect in flutter using the flame game engine. The ultimate goal is to swipe in a direction and have the Rect move in that direction at a constant velocity. I found this code:
void dragUpdate(DragUpdateDetails d)
{
final delta = d.delta;
final size = gameController.screenSize;
double translateX = delta.dx;
double translateY = delta.dy;
// Make sure that the player never goes outside of the screen in the X-axis
if (playerRect.right + delta.dx >= size.width) {
translateX = size.width - playerRect.right;
} else if (playerRect.left + delta.dx <= 0) {
translateX = -playerRect.left;
}
// Make sure that the player never goes outside of the screen in the Y-axis
if (playerRect.bottom + delta.dy >= size.height) {
translateY = size.height - playerRect.bottom;
} else if (playerRect.top + delta.dy <= 0) {
translateY = -playerRect.top;
}
playerRect = playerRect.translate(translateX, translateY);
}
which at least allows for the free movement of the Rect on screen in regards to finger position. I tried messing with the "translate()" method to have the x/y increment/decrement depending on the delta provide, but to no avail. Any hint or point in the right direction would be greatly appreciate.
Instead of using both the HorizontalDragDetector and VerticalDragDetector, you can use the MultiTouchDragDetector and the code will be a lot simpler.
class MyGame extends BaseGame with MultiTouchDragDetector {
Rect _rect = const Rect.fromLTWH(0, 0, 50, 50);
Offset _velocity = Offset.zero;
MyGame();
#override
void onReceiveDrag(DragEvent event) {
event.onEnd = onDragEnd;
}
void onDragEnd(DragEndDetails details) {
_velocity = details.velocity.pixelsPerSecond;
}
#override
void update(double dt) {
super.update(dt);
final timeStepMovement = _velocity * dt;
_rect = _rect.shift(timeStepMovement);
}
#override
void render(Canvas canvas) {
super.render(canvas);
canvas.drawRect(_rect, BasicPalette.white.paint);
}
}
You can set the drag delta X and Y in your dragUpdate(...){ ... } and use those delta values in render(...) { ... }.
Try running this sample app.
import 'dart:ui';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/util.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
BoxGame game = BoxGame();
runApp(game.widget);
Util flameUtil = Util();
flameUtil.fullScreen();
flameUtil.setOrientation(DeviceOrientation.portraitUp);
}
class BoxGame extends Game
with HorizontalDragDetector, VerticalDragDetector {
Size screenSize;
double posX;
double posY;
double dragDX = 0.0;
double dragDY = 0.0;
void render(Canvas canvas) {
// draw a black background on the whole screen
final Rect bgRect =
Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
final Paint bgPaint = Paint();
bgPaint.color = Color(0xff000000);
canvas.drawRect(bgRect, bgPaint);
// draw a box (make it green if won, white otherwise)
if (posX == null) {
final double screenCenterX = screenSize.width / 2;
final double screenCenterY = screenSize.height / 2;
posX = screenCenterX - 75;
posY = screenCenterY - 75;
}
posX += dragDX;
posY += dragDY;
final Rect boxRect = Rect.fromLTWH(posX, posY, 150, 150);
final Paint boxPaint = Paint();
boxPaint.color = Color(0xffffffff);
canvas.drawRect(boxRect, boxPaint);
}
void update(double t) {}
void resize(Size size) {
screenSize = size;
super.resize(size);
}
#override
void onHorizontalDragUpdate(DragUpdateDetails details) {
dragDX = details.delta.dx;
}
#override
void onVerticalDragUpdate(DragUpdateDetails details) {
dragDY = details.delta.dy;
}
#override
void onHorizontalDragEnd(DragEndDetails details) {
dragDX = 0;
}
#override
void onVerticalDragEnd(DragEndDetails details) {
dragDY = 0;
}
}
when I used aync await method at that time it works properly but when i try to load image in flame's component class I got error:
I have created a Background class that extends flame engine's component class. Now I am tring to load a base64 image using the then function, but I get an error but when I use the async await method for image loading it works properly.
class Background extends Component with Resizable {
static final Paint _paint = Paint();
Size imageSize = Size(411.42857142857144, 822.8571428571429);
#override
void render(Canvas c) {
Rect myRect = const Offset(0.0, 0.0) & Size(size.width, size.height);
Flame.images.fromBase64('demo', imageBase).then((value) {
paintImage(canvas: c, rect: myRect, image: value);
});
}
#override
void update(double t) {
// TODO: implement update
}
void paintImage({
#required Canvas canvas,
#required Rect rect,
#required image.Image image,
String debugImageLabel,
double scale = 1.0,
ColorFilter colorFilter,
BoxFit fit,
Alignment alignment = Alignment.center,
Rect centerSlice,
ImageRepeat repeat = ImageRepeat.noRepeat,
bool flipHorizontally = false,
bool invertColors = false,
FilterQuality filterQuality = FilterQuality.low,
bool isAntiAlias = false,
}) {
if (rect.isEmpty) return;
Size outputSize = rect.size;
Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
Offset sliceBorder;
if (centerSlice != null) {
sliceBorder = Offset(
centerSlice.left + inputSize.width - centerSlice.right,
centerSlice.top + inputSize.height - centerSlice.bottom,
);
outputSize = outputSize - sliceBorder as Size;
inputSize = inputSize - sliceBorder as Size;
}
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
final FittedSizes fittedSizes =
applyBoxFit(fit, inputSize / scale, outputSize);
final Size sourceSize = fittedSizes.source * scale;
Size destinationSize = fittedSizes.destination;
if (centerSlice != null) {
outputSize += sliceBorder;
destinationSize += sliceBorder;
}
// Output size is fully calculated.
if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) {
repeat = ImageRepeat.noRepeat;
}
final Paint paint = Paint()..isAntiAlias = isAntiAlias;
if (colorFilter != null) paint.colorFilter = colorFilter;
if (sourceSize != destinationSize) {
paint.filterQuality = filterQuality;
}
paint.invertColors = invertColors;
final double halfWidthDelta =
(outputSize.width - destinationSize.width) / 2.0;
final double halfHeightDelta =
(outputSize.height - destinationSize.height) / 2.0;
final double dx = halfWidthDelta +
(flipHorizontally ? -alignment.x : alignment.x) * halfWidthDelta;
final double dy = halfHeightDelta + alignment.y * halfHeightDelta;
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
final Rect destinationRect = destinationPosition & destinationSize;
final bool needSave = repeat != ImageRepeat.noRepeat || flipHorizontally;
if (needSave) canvas.save();
if (repeat != ImageRepeat.noRepeat) canvas.clipRect(rect);
if (flipHorizontally) {
final double dx = -(rect.left + rect.width / 2.0);
canvas.translate(-dx, 0.0);
canvas.scale(-1.0, 1.0);
canvas.translate(dx, 0.0);
}
if (centerSlice == null) {
final Rect sourceRect = alignment.inscribe(
sourceSize,
Offset.zero & inputSize,
);
if (repeat == ImageRepeat.noRepeat) {
canvas.drawImageRect(image, sourceRect, destinationRect, paint);
} else {
print("no repet else");
}
}
//if (needSave) canvas.restore();
}
}
This is totally not acceptable:
#override
void render(Canvas c) {
Rect myRect = const Offset(0.0, 0.0) & Size(size.width, size.height);
Flame.images.fromBase64('demo', imageBase).then((value) {
paintImage(canvas: c, rect: myRect, image: value);
});
}
The render method must be sync. It takes a canvas object to be rendered right now. You cannot make async operations on here! Of course the canvas will be disposed, it only lives for one frame. The render method is called every frame and must be quick and short lived. When the image actually loads, you no longer will have access to canvas because the whole render cycle will be done. It was already rendered on the screen! You cannot change the past! That doesn't make sense.
What you need to do is to load the image elsewhere and render conditionally if it's loaded. Move the loading to your constructor:
Flame.images.fromBase64('demo', imageBase).then((value) {
this.image = value;
});
And then on the render method, render conditionally:
#override
void render(Canvas c) {
Rect myRect = const Offset(0.0, 0.0) & Size(size.width, size.height);
if (this.image != null) {
paintImage(canvas: c, rect: myRect, image: this.image);
}
}
By creating an image field on your component. Also consider using SpriteComponent instead that does all that for you in the correct way. And never make the render or update methods async ;)