Tappables miss when using CameraComponent - flutter

I followed the documentation on the Flame site to create a Klondike game skeleton, and now am trying to apply the same ideas towards creating a board game. I like using the CameraComponent because it allows for easily resizing the game area as the browser size changes, but I've found that when mixed with Tappable Components the tap events tend to "miss", or not line up with what's visible on the screen. Here's a minimal example:
import 'package:flame/components.dart';
import 'package:flame/experimental.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
void main() {
final game = MyFlameGame();
runApp(GameWidget(game: game));
}
class MyFlameGame extends FlameGame with HasTappables {
#override
Future<void> onLoad() async {
final myComponent = MyComponent()
..size = Vector2(50, 50)
..position = Vector2(250, 250);
final world = World();
world.add(myComponent);
add(world);
final camera = CameraComponent(world: world)
..viewfinder.visibleGameSize = Vector2(1000, 1000)
..viewfinder.position = Vector2(500, 0)
..viewfinder.anchor = Anchor.topCenter;
add(camera);
}
}
class MyComponent extends PositionComponent with Tappable {
#override
bool get debugMode => true;
#override
bool onTapUp(TapUpInfo info) {
print('tap up');
return true;
}
#override
bool onTapDown(TapDownInfo info) {
print('tap down');
return true;
}
}
If you resize the window and click within the box, I'd expect to see console logs but they sometimes don't appear. Occasionally I'm able to find the real Tappable position in the black area which will trigger the console messages. Is there a way to configure the Tappable components to line up with what's visible through the CameraComponent viewport?

This is a known bug in the CameraComponent, or maybe not a bug, it just hasn't been implemented yet. This is one of the reasons why the CameraComponent is still classified as experimental.
The work is ongoing though, and you can get updated by following this issue.

Related

Navigate to a flutter widget from a flame game without losing the game state

My flame game concept is that a player moves, and when the player collides with a star object, a pop up flutter window contains a question shall appear, and when the user closes it, the game state and player position will be restored.How can I do so?
I tried Navigator.push but it doesn't work, it says that no context available :(
Also I tried a different way, which is the overlay, but the Star class cannot access the overlays property, I wrote a method in the main game class "PlayerWithBG" but calling it throws an exception...
This is the code inside Star class
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/sprite.dart';
import 'package:flutter/material.dart';
import 'package:khalooq/Game/PlayerWithBG.dart';
import '../Questions/firstLevelQuestions.dart';
import 'helpers/directions.dart';
class Star extends SpriteAnimationComponent
with HasGameRef, CollisionCallbacks {
Star(Vector2 position)
: super(size: Vector2.all(50), position: position, anchor: Anchor.center);
final gameObject = PlayerWithBG();
late final SpriteAnimation _rotateAnimation;
final double _animationSpeed = .3;
Direction direction = Direction.right;
#override
Future<void> onLoad() async {
super.onLoad();
await _loadAnimations().then((_) => {animation = _rotateAnimation});
add(CircleHitbox());
}
Future<void> _loadAnimations() async {
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: await gameRef.images.load('stars1.png'), columns: 10, rows: 1);
_rotateAnimation = spriteSheet.createAnimation(
row: 0, stepTime: _animationSpeed, from: 0, to: 4);
}
#override
void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollision(intersectionPoints, other);
// gameObject.showQuestion();
// calling the showQuestion() method throws an exception in the method
/*
* Exception has occurred.
_AssertionError ('package:flame/src/game/overlay_manager.dart': Failed assertion: line 51 pos 7: '_builders.containsKey(name)': Trying to add an unknown overlay "Question")
*/
removeFromParent();
//This is what I want to do--------------------------------------------------
// Navigator.push(
// context, MaterialPageRoute(builder: (context) => firstLevelQuestion()));
}
}
And this is the code inside the main game class "PlayerWithBG"
import 'dart:ui';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flutter/services.dart';
import 'Player.dart';
import 'GameBG.dart';
import 'package:flutter/material.dart';
import 'Star.dart';
import 'helpers/directions.dart';
class PlayerWithBG extends FlameGame
with KeyboardEvents, HasCollisionDetection, TapDetector {
Player _player = Player();
GameBG _gameBG = GameBG();
#override
Future<void> onLoad() async {
super.onLoad();
await add(_gameBG);
double bgWidth = _gameBG.size.x;
double bghight = _gameBG.size.y;
await add(_player);
_player.position = Vector2(bgWidth * 0.05, bghight * 0.95);
camera.followComponent(_player,
worldBounds: Rect.fromLTRB(0, 0, _gameBG.size.x, _gameBG.size.y));
//--------------------------------------------------------------------------------------
//Stars are the elements that should open the question when the player collides with them
add(Star(Vector2(bgWidth * 0.10, bghight * 0.95)));
add(Star(Vector2(bgWidth * 0.30, bghight * 0.95)));
}
void showQuestion() {
if (overlays.isActive('Question')) {
overlays.remove('Question');
resumeEngine();
} else {
overlays.add('Question');
pauseEngine();
}
}
onArrowKeyChanged(Direction direction) {
_player.direction = direction;
}
}
And here where I call the game widget with a temp overlay container
GameWidget(
game: game..paused = false,
//here is the overlayer to render the question--------------------
overlayBuilderMap: {
'Question': (BuildContext context, PlayerWithBG game) {
return Center(
child: Container(
width: 100,
height: 100,
color: Colors.orange,
child: Center(child: Text('This is a question')),
),
);
},
},
//-----------------------------------------------------------------
),
You can do this in a few different ways:
1.Use the overlays system
Just like you tried, but to access the overlays you can use the fact that you have the HasGameRef mixin on your component, which gives your access to the gameRef variable and thus gameRef.overlays.add/remove.
You can read more about that here.
2. Use the RouterComponent
This technique is slightly more advanced, but it is definitely worth exploring if you will have a lot of different screens and scenes.
Example:
final router = RouterComponent(
routes: {
'ok-dialog': OverlayRoute(
(context, game) {
return Center(
child: DecoratedContainer(...),
);
},
), // OverlayRoute
'confirm-dialog': OverlayRoute.existing(),
},
);
router.pushNamed('ok-dialog);
You can read a lot more about that here.
3. Use the normal Flutter navigation
The last, but least ergonomic way, is to use Flutter's built-in navigation (or another navigation package).
You can get the context in the same way as you accessed the overlays, when you have the HasGameRef mixin:
gameRef.buildContext;
Depending on how you do the navigation you might have to use the GameWidget.controlled constructor when you create your GameWidget.

How to detect screen boundaries with Flame in Flutter?

I want to detect when an object goes outside of the screen in Flame Flutter. I think there is two way to accomplish this either with Collidable mixin or with Forge2D. If possible explain it in both of them.
Flame version: flame: 1.0.0-releasecandidate.18
It is way overkill to use Forge2D for this (that complicates a lot of other things too). But you can use the built-in collision detection system, or you can check in the update-loop whether it is within the screen or not (this would be the most efficient).
By using the collision detection system we can use the built-in ScreenCollidable and you can do something like this:
class ExampleGame extends FlameGame with HasCollidables {
...
#override
Future<void> onLoad() async {
await super.onLoad();
add(ScreenCollidable());
}
}
class YourComponent extends PositionComponent with HasHitboxes, Collidable {
#override
Future<void> onLoad() async {
await super.onLoad();
// Change this if you want the components to collide with each other
// and not only the screen.
collidableType = CollidableType.passive;
addHitbox(HitboxRectangle());
}
// Do note that this doesn't work if the component starts
// to go outside of the screen but then comes back.
#override
void onCollisionEnd(Collidable other) {
if (other is ScreenCollidable) {
removeFromParent();
}
}
}
and by just calculating it in the update-loop:
class YourComponent extends PositionComponent with HasGameRef {
#override
void update(double dt) {
final topLeft = absoluteTopLeftPosition;
final gameSize = gameRef.size;
if(topLeft.x > gameSize.x || topLeft.y > gameSize.y) {
removeFromParent();
return;
}
final bottomRight = absolutePositionOfAnchor(Anchor.bottomRight);
if(bottomRight.x < 0 || bottomRight.y < 0) {
removeFromParent();
return;
}
}
}
I also recommend that you update to Flame 1.0.0 now when it is released. :)
There is no longer a Collidable mixin.
See the Flame documentation

Migrating to flame v1.0.0

I'm Migrating to flame v1.0.0-rc8 from flame v0.29.4
and I can't find a good roadmap of how to get the initialDimensions, how to get the engine widget via engine.widget, how to init the Sprite object (Previously via Sprite('path_to_asset_file')), and how to set the width and height for SpriteComponent (Previously via SpriteComponent.rectangle).
These are several questions, so I'll give several answers:
How to get the inititialDimensions?
inititialDimensions is no longer needed, onGameResize is called before onLoad which will give you the size of the game. You can also get the size of the game by adding the HasGameRef mixin to your Components and call gameRef.size.
How to get the flutter widget?
You now wrap your game inside of a GameWidget instead of using .widget:
import 'package:flutter/material.dart';
import 'package:flame/game.dart';
void main() {
final myGame = MyGame();
runApp(
GameWidget(
game: myGame,
),
);
}
How to initialize a Sprite?
You usually want a SpriteComponent, and not a raw Sprite.
To create a Sprite:
class MyGame extends FlameGame {
Sprite player;
#override
Future<void> onLoad() async {
player = Sprite.load('player.png');
}
}
To create a SpriteComponent:
class MyGame extends FlameGame {
SpriteComponent player;
#override
Future<void> onLoad() async {
final sprite = await loadSprite('player.png');
player = SpriteComponent(sprite: sprite);
// And you usually want to add your component to the game too.
add(player);
}
}
How to set the size of a component?
Simply do component.size = Vector2(width, height); or component.width = width; + component.height = height

Flutter How can I build my own scroll physics in this framework?

I want to build my own custom scroll physic. I've followed along with a tutorial to learn how I can do this. But I do not understand how this physics and simulation structure works in Flutter. Unfortunately there are not many tutorials or documentations on this topic. Can you give me an explanation of how the physics and simulations work in Flutter. How can I detect swipes ? Are the finals in the "MySimulation" Class defined by Flutter ? Which can I use ? Do I have to write it this or could I use other ways ?
How could I make my own ScrollPhysics in my written framework ?
Thank you for your help
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:math' as math;
class MySimulation extends Simulation {
final double initPosition;
final double velocity;
MySimulation({this.initPosition, this.velocity});
#override
double x(double time) {
return initPosition;
}
#override
double dx(double time) {
return velocity;
}
#override
bool isDone(double time) {
return false;
}
}
class MyScrollPhysics extends ScrollPhysics {
#override
ScrollPhysics applyTo(ScrollPhysics ancestor) {
return MyScrollPhysics();
}
#override
Simulation createClampingScrollSimulation(
ScrollMetrics position, double velocity) {
return MySimulation(
initPosition: 0,
velocity: 0,
);
}
}

Performance issue in drawing using Path Flutter

I am testing the performance in drawing using Flutter. I am using Path to draw line between each point detected by Listener because I have read that the performance would increase using it. I am using Listener because I tried also the Apple Pencil on iPad 2017 by changing the kind property to stylus.
The problem is that I was hoping to get a response in the stroke design similar to notability, it seems much slower, acceptable but not as much as I would like.
So I'm looking for tips to increase performance in terms of speed.
At the following link they recommended using NotifyListener(), but I didn't understand how to proceed. If it really improves performance I would like even an example to be able to implement it.
If Flutter has some limitations when it comes to drawing with your fingers or with a stylus then let me know.
performance issue in drawing using flutter
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
class DrawWidget extends StatefulWidget {
#override
_DrawWidgetState createState() => _DrawWidgetState();
}
class _DrawWidgetState extends State<DrawWidget> {
Color selectedColor = Colors.black;
double strokeWidth = 3.0;
List<MapEntry<Path, Object>> pathList = List();
StrokeCap strokeCap = (Platform.isAndroid) ? StrokeCap.butt : StrokeCap.round;
double opacity = 1.0;
Paint pa = Paint();
#override
Widget build(BuildContext context) {
return Listener(
child: CustomPaint(
size: Size.infinite,
painter: DrawingPainter(
pathList: this.pathList,
),
),
onPointerDown: (details) {
if (details.kind == PointerDeviceKind.touch) {
print('down');
setState(() {
Path p = Path();
p.moveTo(details.localPosition.dx, details.localPosition.dy);
pa.strokeCap = strokeCap;
pa.isAntiAlias = true;
pa.color = selectedColor.withOpacity(opacity);
pa.strokeWidth = strokeWidth;
pa.style = PaintingStyle.stroke;
var drawObj = MapEntry<Path,Paint>(p, pa);
pathList.add(drawObj);
});
}
},
onPointerMove: (details) {
if (details.kind == PointerDeviceKind.touch) {
print('move');
setState(() {
pathList.last.key.lineTo(details.localPosition.dx, details.localPosition.dy);
});
}
},
/*onPointerUp: (details) {
setState(() {
});
},*/
);
}
}
class DrawingPainter extends CustomPainter {
DrawingPainter({this.pathList});
List<MapEntry<Path, Object>> pathList;
#override
void paint(Canvas canvas, Size size) {
for(MapEntry<Path, Paint> m in pathList) {
canvas.drawPath(m.key, m.value);
}
}
#override
bool shouldRepaint(DrawingPainter oldDelegate) => true;
}
I think you should not use setState, rather use state management like Bloc or ChangeNotifier or smth.
Also, just drawing a path with this:
canvas.drawPath(m.key, m.value);
Works for only small stroke widths, it leaves a weird-looking line full of blank spaces when drawing.
I implemented this by using Bloc that updates the UI based on the gesture functions (onPanStart, onPanEnd, onPanUpdate). It holds a List of a data model that I called CanvasPath that represents one line (so from onPanStart to onPanEnd events), and it holds the resulting Path of that line, list of Offsets and Paint used to paint it.
In the end paint() method draws every single Path from this CanvasPath object and also a circle in every Offset.
` for every canvasPath do this:
canvas.drawPath(canvasPath.path, _paint);
for (int i = 0; i < canvasPath.drawPoints.length - 1; i++) {
//draw Circle on every Offset of user interaction
canvas.drawCircle(
canvasPath.drawPoints[i],
_raidus,
_paint);
}`
I made a blog about this, where it is explained in much more details:
https://ivanstajcer.medium.com/flutter-drawing-erasing-and-undo-with-custompainter-6d00fec2bbc2