Sometimes, instead of loading a sprite, it might be simpler to draw it in a canvas.
PositionComponent has a function that allows you to draw graphics as desired, it is called render.
render is similar to Flutter’s build inside every widget. It receives a canvas that allows you to paint on and draw what you want.
You’ll use it to draw the bullets of your spaceship in the game. Let’s see it in action.
Demo
Bullet
Start by defining Bullet in the same way as the other components.
import 'package:flame/components.dart';
class Bullet extends PositionComponent {
}
Next, add the basic measurements for the width, height, size and area.
The width is going to be of 12 and the height 4.
static const double bulletWidth = 12;
static const double bulletHeight = 4;
This makes the size a small 4 by 12 box.
static final Vector2 bulletSize = Vector2(bulletWidth, bulletHeight);
Let’s also make a rectangle that will work as our sprite area.
static const bulletRect = Rect.fromLTWH(
0,
0,
bulletWidth,
bulletHeight,
);
Now, create a new property for velocity and directionAngle. These two properties will tell the bullet at what speed to move and in which direction to do so.
final Vector2 velocity = Vector2.zero();
double directionAngle;
While you are at it, also make a constructor for Bullet, it should receive the initial position and the directionAngle that it should be pointing to.
Bullet({
required this.directionAngle,
super.position,
}) : super(size: Bullet.bulletSize);
directionAngle is important because it should be the same as Spaceship since the Bullet should only go to where the Spaceship is pointing to.
You should also override onLoad, inside it, let’s update the components angle to match directionAngle.
@override
FutureOr<void> onLoad() {
angle = directionAngle;
return super.onLoad();
}
It’s time for the fun stuff, working with render.
Create a void function that overrides render. It should receive canvas as a parameter.
@override
void render(Canvas canvas) {
}
Now, like a real-life canvas, you’ll need something to be able to draw in it. Create a new class variable _bulletPaint and initialize it to a new Paint.
final _bulletPaint = Paint()
You’ll also have to specify a style and a color. In this case, the Bullet should be filled with a white color.
..style = PaintingStyle.fill
..color = const Color(0xFFFFFFFF);
Great! With your _bulletPaint ready, you can start drawing in the canvas. Draw a rectangle using drawRect from canvas. Pass bulletRect and _bulletPaint as parameters. These are the size and paint of the rectangle.
canvas.drawRect(Bullet.bulletRect, _bulletPaint);
Then, override update. You’ll want to also move the component’s position and adjust velocity in relation to the default speed of the game and the direction at which it should be moving.
@override
void update(double dt) {
velocity.x = GameConstants.bulletSpeed * cos(directionAngle);
velocity.y = GameConstants.bulletSpeed * sin(directionAngle);
position += velocity * dt;
super.update(dt);
}
Also, remember to call removeFromParent when the component goes off-screen.
if (position.x > GameConstants.cameraWidth || position.x < 0) {
removeFromParent();
}
if (position.y > GameConstants.cameraHeight || position.y < 0) {
removeFromParent();
}
MeteormaniaGame
It’s time to add Bullet to the game world.
Add a new property to MeteormaniaGame to indicate whether the action for shooting is active or not.
bool _isShooting = false;
Now, for shooting a bullet you’ll need two things: checking if the spaceship is already shooting and the direction that the player’s Spaceship is facing towards.
Since that is defined internally in initializeGame, you’ll need to pull out the variable to be a class property.
Declare a new class property for the _spaceship.
Spaceship? _spaceship;
Then, in initializeGame, update the code to initialize _spaceship instead and add it to _world.
_spaceship = Spaceship()
..anchor = Anchor.center
..position = Vector2(
GameConstants.cameraWidth / 2,
GameConstants.cameraHeight / 2,
);
Since the class property is nullable, you’ll have to use ! operator when calling add
_world.add(_spaceship!);
Now, create a new function called shootBullet.
void shootBullet() {
}
Start by checking that _isShooting is false and also let’s use manager to check that the game is still going with isGameOver.
if (!_isShooting && !manager.isGameOver) {
}
Then, set _isShooting to true.
_isShooting = true;
You might have to remove final from variable declaration if your IDE is set to format on save.
Use _world to add a new Bullet component. As directionAngle you’ll want to pass the angle that _spaceship is facing, but you’ll also have to adjust it since the sprite is pointing upwards.
This is not as complicated as it sounds since you can just add 3 * pi / 2.
_world.add(
Bullet(directionAngle: _spaceship!.angle + 3 * pi / 2)
..anchor = Anchor.center
..position = _spaceship!.position,
);
3 * pi / 2 is equivalent to 45 degrees, by default the sprite for Spaceship starts 45 degrees. So adding it to _spaceship.angle gives you a new directionAngle that’s slightly rotated to match what Bullet needs.
Set _isShooting back to false.
_isShooting = false;
As the final step, call shootBullet inside onTapUp to shoot bullets when the player taps on the screen.
shootBullet();
Build and run Meteormania and tap to test out Bullet.