How to move a Vector2 using PositionComponent with Flame/Flutter? - flutter

I am trying to move a Vector2's x position, the update is being called, the x value is changing but the white square component does not move, what am I missing?
i am using flame 1.0.0-releasecandidate.11.
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/palette.dart';
class PlayerComponent extends PositionComponent {
static const squareSize = 128.0;
static Paint white = BasicPalette.white.paint();
#override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), white);
super.render(canvas);
}
#override
void update(double dt) {
x += 10 * dt;
print(x);
super.update(dt);
}
#override
void onMount() {
super.onMount();
size.setValues(squareSize, squareSize);
anchor = Anchor.center;
}
}
class AnimalVillageGame extends BaseGame {
#override
Future<void> onLoad() async {
add(PlayerComponent()
..x = 100
..y = 100
);
}
}

You render method renders the size as a zero-bound rectangle, size.toRect() returns a Rect at the position 0,0.
Instead you can use the toRect method from the PositionComponent class:
#override
void render(Canvas canvas) {
canvas.drawRect(toRect(), white);
super.render(canvas);
}
This method returns the rect of the component, based on the x, y position and the size.
Edit:
As mentioned by spydon, the super.render is called after the rendering to the canvas, it would be better to call the super method first as it will automatically handle things like translation, rotation, etc for you.
It prepares the canvas for your own rendering.

Related

Flutter canvas not rendering as expected

I am trying to render a grid with flutter canvas with Canvas.drawLine method.
Some lines are rendered semi-transparent and some are not even being rendered on the canvas.
class Background extends PositionComponent with HasGameRef<RokokoGame>{
Offset start = Offset.zero;
Offset end = Offset.zero;
// will be different across devices
late final double canvasX;
late final double canvasY;
final int cellSize = GameConfig.cellSize;
Background(this.canvasX, this.canvasY);
#override
Future<void>? onLoad() {
start = Offset(0, 0);
end = Offset(this.canvasX, this.canvasY);
}
#override
void render(Canvas canvas) {
canvas.drawRect(Rect.fromPoints(Offset.zero, end), Styles.white);
_drawVerticalLines(canvas);
_drawHorizontalLines(canvas);
}
void _drawVerticalLines(Canvas c) {
for (double x = start.dx; x <= end.dx; x += cellSize) {
c.drawLine(Offset(x, start.dy), Offset(x, end.dy), Styles.red);
}
}
void _drawHorizontalLines(Canvas c) {
for (double y = start.dy; y <= end.dy; y += cellSize) {
c.drawLine(Offset(start.dx, y), Offset(end.dx, y), Styles.blue);
}
}
}
So, i was able to solve the issue by applying some stroke width in my Styles class.
class Styles {
static Paint white = BasicPalette.white.paint();
static Paint blue = BasicPalette.blue.paint()..strokeWidth = 3;
static Paint red = BasicPalette.red.paint()..strokeWidth = 3;
}

GPU Jank when generate endless 2D terrain

I'm making a runner game which has the infinity mode.
So, I got to generate a terrain every 1s approximately.
It has some lag with my LG Q9 One, I checked the performance and it appeared that there's some problem with Raster thread.
I tried the game on Samsung Galaxy Note 10 which has better gpu and it shows less janks(but it has janks anyway).
I adds my code here. Any better way to enhance the performance?
class MyGame extends FlameGame {
final Grid grid = Grid();
final EndlessMap endlessMap = EndlessMap();
final Runner runner = Runner();
double speed = 100.0;
#override
Future<void>? onLoad() {
add(grid);
add(endlessMap);
add(runner);
return super.onLoad();
}
}
/// Camera follows the runner.
class Runner extends PositionComponent with HasGameRef<MyGame> {
#override
Future<void>? onLoad() {
gameRef.camera.followComponent(this);
return super.onLoad();
}
#override
void update(double dt) {
x += gameRef.speed * dt;
super.update(dt);
}
}
/// Set the size of tile and grid
class Grid extends Component with HasGameRef<MyGame> {
late int rows = 10;
late int columns;
late int mapColumns;
late double tileSize;
#override
void onGameResize(Vector2 size) {
tileSize = (size.y / rows).floorToDouble();
columns = (size.x / tileSize).ceil();
mapColumns = columns + 2;
super.onGameResize(size);
}
}
/// Generate terrains endlessly
class EndlessMap extends PositionComponent with HasGameRef<MyGame> {
late final Sprite terrainSprite;
late final List<SpriteComponent> terrainSpritePool;
int firstTerrainIndex = 0;
// Init terrain sprite
#override
Future<void> onLoad() async {
terrainSprite = Sprite(
await Flame.images.load('terrains.png'),
srcPosition: Vector2(4.0, 0.0),
srcSize: Vector2(32.0, 32.0 * 10),
);
terrainSpritePool = List.generate(
gameRef.grid.mapColumns,
(index) => SpriteComponent(sprite: terrainSprite),
);
for (var i = 0; i < terrainSpritePool.length; i++) {
terrainSpritePool[i].size.x = gameRef.grid.tileSize;
terrainSpritePool[i].position = Vector2(
i * gameRef.grid.tileSize,
gameRef.grid.tileSize,
);
add(terrainSpritePool[i]);
}
return super.onLoad();
}
#override
void update(double dt) {
final dx = gameRef.speed * dt;
final lastTerrainIndex = firstTerrainIndex == 0
? terrainSpritePool.length - 1
: firstTerrainIndex - 1;
// When the first terrain is behind the camera,
if (terrainSpritePool[firstTerrainIndex].position.x +
gameRef.grid.tileSize <=
gameRef.camera.position.x + dx) {
// Move the first terrain to the end
terrainSpritePool[firstTerrainIndex].size.x = gameRef.grid.tileSize;
terrainSpritePool[firstTerrainIndex].position = Vector2(
terrainSpritePool[lastTerrainIndex].position.x + gameRef.grid.tileSize,
gameRef.grid.tileSize,
);
firstTerrainIndex = (firstTerrainIndex + 1) % terrainSpritePool.length;
}
super.update(dx);
}
}
You can find a whole code in github.
2022.09.12. ----------------
It seems like the issue with that my phone(LG Q9 One) has a bad gpu to run it normally. I removed all the codes except moving the camera, it still shows janks.
In order to reduce the shader compilation and run your animations smoothly, there is an article in the official flutter website explaining about a solution
Hope this helps
Try to not create new objects in the update method.
So when you are doing:
terrainSpritePool[firstTerrainIndex].position = Vector2(
terrainSpritePool[lastTerrainIndex].position.x + gameRef.grid.tileSize,
gameRef.grid.tileSize,
);
Since the SpriteComponent already has a Vector2 you can re-use it like this:
terrainSpritePool[firstTerrainIndex].position.setValues(
terrainSpritePool[lastTerrainIndex].position.x + gameRef.grid.tileSize,
gameRef.grid.tileSize,
);

What is the purpose of "angle %= 2 * math.pi;" in the bellow code?

I am running this example from Flame engine source, and I wonder what is the purpose of angle %= 2 * math.pi; in the update method. Commenting it also doesn't affect the circular animation of rectangle! If there is a mathematical reason please put a link to the related article if possible.
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame/palette.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
GameWidget(
game: MyGame(),
),
);
}
/// This example simply adds a rotating white square on the screen, if you press
/// somewhere other than on the existing square another square will be added and
/// if you press on a square it will be removed.
class MyGame extends FlameGame with DoubleTapDetector, HasTappables {
bool running = true;
#override
Future<void> onLoad() async {
add(Square(Vector2(100, 200)));
}
#override
void onTapUp(int id, TapUpInfo info) {
super.onTapUp(id, info);
if (!info.handled) {
final touchPoint = info.eventPosition.game;
add(Square(touchPoint));
}
}
#override
void onDoubleTap() {
if (running) {
pauseEngine();
} else {
resumeEngine();
}
running = !running;
}
}
class Square extends PositionComponent with Tappable {
static const speed = 0.25;
static const squareSize = 128.0;
static Paint white = BasicPalette.white.paint();
static Paint red = BasicPalette.red.paint();
static Paint blue = BasicPalette.blue.paint();
Square(Vector2 position) : super(position: position);
#override
Future<void> onLoad() async {
super.onLoad();
size.setValues(squareSize, squareSize);
anchor = Anchor.center;
}
#override
void render(Canvas canvas) {
canvas.drawRect(size.toRect(), white);
canvas.drawRect(const Rect.fromLTWH(0, 0, 30, 30), red);
canvas.drawRect(Rect.fromLTWH(width / 2, height / 2, 3, 3), blue);
}
#override
void update(double dt) {
super.update(dt);
angle += speed * dt;
angle %= 2 * math.pi; //<---- What is the reason behind this line?
}
#override
bool onTapUp(TapUpInfo info) {
removeFromParent();
return true;
}
}
At some point, after a very long time if you don't have a high rotational speed, the angle will overflow what a double can fit.
The angle here is in radians, and one full circle is 2π (360°).
Since when we have rotated a full circle we can just as well go back to 0 again we use modulo to remove all the "unused" full circles that are in currently in angle.
What will happen if exceeds 2*pi.
Nothing will happen, it will just continue rotating, but angle will keep on growing to an unnecessarily large value.
It ensures that angle never exceeds 2×PI, the maximum value of an angle in radians. By performing a modulus by this value, any number that exceeds 2×PI will just wrap around from 0.

Flutter update is giving me this error: The method '*' was called on null

I have a flutter app using the flame library. I'm trying to make an object move in a flutter game. When I run the update function, I get the following error:
The method '*' was called on null.
Receiver: null
Tried calling: *(0.0)
Seems like something isn't initialized and the update function is ran before that something is initialized. When I comment out player.update(t) it works, but the update function doesn't get called. What am I doing wrong ad how can I fix it? Here's my code:
Game Controller Class
class GameController extends Game {
Size screenSize;
Player player;
GameController() {
initialize();
}
void initialize() async {
final initDimetion = await Flame.util.initialDimensions();
resize(initDimetion);
player = Player(this);
}
void render(Canvas c) {
Rect bgRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
Paint bgPaint = Paint()..color = Color(0xFFFAFAFA);
c.drawRect(bgRect, bgPaint);
player.render(c);
}
void update(double t) {
if (player is Player) { // Tried adding this if statement but it didn't work
player.update(t);
}
}
void resize(Size size) {
screenSize = size;
}
}
Player Class
class Player {
final GameController gameController;
Rect playerRect;
double speed;
Player(this.gameController) {
final size = 40.0;
playerRect = Rect.fromLTWH(gameController.screenSize.width / 2 - size / 2,
gameController.screenSize.height / 2 - size / 2, size, size);
}
void render(Canvas c) {
Paint color = Paint()..color = Color(0xFF0000FF);
c.drawRect(playerRect, color);
}
void update(double t) {
double stepDistance = speed * t;
Offset stepToSide = Offset.fromDirection(90, stepDistance);
playerRect = playerRect.shift(stepToSide);
}
}
You never initialize the speed attribute of Player to a value. So speed * t in Player.update causes this error.
Simply initialize the speed attribute in the constructor
Player(this.gameController) {
final size = 40.0;
this.speed = 0;
playerRect = Rect.fromLTWH(gameController.screenSize.width / 2 - size / 2,
gameController.screenSize.height / 2 - size / 2, size, size);
}

Flutter: Concurrent modification during iteration: Instance(length:2) of '_GrowableList'

This is a game with a small rectangle randomly appearing on the screen and the player has to tap it in order to get rid of it and a new one will spawn on another part of the screen.
My code works and all but every time I tap the rectangle I drew on the screen, I get the error below. How do I fix this?
Main.dart:
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flame/flame.dart';
import 'package:flame/util.dart';
import 'package:flutter/services.dart';
import 'package:langaw/langaw-game.dart';
import 'package:flutter/gestures.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Util flameUtil = Util();
await flameUtil.fullScreen();
await flameUtil.setOrientation(DeviceOrientation.portraitUp);
LangawGame game = LangawGame();
runApp(game.widget);
TapGestureRecognizer tapper = TapGestureRecognizer();
tapper.onTapDown = game.onTapDown;
flameUtil.addGestureRecognizer(tapper);
}
fly.dart:
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:langaw/langaw-game.dart';
class Fly {
Rect flyRect;
Paint flyPaint;
bool isDead = false, isOffScreen = false;
final LangawGame game;
// initialized with the game instance, along with positions x and y
Fly({this.game, double x, double y}) {
flyRect = Rect.fromLTWH(x, y, game.tileSize, game.tileSize);
flyPaint = Paint();
flyPaint.color = Color(0xff6ab04c);
}
// needs a render and update method
// this method will do the drawing
void render(Canvas canvas) {
canvas.drawRect(flyRect, flyPaint);
}
void update(double t) {
if (isDead) {
flyRect = flyRect.translate(0, game.tileSize * 12 * t);
if (flyRect.top > game.screenSize.height) {
isOffScreen = true;
}
}
}
void onTapDown() {
flyPaint.color = Color(0xffff4757);
isDead = true;
game.spawnFly();
}
}
langaw-game.dart:
import 'dart:ui';
import 'package:flame/game.dart';
import 'package:flame/flame.dart';
import 'package:langaw/components/fly.dart';
import 'dart:math';
import 'package:flutter/gestures.dart';
class LangawGame extends Game {
Size screenSize;
double tileSize;
List<Fly> flies;
Random rnd;
LangawGame() {
initialize();
}
void initialize() async {
flies = List<Fly>();
rnd = Random();
resize(await Flame.util.initialDimensions());
spawnFly();
}
// this method will do the drawing
void render(Canvas canvas) {
Rect bgRect = Rect.fromLTWH(0, 0, screenSize.width, screenSize.height);
Paint bgPaint = Paint();
bgPaint.color = Color(0xff576574);
canvas.drawRect(bgRect, bgPaint);
// now the flies are rendered on top of the background
flies.forEach((Fly fly) => fly.render(canvas));
// print(screenSize.toString());
}
void update(double t) {
flies.forEach((Fly fly) => fly.update(t));
flies.removeWhere((Fly fly) => fly.isOffScreen);
}
void spawnFly() {
double xPos = rnd.nextDouble() * (screenSize.width - tileSize);
double yPos = rnd.nextDouble() * (screenSize.height - tileSize);
flies.add(Fly(game: this, x: xPos, y: yPos));
}
void resize(Size size) {
screenSize = size;
tileSize = screenSize.width / 9;
}
void onTapDown(TapDownDetails d) {
flies.forEach((Fly fly) {
if (fly.flyRect.contains(d.globalPosition)) {
fly.onTapDown();
}
});
}
}
The following ConcurrentModificationError was thrown while handling a gesture:
Concurrent modification during iteration: Instance(length:2) of '_GrowableList'.
When the exception was thrown, this was the stack:
#0 List.forEach (dart:core-patch/growable_array.dart:286:36)
#1 LangawGame.onTapDown (package:langaw/langaw-game.dart:57:11)
#2 TapGestureRecognizer.handleTapDown.<anonymous closure> (package:flutter/src/gestures/tap.dart:463:51)
#3 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
#4 TapGestureRecognizer.handleTapDown (package:flutter/src/gestures/tap.dart:463:11)
...
Handler: "onTapDown"
Recognizer: TapGestureRecognizer#a4e24
state: possible
button: 1
I ran in the same problem today when following the tutorial on flutter game (nice tutorial BTW except I think this small bug, I will post a comment soon when I have time).
The problem is that the LangawGame.onTapDown iterates on the flies list, and during the iteration calls Fly.onTapDown() who adds an element to the list in the spawnFly method.
So the list gets modified while it is being iterated... which IMHO sounds like a bug.
The simple solution is to make a copy of the list for iteration with List.from:
ie in LangawGame:
void onTapDown(TapDownDetails d) {
List<Fly>.from(flies).forEach((Fly fly) {
if (fly.flyRect.contains(d.globalPosition)) {
fly.onTapDown();
}
});
There are more performant solutions, but with more code :)
Remove following code in in class Fly:
game.spawnFly();
Add the following code in langaw-game.dart -> update()
if(flies.length<1){
spawnFly();
}
You can use ( Dart documentation: https://api.dartlang.org/stable/2.1.0/dart-core/Map/removeWhere.html )
Map.removeWhere((key, value) => toRemove.contains(key));