Collision detection using pure jQuery is not giving desired output

Viewed 56

I am trying to develop a very simple game where the ship (red box) will move left-right when user clicks on playground.

There are some moving walls (black boxes) as obstacles that the ship should avoid colliding with.

If any collision happens, the walls will stop moving and a text will be printed out in console.

I have succeeded to get this as close as I can. But its working sometime, not always. You can see it in the code below, try to collide with wall. Sometime it will stop them and print text, sometime it will just ignore the collision as if nothing happens.

I have no clue why this is happening.

Here is the code.

$('document').ready(function() {

  var $totalHeight = $('.inner').height(); //of walls
  var $maxHeight = Math.ceil(Math.ceil($totalHeight / 3) - (Math.ceil($totalHeight / 3) * 30) / 100); //30% of total wall height

  $('.wall').each(function(i, obj) {
    $(this).height($maxHeight);
    $('.wall.four').css({
      'height': $wallGap
    });
  })

  var $wallGap = Math.ceil($totalHeight / 3) - $maxHeight;
  var $wallOneTop = 0;
  var $wallTwoTop = $maxHeight + $wallGap;
  var $wallThreeTop = ($maxHeight * 2) + ($wallGap * 2);
  var $wallFourTop = -$('.wall.four').height() - $wallGap;

  $('.wall.one').css({
    'top': $wallOneTop
  });
  $('.wall.two').css({
    'top': $wallTwoTop
  });
  $('.wall.three').css({
    'top': $wallThreeTop
  });
  $('.wall.four').css({
    'top': $wallFourTop
  });


  function moveWall(wallObj) {
    var $currentTop = wallObj.position().top;
    var $limitTop = $('.inner').height();

    if ($currentTop >= $limitTop) {
      var $rand = Math.floor(Math.random() * ($maxHeight - $wallGap + 1) + $wallGap);
      wallObj.height($rand);
      var $top = -(wallObj.height());
    } else {
      var $top = (wallObj.position().top) + 5;
    }
    var $collide = checkCollision(wallObj);
    wallObj.css({
      'top': $top
    });
    return $collide;
  }

  var $wallTimer = setInterval(function() {
    $('.wall').each(function(i, obj) {
      var $status = moveWall($(this));
      if ($status == true) {
        clearInterval($wallTimer);
      }
    })
  }, 40);


  function checkCollision(wallObj) {
    var $ship = $('.ship');
    var $shipWidth = $ship.width();
    var $shipHeight = $ship.height();
    var $shipLeft = $ship.position().left;
    var $shipRight = $shipLeft + $shipWidth;
    var $shipTop = $ship.position().top;
    var $shipBottom = $shipTop + $shipHeight;

    var $wall = wallObj;
    var $wallWidth = wallObj.width();
    var $wallHeight = wallObj.height();
    var $wallLeft = wallObj.position().left;
    var $wallRight = $wallLeft + $wallWidth;
    var $wallTop = wallObj.position().top;
    var $wallBottom = $wallTop + $wallHeight;

    if (
      $shipLeft >= $wallRight ||
      $shipRight <= $wallLeft ||
      $shipTop >= $wallBottom ||
      $shipBottom <= $wallTop
    ) {
      return false;
    } else {
      console.log("dhumm!");
      return true;
    }
  }







  $('.outer .inner').click(function() {
    var $ship;
    $ship = $('.ship');
    $shipLeft = $ship.position().left;
    $shipRight = $shipLeft + $ship.width();

    $inner = $('.inner');
    $innerLeft = $inner.position().left;
    $innerRight = $innerLeft + $inner.width();


    if (($shipLeft < $inner.width() - $ship.width())) {
      $ship.animate({
        "left": $inner.width() - $ship.width()
      }, 500, "linear");
    } else if (($shipRight >= $inner.width())) {
      $ship.animate({
        "left": '0'
      }, 500, "linear");
    }

  });


});
.outer {
  background: #fff;
  border: 20px solid #efefef;
  width: 400px;
  height: 600px;
  display: inline-block;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  overflow: hidden;
}

.outer .inner {
  background: #fff;
  height: 100%;
  width: 100%;
  margin: auto;
  position: relative;
  overflow: hidden;
}

.outer .inner .wall {
  width: 5px;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  background: #000;
}

.outer .inner .ship {
  width: 15px;
  height: 15px;
  background: red;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="outer">
  <div class="inner">
    <div class="wall one"></div>
    <div class="wall two"></div>
    <div class="wall three"></div>
    <div class="wall four"></div>

    <div class="ship"></div>
  </div>
</div>

1 Answers

As freefomn-m already said. Check for collision in the animation cycle of the ship, not the walls.

For this I use the second type of parameters for jQuery's .animate method

.animate( properties, options )

I use the "progress" option to check the collision in every movement cycle of the ship.

console.clear();
$('document').ready(function() {

  var collided = false;
  var collidedWith = null;
  var $ship = $('.ship');
  var $walls = $('.wall')
  
  var $totalHeight = $('.inner').height(); //of walls
  var $maxHeight = Math.ceil(Math.ceil($totalHeight / 3) - (Math.ceil($totalHeight / 3) * 30) / 100); //30% of total wall height

  $('.wall').each(function(i, obj) {
    $(this).height($maxHeight);
    $('.wall.four').css({
      'height': $wallGap
    });
  })

  var $wallGap = Math.ceil($totalHeight / 3) - $maxHeight;
  var $wallOneTop = 0;
  var $wallTwoTop = $maxHeight + $wallGap;
  var $wallThreeTop = ($maxHeight * 2) + ($wallGap * 2);
  var $wallFourTop = -$('.wall.four').height() - $wallGap;

  $('.wall.one').css({
    'top': $wallOneTop
  });
  $('.wall.two').css({
    'top': $wallTwoTop
  });
  $('.wall.three').css({
    'top': $wallThreeTop
  });
  $('.wall.four').css({
    'top': $wallFourTop
  });


  function moveWall(wallObj) {
    var $currentTop = wallObj.position().top;
    var $limitTop = $('.inner').height();

    if ($currentTop >= $limitTop) {
      var $rand = Math.floor(Math.random() * ($maxHeight - $wallGap + 1) + $wallGap);
      wallObj.height($rand);
      var $top = -(wallObj.height());
    } else {
      var $top = (wallObj.position().top) + 5;
    }
    //    var $collide = checkCollision(wallObj);
    wallObj.css({
      'top': $top
    });
    // return $collide;
  }

  var $wallTimer = setInterval(function() {
    $walls.each(function(i, obj) {
      moveWall($(this));
      if (collided) {
        clearInterval($wallTimer);
      }
    })
  }, 40);


  function checkCollision() {
    var $shipWidth = $ship.width();
    var $shipHeight = $ship.height();
    var $shipLeft = $ship.position().left;
    var $shipRight = $shipLeft + $shipWidth;
    var $shipTop = $ship.position().top;
    var $shipBottom = $shipTop + $shipHeight;
    
    $('.wall').each(function(i) {

      var $wall = $(this);
      var $wallWidth = $wall.width();
      var $wallHeight = $wall.height();
      var $wallLeft = $wall.position().left;
      var $wallRight = $wallLeft + $wallWidth;
      var $wallTop = $wall.position().top;
      var $wallBottom = $wallTop + $wallHeight;

      if (
        $shipLeft < $wallRight &&
        $shipRight > $wallLeft &&
        $shipTop < $wallBottom &&
        $shipBottom > $wallTop
      ) {
        console.log("dhumm!");
        collided = true;
        collidedWith = $wall
        $wall.addClass('crashed')
        $ship.addClass('crashed')
        $ship.stop();
        return false;
      }
    })
  }







  $('.outer .inner').click(function() {
    var $ship;
    $ship = $('.ship');
    $shipLeft = $ship.position().left;
    $shipRight = $shipLeft + $ship.width();

    $inner = $('.inner');
    $innerLeft = $inner.position().left;
    $innerRight = $innerLeft + $inner.width();


    if (($shipLeft < $inner.width() - $ship.width())) {
      $ship.animate({
        "left": $inner.width() - $ship.width()
      }, {
        "duration": 500,
        "easing": "linear",
        "progress": checkCollision,
      });
    } else if (($shipRight >= $inner.width())) {
      $ship.animate({
        "left": '0'
      }, {
        "duration": 500,
        "easing": "linear",
        "progress": checkCollision,
      });
    }

  });


});
.outer {
  background: #fff;
  border: 20px solid #efefef;
  width: 400px;
  height: 600px;
  display: inline-block;
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  margin: auto;
  overflow: hidden;
}

.outer .inner {
  background: #fff;
  height: 100%;
  width: 100%;
  margin: auto;
  position: relative;
  overflow: hidden;
}

.outer .inner .wall {
  width: 5px;
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  background: #000;
}
.outer .inner .wall.crashed {
  background: red;
}


.outer .inner .ship {
  width: 15px;
  height: 15px;
  background: orange;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

.outer .inner .ship.crashed {
  background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="outer">
  <div class="inner">
    <div class="wall one"></div>
    <div class="wall two"></div>
    <div class="wall three"></div>
    <div class="wall four"></div>

    <div class="ship"></div>
  </div>
</div>

As a recommendation how I would do this from scratch.

Use an update cycle that is called by either setInterval or setTimeout, or even better with requestAnimationFrame. The updatecycle would be responsible for the time progress and orchestrate the different objects. The structure would be like this.

jQuery(function($) { // same as $('document').ready()
  var ship = ...;
  var boundaries = ...;
  var walls = ...;
  var clickEvents = [];

  document.addEventListener('click', function(e) {clickEvents.push(e)})
  var handleEvents = function() {}
  var setupWalls = function () {}
  var setupShip= function () {}
  var moveWalls = function () {}
  var moveShip = function () {}
  var checkCollision() {}

  var setup = function() {
    setupWalls();
    setupShip();
    // set the initial positions of the ships and the walls
  }

  var update = function() {
     handleEvents();
     moveWalls();
     moveShips();
     var collided = checkCollision();
     if (!collided) {
         setTimeout(update, 30);
     }
  }
  setup();
  update();
})
Related