HTML JS Canvas游戏:Tile Collision Bug让玩家传送出去

时间:2017-10-24 12:30:44

标签: javascript html5 canvas collision-detection tiles

我是游戏开发的初学者,并且一直在努力在一系列瓷砖和玩家矩形之间完成碰撞。这个游戏具有跳跃和重力。首先碰撞工作,但非常笨重。有时候当玩家最终在一个瓷砖的顶部和一个小的边缘时,它会立即传送到右侧或左侧(取决于什么边缘/角落)和它的下降。当与瓷砖底部碰撞时也会发生这种情况;玩家将立即传送到侧面并进一步向上移动。根据我的理解,瓦片碰撞检测器将碰撞与一侧或另一侧混淆,因为当玩家碰到瓦片的边缘时,检测器将其读取为好像它与两者碰撞并决定将玩家置于其他基础上的最高坐标速度(又名speedX和speedY)。我通过设置speedY = 0每次碰到瓷砖的顶部来解决这个问题,这解决了问题,但另一个问题就出来了。现在,如果玩家位于一个平铺的顶部,然后摔倒并且很快就会向前移动,它不会与平铺的一侧碰撞,但它会很快再次回到它的顶部。

我只需要一些关于如何解决这个问题的提示,因为我尝试的所有内容都会导致另一个问题。我听说这是开发基于2D磁贴的游戏中常见的错误。

这是一个包含操作代码的jsfiddle:https://jsfiddle.net/8121u356/

这是我整个代码的显示:

function startGame() {
    gameArea.start();
    actor = new player(32, 32, "green", 32, 32);
}

var mapArray = [
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,0],
    [0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,1,1,1,0,0,0,1,1,0,0,0,0,0,0],
    [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]

];

var levelRows = 20;
var levelCols = 20;
 var gameArea = {
     canvas : document.getElementById('canvas'),

     start : function() {
         this.context = this.canvas.getContext("2d");
         document.body.insertBefore(this.canvas, document.body.childNodes[0]);
         requestAnimationFrame(updateGameArea);
         window.addEventListener('keydown', function (e) {
             gameArea.keys = (gameArea.keys || []);
             gameArea.keys[e.keyCode] = true;
         });

         window.addEventListener('keyup', function (e) {
             gameArea.keys = (gameArea.keys || []);
             gameArea.keys[e.keyCode] = false;
         })
     },

     clear : function(){
         this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
     },
     render : function() {
         context = this.canvas.getContext("2d");
         var tileSize = 32;
         for(i=0;i<levelRows;i++){
             for(j=0;j<levelCols;j++){
                 if(mapArray[i][j]==1){
                     context.fillStyle = "gray";
                     context.fillRect(j*tileSize,i*tileSize,tileSize,tileSize);
                 }
             }
         }
     }
 };

function TileCollisionManager(object) {
    let tileSize = 32;
    let baseCol = Math.floor(object.x / tileSize);
    let baseRow = Math.floor(object.y / tileSize);
    let colOverlap = object.x % tileSize;
    let rowOverlap = Math.floor(object.y % tileSize);

    if (object.speedX > 0) { 
        if ((mapArray[baseRow][baseCol + 1] && !mapArray[baseRow][baseCol]) ||
            (mapArray[baseRow + 1][baseCol + 1] && !mapArray[baseRow + 1][baseCol] && rowOverlap)) {
            object.x = baseCol * tileSize;
        }
    }

    if (object.speedX < 0) {
        if ((!mapArray[baseRow][baseCol + 1] && mapArray[baseRow][baseCol]) ||
            (!mapArray[baseRow + 1][baseCol + 1] && mapArray[baseRow + 1][baseCol] && rowOverlap)) {
            object.x = (baseCol + 1) * tileSize;
        }
    }

    if (object.speedY > 0) { 
        if ((mapArray[baseRow + 1][baseCol] && !mapArray[baseRow][baseCol]) || 
            (mapArray[baseRow + 1][baseCol + 1] && !mapArray[baseRow][baseCol + 1] && colOverlap)) {
            object.y = ((baseRow) * tileSize);
            object.jumping = false;
            object.speedY = 0;
        }
    }

    if (object.speedY < 0) { 
        if ((!mapArray[baseRow + 1][baseCol] && mapArray[baseRow][baseCol]) ||
            (!mapArray[baseRow + 1][baseCol + 1] && mapArray[baseRow][baseCol + 1] && colOverlap)) {
            object.y = (baseRow + 1) * tileSize;
            object.speedY = 5;
        }
    }
 }
  function updateGameArea() {
      gameArea.clear();
      gameArea.render();
      actor.update();
      actor.newPos();
      actor.speedX = 0;
      actor.speedY += actor.gravity;


      if (gameArea.keys && gameArea.keys[39]) {
          actor.speedX = 4;
      }
      if (gameArea.keys && gameArea.keys[37]) {
          actor.speedX = -4;
      }
      if (gameArea.keys && gameArea.keys[32]) { 
          if (!actor.jumping) {
              actor.jumping = true;
              actor.speedY = -actor.speed * 3;
          }
      }


      TileCollisionManager(actor);
      requestAnimationFrame(updateGameArea);
  }


  function player (width, height, color, x, y) { 
      this.width = width;
      this.height = height;
      this.x = x;
      this.y = y;
      this.speedX=0;
      this.speedY=0;
      this.gravity=0.3;
      this.speed=3;
      this.jumping=false;
      this.color = color;
      this.update = function () {
          ctx = gameArea.context;
          ctx.fillStyle = this.color;
          ctx.fillRect(
              this.x,
              this.y,
              this.width, this.height);
      };
      this.newPos = function () {
          this.x += this.speedX;
          this.y += this.speedY;
      };

1 个答案:

答案 0 :(得分:2)

为您快速解决问题。

我见过你第三次发布这个问题了。您没有得到答案,因为最好的解决方案是代码很多,很复杂,并且需要对代码进行大量更改。

所以我所做的就是创建一个非常快速和简单的解决方案。

以正确的顺序解决碰撞。

我没有检查移动结束时的位置,而是更改了代码以检查移动的每个像素。这是必需的,因为当玩家从一个位置移动到下一个位置时,你必须以正确的顺序找到碰撞。如果你撞到顶部或底部之前的一侧墙壁,或者绕过它的另一边有所不同,那就是导致你遇到问题的原因。你先检查x然后检查y,这在很多情况下是错误的。

我还向名为canMove的actor添加了一个对象。它有4个属性,在每个帧的开头设置,用于防止玩家向被阻挡的方向移动。如果你让玩家沿着一个被阻挡的方向移动,那么当你将钥匙向下朝那个方向移动时,它将被卡在墙上。

我入侵了你的代码

对不起,我有点乱,但时间不够。

另外为了帮助我编写我做了一些其他mod的更改,我缩放了该游戏以适应窗口(缩放和调整大小都在clear函数中完成)。我更改了键盘界面以防止按下按键的默认设置并设置箭头跳跃以及空间(我讨厌使用空间跳转:P)。同时更改地图以使用字符串,因为在更改中输入数组是一种痛苦。

我不确定你是怎么想让演员在被击中时做出反应的。我这样做是为了让它以与向上移动相同的速度反弹,但它确实让它更难跳跃并滑入狭窄的段落。

所以我认为我已经完成了大部分工作,所以你可以继续你的游戏。

如果您有任何疑问,请在评论中提问。

&#13;
&#13;
   // NOTE  var | 0 is the same as Math.floor(var)

var mapArray = [
        "#                  #",
        "#                  #",
        "#  ###             #",
        "#           #      #",
        "#       ##  #####  #",
        "#                  #",
        "#                  #",
        "#    ##            #",
        "#      ##          #",
        "#                  #",
        "#      #####       #",
        "#                  #",
        "#                  #",
        "#       #####      #",
        "#                  #",
        "#                  #",
        "#       #     ##   #",
        "#      ###         #",
        "#     ##### ##     #",
        "####################",
    ].map(row => row.split("").map(cell=>cell==="#" ? 1 : 0));


    var levelRows = 20;
    var levelCols = 20;
    var tileX = 32;
    var tileY = 32;
    var gameArea = {
     canvas : document.getElementById('canvas'),
     ctx : document.getElementById('canvas').getContext("2d"),
     keys : {  // set them here so that can block defaults
        "37" : false,
        "38" : false,     // also jump
        "39" : false,
        "32" : false,     // jump
     },

     start : function() {
         document.body.insertBefore(this.canvas, document.body.childNodes[0]);
         requestAnimationFrame(updateGameArea);
         function keyEvent(e) { 
             if(gameArea.keys["" + e.keyCode] !== undefined){
              gameArea.keys["" + e.keyCode] = e.type === "keydown" 
              e.preventDefault();
             }
         }
         addEventListener('keydown', keyEvent);
         addEventListener('keyup', keyEvent);
         focus();
        },

        clear(){
            var minSize = Math.min(innerWidth,innerHeight);
            if (this.ctx.canvas.width !== minSize|| this.ctx.canvas.height !== minSize) {
                this.ctx.canvas.width = minSize;
                this.ctx.canvas.height = minSize;
            }
                
            this.ctx.setTransform(1,0,0,1,0,0);
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            
            // the next line scales the canvas rendering to fit.
            this.ctx.setTransform(
                minSize / (levelCols * tileX),
                0,
                0,
                minSize/ (levelRows * tileY),
                0,0
            );
        },
        render() {
            var ctx = this.ctx;
            for(i=0;i<levelRows;i++){
                for(j=0;j<levelCols;j++){
                    if(mapArray[i][j]==1){
                        ctx.fillStyle = "gray";
                        ctx.fillRect(j*tileX,i*tileY,tileX,tileY);
                    }
                }
            }
        }
    };

    function updateGameArea() { 
        gameArea.clear();
        actor.canMove.check();
        actor.speedX = 0;
        if(actor.canMove.down){
            actor.speedY += actor.gravity;
        }

        if (gameArea.keys[39] && actor.canMove.right) {
            actor.speedX = 4;
        }
        if (gameArea.keys[37]  && actor.canMove.left) {
            actor.speedX = -4;
        }
        if (actor.canMove.up && (gameArea.keys[32] || gameArea.keys[38])) { //jump
            if (!actor.jumping) {
                actor.jumping = true;
                actor.speedY = -actor.speed * 3;
            }
        }
        actor.move();  // collision is done here
        
        gameArea.render();
        actor.draw();

        requestAnimationFrame(updateGameArea);
    }

    function Player (width, height, color, x, y) { //player component
        this.width = width;
        this.height = height;
        this.x = x;
        this.y = y;
        this.speedX=0;
        this.speedY=0;
        this.gravity=0.3;
        this.speed=3;
        this.jumping=false;
        this.color = color;
        this.canMove = {
           left : true,
           right : true,
           up : true,
           down : true,
           actor : this,
           clear(){
              this.left = true;
              this.right = true;
              this.up = true;
              this.down = true;
           },
           check(){
              this.clear();
              var x = this.actor.x | 0;
              var y = this.actor.y | 0;
              var cx = x / tileX | 0;
              var cy = y / tileY | 0;
              if(x % tileX === 0){
                 if(getMap(cx-1,cy) === 1){                 
                   this.left = false;
                   if(y % tileY !== 0 && getMap(cx-1,cy +1) === 1){
                      this.left = false;
                   }
                 }
                 if(getMap(cx+1,cy) === 1){
                   this.right = false;
                   if(y % tileY !== 0 && getMap(cx+1,cy +1) === 1){
                      this.right = false;
                   }
                 }
              }
              if(y % tileY === 0){
                 if(getMap(cx,cy-1) === 1){                 
                   this.up = false;
                   if(x % tileX !== 0 && getMap(cx+1,cy -1) === 1){
                      this.up = false;
                   }
                 }
                 if(getMap(cx,cy+1) === 1){
                   this.down = false;
                   if(x % tileX !== 0 && getMap(cx+1,cy +1) === 1){
                      this.down = false;
                   }
                 }
              }
           }
        };

        this.draw = function () {
            var ctx = gameArea.ctx;
            ctx.fillStyle = this.color;
            ctx.fillRect(  this.x,this.y, this.width, this.height);
        };
        this.move = function() {
          var x = this.x;
          var y = this.y;
          var sx = this.speedX;
          var sy = this.speedY;
          var speed = Math.sqrt(sx * sx + sy * sy);
          if(speed > 0){
            sx /= speed;
            sy /= speed;
            for(var i = 0; i < speed; i++){
              var xx = (x + sx * i) | 0;
              var yy = (y + sy * i) | 0;
              var cx = xx / tileX | 0;
              var cy = yy / tileY | 0;
              if(sy > 0){
                if(getMap(cx,cy+1) === 1 || (xx % tileX !== 0 && getMap(cx + 1,cy+1))){
                  this.y = y = cy * tileY;
                  this.speedY = sy = 0;
                  this.jumping = false;
                }
              }else if(sy < 0){
                if(getMap(cx,cy) === 1 || (xx % tileX !== 0 && getMap(cx + 1,cy))){
                  cy += 1;
                  this.y = y = cy * tileY;
                  this.speedY = sy = -sy;  // changing -sy to 0 will stick momentarily to the roof.
                }
              }
              if(sx > 0){
                if(getMap(cx+1,cy) === 1 ||  (yy % tileY !== 0 && getMap(cx + 1,cy+1))){
                  this.x = x = cx * tileX;
                  this.speedX = sx = 0;
                }

              }else if(sx < 0){
                if(getMap(cx,cy) === 1 || (yy % tileY !== 0 && getMap(cx,cy+1))){
                  cx += 1;
                  this.x = x = cx * tileX;
                  this.speedX = sx = 0;
                }          
              }
            }
          }
          this.x += this.speedX;
          this.y += this.speedY;
        }
    }

    function getMap(x,y){
      if(y < 0 || y >= levelRows || x < 0 || x >= levelCols){
         return 1;
      }
      return mapArray[y][x];
    }
 

    gameArea.start();
    actor = new Player(32, 32, "green", 32, 32);
&#13;
canvas { 
   position : absolute;
   top : 0px;
   left : 0px;
}
&#13;
<canvas id = "canvas" width="640" height="640"></canvas>
&#13;
&#13;
&#13;