TankCombat series of articles
If you don’t know about Flame, check it out here:
A look at Flutter’s performance in game development and its cross-platform advantages
Flutter&Flame — TankCombat game development (part 1)
Flutter&Flame — TankCombat game development part 2
Flutter&Flame — TankCombat game development part 3
Flutter&Flame — TankCombat game development (4)
rendering
Pretty good, let me add it to give you an overall impression of what you are doing 🙂
starts
In this chapter, we start making designs for firing shells and enemy tanks
fire
Remember this code?
Row(children: [SizedBox(width: 48)), FireButton(onTap: tankGame.onFireButtonTap, ), Spacer(), FireButton( onTap: tankGame.onFireButtonTap, ), SizedBox(width: 48), ], ),Copy the code
In the runApp method in the main function, these are our fire buttons. As you can see, the click event triggers the onFireButtonTap method in the game. Let’s see how it works:
void onFireButtonTap(){ if(blueBulletNum < 20){ bullets.add(Bullet(this,BulletColor.BLUE,tank.tankId ,position: tank.getBulletOffset(),angle: tank.getBulletAngle())); }}Copy the code
Add a bullet to the bullets(list) and pass the location of the tank and the game object to the bullet. The other two parameters are left out. Let’s start with the class bullet
Bullet
First let’s make bullet integrate with baseComponent and create some variables. You can abstract bullets into tanks so they are essentially indistinguishable and even simpler
As follows:
class Bullet extends BaseComponent{ final TankGame game; final double speed; // Bullet speed Offset position; // Double Angle = 0; // Bullet Angle bool isOffScreen = false; Final Sprite blueSprite = Sprite('tank/bullet_blue.webp'), // Whether to hit bool isHit = false; }Copy the code
Now that some of the bullet’s basic properties are declared, we can manipulate the bullet in the Render and Update methods. Look at the update:
@override void update(double t) {// If (isHit) return; if(isOffScreen)return; Position = position + offset.fromdirection (Angle,speed * t); position = position + Offset (Angle,speed * t); IsOffScreen if (position.dx < -50) {isOffScreen = true; } if (position.dx > game.screenSize.width + 50) { isOffScreen = true; } if (position.dy < -50) { isOffScreen = true; } if (position.dy > game.screenSize.height + 50) { isOffScreen = true; }}Copy the code
Look at the render:
@override void render(Canvas Canvas) {// If (isHit) return; // If (isHit) return; if(isOffScreen)return; canvas.save(); Canvas. Translate (position.dx, position.dy); canvas.rotate(angle); BlueSprite. RenderRect (canvas, rect. fromLTWH(-4, -2, 8, 4)); canvas.restore(); }Copy the code
Ok, that’s it, and now we’re back in the game
TankGame
All components need to be associated with the game, otherwise there is no way to update and render the screen (only the game).
Since we’re sure there’s more than one bullet, let’s create a list
List<Bullet> bullets; / / shellsCopy the code
It is then instantiated in resize
@override void resize(Size size) { screenSize = size; //initEnemyTank(); if(bg == null){ bg = BattleBackground(this); } if(tank == null){ tank = Tank( this,position: Offset(screenSize.width/2,screenSize.height/2), ); } if(bullets == null){ bullets = List(); }}Copy the code
We then pass it the key parameter t in update and call the bullet’s update method
@override void update(double t) {bullets. ForEach ((element) {// bullets element.update(t); } / / remove bullets fly out of the screen. The removeWhere ((element) = > element. The isHit | | element. IsOffScreen); }Copy the code
We call the render method of the bullet in the Render method and pass the canvas to it
@override
void render(Canvas canvas) {
bg.render(canvas);
//tank
tank.render(canvas);
//bullet
bullets.forEach((element) {
element.render(canvas);
});
}
Copy the code
Now that we have completed the function of the tank firing shells, let’s go through the general process:
The above images can also help you understand how Component/Sprite works in game.
Right now we can fire shells, but we can’t hit people. In other words, we need to add some enemies first.
The enemy tank TankModel
There are many functions that can be shared between enemy tanks and player tanks. Let’s first abstract a TankModel for enemy tanks:
abstract class TankModel{ final int id; final TankGame game; Sprite bodySprite,turretSprite; // Offset position; TankModel(this.game,this.bodySprite,this.turretSprite,this.position): id = DateTime.now().millisecondsSinceEpoch+Random().nextInt(100); Final int seedNum = 50; final int seedRatio = 2; Double movedDis = 0; // Final double speed = 80; // Final double turnSpeed = 40; // double bodyAngle = 0; // Double turretAngle = 0; // Double targetBodyAngle; // Double targetTurretAngle; //tank alive bool isDead = false; // Move to target position Offset targetOffset; Final double ration = 0.7; // Obtain the bulletoffset (); /// double getBulletAngle(); }Copy the code
It’s a bunch of properties. There’s nothing to talk about. Now we are building tanks based on the model, here we will build a green enemy tank
GreenTank
First we inherit tankModel and then mix in baseComponent as follows:
Class GreenTank extends TankModel with BaseComponent{// Tank bodyRect; // Tank barrel Rect turretRect; GreenTank(TankGame game, Sprite bodySprite, Sprite turretSprite,Offset position) : super(game, bodySprite, turretSprite,position){ bodyRect = Rect.fromLTWH(-20*ration, -15*ration, 38*ration, 32*ration); turretRect = Rect.fromLTWH(-1, -2*ration, 22*ration, 6*ration); generateTargetOffset(); } void generateTargetOffset(){ double x = Random().nextDouble() * (game.screenSize.width - (seedNum * seedRatio)); double y = Random().nextDouble() * (game.screenSize.height - (seedNum * seedRatio)); targetOffset = Offset(x,y); Offset temp = targetOffset - position; targetBodyAngle = temp.direction; targetTurretAngle = temp.direction; } @override void render(Canvas canvas) { if(isDead) return; drawBody(canvas); } @override void update(double t) { rotateBody(t); rotateTurret(t); moveTank(t); }}Copy the code
The constructor initializes some basic properties, which we’ve already covered and won’t repeat. Let’s look at this extra method
void generateTargetOffset(){
double x = Random().nextDouble() * (game.screenSize.width - (seedNum * seedRatio));
double y = Random().nextDouble() * (game.screenSize.height - (seedNum * seedRatio));
targetOffset = Offset(x,y);
Offset temp = targetOffset - position;
targetBodyAngle = temp.direction;
targetTurretAngle = temp.direction;
}
Copy the code
This method is used to generate a random target point for the tank to drive through, and from the target point we save the target Angle (turret and hull).
The render and update functions are basically the same as before, except for moveTank (t), which looks like this:
void moveTank(double t) { if(targetBodyAngle ! = null){ if(targetOffset ! MovedDis += speed * t; If (movedDis < 100){if(bodyAngle == targetBodyAngle){//tank position = position + Offset.fromDirection(bodyAngle,speed*t); Position = position + Offset. FromDirection (bodyAngle,turnSpeed*t); }}else{// We recalculate the new target point movedDis = 0; generateTargetOffset(); }}}}Copy the code
After the above movement, our enemy tanks will no longer “head to the south wall”, but walk a distance will turn itself, more flexible and vivid.
Ok, the enemy tanks are complete, we are starting to combine them with game
Combination of start
TankGame
We added two lists to the game to manage enemy tanks of two colors
List<GreenTank> gTanks = [];
List<SandTank> sTanks = [];
Copy the code
We then initialize four enemy tanks in the tankGame constructor initialization
TankGame(){ observer = GameObserver(this); initEnemyTank(); } void initEnemyTank() {var turretSprite = Sprite('tank/t_turret_green.webp'); var bodySprite= Sprite('tank/t_body_green.webp'); GTanks. Add (GreenTank (this, bodySprite turretSprite, Offset (100100))); GTanks. Add (GreenTank (this, bodySprite turretSprite, Offset (100, screenSize. Height * 0.8))); ///sand var turretSpriteS = Sprite('tank/t_turret_sand.webp'); var bodySpriteS = Sprite('tank/t_body_sand.webp'); STanks. Add (SandTank (this, bodySpriteS turretSpriteS, Offset (screenSize. Width - 100100))); STanks. Add (SandTank (this, bodySpriteS turretSpriteS, Offset (screenSize. Width - 100, screenSize. Height * 0.8))); }Copy the code
Now we have two tanks ready to go in our warehouses gTanks and sTanks. Now start them!
In the update and render methods we add the following code:
update
gTanks.forEach((element) { element.update(t); }); sTanks.forEach((element) { element.update(t); }); // Remove death tank gTanks. RemoveWhere ((element) => element.isdead); sTanks.removeWhere((element) => element.isDead);Copy the code
render
gTanks.forEach((element) {
element.render(canvas);
});
sTanks.forEach((element) {
element.render(canvas);
});
Copy the code
Functions have been mentioned above.
Now when you run it, you can see 4 enemy tanks running all over the map, but they don’t fire yet, let’s add this feature.
Computer firing function
First of all, we consider that the blue, green and yellow tank shells are different, and other functions may be added later. In order to distinguish them, we will add an enumeration to the bullet class file:
enum BulletColor{
BLUE,GREEN,SAND
}
Copy the code
Then we added a way for enemy tanks to fire in the game:
void enemyTankFire<T extends TankModel>(BulletColor color,T tankModel){
bullets.add(Bullet(this,color,tankModel.id
,position: tankModel.getBulletOffset(),angle: tankModel.getBulletAngle()));
}
Copy the code
The principle is the same as the player tank firing. To avoid too many shells getting stuck, we increased the ammo limit for the enemy
Add two variables to game
Int sandBulletNum = 0; Int blueBulletNum = 0;Copy the code
The game update method counts the number of shells on screen
blueBulletNum = 0;
greenBulletNum = 0;
sandBulletNum = 0;
bullets.forEach((element) {
switch(element.bulletColor){
case BulletColor.BLUE:
blueBulletNum ++;
break;
case BulletColor.GREEN:
greenBulletNum ++;
break;
case BulletColor.SAND:
sandBulletNum ++;
break;
}
element.update(t);
});
Copy the code
We then added two lines of code to the enemy tank greenTank update method:
@override void update(double t) { rotateBody(t); rotateTurret(t); moveTank(t); If (game.greenbulletnum < 10){game.enemytankfire (bulletcolor.green, this); }}Copy the code
Now when we run it, we’ll see the enemy’s small tanks running all over the place, turning their turrets and firing!
Ok, we’re almost done. In the next chapter we’ll add the ability to destroy tanks with shells and explosions, as well as the GameObserver design.
Thanks for reading, give a like if you like :)Copy the code
DEMO
Tanks war