DEV Community

HarmonyOS
HarmonyOS

Posted on

Building Space Shooter with ArkTS-2

Read the original article:Building Space Shooter with ArkTS-2

Introduction

Welcome back, everybody. In the first part, we have created the main user interface and implemented core functions for our game. Now we will add enemies and bullets. If you haven't seen part 1, check it out here.

Enemies

We will use the Ship class for enemies. Our game will have multiple enemies, thus we will store them in an array.

  • Define the array:
// Game.ets
private enemies: Ship[] = []
Enter fullscreen mode Exit fullscreen mode
  • We will add enemies every 3 seconds, and decrease the gap as the user gains score.
// Game.ets
private lastEnemyTime = Date.now()

// iterate()
// place after clear canvas
const now = Date.now()
// add enemy
if (now - this.lastEnemyTime >= Math.max(1000, 3000 - this.score * 50)) {
  this.lastEnemyTime = now
  this.enemies.push(new Ship(
      this.canvasWidth, 
      this.canvasHeight, 
      this.PIXELSIZE, 
      ShipType.ENEMY))
}
Enter fullscreen mode Exit fullscreen mode
  • And draw them;
// draw enemies (just before the user)
this.enemies.forEach((enemy): void => enemy.draw(this.canvasContext))
Enter fullscreen mode Exit fullscreen mode

Now we have enemies, but you cannot see them yet. Because we create them just outside the top edge of the canvas, let's move them.

Since our ships are 7 PIXELSIZE long, we will move them 7 PIXELSIZE every second. Since we update our screen FRAMECOUNT times in a second, we will move the enemies PIXELSIZE / (FRAMECOUNT / 7) long in every iteration.

// Ship
private frameCount: number; // define parameter

constructor(..., frameCount: number) // update constructor
  this.frameCount = frameCount
  // ...
}

// define move
moveForward() {
  this.leftTop.y += this.pixelSize / (this.frameCount / 7)
}
Enter fullscreen mode Exit fullscreen mode

Do not forget to update user ship and enemy initializations in the Game class.

We also need a way to remove enemies from the list when they disappear from the screen.

// Ship
// define parameters
private canvasWidth: number;
private canvasHeight: number;

// modify constructor
constructor(...) {
  this.canvasWidth = canvasW
  this.canvasHeight = canvasH
  ...
}

// define function
inGame(): boolean {
  return this.leftTop.y < this.canvasHeight
}
Enter fullscreen mode Exit fullscreen mode

Go to iteration() and move enemies.

// move enemies (just after clearing the canvas)
this.enemies.forEach((enemy): void => enemy.moveForward())
Enter fullscreen mode Exit fullscreen mode

Decrement the user's lives and remove enemies that pass the user.

// decrement lives
for (let i = 0; i < this.enemies.length; i++) {
  if (!this.enemies[i].inGame()) {
    this.lives--

    if (this.lives === 0) {
      this.end()
      return
    }
  }
}
// remove enemies
this.enemies = this.enemies.filter((enemy) => enemy.inGame())
Enter fullscreen mode Exit fullscreen mode

We will use the end() function to stop the interval, thus the game.

private end() {
  clearInterval(this.intervalId)
  this.intervalId = undefined
}
Enter fullscreen mode Exit fullscreen mode

Let's run the app and face our enemies.

Bullet

So, the time has come. Create Bullet.ets and place under the model directory.

import Point from './Point';

export default class Bullet {
  private pixelSize: number
  point: Point;
  hit: boolean = false

  constructor(size: number, point: Point) {
    this.pixelSize = size
    this.point = point
  }

  inGame(): boolean {
    return this.point.y > 0
  }

  move() {
    this.point.y -= this.pixelSize / 2
  }

  draw(ctx: CanvasRenderingContext2D) {
    ctx.fillStyle = '#FF0000'
    ctx.fillRect(this.point.x, this.point.y, this.pixelSize, this.pixelSize)
  }

  clear(ctx: CanvasRenderingContext2D) {
    ctx.clearRect(this.point.x, this.point.y, this.pixelSize, this.pixelSize)
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's modify our ship to shoot.

// Ship.ets

fire(): Bullet {
  return new Bullet(this.pixelSize,
    {
      x: this.leftTop.x + this.pixelSize * 2,
      y: this.leftTop.y - this.pixelSize
    })
}
Enter fullscreen mode Exit fullscreen mode

Back to the Game.

private bullets: Bullet[] = []
private lastFireTime = Date.now()
Enter fullscreen mode Exit fullscreen mode

Now the iterate(). We will add new bullets and move them.

  • Move:
// this line already exists
this.enemies = this.enemies.filter((enemy) => enemy.inGame())

// add these
// move bullets
this.bullets.forEach((bullet): void => bullet.move())
this.bullets = this.bullets.filter((bullet) => bullet.inGame())
Enter fullscreen mode Exit fullscreen mode
  • Add new:
// add enemy
if (...) {...}

// fire
if (now - this.lastFireTime >= 1000) {
  this.lastFireTime = now
  this.bullets.push(this.userShip?.fire())
}

// draw bullets
this.bullets.forEach((bullet): void => bullet.draw(this.canvasContext))
Enter fullscreen mode Exit fullscreen mode

The result should look like this.

You see that our bullets do not have any effect on the enemies. Let's change it.

  • Modify the ship to make it destructible.
hit: boolean = false

checkHit(bullet: Bullet): boolean {
  if (bullet.point.y <= this.leftTop.y + this.pixelSize * 3 &&
    bullet.point.y >= this.leftTop.y + this.pixelSize * 2 &&
    bullet.point.x >= this.leftTop.x - this.pixelSize / 2 &&
    bullet.point.x <= this.leftTop.x + this.pixelSize * 4.5) {
    this.hit = true
    bullet.hit = true
    return true
  }

  return false
}
Enter fullscreen mode Exit fullscreen mode
  • And clear it from existence.
clear(ctx: CanvasRenderingContext2D) {
  ctx.clearRect(
    this.leftTop.x, this.leftTop.y, this.pixelSize * 5, this.pixelSize * 6
  )
}
Enter fullscreen mode Exit fullscreen mode
  • Now the iterate().
// at the end of the function
for (let i = 0; i < this.enemies.length; i++) {
  for (let j = 0; j < this.bullets.length; j++) {
    if (this.enemies[i].checkHit(this.bullets[j])) {
      this.enemies[i].clear(this.canvasContext)
      this.bullets[j].clear(this.canvasContext)
      this.score++
    }
  }
}
this.enemies = this.enemies.filter((enemy) => !enemy.hit)
this.bullets = this.bullets.filter((bullet) => !bullet.hit)
Enter fullscreen mode Exit fullscreen mode

Now you can hit your enemies.

Conclusion

We have implemented enemies and bullets, but we cannot do much without moving, right? We will continue in the third part.

See you all in new adventures. :)

~ Fortuna Favet Fortibus

Written by Mehmet Karaaslan

Top comments (0)