how to let SpriteAnimationComponent follow BodyComponent, which can fall due to gravity. currently SpriteAnimationComponent still keep in the air, here is my code:
class Chick extends BodyComponent {
Vector2 fallPosition;
SpriteAnimationComponent chick = SpriteAnimationComponent();
Chick({
this.fallPosition,
});
final spriteSize = Vector2(32, 34) / 2;
#override
Future<void> onLoad() async {
await super.onLoad();
var spriteData = SpriteAnimationData.sequenced(
amount: 14, stepTime: 0.1, textureSize: Vector2(32, 34));
chick = SpriteAnimationComponent.fromFrameData(
spriteSize, ImageTool.image('chicken.png'), spriteData)
..x = fallPosition.x
..y = fallPosition.y
..anchor = Anchor.center
..size = spriteSize;
await addChild(chick);
}
#override
void update(double dt) {
chick.position = body.position;
print('body.position ! ${body.position} == $fallPosition}');
super.update(dt);
}
#override
Body createBody() {
debugMode = true;
final shape = CircleShape()..radius = spriteSize.x / 2;
var position = fallPosition.clone();
var worldPosition = viewport.getScreenToWorld(position);
final fixtureDef = FixtureDef()
..shape = shape
..restitution = 0.1
..density = 0.1
..friction = 0.1;
final bodyDef = BodyDef()
..userData = this
..angularDamping = 0.1
..position = worldPosition
..type = BodyType.DYNAMIC;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
I realize fallPosition, fallPosition, and chick.position has different value, but I dont know how to make this SpriteAnimationComponent follow Body, thanks for any help!
Add the SpriteBodyComponent as a child to the BodyComponent by simply running add, and remove the attempts that you have at synchronizing the positions, this will be done automatically once you have added it as a child.
To do this you also need to upgrade to a newer version of Flame (preferably v1.4.0 or newer) and a new version of flame_forge2d (> 0.12.3). Do note that addChild has been renamed to add.
Related
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;
}
I have SpriteComponent named Obstacle. I set the priority value to 0 in the super method. But the Obstacle component is regenerated with the update method. It disappears when it exits the screen. When it reoccurs, the priorty value becomes invalid. I am sharing sample screenshots.
first img
second img
The player stays behind the obstacle. GameOverPanel always at the back.
#override
Future<void> onLoad() async {
add(ScreenHitbox());
add(gameRef.homeBackgroundParallaxComponent);
add(gameRef.homeBaseParallax);
add(gameRef.obstacleManager..priority = 0);
add(gameRef.playerManager..priority = 1);
add(gameRef.gameOverPanel..priority = 2);
}
#override
void update(double dt) {
super.update(dt);
gameRef.obstacleManager.updateObstacle(GameConst.gameSpeed * dt);
gameRef.playerManager.changePositionDown(value: GameConst.gravity * dt);
}
Game Over Panel
class GameOverPanel extends Component with HasGameRef<HopyBirdGame> {
GameOverPanel() : super(priority: 4);
#override
Future<void> onLoad() async {
add(GameOverText());
}
#override
void renderTree(Canvas canvas) {
if (gameRef.gameOver) {
super.renderTree(canvas);
}
}
}
class GameOverText extends SpriteComponent with HasGameRef<HopyBirdGame> {
GameOverText() : super(size: Vector2(300, 80), anchor: Anchor.center);
#override
Future<void>? onLoad() async {
sprite = await gameRef.loadSprite(Assets.gameOver.path);
position = Vector2(gameRef.screenWidth / 2, gameRef.screenHeight / 2);
return super.onLoad();
}
}
-Obstacle
class Obstacle extends SpriteComponent
with HasGameRef<HopyBirdGame>, CollisionCallbacks {
final ObstacleType obstacleType;
Obstacle({
required this.obstacleType,
super.position,
super.priority,
}) : super(
size: Vector2(GameConst.obstacleWidth, GameConst.obstacleHeight),
anchor: Anchor.center,
);
Obstacle.empty({this.obstacleType = ObstacleType.empty});
#override
Future<void> onLoad() async {
switch (obstacleType) {
case ObstacleType.up:
sprite = await gameRef.loadSprite(
Assets.redPipeUp.path,
);
break;
case ObstacleType.down:
sprite = await gameRef.loadSprite(
Assets.redPipeDown.path,
);
break;
case ObstacleType.empty:
return;
}
add(RectangleHitbox());
}
}
class Obstacles extends Component with HasGameRef<HopyBirdGame> {
final Obstacle upObstacle;
final Obstacle downObstacle;
bool isCreateNextObstacles = true;
Obstacles({
required this.upObstacle,
required this.downObstacle,
super.priority,
});
#override
Future<void>? onLoad() {
upObstacle.priority = 1;
downObstacle.priority = 1;
createChildren();
return super.onLoad();
}
void createChildren() {
final children = [upObstacle, downObstacle];
addAll(children);
}
}
Obstacle Manager
class ObstacleManager extends Component with HasGameRef<HopyBirdGame> {
ObstacleManager() : super(priority: 1);
ListQueue<Obstacles> history = ListQueue();
#override
Future<void> onLoad() async {
createObstacles();
}
void updateObstacle(double value) {
if (!gameRef.gameOver) {
if (history.isNotEmpty) {
history.last.upObstacle.position.x -= value;
history.last.downObstacle.position.x -= value;
if (history.last.upObstacle.position.x <= gameRef.screenWidth / 2) {
createObstacles();
final isThereAnyObstaclesOutsideScreen = history.any(
(element) =>
element.downObstacle.position.x <=
-(GameConst.obstacleWidth / 2),
);
if (isThereAnyObstaclesOutsideScreen) {
final obstaclesOutsideScreen = history
.lastWhere((element) => element.downObstacle.position.x <= 0);
obstaclesOutsideScreen.removeFromParent();
gameRef.remove(obstaclesOutsideScreen);
}
}
if (history.length >= 2) {
history.elementAt(history.length - 2).upObstacle.position.x -= value;
history.elementAt(history.length - 2).downObstacle.position.x -=
value;
}
}
}
}
void createObstacles() {
final obstaclesGap =
gameRef.screenHeight - gameRef.difficulty.spaceForPlayers;
final gapAboveTheObstacleOnScreen =
((2 * GameConst.obstacleHeight) - obstaclesGap) / 2;
final randomNumber = _randomGenerator(
min: -gameRef.difficulty.distanceAxisYToGoUpAndDown,
max: gameRef.difficulty.distanceAxisYToGoUpAndDown,
);
final axisYOfUpObstacle = (GameConst.obstacleHeight / 2) -
gapAboveTheObstacleOnScreen +
randomNumber;
final axisYOfDownObstacle = axisYOfUpObstacle +
GameConst.obstacleHeight +
gameRef.difficulty.spaceForPlayers;
final obstacles = Obstacles(
upObstacle: Obstacle(
obstacleType: ObstacleType.up,
position: Vector2(
gameRef.screenWidth + (GameConst.obstacleWidth / 2),
axisYOfUpObstacle,
),
),
downObstacle: Obstacle(
obstacleType: ObstacleType.down,
position: Vector2(
gameRef.screenWidth + (GameConst.obstacleWidth / 2),
axisYOfDownObstacle,
),
),
);
history.add(obstacles);
gameRef.add(obstacles);
}
double _randomGenerator({required double min, required double max}) {
final gap = (max - min).toInt();
return min + Random().nextInt(gap);
}
}
EDIT: The same problem applies in game over panel.
Game Over Panel
class GameOverPanel extends Component with HasGameRef<HopyBirdGame> {
GameOverPanel() : super();
#override
Future<void> onLoad() async {
add(GameOverText());
}
#override
void renderTree(Canvas canvas) {
if (gameRef.gameOver) {
super.renderTree(canvas);
}
}
}
class GameOverText extends SpriteComponent with HasGameRef<HopyBirdGame> {
GameOverText()
: super(
size: Vector2(300, 80),
anchor: Anchor.center,
);
#override
Future<void>? onLoad() async {
sprite = await gameRef.loadSprite(Assets.gameOver.path);
position = Vector2(gameRef.screenWidth / 2, gameRef.screenHeight / 2);
return super.onLoad();
}
}
Similarly, the first assignment is made in the Flame Game class.
I'm guessing (since the code for the ObstacleManager isn't present) that you are adding the obstacles directly to the game from the ObstacleManager to the component tree and that you aren't adding the priority to the actual Obstacle components.
If this is not the case, please update the question with the code for the ObstacleManager.
EDIT: After code upload.
It doesn't help that you are giving priorities to the manager classes when the components are not added as children to them, but to the gameRef. Since the player manager class isn't included in the question you either have to set the Obstacles to priority -1, or you have to give the player priority 1 (and obstacles will default to 0).
gameRef.add(obstacles..priority = -1);
This will make the obstacles be behind the background, so you have to also change the background parallaxes:
add(gameRef.homeBackgroundParallaxComponent..priority = -3);
add(gameRef.homeBaseParallax..priority = -2);
So it will be better to just set the player priority to 1 where you are adding that one to the game, because then you don't have to change all the other classes.
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,
);
From createBoundaries used in examples, I see how to create "Walls"/EdgeShape.
When moving the camera I get lines drawn. Du you know how to not draw these lines or make them transparent - looked in the FixtureDef?
import 'package:flame_forge2d/flame_forge2d.dart';
class Wall extends BodyComponent {
final Vector2 start;
final Vector2 end;
Wall(this.start, this.end);
#override
Body createBody() {
final shape = EdgeShape()..set(start, end);
final fixtureDef = FixtureDef(shape)
..restitution = 0.0
..friction = 0.3;
final bodyDef = BodyDef()
..userData = this // To be able to determine object in collision
..position = Vector2.zero()
..type = BodyType.static;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
Set renderBody = false or override render to be empty in the BodyComponent, Wall in this case.
I'm trying to create a polygon at the center of the screen with a mouse joint, very simple.
A CircleShape works great. Also the mouse joint behaves strangely and I couldn't find a pattern.
All code is in the main file here. I kept the code to a minimum.
Vector2 vec2Median(List<Vector2> vecs) {
var sum = Vector2(0, 0);
for (final v in vecs) {
sum += v;
}
return sum / vecs.length.toDouble();
}
void main() {
final game = MyGame();
runApp(GameWidget(game: game));
}
class MyGame extends Forge2DGame with MultiTouchDragDetector, HasTappables {
MouseJoint? mouseJoint;
static late BodyComponent grabbedBody;
late Body groundBody;
MyGame() : super(gravity: Vector2(0, -10.0));
#override
Future<void> onLoad() async {
final boundaries = createBoundaries(this); //Adding boundries
boundaries.forEach(add);
groundBody = world.createBody(BodyDef());
final center = screenToWorld(camera.viewport.effectiveSize / 2);
final poly = Polygon([
center + Vector2(0, 0),
center + Vector2(0, 5),
center + Vector2(5, 0),
center + Vector2(5, 5)
], bodyType: BodyType.dynamic);
add(poly);
grabbedBody = poly;
}
#override
bool onDragUpdate(int pointerId, DragUpdateInfo details) {
final mouseJointDef = MouseJointDef()
..maxForce = 3000 * grabbedBody.body.mass * 10 //Not neccerly needed
..dampingRatio = 1
..frequencyHz = 5
..target.setFrom(grabbedBody.body.position)
..collideConnected = false //Maybe set to true
..bodyA = groundBody
..bodyB = grabbedBody.body;
mouseJoint ??= world.createJoint(mouseJointDef) as MouseJoint;
mouseJoint?.setTarget(details.eventPosition.game);
return false;
}
#override
bool onDragEnd(int pointerId, DragEndInfo details) {
if (mouseJoint == null) {
return true;
}
world.destroyJoint(mouseJoint!);
mouseJoint = null;
return false;
}
}
abstract class TappableBodyComponent extends BodyComponent with Tappable {
final Vector2 position;
final BodyType bodyType;
TappableBodyComponent(this.position, {this.bodyType = BodyType.dynamic});
#override
bool onTapDown(_) {
MyGame.grabbedBody = this;
return false;
}
Body tappableBCreateBody(Shape shape) {
final fixtureDef = FixtureDef(shape)
..restitution = 0.8
..density = 1.0
..friction = 0.4;
final bodyDef = BodyDef()
// To be able to determine object in collision
..userData = this
..angularDamping = 0.8
..position = position
..type = bodyType;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class Polygon extends TappableBodyComponent {
final List<Vector2> vertecies;
Polygon(this.vertecies, {BodyType bodyType = BodyType.dynamic})
: super(vec2Median(vertecies), bodyType: bodyType);
#override
Body createBody() {
final shape = PolygonShape()..set(vertecies);
return tappableBCreateBody(shape);
}
}
tappableBCreateBody encapsulate Tappable and body creation methods, Polygon is the object I'm trying to create, vec2Median returns the center of the polygon (by vertices).
Thank you very much!
I think that you have to remove center from the vertices and only add that as the position of the BodyComponent instead, like you already do in the super call of your Polygon class.