Search code examples
javascriptforeachcollisionevent-listeneroverlapping

How to check if a div overlaps with another div every second?


I am making a quick game about moving the player to dodge bullets, but I am having problems with how to detect if any bullet colided with the player. In game engines there would usually just exist eventlisteners for coliding of two objects, since I haven't found anything like that for webb development, I thought I could just use an infinite loop to check each bullet if it has collided with the player.

I can't do a while (true) loop, because it just crashes. I tried to make an interval in the spawn function of each bullets, but I couldn't figure out how to clear the interval after the bullet despawns. And I have also tried to just make an interval that would loop through existing bullets at the moment and check if the bullet is coliding, but for some reason I get this error: Uncaught TypeError: bullets.forEach is not a function here is the code:


function checkOverlap(){
    let bullets = document.getElementsByClassName("bullet");
    if (bullets.length > 0){
        bullets.forEach(bullet => {
            let overlap = !(bullet.right < player.left || 
                    bullet.left > player.right || 
                    bullet.bottom < player.top || 
                    bullet.top > player.bottom)
            if (overlap){
                console.log("overlap");
            }
        });
    }
}
setInterval(checkOverlap, 1000);

I also wonder if this is even posible to detect the overlapping this way. Since I am using transition and left, top, right, down css properties to move my bullets, would it detect or do I need to use something else?


Solution

  • You can't access forEach because document.getElementsByClassName doesn't return an Array - it returns a NodeList which behaves somewhat like an Array, but deviates in other ways - for one thing its prototype has no forEach method.

    You can easily convert to an Array by doing the following instead:

    let bullets = [ ...document.getElementsByClassName('bullet') ];
    

    As far as collision goes, I recommend you use getBoundingClientRect (I often confused its spelling for getClientBoundingRect, now I remind myself that B preceeds C :D). This returns the absolute geometry of an html element, taking everything into account:

    let doCollide = (player, bullet) => {
      let playerBound = player.getBoundingClientRect();
      let bulletBound = bullet.getBoundingClientRect();
    
      // (px, py) and (bx, by) represent the center of the player and bullet
      let px = playerBound.left + playerBound.width * 0.5;
      let py = playerBound.top + playerBound.height * 0.5;
      let bx = bulletBound.left + bulletBound.width * 0.5;
      let by = bulletBound.top + bulletBound.height * 0.5;
    
      let wOff = (playerBound.width + bulletBound.width) * 0.5;
      let hOff = (playerBound.height + bulletBound.height) * 0.5;
    
      // Collision occurs when bullet and player are separated on neither x nor y axes
      return Math.abs(px - bx) < wOff && Math.abs(py - by) < hOff;
    }
    
    let player = document.getElementsByClassName('player')[0];
    let bullet = document.getElementsByClassName('bullet')[0];
    
    let testFn = () => {
    
      player.style.left = `${Math.floor(Math.random() * 300)}px`;
      player.style.top = `${Math.floor(Math.random() * 150)}px`;
      bullet.style.left = `${Math.floor(Math.random() * 300)}px`;
      bullet.style.top = `${Math.floor(Math.random() * 150)}px`;
      
      if (doCollide(player, bullet)) {
        player.classList.add('coll');
        bullet.classList.add('coll');
      } else {
        player.classList.remove('coll');
        bullet.classList.remove('coll');
      }
      
    };
    
    setInterval(testFn, 1500);
    testFn();
    html, body { position: relative; width: 100%; height: 100%; overflow: hidden; padding: 0; margin: 0; }
    
    .player, .bullet { position: absolute; }
    .player { width: 150px; height: 150px; box-shadow: inset 0 0 0 4px rgba(0, 150, 0, 1); }
    .bullet { width: 60px; height: 90px; box-shadow: inset 0 0 0 4px rgba(0, 210, 0, 1); }
    
    .player.coll { box-shadow: inset 0 0 0 4px rgba(200, 0, 0, 1); }
    .bullet.coll { box-shadow: inset 0 0 0 4px rgba(255, 0, 0, 1); }
    <div class="player"></div>
    <div class="bullet"></div>