Typescript Keyboard Input to move game objects

So now that we have some basic keyboard input and are using it to move around an asteroid, we need to take this a little further and add a space ship that we can steer around the asteroids. Later we can add bullets and the ability for the space ship to shoot them. Let's start of by creating the ship object.

class cSpaceShip implements iShape {
 public velocity: cVector = new cVector(0, 0);
 public orientation: cVector = new cVector(1, 0);
 public maxSpeedSQ: number = 100;
 private _maxSpeed: number = 10;
 public acceleration: number = 0.2;
 public x: number = 0;
 public y: number = 0;
 public lineWidth: number = 5;
 public color: string = "white";
 public size: number = 20;
 public rotation: number = 0;
 public pointList: Array<cVector> = new Array<cVector>();

 private _tempPoint: cVector = new cVector(0, 0);

 public accelerate(): void {
  if (this.velocity.x == 0 && this.velocity.y == 0) {
   this.velocity.copy(this.orientation);
   this.velocity.multiply(this.acceleration);
  }

  this._tempPoint.copy(this.orientation);
  this._tempPoint.multiply(this.acceleration);
  this.velocity.add(this._tempPoint);
  if (this.velocity.magSq() >= this.maxSpeedSQ) {
   this.velocity.multiply(this.maxSpeed / this.velocity.magnitude());
  }
 }

 public decelerate(): void {
  this.velocity.multiply(0.9);

  if (this.velocity.magSq() < 1) {
   this.velocity.x = 0;
   this.velocity.y = 0;
  }
 }

 get maxSpeed(): number {
  return Math.sqrt(this.maxSpeedSQ);
 }

 set maxSpeed(value: number) {
  this._maxSpeed = value;
  this.maxSpeedSQ = value * value;
 }

 public draw = (): void => {
  this.x += this.velocity.x;
  this.y += this.velocity.y;

  if (this.x < -this.size * 2) {
   this.x = 1280 + this.size * 2;
  }
  else if (this.x > 1280 + this.size * 2) {
   this.x = -2 * this.size;
  }

  if (this.y < -this.size * 2) {
   this.y = 720 + this.size * 2;
  }
  else if (this.y > 720 + this.size * 2) {
   this.y = -2 * this.size;
  }

  ctx.save();
  ctx.translate(this.x, this.y);
  ctx.rotate(this.rotation);
  ctx.beginPath();

  ctx.strokeStyle = this.color;
  ctx.lineWidth = this.lineWidth;

  ctx.moveTo(this.pointList[this.pointList.length - 1].x, this.pointList[this.pointList.length - 1].y);

  for (var i: number = 0; i < this.pointList.length; i++) {
   ctx.lineTo(this.pointList[i].x, this.pointList[i].y);
  }

  ctx.closePath();

  ctx.stroke();
  ctx.restore();
 }

 public turnLeft = (): void => {
  this.rotation -= 0.1;
  if (this.rotation < 0) {
   this.rotation += Math.PI * 2;
  }
  this.orientation.x = 1;
  this.orientation.y = 0;
  this.orientation.rotate(-this.rotation);
 }

 public turnRight = (): void => {
  this.rotation += 0.1;
  this.rotation %= Math.PI * 2;
  this.orientation.x = 1;
  this.orientation.y = 0;
  this.orientation.rotate(-this.rotation);
 }

 constructor(x: number, y: number, size: number, color: string = "white", line_width: number = 2) {
  this.x = x;
  this.y = y;
  this.size = size;

  this.pointList.push(new cVector(3 * size, 0));
  this.pointList.push(new cVector(-2 * size, -2 * size));
  this.pointList.push(new cVector(-1 * size, 0));
  this.pointList.push(new cVector(-2 * size, 2 * size));

  this.color = color;
  this.lineWidth = line_width;
 }

}

The movement of our Space Ship is going to be a lot different than the simplified movement we put on the asteroid when you pressed the arrow keys. The Space Ship will need to rotate on the left and right arrow keys, accelerate when you press the up key, and decelerate when you press the down arrow key.

New Vector Class

Like the asteroid object we defined in the earlier tutorial, we are going to need to create a point list and push in values. One difference is we will be changing the cPoint class to a cVector class. The cPoint class was a simple x,y pair of coordinate values. cVector is going to have many additional methods and attributes we will need to use as our game gets more advanced. Below I have the code for the new cVector class that we are using to replace the cPoint class.

class cVector {
 public x: number = 0;
 public y: number = 0;

 constructor(x: number = 0, y: number = 0) {
  this.x = x;
  this.y = y;
 }

 public magnitude = (): number => {
  return Math.sqrt(this.x * this.x + this.y * this.y);
 }

 public magSq = (): number => {
  return this.x * this.x + this.y * this.y;
 }

 public normalize = (magnitude: number = 1): cVector => {
  var len: number = Math.sqrt(this.x * this.x + this.y * this.y);
  this.x /= len;
  this.y /= len;
  return this;
 }

 public zero = (): void => {
  this.x = 0;
  this.y = 0;
 }

 public copy = (point: cVector): void => {
  this.x = point.x;
  this.y = point.y;
 }

 public rotate = (radians: number): void => {
  var cos: number = Math.cos(radians);
  var sin: number = Math.sin(radians);
  var x: number = (cos * this.x) + (sin * this.y);
  var y: number = (cos * this.y) - (sin * this.x);
  this.x = x;
  this.y = y;
 }

 public getAngle = (): number => {
  return Math.atan2(this.y, this.x);
 }

 public multiply = (value: number): void => {
  this.x *= value;
  this.y *= value;
 }

 public add = (value: cVector): void => {
  this.x += value.x;
  this.y += value.y;
 }

 public subtract = (value: cVector): void => {
  this.x -= value.x;
  this.y -= value.y;
 }

}

A vector is a property that has both magnitude and direction. Because this is a 2D game, we need an x and a y value for this vector. However, unlike a simple point, there are many more methods we can give to a vector. We can add or subtract two vectors. We can multiply the vectors by a scalar value. We can get the angle of this vector. We can also rotate the vector around 0,0. We can normalize the vector, or set it's magnitude to a specific value. We can also get the current magnitude or the squared value of the magnitude. All these methods will be used eventually as we write our game and I'll try to explain their usefullness as we go.

Changing the keyboard input

So now we need to change the keyboard input function to turn the ship left and right, accelerate it and decelerate it. We will also need to add an instance of the space ship to represent the player's space ship.

Here's what the new keyboarInput function will look like:

var space_ship: cSpaceShip = new cSpaceShip(200, 450, 5);

function keyboardInput(event: KeyboardEvent) {
   // PRESS LEFT ARROW OR 'A' KEY
   if (event.keyCode == 37 || event.keyCode == 65) {
      space_ship.turnLeft();
   }
   // PRESS UP ARROW OR 'W' KEY
   else if (event.keyCode == 38 || event.keyCode == 87 ) {
      space_ship.accelerate();
   }
   // PRESS RIGHT ARROW OR 'D' KEY
   else if (event.keyCode == 39 || event.keyCode == 68 ) {
      space_ship.turnRight();
   }
   // PRESS DOWN ARROW OR 'S' KEY
   else if (event.keyCode == 40 || event.keyCode == 83 ) {
      space_ship.decelerate();
   }
}

Later we can add Space Bar input and map that to the ship shooting. Right now we haven't defined the weapon, so we're gonig to need to come back to that.

Velocity and our Player's Space Ship

When you press the Up Arrow key, or the W key you are accelerating your ship in the direction it is facing. We are using some basic trigonometry. We need to keep track of an 'orientation' vector, and a 'velocity' vector. The velocity vector keeps track of the speed and direction the ship is actually moving. When you change direction the new acceleration will add to the velocity in the direction of your new orientation.

Let's take another look at our acceleration code in the cSpaceShip class to see how this works.

 public accelerate(): void {
  if (this.velocity.x == 0 && this.velocity.y == 0) {
   this.velocity.copy(this.orientation);
   this.velocity.multiply(this.acceleration);
  }

  this._tempPoint.copy(this.orientation);
  this._tempPoint.multiply(this.acceleration);
  this.velocity.add(this._tempPoint);
  if (this.velocity.magSq() >= this.maxSpeedSQ) {
   this.velocity.multiply(this.maxSpeed / this.velocity.magnitude());
  }
 }

The basics of the accelerate method is to take the ship's current orientation, multiply that by the acceleration scalar value, and add it to the velocity vector. If the Velocity vector has a magnitude longer than the maximum speed, we have to limit that value to the maximum speed. Turning the ship with the left and right arrow keys has to change both the rotation value of the ship as well as the orientation vector. Here's that code from the cSpaceShip object.

 public turnLeft = (): void => {
  this.rotation -= 0.1;
  if (this.rotation < 0) {
   this.rotation += Math.PI * 2;
  }
  this.orientation.x = 1;
  this.orientation.y = 0;
  this.orientation.rotate(-this.rotation);
 }

 public turnRight = (): void => {
  this.rotation += 0.1;
  this.rotation %= Math.PI * 2;
  this.orientation.x = 1;
  this.orientation.y = 0;
  this.orientation.rotate(-this.rotation);
 }

This is what we have so far. I would recommend using the WASD keys to move the ship around

Check out the full source code we're using for the above app.

Part 3 - Projectiles and simple object pooling

Part 1 - Basic Keyboard Input

TypeScript Key Codes