HTML JS Canvas游戏:Tile Collision Bug让玩家传送 [英] HTML JS Canvas Game: Tile Collision Bug Makes Player Teleport Up

查看:87
本文介绍了HTML JS Canvas游戏:Tile Collision Bug让玩家传送的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm a beginner at game development and have been struggling with getting collision done right between an array of tiles and a player rectangle. This game features jumping and gravity. First of all the collision works, but is very clunky. Sometimes when the player ends up on top of a tile and a little to the edge, it will instantly teleport to either the right or left side (depending on what edge/corner) and fall of it. This also happens when colliding with the bottom of the tile; the player will instantly teleport to the side and go up further. From what I understand the tile collision detector confuses the collision with one or the other side because when the player hits the edge of a tile the detector reads it as if it collided with both and decides to place the player elsewhere base on the highest coordinate velocity (aka speedX and speedY). I figured this out by setting speedY = 0 every time it hits the top of a tile, which fixed the issue, but another one came out of it. Now, if the player is on top of a tile and then falls of and shortly strafe back, it doesn't collide with the tile's side, but it rather quickly goes back on top of it again.

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

I just need some tips on how i should resolve this, because everything I try leads to another problem. I've heard this is a common bug among developing 2D tile based games.

这是一个jsfiddle,代码在运行: https://jsfiddle.net/8121u356/

Here is a jsfiddle with the code in action: https://jsfiddle.net/8121u356/

这是我的显示整个代码:

And here is the display of my entire code:

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;
      };


推荐答案

快速修复。



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

A quick fix for you.

I have seen you post this question for the 3rd time. You are not getting an answer because the best solution is rather a lot of code, complex, and requires a lot of changes to your code.

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

So what I have done is create a very quick and simple solution.

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

Rather than check the position at the end of a move, I changed the code to check at every pixel moved. This is needed as you have to find the collisions in the correct order as the player moves from one position to the next. If you hit a wall on the side before the top or bottom, or the other way around it makes a difference, and is what is causing you to have problems. You checked x first then y, which for many situation is the wrong way around.

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

I also added a object to the actor called canMove It has 4 properties that are set at the start of each frame and are used to prevent the player moving in a direction that is blocked. If you let the player move in a blocked direction it will get stuck on the wall while you have the key down in that direction.

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

Sorry I made a bit of a mess but am short on time.

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

Also to help me write the changes I made a few other mods, I scaled that game to fit the window (the scaling and resize is all done in the clear function). I changed the keyboard interface to prevent default on keys pressed and set up arrow to jump as well as space (I hate using space to jump :P). Also change the map to use strings as it is a pain typing in changes as an array.

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

I was not sure how you wanted the actor to react when it is hit on the head. I made it so that it bounces down at the same speed as it moves up, but it does make it harder to jump and slide into narrow passages.

所以我想我大部分都完成了,所以你可以继续你的游戏。

So I think I have most of it done, so you can move on with your game.

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

If you have questions, ask in the comments.

   // 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);

canvas { 
   position : absolute;
   top : 0px;
   left : 0px;
}

<canvas id = "canvas" width="640" height="640"></canvas>

这篇关于HTML JS Canvas游戏:Tile Collision Bug让玩家传送的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆