Advertisement
Guest User

Chapter 6: Tank Game Course

a guest
May 28th, 2022
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var TankGame = (function() {
  2.   var TARGET_WIDTH = 801; // Desired width
  3.   var TARGET_HEIGHT = 601;
  4.   var WIDTH; // Actual width of game, scaled to fit screen
  5.   var HEIGHT;
  6.   var GUI_HEIGHT = 150;
  7.   var DEBUG = false;
  8.   // WASD
  9.   var P1_UP = 87;
  10.   var P1_DOWN = 83;
  11.   var P1_LEFT = 65;
  12.   var P1_RIGHT = 68;
  13.   var P1_FIRE = 49; // Character 1
  14.  
  15.   // Arrow keys
  16.   var P2_UP = 38;
  17.   var P2_DOWN = 40;
  18.   var P2_LEFT = 37;
  19.   var P2_RIGHT = 39;
  20.   var P2_FIRE = 189; // Character -
  21.  
  22.   // Other settings
  23.   var TANK_SIZE = 15;
  24.   var TANK_SPEED = 1;
  25.   var TANK_TURN_SPEED = 5;
  26.   var WALL_WIDTH = 2;
  27.   var CELL_SIZE = 50;
  28.   var RESET_COUNTER_MAX = 200; // Time to start next round (frames)
  29.   var EPSILON = 0.001; // Used for comparing floats
  30.  
  31.   // Optimization: skip collision checks between distant objects
  32.   // NOTE: This only works if there are no large objects in the scene
  33.   var MAX_DIST_FOR_COLLISIONS = 2; // (this is multiplied by CELL_SIZE)
  34.  
  35.   // Global variables (Do not attempt to configure)
  36.   var TANK_P1, TANK_P2;
  37.   var CELLS_X, CELLS_Y;
  38.   var CANVAS, CTX, KEYSTATE, GAME_OBJECTS;
  39.   var PRERENDERED_CANVAS, PRERENDERED_CTX, PRERENDERED_REDRAW_NEEDED;
  40.   var GUI_REDRAW_NEEDED;
  41.   var END_ROUND = false;
  42.   var RESET_COUNTER;
  43.  
  44.   var P1 = 1;
  45.   var P2 = 2;
  46.   var P1_SCORE = 0;
  47.   var P2_SCORE = 0;
  48.  
  49.  
  50.  
  51.  
  52.   /*
  53.   ===============================================================================
  54.   -------------------------------------CLASSES-----------------------------------
  55.   ===============================================================================
  56.   */
  57.  
  58.   function deg2rad(degrees) {
  59.     return degrees * (Math.PI/180);
  60.   }
  61.  
  62.   class Vector2d {
  63.     constructor(x, y) {
  64.       if (typeof x == 'undefined' || typeof  y == 'undefined') {
  65.         throw "Invalid arguments";
  66.       }
  67.       this.x = x;
  68.       this.y = y;
  69.     }
  70.  
  71.     rotate(radians) {
  72.       // Rotates coordinates counterclockwise
  73.       //radians=-radians
  74.       if (radians != 0) {
  75.         var x = this.x;
  76.         var y = this.y;
  77.         this.x = x * Math.cos(radians) - y * Math.sin(radians);
  78.         this.y = x * Math.sin(radians) + y * Math.cos(radians);
  79.       }
  80.       return this;
  81.     }
  82.  
  83.     add(vector) {
  84.       if (vector instanceof Vector2d) {
  85.         this.x += vector.x;
  86.         this.y += vector.y;
  87.       }
  88.       else throw "Invalid argument";
  89.       return this;
  90.     }
  91.  
  92.     subtract(vector) {
  93.       if (vector instanceof Vector2d) {
  94.         this.x -= vector.x;
  95.         this.y -= vector.y;
  96.       }
  97.       else throw "Invalid argument";
  98.       return this;
  99.     }
  100.  
  101.     multiply(value) {
  102.       if (isNaN(value) === false) {
  103.         this.x *= value;
  104.         this.y *= value;
  105.       }
  106.       else throw "Invalid argument";
  107.       return this;
  108.     }
  109.  
  110.     get_dot_product(other) {
  111.       // Calculates the dot product between given vector
  112.       if (other instanceof Vector2d) return this.x*other.x + this.y*other.y;
  113.       else throw "Invalid argument";
  114.     }
  115.  
  116.     get_unit_vector() {
  117.       var c = this.get_magnitude();
  118.       var unit_vec = new Vector2d(this.x/c, this.y/c);
  119.       return unit_vec;
  120.     }
  121.  
  122.     get_right_normal() {
  123.       return new Vector2d(this.y, -this.x);
  124.     }
  125.  
  126.     get_magnitude() {
  127.       return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
  128.     }
  129.  
  130.     get_magnitude_squared() {
  131.       return Math.pow(this.x, 2) + Math.pow(this.y, 2);
  132.     }
  133.  
  134.     get_inverted() {
  135.       return new Vector2d(-this.x, -this.y);
  136.     }
  137.  
  138.     clone() {
  139.       return new Vector2d(this.x, this.y);
  140.     }
  141.  
  142.     reflect(vector) {
  143.       /*
  144.         Reflects this vector around given unit vector.
  145.         vec1 - (2*vec2*(vec2.vec1))
  146.       */
  147.       if (vector instanceof Vector2d) {
  148.         var vec2 = vector.clone(); // Avoid modifying given vector
  149.         vec2.multiply(vec2.get_dot_product(this));
  150.         vec2.multiply(2);
  151.         this.subtract(vec2);
  152.         return this;
  153.       }
  154.       else throw "Invalid argument";
  155.     }
  156.   };
  157.  
  158.   class GameObject {
  159.     constructor(x, y, width, height, movable = false) {
  160.       this.destructible = true; // Can this object be destroyed?
  161.       this.max_hp = 1000;
  162.       this.hp = this.max_hp;
  163.       this.pos = new Vector2d(x, y);
  164.       this.width = width;
  165.       this.height = height;
  166.       this.rotation = 0; // In degrees
  167.       this.velocity = new Vector2d(0, 0);
  168.       this.movable = movable; // Can be moved by collisions
  169.       this.color = {};
  170.       this.color.r = 0;
  171.       this.color.g = 0;
  172.       this.color.b = 0;
  173.       this.verts = [];
  174.       this.rotated_verts = [];
  175.       this.ignored_collision_objs = [this];
  176.       this.circle = false;
  177.       this.radius = 0;
  178.       this.unstoppable = false; // Lets object move through destructible objects
  179.  
  180.       var w = this.width / 2;
  181.       var h = this.height / 2;
  182.       this.verts.push(new Vector2d(-w, -h));
  183.       this.verts.push(new Vector2d(-w, h));
  184.       this.verts.push(new Vector2d(w, h));
  185.       this.verts.push(new Vector2d(w, -h));
  186.  
  187.       GAME_OBJECTS.push(this);
  188.       if (this.movable == false) {
  189.         PRERENDERED_REDRAW_NEEDED = true;
  190.       }
  191.  
  192.     }
  193.  
  194.     damage(amount) {
  195.       if (this.destructible && this.hp > 0) {
  196.         if (amount > 0) {
  197.           this.hp -= amount;
  198.           this.color_by_damage();
  199.         }
  200.         else console.log("WARNING: Attempted to damage by negative amount!!!");
  201.       }
  202.     }
  203.  
  204.     set_unstoppable(value) {
  205.       this.unstoppable = value;
  206.     }
  207.  
  208.     set_destructible(value) {
  209.       this.destructible = value;
  210.     }
  211.  
  212.     color_by_damage() {
  213.       var red = Math.round(255 - (255 * (this.hp/this.max_hp)));
  214.       this.color.r = red;
  215.     }
  216.  
  217.     rotate(degrees) {
  218.       if (degrees != 0) {
  219.         this.rotation += degrees;
  220.         while (this.rotation < 0) this.rotation += 360;
  221.         while (this.rotation > 360) this.rotation -= 360;
  222.         this.calculate_rotated_verts();
  223.       }
  224.     }
  225.  
  226.     move(vector) {
  227.       if (vector instanceof Vector2d) {
  228.         this.pos.add(vector);
  229.       }
  230.       else throw "Invalid argument";
  231.     }
  232.  
  233.     get_rect(local_coordinates) {
  234.       var x = this.pos.x;
  235.       var y = this.pos.y;
  236.       var w = this.width / 2;
  237.       var h = this.height / 2;
  238.       if (local_coordinates) return [-w, -h, w*2, h*2];
  239.       else return [x-w, y-h, w*2, h*2];
  240.     }
  241.  
  242.     calculate_rotated_verts() {
  243.       var radians = deg2rad(this.rotation)
  244.       this.rotated_verts = [];
  245.       for (var vert of this.verts) {
  246.         this.rotated_verts.push(vert.clone().rotate(radians));
  247.       }
  248.       this.rotated_verts;
  249.     }
  250.  
  251.     get_verts() {
  252.       if (this.rotated_verts.length === 0) this.calculate_rotated_verts();
  253.       return this.rotated_verts;
  254.     }
  255.  
  256.     on_collision(obj) {
  257.       /*
  258.         Default GameObjects do nothing on collision. Child classes can use this
  259.         for example to damage or give powerups to tanks on collision.
  260.       */
  261.       //console.log(typeof(obj));
  262.     }
  263.  
  264.     destroy() {
  265.       var i = GAME_OBJECTS.indexOf(this);
  266.       delete GAME_OBJECTS[i];
  267.       if (this.movable == false) {
  268.         PRERENDERED_REDRAW_NEEDED = true;
  269.       }
  270.     }
  271.  
  272.     update() {
  273.       /*
  274.         Moves GameObject by its velocity and checks for collisions.
  275.         If collisions are found, attempts to solve them by moving itself.
  276.       */
  277.       if (this.hp <= 0) {
  278.         this.destroy();
  279.         return;
  280.       }
  281.       if (this.movable) {
  282.         // Move by velocity, if it has any
  283.         this.move(this.velocity);
  284.  
  285.         // Get all colliding objects
  286.         var collisions = GetCollisions(this);
  287.  
  288.         var attempts = 0; // Track attempts to prevent infinite loops
  289.         var done = false;
  290.         var max_attempts = 5;
  291.         while(done === false && attempts < max_attempts) {
  292.           var prev_pos = this.pos.clone();
  293.           var prev_velo = this.velocity.clone();
  294.           done = true;
  295.           if (collisions.length > 0) {
  296.             //console.log(collisions);
  297.           }
  298.           for (var i = 0; i < collisions.length; i++) {
  299.             // Loop over all collisions one at a time
  300.             var collision = collisions[i];
  301.             var obj1 = collision.obj1; // This object (redundant)
  302.             var obj2 = collision.obj2; // The colliding object
  303.             var dir = collision.direction.clone();
  304.             if (this.unstoppable && obj2.destructible == true) {
  305.               // Don't attempt to solve collisions for unstoppable objects
  306.               // unstoppable objects can go through almost anything.
  307.               obj1.on_collision(obj2);
  308.               obj2.on_collision(obj1);
  309.               break;
  310.             }
  311.             this.move(dir.clone().multiply(collision.magnitude));
  312.             this.velocity.reflect(dir);
  313.  
  314.  
  315.             // Get all new collisions after moving
  316.             var new_collisions = GetCollisions(this);
  317.             if (new_collisions.length === 0) {
  318.               // Success! No new collisions found
  319.               obj1.on_collision(obj2);
  320.               obj2.on_collision(obj1);
  321.               break; // Don't check any other collisions
  322.             }
  323.             else if (i < collisions.length-1) {
  324.               // Fail! Move back to original position and attempt to solve the next collision
  325.               this.pos = prev_pos;
  326.               this.velocity = prev_velo;
  327.             }
  328.             else {
  329.               // Fail! No collisions remaining. Try to resolve collisions from the new position
  330.               obj1.on_collision(obj2);
  331.               obj2.on_collision(obj1);
  332.               collisions = new_collisions;
  333.               done = false;
  334.               attempts++;
  335.             }
  336.           }
  337.  
  338.         }
  339.         if(attempts > 1) {
  340.           console.log("Attempted to resolve collisions " + attempts + " times");
  341.         }
  342.       }
  343.     }
  344.  
  345.     draw(context) {
  346.       context.save();
  347.       let color = "rgb(" + this.color.r + "," + this.color.g + "," + this.color.b + ")";
  348.       context.fillStyle = color;
  349.       context.translate(this.pos.x, this.pos.y);
  350.       context.beginPath();
  351.       if (this.circle === true) {
  352.         context.arc(0, 0, this.radius, 0, 2 * Math.PI);
  353.       }
  354.       else {
  355.         var verts = this.get_verts();
  356.         context.moveTo(verts[0].x, verts[0].y);
  357.         for (var vert of verts) {
  358.           context.lineTo(vert.x, vert.y);
  359.         }
  360.         context.lineTo(vert.x, vert.y);
  361.       }
  362.       context.fill();
  363.       context.restore();
  364.     }
  365.   };
  366.  
  367.   var GunTypes = {
  368.     // Enumeration for different types of guns
  369.     'normal' : 1,
  370.     'machinegun' : 2,
  371.     'heavy' : 3,
  372.   }
  373.  
  374.   class Gun {
  375.     /*
  376.       Handles the firing of tank guns.
  377.     */
  378.     constructor(tank) {
  379.       this.bullet_size = 5;
  380.       this.bullet_speed = 1.5;
  381.       this.ammo = 1000; // Max shots for current gun
  382.       this.clip = 5; // Max simultaenous shots
  383.       this.fire_delay = 0.5;
  384.       this.last_shot = Date.now();
  385.       this.tank = tank; // Used for ignoring collisions when firing
  386.       this.type = GunTypes.normal;
  387.       this.damage_amount = 4;
  388.       this.randomize_direction = false;
  389.     }
  390.  
  391.     fire(x, y, direction) {
  392.       var bullet;
  393.       if (this.randomize_direction) {
  394.         direction += Math.random() * 10 - 5; // Add a random offset of +-5 degrees
  395.       }
  396.       if (this.clip > 0 && this.ammo > 0) {
  397.         if (Date.now() - this.last_shot > this.fire_delay * 1000) {
  398.           GUI_REDRAW_NEEDED = true; // GUI has info about remaining ammo
  399.           this.clip--;
  400.           this.ammo--;
  401.           this.last_shot = Date.now();
  402.           bullet = new Bullet(x, y, direction, this.damage_amount, this, this.bullet_size, this.bullet_speed);
  403.         }
  404.       }
  405.       return bullet;
  406.     }
  407.  
  408.     reload() {
  409.       // Adds one bullet to clip.
  410.       this.clip++;
  411.     }
  412.  
  413.     get_name() {
  414.       return "40mm gun";
  415.     }
  416.  
  417.     get_ammo_str() {
  418.       switch (this.type) {
  419.         case GunTypes.normal:
  420.           return "infinite";
  421.           break;
  422.         default:
  423.           return this.ammo;
  424.       }
  425.     }
  426.   };
  427.  
  428.   class Machinegun extends Gun {
  429.     constructor(tank) {
  430.       super(tank);
  431.       this.bullet_size = 2;
  432.       this.bullet_speed = 2;
  433.       this.ammo = 75;
  434.       this.clip = 25;
  435.       this.fire_delay = 0.1;
  436.       this.damage_amount = 1;
  437.       this.randomize_direction = true;
  438.       this.type = GunTypes.machinegun;
  439.     }
  440.  
  441.     get_name() {
  442.       return ".50 caliber machine gun";
  443.     }
  444.   };
  445.  
  446.   class Heavygun extends Gun {
  447.     constructor(tank) {
  448.       super(tank);
  449.       this.bullet_size = 20;
  450.       this.bullet_speed = 1.5;
  451.       this.ammo = 3;
  452.       this.clip = 3;
  453.       this.fire_delay = 1;
  454.       this.damage_amount = 1000;
  455.       this.randomize_direction = false;
  456.       this.type = GunTypes.heavy;
  457.     }
  458.  
  459.     fire(x, y, direction) {
  460.       // Override firing to make the bullet unstoppable
  461.       var bullet = super.fire(x, y, direction);
  462.       if (bullet)
  463.         bullet.set_unstoppable(true);
  464.     }
  465.  
  466.     get_name() {
  467.       return "155mm heavy gun";
  468.     }
  469.   };
  470.  
  471.   class Tank extends GameObject {
  472.     constructor(x, y, player) {
  473.       super(x, y, TANK_SIZE, TANK_SIZE, true); // Movable=true
  474.       this.player = player;
  475.       this.speed = 1;
  476.       this.turn_speed = 5;
  477.       this.fire_delay = 0;
  478.       this.max_fire_delay = 30;
  479.       this.max_ammo = 5;
  480.       this.ammo = this.max_ammo;
  481.       this.max_hp = 10;
  482.       this.hp = this.max_hp;
  483.       this.color_by_damage();
  484.  
  485.       // Add a gun
  486.       this.set_gun(GunTypes.normal);
  487.       var w = this.width / 2;
  488.       var h = this.height / 2;
  489.       var last_vert = this.verts.pop();
  490.       this.verts.push(new Vector2d(w, h/2));
  491.       this.verts.push(new Vector2d(w*2, h/2));
  492.       this.verts.push(new Vector2d(w*2, -h/2));
  493.       this.verts.push(new Vector2d(w, -h/2));
  494.       this.verts.push(last_vert);
  495.     }
  496.  
  497.     set_gun(type) {
  498.       GUI_REDRAW_NEEDED = true; // GUI has info about player weapons
  499.       switch (type) {
  500.         case GunTypes.normal :
  501.           this.gun = new Gun(this);
  502.           break;
  503.         case GunTypes.machinegun :
  504.           this.gun = new Machinegun(this);
  505.           break;
  506.         case GunTypes.heavy :
  507.           this.gun = new Heavygun(this);
  508.           break;
  509.         default :
  510.           console.log("Invalid gun type given " + type);
  511.           this.gun = new Gun(this);
  512.           break;
  513.       }
  514.     }
  515.  
  516.     destroy() {
  517.       END_ROUND = true;
  518.       this.player === P1 ? P2_SCORE++ : P1_SCORE++;
  519.       for (var i = 0; i < 360; i += 60) {
  520.         // Spawn a ring of bullets on death
  521.         var radians = deg2rad(i);
  522.         var damage = 4;
  523.         var off_x = this.width * Math.cos(radians);
  524.         var off_y = this.width * Math.sin(radians);
  525.         new Bullet(this.pos.x + off_x, this.pos.y + off_y, i, damage);
  526.       }
  527.       super.destroy();
  528.     }
  529.  
  530.     update() {
  531.       /*
  532.         Checks for user input and checks collisions.
  533.       */
  534.       if (this.fire_delay > 0) this.fire_delay--;
  535.       var p = this.player;
  536.       var radians = deg2rad(this.rotation);
  537.  
  538.       if ((p == P1 && KEYSTATE[P1_UP]) || (p == P2 && KEYSTATE[P2_UP])) {
  539.         this.velocity.x = this.speed * Math.cos(radians);
  540.         this.velocity.y = this.speed * Math.sin(radians);
  541.       }
  542.       else if ((p == P1 && KEYSTATE[P1_DOWN]) || (p == P2 && KEYSTATE[P2_DOWN])) {
  543.         this.velocity.x = -this.speed * Math.cos(radians);
  544.         this.velocity.y = -this.speed * Math.sin(radians);
  545.       }
  546.       else {
  547.         this.velocity = new Vector2d(0, 0);
  548.       }
  549.       if ((p == P1 && KEYSTATE[P1_LEFT]) || (p == P2 && KEYSTATE[P2_LEFT])) {
  550.         this.rotate(-this.turn_speed);
  551.  
  552.       }
  553.       else if ((p == P1 && KEYSTATE[P1_RIGHT]) || (p == P2 && KEYSTATE[P2_RIGHT])) {
  554.         this.rotate(this.turn_speed);
  555.       }
  556.  
  557.       super.update(); // Move and check collisions before firing
  558.  
  559.       if ((p == P1 && KEYSTATE[P1_FIRE]) || (p == P2 && KEYSTATE[P2_FIRE])) {
  560.         if (this.gun.ammo > 0) {
  561.           var off_x = this.width * 0.9 * Math.cos(radians);
  562.           var off_y = this.width * 0.9 * Math.sin(radians);
  563.           this.gun.fire(this.pos.x + off_x, this.pos.y + off_y, this.rotation);
  564.         }
  565.         else {
  566.           this.set_gun(GunTypes.normal); // Replace all special guns with a regular gun
  567.         }
  568.  
  569.       }
  570.     }
  571.   };
  572.  
  573.   var PowerupType = {
  574.     'machinegun' : 1,
  575.     'heavy' : 2,
  576.     'speed' : 3,
  577.     'get_random_type' :
  578.       function get_random_type() {
  579.         return Math.ceil(Math.random() * 3);
  580.       }
  581.   };
  582.  
  583.   class Powerup extends GameObject {
  584.     constructor(x, y, type) {
  585.       super(x, y, 10, 10, true);
  586.       this.type = type;
  587.       this.max_hp = 20;
  588.       this.hp = this.max_hp;
  589.       this.last_damage_tick = Date.now(); // Cause damage to self every second
  590.       this.turn_speed = 5;
  591.       this.re_color();
  592.     }
  593.  
  594.     re_color() {
  595.       switch(this.type) {
  596.         case PowerupType.machinegun:
  597.           this.color = {"r":0, "g": 200, "b": 200};
  598.           break;
  599.         case PowerupType.heavy:
  600.           this.color = {"r":0, "g": 50, "b": 120};
  601.           break;
  602.         case PowerupType.speed:
  603.           this.color = {"r":0, "g": 255, "b": 255};
  604.           break;
  605.         default:
  606.           console.log("Powerup has invalid type!");
  607.           this.color = {"r":0, "g": 10, "b": 10};
  608.       }
  609.     }
  610.  
  611.     update() {
  612.       this.rotate(this.turn_speed);
  613.       if (Date.now() - this.last_damage_tick > 1000) {
  614.         this.last_damage_tick = Date.now();
  615.         this.damage(1); // Max time to live is 20 seconds;
  616.       }
  617.       super.update();
  618.     }
  619.  
  620.     on_collision(obj) {
  621.       if (obj instanceof Tank) {
  622.         switch (this.type) {
  623.           case PowerupType.machinegun:
  624.             obj.set_gun(GunTypes.machinegun);
  625.             break;
  626.           case PowerupType.heavy:
  627.             obj.set_gun(GunTypes.heavy);
  628.             break;
  629.           case PowerupType.speed:
  630.             obj.speed++;
  631.             break;
  632.           default:
  633.             console.log("Powerup has invalid type!");
  634.         }
  635.         this.destroy();
  636.       }
  637.     }
  638.   };
  639.  
  640.   class Bullet extends GameObject {
  641.     constructor(x, y, direction, damage, gun, size, speed) {
  642.       if (typeof speed == 'undefined') speed = 1.5;
  643.       if (typeof size == 'undefined') size = 5;
  644.       super(x, y, size, size, true);
  645.       this.max_time_to_live = 15000; // After this time the bullet disappears (milliseconds)
  646.       this.remaining_bounces = 10;
  647.       this.first_bounce = true;
  648.       this.spawn_time = Date.now();
  649.       this.ignore_owner_for = 3 * size / speed; // HACK: this value is just randomly guessed (milliseconds)
  650.       this.speed = speed;
  651.       this.gun = gun;
  652.       if (this.gun && this.gun.tank) this.ignored_collision_objs.push(this.gun.tank); // Don't collide with tank before first bounce
  653.       this.color.r =  Math.round(Math.random() * 255);
  654.       this.color.g =  Math.round(Math.random() * 255);
  655.       this.color.b =  Math.round(Math.random() * 255);
  656.       var radians = deg2rad(direction);
  657.       this.velocity.x = this.speed * Math.cos(radians);
  658.       this.velocity.y = this.speed * Math.sin(radians);
  659.       this.damage_amount = damage;
  660.       this.radius = this.width/2;
  661.       this.circle = true;
  662.     }
  663.  
  664.     update() {
  665.       super.update();
  666.       if (Date.now() - this.spawn_time > this.max_time_to_live) {
  667.         // This bullet has been around long enough, destroy it
  668.         this.destroy();
  669.       }
  670.     }
  671.  
  672.     on_collision(obj) {
  673.       this.remaining_bounces--;
  674.       if (this.first_bounce && (Date.now() - this.spawn_time > this.ignore_owner_for) && this.gun && this.gun.tank) {
  675.         // After first bounce bullet can collide with the shooting tank
  676.         this.first_bounce = false;
  677.         var ind = this.ignored_collision_objs.indexOf(this.gun.tank);
  678.         if (ind > -1) delete this.ignored_collision_objs[ind];
  679.       }
  680.  
  681.       if (this.remaining_bounces < 1) {
  682.         this.destroy();
  683.       }
  684.  
  685.       obj.damage(this.damage_amount);
  686.       if (obj instanceof Tank) {
  687.         this.destroy();
  688.       }
  689.     }
  690.  
  691.     destroy() {
  692.       if (this.gun) this.gun.reload();
  693.       super.destroy();
  694.     }
  695.  
  696.   };
  697.  
  698.  
  699.  
  700.  
  701.   class Collision {
  702.     constructor(game_obj1, game_obj2) {
  703.       this.obj1 = game_obj1;
  704.       this.obj2 = game_obj2;
  705.       this.has_collided = false;
  706.       this.direction = new Vector2d(0, 0); // Direction of penetration
  707.       this.magnitude = Number.NEGATIVE_INFINITY; // Shortest distance of penetration
  708.     }
  709.   }
  710.  
  711.   function getSATCollision(game_obj1, game_obj2) {
  712.    
  713.     var obj1 = game_obj1;
  714.     var obj2 = game_obj2;
  715.     let temp_pos1 = obj1.pos.clone();
  716.     var d_origins = temp_pos1.subtract(obj2.pos).get_magnitude();
  717.     var collision = new Collision(game_obj1, game_obj2);
  718.     collision.has_collided = false;
  719.     var verts1 = game_obj1.get_verts();
  720.     var verts2 = game_obj2.get_verts();
  721.     var pos1 = game_obj1.pos.clone();
  722.     var pos2 = game_obj2.pos.clone();
  723.  
  724.  
  725.     if (d_origins > MAX_DIST_FOR_COLLISIONS * CELL_SIZE) {
  726.       // Optimization: Skip further checks for distant objects
  727.       return collision;
  728.     }
  729.  
  730.  
  731.     for (var i = 0; i < verts1.length + verts2.length; i++) {
  732.       // Calculate next axis by taking the normal of a side of one object
  733.       if (i < verts1.length) {
  734.         var vert = verts1[i];
  735.         if (i < verts1.length-1) var next_vert = verts1[i+1];
  736.         else var next_vert = verts1[0];
  737.       }
  738.       else {
  739.         var vert = verts2[i - verts1.length];
  740.         if (i < verts1.length+verts2.length-1) var next_vert = verts2[i+1 - verts1.length];
  741.         else var next_vert = verts2[0];
  742.       }
  743.       var side = next_vert.clone().subtract(vert).get_unit_vector();
  744.       var axis = side.get_right_normal();
  745.  
  746.       // Get minimum and maximum projections on axis from center of obj1
  747.       var min_dist1 = verts1[0].get_dot_product(axis);
  748.       var max_dist1 = min_dist1;
  749.       for (var j = 1; j < verts1.length; j++) {
  750.         var distance = verts1[j].get_dot_product(axis);
  751.         if (distance < min_dist1) min_dist1 = distance;
  752.         else if (distance > max_dist1) max_dist1 = distance;
  753.       }
  754.  
  755.       // Get minimum and maximum projections on axis from center of obj2
  756.       var min_dist2 = verts2[0].get_dot_product(axis);
  757.       var max_dist2 = min_dist2;
  758.       for (var j = 1; j < verts2.length; j++) {
  759.         var distance = verts2[j].get_dot_product(axis);
  760.         if (distance < min_dist2) min_dist2 = distance;
  761.         else if (distance > max_dist2) max_dist2 = distance;
  762.       }
  763.  
  764.       // Calculate the distance between objects and flip axis if necessary
  765.       var d = new Vector2d(pos2.x - pos1.x, pos2.y - pos1.y).get_dot_product(axis);
  766.  
  767.      
  768.       var gap1 = d - max_dist1 + min_dist2;
  769.       var gap2 = - d - max_dist2 + min_dist1;
  770.       if (gap1 >= -EPSILON || gap2 >= -EPSILON) {
  771.         // No collision on this axis - these objects cannot be colliding!
  772.         collision.has_collided = false;
  773.         return collision;
  774.       }
  775.       if (gap1 > gap2 && gap1 > collision.magnitude) {
  776.         collision.magnitude = gap1;
  777.         collision.direction = axis;
  778.       }
  779.       if (gap2 > gap1 && gap2 > collision.magnitude) {
  780.         collision.magnitude = gap2;
  781.         collision.direction = axis.get_inverted();
  782.       }
  783.     }
  784.     collision.has_collided = true;
  785.     return collision;
  786.   }
  787.  
  788.   function GetCollisions(obj) {
  789.     /*
  790.       Checks collisions given gameobject and all other gameobjects.
  791.     */
  792.     var ign1 = obj.ignored_collision_objs;
  793.     var collisions = [];
  794.     for (obj_ind in GAME_OBJECTS) {
  795.       var other_obj = GAME_OBJECTS[obj_ind];
  796.       var ign2 = other_obj.ignored_collision_objs;
  797.       if (ign1.indexOf(other_obj) === -1 && ign2.indexOf(obj) === -1) {
  798.         var collision = getSATCollision(obj, other_obj);
  799.         if (collision.has_collided === true) {
  800.           collisions.push(collision);
  801.         }
  802.       }
  803.     }
  804.     return collisions;
  805.   }
  806.  
  807.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement