Flutter Flame: proper Viewport to match the screen size - flutter

How can I find out the screen size and place my game item according to screen resolution? I want to run my game on the web client.
I want to resize my red game component so fit in the screen and position in center.
LibGdx has some good java class for this concept: Link

You can use the FixedResolutionViewport and set it to the smallest edge if you always want it as a square:
class MyGame extends FlameGame {
Future<void> onLoad() async {
double maxSide = min(size.x, size.y);
camera.viewport = FixedResolutionViewport(Vector2.all(maxSide));
}
}
If you want the game's (viewport) size inside of another component you can add the HasGameRef mixin and use the game's size variable in the same way:
class MyComponent extends Component with HasGameRef {
MyComponent() : super(anchor: Anchor.center);
Future<void> onLoad() async {
final gameSize = gameRef.size;
// To add a position component in the center of the screen for example:
// (when the camera isn't moved)
position = gameSize/2;
}
}
If you want other sizes I recommend to look at game.camera and game.camera.viewport which offers a few other options too.

Related

Shooting bullets in Flame Forge2d

I'm playing around with Forge2d on Flutter Flame and created a Ninja which throws Kanuies (a character shooting bullets basically).
If I create Kanuies and the Ninja separately then add them separately to the game world, all will be fine. However, I want the Ninja class to be responsible for login for throwing the Kanui. So I was wondering what is the right way to add a bullet component to a character component in Forge2d.
In my current code, inside Ninja Class, if I call add(Kanui) there will be no graphics shown about Kanuie NOT even with debugMode=true .
However, if use addToParrent(Kanui) it will be fine again.
Below is the link to my code. Please have a look and suggest corrections.
I'll provide some snippets here as well.
https://github.com/bmd007/flame_playground/blob/154cc0a9a99cc4bd5732e8d0c94bfa38093b0298/lib/my_girl.dart#L134
Ninja Class:
class MyGirl extends BodyComponent {
late SpriteAnimationComponent component;
Queue<MyGirlKanui> kanuies = Queue<MyGirlKanui>();
#override
Future<void> onLoad() async {
await super.onLoad();
idleAnimation = await gameRef.loadSpriteAnimation("red_girl/idle_spriteSheet.png", idleAnimationData);
component = SpriteAnimationComponent()
..animation = idleAnimation
..size = Vector2.all(6)
..anchor = Anchor.center;
add(component);
kanuies.add(MyGirlKanui(initialPosition));
}
#override
Body createBody() {
final shape = PolygonShape()..setAsBoxXY(3, 3);
final fixtureDefinition = FixtureDef(shape, density: 2, restitution: 0.1, friction: 2);
final bodyDefinition = BodyDef(position: initialPosition, type: BodyType.dynamic)..fixedRotation = true;
return world.createBody(bodyDefinition)..createFixture(fixtureDefinition);
}
throwKanui() async {
if (kanuies.isNotEmpty) {
var kanui = kanuies.removeFirst();
// await parent?.add(kanui);
await add(kanui);
kanui.component.position = component.position;
kanui.body.linearVelocity.x = 30;
}
}
I call the throw method when a UI button is pressed.
In Forge2D you shouldn't add any bodies as children to other components.
You can add the HasGameRef<Forge2DGame> mixin to the component and then you can add the bullets directly to the game.
Also don't forget to put the body of the bullet to isBullet = true if the "bullet" is moving very fast, otherwise you could end up with tunneling (where the body passes through the object that it is supposed to hit).

why camera from my doc is different to the example

I followed a tutorial and code looks like this:
class MyGame extends Forge2DGame{
#override
Future<void> onLoad() async {
camera.toString(); // <= this camera is a Vector2
}
}
But what I learned from another tutorial is like this:
class SpacescapeGame extends FlameGame{
#override
Future<void> onLoad() async {
camera.shake(); // <= this camera is a Camera class
}
}
What I understand is Forge2DGame extends BaseGame, BaseGame extends FlameGame https://pub.dev/documentation/bonfire/latest/base_base_game/BaseGame-class.html
but why I cant use camera.shake as second tutorial showed? thank you for your explanation!
I recommend that you read the Flame documentation instead of the bonfire documentation if you want to write a Flame game that is not involving bonfire.
The camera for Forge2DGame is the same camera as is used for FlameGame, you have most likely added some other variable that is also called camera which is shadowing the real camera.
Also make sure that you are on the latest version of flame (1.4.0) and the latest version of flame_forge2d (0.12.3).

Efficient Canvas rendering in Flutter

I have been trying to develop a drawing app and all the example codes including the one's from The Boring Flutter Development Show don't translate well into real-world usage.
The main problem being that CustomPaint's paint operation is too expensive and re-draws every point, every frame. And as the points increase the flutter app's render time per frame increases significantly
From the time I spent finding a solution to this problem, I found these
RepaintBoundary : Rasterizes layers
Custom Widget using SingleChildRenderObjectWidget and RenderProxyBox : must implement a paint method and no way of passing a controller
I don't think any of the above solutions work well for my needs: smooth canvas drawing operations without re-paint. I even tried simplifying the points and that didn't work either because of the inherent mechanism of CustomPaint
If there was a way to pass a canvas as a widget and attaching a controller it'll be easy to store the captured points and use basic canvas operations like canvas.drawPath() or canvas.drawLine() much efficiently
Any suggestion would be helpful. Thank you!
This is the Controller class
class DrawingController extends ChangeNotifier {
List<Offset> _points = []
.... // Perform operations on data points
....
void add(Offset point) {
_points.add(point);
notifyListeners();
}
}
This is the CustomPaint class
class Painter extends CustomPainter {
DrawingController controller;
Painter(this.controller) : super(repaint: controller); //This is important
#override
void paint(Canvas canvas, Size size) {
//Paint function
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
And in the Gesture detector simply call controller.add(point) to add new points as you grab them
I am not sure this is the most efficient way to paint but it reduced the paint times down to 13ms on a 60hz screen and 9ms on a 120Hz screen. One noticeable drawback is that a single stroke (onTapdownEvent to an onDragEndEvent) renders quite slowly(up to 18ms) when the stroke has many points. This problem disappears as soon as you start a new stroke. I tried asking on several forums and this is the best I could come up with after digging through the flutter source code.
I profiled this functionality on a snapdragon 855 device so the processor is not a bottleneck. If anyone finds a better solution please post it.
A helpful tip is to add a Timer.run(setState()). This way it only updates when it can. Instead of loading update1 update2 update3...etc. It loads update1 then if it can it loads update2 but if update1 took to long it goes to update3. In general it just looks smoother but doesn't necessarily speed it up. Sample:
scaleStart(ScaleStartDetails d) => startPoint = d.focalPoint - _offset;
scaleUpdate(ScaleUpdateDetails d) {
_offset = d.focalPoint - startPoint;
scale = min(max(1, scale * (d.scale + 13) / 14), 128);
Timer.run(() => setState(() {}));
}

How to detect a swipe in a game with flutter/flame

I want to create a game in flutter with flame. For this game I want to detect swipes.
I could implement a tap recognition with the help of a tutorial. But I could not implement it with swipe detection.
my main with Taprecognition looks like this:
My main function is
void main() async{
Util flameUtil = Util();
await flameUtil.fullScreen();
await flameUtil.setOrientation(DeviceOrientation.portraitUp);
GameManager game = GameManager();
runApp(game.widget);
TapGestureRecognizer tapper = TapGestureRecognizer();
tapper.onTapDown = game.onTapDown;
flameUtil.addGestureRecognizer(tapper);
}
In my GameManager class I do have:
class GameMAnager extends Game{
// a few methods like update, render and constructor
void onTapDown(TapDownDetails d) {
if (bgRect.contains(d.globalPosition)) { //bgRect is the background rectangle, so the tap works on the whole screen
player.onTapDown();
}
}
And my player class contains:
void onTapDown(){
rotate();
}
Now I want to change this to rotate in the direction of the swipe instead of onTapDown.
I tried to somehow add
GestureDetector swiper = GestureDetector();
swiper.onPanUpdate = game.onPanUpdate;
to my main and
void onPanUpdate() {
}
to my gameManager class. But I cannot find anything similar to TapDownDetails for panning.
Any suggestions on this?
I saw some help for this to wrap the widget in a GestureDetector and use it like this:
GestureDetector(onPanUpdate: (details) {
if (details.delta.dx > 0) {
// swiping in right direction
}
});
But I couldn't make it work on my project.
You can use the HorizontalDragGestureDetector (or PanGestureRecognizer if you need both axes)
use the following in your main method
HorizontalDragGestureRecognizer tapper = HorizontalDragGestureRecognizer();
tapper.onUpdate = game.dragUpdate;
and then the following in your GameManager
void dragUpdate(DragUpdateDetails d) {
// using d.delta you can then track the movement and implement your rotation updade here
}
That should do the trick :D
If you need both axes, you can use PanGestureRecognizer (as #Marco Papula said).
Use this in your main method:
PanGestureRecognizer panGestureRecognizer = PanGestureRecognizer();
panGestureRecognizer.onEnd = game.onPanUpdate;
flameUtil.addGestureRecognizer(panGestureRecognizer);
and your onPanUpdate method:
void onPanUpdate(DragEndDeatils d) {
if(d.velocity.pixelsPerSecond.dx.abs()>d.velocity.pixelsPerSecond.dy.abs()) {
// X Axis
snake.velocity = d.velocity.pixelsPerSecond.dx<0 ? LeftSwipe : RightSwipe;
} else {
// Y Axis
snake.velocity = d.velocity.pixelsPerSecond.dy<0 ? UpSwipe : DownSwipe;
}
}
If you are using a newer (v1) version of Flame, you no longer need to wrap it in your own GestureDetector (thought you can). Now Flame has built in wrappers for all events, including panning! You can mix your game with:
class MyGame extends BaseGame with PanDetector {
// ...
}
And implement onPanUpdate to get the desired behaviour; or use a myriad of any other detectors (check the documentation for more options and details on how to use it).

Looking for a synchronous alternative to Flutter's .toImage() method

At the moment I'm experimenting with Flutter and the Flame game engine.
To do so I'm extending Flame's BaseGame class and do some heavy processing inside it's constructor.
The heavy processing includes composing an Image out of other images and ultimatively drawing it onto a temporary Canvas and the result is stored in a Picture object.
ui.PictureRecorder rec = new ui.PictureRecorder();
Canvas tempCanvas = new Canvas(rec, bgRect);
// left out picture operations
ui.Picture pic = rec.endRecording();
To finally get an Image object, I need to call the asynchronous .toData() method which returns a Future.
I'm wrapping the call in an async method getImage()
getImage(ui.Picture pic, Rect bgRect) async {
background = await pic.toImage(bgRect.width.toInt(), bgRect.height.toInt());
done = true;
}
(background is a class variable of type Image which is used inside the render() method of the BaseGame class)
Problem is, because it's asynchronous the rest of my statements inside the game's constructor get executed and after it finishes, the render() method fires but the background might not be available yet.
To workaround, I added a class variable done of type bool which gets set to true inside the getImage() method.
Now I modified the render() to wait for done to be true.
void render(Canvas canvas) {
if (done) {
canvas.drawImage(background, new Offset(0.0, 0.0), new Paint());
}
}
Of course this ain't to elegant.
Is there a way to wait for the .toImage() method to finish inside the constructor function of the extended BaseGame class?
I tried making the constructor async like:
class TheGame extends BaseGame {
Image background;
bool done = false;
TheGame(DeviceOrientation orientation) async {
}
}
but this gives me the error:
The modifier 'async' can't be applied to the body of a constructor
What else could I try to make it 'synchronous'?
If you really need the image before the first frame is rendered, you can just create a static method which is responsible for creating TheGame
class TheGame extends BaseGame {
final Image background;
TheGame._(DeviceOrientation orientation, this.background);
static Future<TheGame> create(DeviceOrientation orientation) async {
return TheGame._(orientation, await generateImage());
}
}
but I assume it doesn't really hurt if you render a few frames without a background image, then I would suggest you simply check background != null instead of the done property, which feels a bit redundant.