JavaScript画布游戏 - 碰撞检测

5

我正在开发一个小型的瓦片引擎游戏。目前我正在实现基于块的简单碰撞检测,但是我遇到了一些问题。我在谷歌上搜索了几个小时,查看了不同的实现方式,但似乎无法理解它。我目前的尝试(只有当玩家向移动时才检测到碰撞)大部分都能正常工作,但允许玩家通过障碍物的底部部分。碰撞使用普通映射数组来检测碰撞,映射中的任何值为2的对象表示为实体。

我了解我需要做什么的概念,在移动玩家之前,计算玩家将要结束的地图块。检查已为该地图块分配的值。如果是2,则不允许玩家移动。

我的问题是找出玩家将要进入的地图块。技术上,有时候玩家可以同时处于4个地图块中。我尝试使用起点和四角检测来解决这个问题,但我无法使其正常工作。

JS Fiddle 地址 - https://jsfiddle.net/j1xqxze8/

我的代码;

    var Player = function() {
        this.width = 16;
        this.height = 16;
        this.position   = {};
        this.position.x = 32;
        this.position.y = 32;
        this.speed      = 8;

        this.render = function() {
            window.context.fillStyle = 'white';
            window.context.fillRect(this.position.x, this.position.y, this.width, this.height);
        };

        var _self = this;

        this.didCollide = function(dir) {
            if(dir == 'right'){
                var newBlock = window.tileMap.getCell(Math.floor((_self.position.x + _self.width) / 32), Math.floor((this.position.y + this.height / 2) / 32));

                if(newBlock == 2)
                    return true;
            }
        };

        window.addEventListener('keydown', function(e) {
            if(e.keyCode == 38 || e.keyCode == 87){
                _self.position.y -= _self.speed;
            }

            if(e.keyCode == 40 || e.keyCode == 83){
                _self.position.y += _self.speed;
            }

            if(e.keyCode == 37 || e.keyCode == 65){
                _self.position.x -= _self.speed;
            }

            if(e.keyCode == 39 || e.keyCode == 68){
                if(!_self.didCollide('right')){
                    _self.position.x += _self.speed;
                }
            }
        })
    };

var TileMap = function() {
    this.map = [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 1],
        [1, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ];

    this.tileSize = 32;
    this.colors = ['black', 'red', 'green'];

    this.getCell = function(x, y){
      return this.map[y][x];
    };

        this.render = function(){
            for(var x = 0; x < this.map.length; x++){
                for(var y = 0; y < this.map.length; y++){
                    // SWAP Y AND X IN THE FILLSTYLE DUE TO BACKWARDS/MIRRORED JS ARRAY READING
                    window.context.fillStyle = this.colors[this.map[y][x]];
                    window.context.fillRect((x * this.tileSize) - window.camera.position.x, (y * this.tileSize) - window.camera.position.y, this.tileSize, this.tileSize);

                    window.context.strokeStyle = 'yellow';
                    window.context.strokeRect((x * this.tileSize) - window.camera.position.x, (y * this.tileSize) - window.camera.position.y, this.tileSize, this.tileSize);
                }
            }
        }
    };

1
由于您在每个按键按下时将玩家移动8个位置,因此在按键按下时,您必须测试这8个位置中的每一个,以查看是否发生碰撞。 - markE
我已尝试过不同速度(包括1,以便仅更新位置一次),即使在这个级别上,仍然存在深度剪切至物体的问题 - 虽然谢谢 :) - Lewis
我添加了一个(粗略的,未经测试的)答案,展示如何测试每个中间位置以查看是否发生碰撞。 - markE
2个回答

2
你需要通过将速度加/减到当前x/y位置来计算玩家的新位置。然后你需要计算玩家在新位置上覆盖的像素范围。接下来,你需要计算与像素范围相对应的单元格范围。然后你需要循环遍历单元格范围以查看是否存在任何碰撞。请注意,在计算玩家右侧/底部覆盖的像素时,需要添加x/y和宽度/高度,然后减去1。
this.didCollide = function(dir) {
    if(dir == 'right'){
        var newBlock = window.tileMap.getCell(Math.floor((_self.position.x + _self.width) / 32), Math.floor((this.position.y + this.height / 2) / 32));
        if(newBlock == 2)
            return true;
    }
};

to...

this.didCollide = function(dir) {
    if(dir == 'right'){
        var col1 = Math.floor((_self.position.x + _self.speed) / 32);
        var col2 = Math.floor((_self.position.x + _self.speed + _self.width - 1) / 32);
        var row1 = Math.floor((_self.position.y) / 32);
        var row2 = Math.floor((_self.position.y + _self.height - 1) / 32);
        document.getElementById("player").textContent = "player: " + _self.position.x + " " + _self.position.y + " " + _self.width + " " + _self.height;
        document.getElementById("cells").textContent = "cells: " + col1 + " " + col2 + " " + row1 + " " + row2;
        for (var c = col1; c <= col2; c++) {
            for (var r = row1; r <= row2; r++) {
                var newBlock = window.tileMap.getCell(c, r);
                if(newBlock == 2) {
                    return true;
                }
            }
        }
    }
    return false;
};

1

由于你在每次按键时将玩家移动8个位置,因此在按键时必须测试这8个中间位置是否发生碰撞。

警告:未经测试的代码--可能需要一些微调

window.addEventListener('keydown', function(e) {
    // save x,y before the move
    var beginningX=_self.position.x;
    var beginningY=_self.position.y;

    // test each interim positon between the beginning & 
    // current position for collisions
    // if a collision occurs, stop at the collision position
    if(e.keyCode == 38 || e.keyCode == 87){
        _self.position.y -= _self.speed;
        _self.position.y = testInterimVerticalCollisions(
            beginningY, _self.position.y, _self.position.x);
    }

    if(e.keyCode == 40 || e.keyCode == 83){
        _self.position.y += _self.speed;
        _self.position.y = testInterimVerticalCollisions(
            beginningY, _self.position.y, _self.position.x);
    }

    if(e.keyCode == 37 || e.keyCode == 65){
        _self.position.x -= _self.speed;
        _self.position.x = testInterimHorizontalCollisions(
            beginningX, _self.position.x, _self.position.y);
    }

    if(e.keyCode == 39 || e.keyCode == 68){
        _self.position.x += _self.speed;
        _self.position.x = testInterimHorizontalCollisions(
            beginningX, _self.position.x, _self.position.y);
        }
    }
})

// test if any interim movement caused a collision
// if yes, return the x that caused the collision
// if no, return the ending x
function testInterimHorizontalCollisions(beginningX,endingX,y){
    for(var x=beginningX;x<=endingX;x++){
        // TODO: adjust for camera position offset
        var cellX = parseInt(x/cellWidth);
        var cellY = parseInt(y/cellHeight);
        if(getCell(cellX,cellY)==2){return(x);}
    }
    return(endingX);
}

// test if any interim movement caused a collision
// if yes, return the y that caused the collision
// if no, return the ending y
function testInterimVerticalCollisions(beginningY,endingY,x){
    for(var y=beginningY;y<=endingY;y++){
        // TODO: adjust for camera position offset
        var cellX = parseInt(x/cellWidth);
        var cellY = parseInt(y/cellHeight);
        if(getCell(cellX,cellY)==2){return(y);}
    }
    return(endingY);
}

1
谢谢!最终我采用了基于这个答案的解决方案。总有一天我会轻松掌握这些技能,但现在还不行 ;) - Lewis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接