5️⃣

Ch. 5 GameObject subclasses

Tank Subclass

First we will create the class:
class Tank extends GameObject { //code }
 
The first method that we need to add to the tank subclass is the constructor. If we look back at the GameObject class and look at that constructor, we put a boolean as true so that this game object can be moveable.
constructor(x, y, player) { super(x, y, TANK_SIZE, TANK_SIZE, true); // Movable=true this.player = player; this.speed = 1; this.turn_speed = 5; this.fire_delay = 0; this.max_fire_delay = 30; this.max_ammo = 5; this.ammo = this.max_ammo; this.max_hp = 10; this.hp = this.max_hp; this.color_by_damage(); // Add a gun this.set_gun(GunTypes.normal); var w = this.width / 2; var h = this.height / 2; var last_vert = this.verts.pop(); this.verts.push(new Vector2d(w, h/2)); this.verts.push(new Vector2d(w*2, h/2)); this.verts.push(new Vector2d(w*2, -h/2)); this.verts.push(new Vector2d(w, -h/2)); this.verts.push(last_vert); }
 
If we look back to the gun class, we incorporate the class here by adding a gun to the tank. The this.verts.push is where the gun is located on the tank.
Next we need to have a method called set_gun. The point of this is so that each tank can switch between guns, otherwise there is no point of having multiple guns. The body of this method will be a switch statement that depends on the type of gun.
set_gun(type) { GUI_REDRAW_NEEDED = true; // GUI has info about player weapons switch (type) { case GunTypes.normal : this.gun = new Gun(this); break; case GunTypes.machinegun : this.gun = new Machinegun(this); break; case GunTypes.heavy : this.gun = new Heavygun(this); break; default : console.log("Invalid gun type given " + type); this.gun = new Gun(this); break; } }
 
A very important feature is that each tank can be destroyed. When a tank gets destroyed, a point needs to be added to the other player's score.
destroy() { END_ROUND = true; this.player === P1 ? P2_SCORE++ : P1_SCORE++; for (var i = 0; i < 360; i += 60) { // Spawn a ring of bullets on death var radians = deg2rad(i); var damage = 4; var off_x = this.width * Math.cos(radians); var off_y = this.width * Math.sin(radians); new Bullet(this.pos.x + off_x, this.pos.y + off_y, i, damage); } super.destroy(); }
 
The next method that we will add is the update method. We constantly need to update, because we want live updates, and the only way we can achieve that is if we update constantly. The key word that is used a lot here is P#_direction. This means if a player 1 or 2, goes up, down, left, right it updates the screen as well as changes the speed. Another thing that we need to create is for the tank to change direction so that is why we use some trig functions.
update() { /* Checks for user input and checks collisions. */ if (this.fire_delay > 0) this.fire_delay--; var p = this.player; var radians = deg2rad(this.rotation); if ((p == P1 && KEYSTATE[P1_UP]) || (p == P2 && KEYSTATE[P2_UP])) { this.velocity.x = this.speed * Math.cos(radians); this.velocity.y = this.speed * Math.sin(radians); } else if ((p == P1 && KEYSTATE[P1_DOWN]) || (p == P2 && KEYSTATE[P2_DOWN])) { this.velocity.x = -this.speed * Math.cos(radians); this.velocity.y = -this.speed * Math.sin(radians); } else { this.velocity = new Vector2d(0, 0); } if ((p == P1 && KEYSTATE[P1_LEFT]) || (p == P2 && KEYSTATE[P2_LEFT])) { this.rotate(-this.turn_speed); } else if ((p == P1 && KEYSTATE[P1_RIGHT]) || (p == P2 && KEYSTATE[P2_RIGHT])) { this.rotate(this.turn_speed); } super.update(); // Move and check collisions before firing if ((p == P1 && KEYSTATE[P1_FIRE]) || (p == P2 && KEYSTATE[P2_FIRE])) { if (this.gun.ammo > 0) { var off_x = this.width * 0.9 * Math.cos(radians); var off_y = this.width * 0.9 * Math.sin(radians); this.gun.fire(this.pos.x + off_x, this.pos.y + off_y, this.rotation); } else { this.set_gun(GunTypes.normal); // Replace all special guns with a regular gun } } } };
 
We also will add a PowerupType method so we can associate a number with a type of gun.
var PowerupType = { 'machinegun' : 1, 'heavy' : 2, 'speed' : 3, 'get_random_type' : function get_random_type() { return Math.ceil(Math.random() * 3); } };

PowerUp Subclass

This class is relatively short, because all we need to do is change the values of the constructor, and for a visual aspect, change the color of the tank.
constructor(x, y, type) { super(x, y, 10, 10, true); this.type = type; this.max_hp = 20; this.hp = this.max_hp; this.last_damage_tick = Date.now(); // Cause damage to self every second this.turn_speed = 5; this.re_color(); }
 
For the next method we will add a switch statement because depending on the gun type, the color of the tank should change color.
re_color() { switch(this.type) { case PowerupType.machinegun: this.color = {"r":0, "g": 200, "b": 200}; break; case PowerupType.heavy: this.color = {"r":0, "g": 50, "b": 120}; break; case PowerupType.speed: this.color = {"r":0, "g": 255, "b": 255}; break; default: console.log("Powerup has invalid type!"); this.color = {"r":0, "g": 10, "b": 10}; } }
 
You can change the values of red, green and blue if you can the color of the tank to be different. https://redketchup.io/color-picker You can use this website to choose a color.
Now we need to update the tank, because we need to update it to a powerup or when it gets damaged by a powerup from the other player.
update() { this.rotate(this.turn_speed); if (Date.now() - this.last_damage_tick > 1000) { this.last_damage_tick = Date.now(); this.damage(1); // Max time to live is 20 seconds; } super.update(); }
 
Our last method is on_collision because we need to set the gun type based on each powerup. For this we will use a switch statement.
on_collision(obj) { if (obj instanceof Tank) { switch (this.type) { case PowerupType.machinegun: obj.set_gun(GunTypes.machinegun); break; case PowerupType.heavy: obj.set_gun(GunTypes.heavy); break; case PowerupType.speed: obj.speed++; break; default: console.log("Powerup has invalid type!"); } this.destroy(); } } };

Bullet subclass

Bullet subclass:
class Bullet extends GameObject { //code }
 
Our constructor contains a lot of values from trial and error, for how fast you want a bullet to move. And there are bounces for how much you want a bullet to bounce against the wall.
constructor(x, y, direction, damage, gun, size, speed) { if (typeof speed == 'undefined') speed = 1.5; if (typeof size == 'undefined') size = 5; super(x, y, size, size, true); this.max_time_to_live = 15000; // ^ After this time the bullet disappears (milliseconds) this.remaining_bounces = 10; this.first_bounce = true; this.spawn_time = Date.now(); this.ignore_owner_for = 3 * size / speed; // ^ HACK: this value is just randomly guessed (milliseconds) this.speed = speed; this.gun = gun; if (this.gun && this.gun.tank) this.ignored_collision_objs.push(this.gun.tank); // ^ Don't collide with tank before first bounce this.color.r = Math.round(Math.random() * 255); this.color.g = Math.round(Math.random() * 255); this.color.b = Math.round(Math.random() * 255); var radians = deg2rad(direction); this.velocity.x = this.speed * Math.cos(radians); this.velocity.y = this.speed * Math.sin(radians); this.damage_amount = damage; this.radius = this.width/2; this.circle = true; }
 
For our update method we are just saying that if the bullet exists too long we delete it.
update() { super.update(); if (Date.now() - this.spawn_time > this.max_time_to_live) { // This bullet has been around long enough, destroy it this.destroy(); } }
 
For the on_collision method, we just specify how much less damage it does the more walls it bounces off.
on_collision(obj) { this.remaining_bounces--; if (this.first_bounce && (Date.now() - this.spawn_time > this.ignore_owner_for) && this.gun && this.gun.tank) { // After first bounce bullet can collide with the shooting tank this.first_bounce = false; var ind = this.ignored_collision_objs.indexOf(this.gun.tank); if (ind > -1) delete this.ignored_collision_objs[ind]; } if (this.remaining_bounces < 1) { this.destroy(); } obj.damage(this.damage_amount); if (obj instanceof Tank) { this.destroy(); } }
 
This is just to destroy the gun because when we delete the tank, the gun isnt also deleted.
destroy() { if (this.gun) this.gun.reload(); super.destroy(); } };
 
ALL OF CHAPTER FIVE CODE https://pastebin.com/dx4QNdcD