帆布墙碰撞检测

时间:2015-11-22 19:48:31

标签: javascript html html5 canvas html5-canvas

我正在制作一个游戏,并且有一面墙,我不希望玩家通过。我正在使用html5画布并且有一个播放器对象来保存x和y值。墙壁位于x:650和y:0。由于当x坐标为630时,播放器为20x20像素,因此触及墙壁。

if(player.x > 630 && player.y <= 500) {
    player.x = 630;
}

这段代码有什么问题?我感谢任何帮助!

3 个答案:

答案 0 :(得分:3)

<强>答案

您提供的代码是可以的,它没有任何问题。所以我怀疑问题出在代码的其他地方,很可能是在移动代码中。如果您在墙壁测试后移动播放器然后显示它,播放器可能会开始爬到墙上,但如果没有剩下的代码,很难知道您的代码有什么问题。

我已经详细介绍了正确的碰撞测试方法,因为有两个答案只显示部分解决方案。它作为碰撞测试的一般指南,可能无法直接应用于该问题。

帧间移动

从曲面反射物体的正确方法。

你必须考虑到球在帧之间移动,并且碰撞可能在前一帧中的任何时间发生。碰撞后球与墙壁的距离取决于前一帧撞击墙壁的时间。如果球缓慢或快速移动,这一点很重要。

var dx = 10; // delta x velocity of object in pixels
var wx = 10; // width of object in pixels
var px = 90;  // position of object in pixels
var wallX = 105; // position of wall


px += dx;  // move the ball. Its position is now  100.
           // its right side is at px + wx = 110.
// test if it has it the wall
if(px+wx > wallX){
    dx = -dx; // reflect delta x
    // The object is 5 pixel into the wall.
    // The object has hit the wall some time during the last frame
    // We need to adjust the position as the ball may have been
    // traveling away from the wall for some time during the last frame.
    var dist = (px+wx)-wallX; // get the distance into the wall
    px -= dist*2; // the object hit the wall at position 95 and has been 
                  // traveling away since then so it is easy to just 
                  // subtract 2 times the distance the ball entered the wall
    // the above two lines can be done in one
    // px -= ((px+wx)-wallX)*2;
}

为何重要

下面是一个球在画布内弹跳的模拟。

为了说明球在帧之间移动,它已被运动模糊以显示其在帧之间的运动。请注意,这不是完美的解决方案,因为假设弹跳是在球处于线性运动时发生的,而实际上它是在自由落体和恒定加速度下。但它仍然节约能源。

在正确的测试中,球弹回的高度会随着时间的推移而保持不变。没有能量损失或获得。

右键单击以关闭帧间调整,您会注意到每帧的球开始减小其高度。这是因为在每次碰撞时球都会失去一点能量,因为在碰撞试验后定位时,不会考虑前一帧中的运动。当碰撞发生在精确的帧时间时,它将稳定在一个恒定的速率。什么时候提前很难确定。

左键单击以减慢模拟帧速率,再次单击鼠标左键以恢复正常。

下面的代码实际上并不是答案的一部分,它是为了证明在碰撞测试期间没有正确调整位置对模拟整体精度的影响。

// helper functions. NOT part of the answer
var canvas = document.getElementById("canV"); 
var ctx = canvas.getContext("2d");
var mouseButton = 0;
canvas.addEventListener('mousedown',function(event){mouseButton = event.which;});
canvas.addEventListener('mouseup'  ,function(){mouseButton = 0;});
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);
var currentSurface = ctx;
var createImage = function (w, h) {// create an canvas image of size w,h and attach context 2d
    var image = document.createElement("canvas");  
    image.width = w;
    image.height = h !== undefined?h:w; 
    currentSurface = image.ctx = image.getContext("2d"); 
    return image;
}  
var setColour = function (fillC, strokeC, lineW) { 
    currentSurface.fillStyle = fillC !== undefined ? fillC : currentSurface.fillStyle;
    currentSurface.strokeStyle = strokeC !== undefined ? strokeC : currentSurface.strokeStyle;
    currentSurface.lineWidth = lineW !== undefined ? lineW : currentSurface.lineWidth;
}
var circle = function(x,y,r,how){
    currentSurface.beginPath();
    currentSurface.arc(x,y,r,0,Math.PI*2);
    how = how.toLowerCase().replace(/[os]/g,"l"); // how to draw
    switch(how){
        case "f":  // fill
            currentSurface.fill();
            break;
        case "l":
            currentSurface.stroke();
            break;
        case "lf":
            currentSurface.stroke();
            currentSurface.fill();
            break;
        case "fl":
            currentSurface.fill();
            currentSurface.stroke();
            break;
    }
}
function createGradImage(size,col1,col2){
    var image = createImage(size);
    var g = currentSurface.createLinearGradient(0,0,0,currentSurface.canvas.height);
    g.addColorStop(0,col1);
    g.addColorStop(1,col2);
    currentSurface.fillStyle = g;
    currentSurface.fillRect(0,0,currentSurface.canvas.width,currentSurface.canvas.height);    
    return image;
}
function createColouredBall (ballR,col) {
    var ball = createImage(ballR*2);
    var unit = ballR/100;
    setColour("black");
    circle(ballR,ballR,ballR,"f");
    setColour("hsl("+col+",100%,30%)");
    circle(ballR-unit*3,ballR-unit*3,ballR-unit*7,"f");
    setColour("hsl("+col+",100%,50%)");
    circle(ballR-unit*10,ballR-unit*10,ballR-unit*16,"f");
    setColour("White");
    circle(ballR-unit*50,ballR-unit*50,unit*16,"f");
    
    return ball;
}
//===================================    
//    _                          
//   /_\  _ _  ____ __ _____ _ _ 
//  / _ \| ' \(_-< V  V / -_) '_|
// /_/ \_\_||_/__/\_/\_/\___|_|  
//                              
// ==================================
// Answer code

// lazy coder variables
var w = canvas.width;
var h = canvas.height;

// ball is simulated 5cm 
var pixSize = 0.24; // in millimeters for simulation

// Gravity is 9.8 ms^2 so convert to pixels per frame squared
// Assuming constant 60 frames per second. ()
var gravity = 9800*pixSize/60; 
gravity *= 0.101; // because Earth's gravity is stupidly large let's move to Pluto

// ball 5cm 
var ballR = (25/pixSize)/2;          // radius is 2.5cm for 5cm diamiter ball
var ballX = w/2;                     // get center of canvas
var ballY = ballR+3;                 // start at the top
var ballDX = (Math.random()-0.5)*15; // start with random x speed
ballDX += ballDX < 0 ? -5 : 5;       // make sure it's not too slow
var ballDY = 0;                      // star with no downward speed;
var ballLastX = ballX;
var ballLastY = ballY;

//create an image of the Ball
var ball = createColouredBall(ballR,Math.floor(Math.random()*360)); // create an image of ball

// create a background. Image is small as it does not have much detail in it
var background = createGradImage(16,"#5af","#08C");
// time to run for
var runFor = 10*60; // ten secons yimes 60 frames per second

// draws the ball motion blured. This introduces extra complexity
var drawMotionBlur = function(image,px,py,dx,dy,steps){
    var i,sx,sy;
    sx = dx / steps;
    sy = dy / steps;
    px -= dx; // move back to start position
    py -= dy; 
    ctx.globalAlpha = 1/(steps*0.8); // set alpha to slightly higher for each step
    for(i = 0; i < steps; i+= 1){
        ctx.drawImage(image,px+i*sx,py+i*sy);
    }
    ctx.globalAlpha = 1; // reset alpha
    
}
// style for text
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.textAlign = "center";
ctx.lineJoin = "round"; // stop some letters getting ears.
ctx.lineWidth = 3;
ctx.textBaseline = "bottom";
var textCenterX = w/2;
var maxHeight = Infinity;
var lastMaxHeight = ballY;
var slowMotion = false;  // slow motion flag
var frameTravel = true;  // use frame travel in collision test 
var update = function(){
    var blurSteps = 10;  // motion blur ball render steps
    const bSteps = 10;
    if(mouseButton === 1){
        slowMotion = ! slowMotion;
        mouseButton = 0;
    }
    if(mouseButton === 3){
        frameTravel = ! frameTravel;
        ballX = w/2;                     // get center of canvas
        ballY = ballR+3;                 // start at the top
        ballDY = 0;                      // start at 0 y speed
        mouseButton = 0;
    }
    // clear the canvas with background canvas image
    ctx.drawImage(background,0,0,w,h);
    
    ballDY += gravity; // accelrate due to grav
    // add deltas to ball position
    ballX += ballDX; 
    ballY += ballDY;
    // test for collison on left and right walls. Need to 
    // ajust for motion blur
    if (ballX < ballR) {
        ballDX = -ballDX; // refect delta x
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballX - ballR) / -ballDX));
            // get position it should have traveled since
            ballX -= (ballX - ballR) * 2;
        }else{
            ballX = ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    } else
    if (ballX > w - ballR) {
        ballDX = -ballDX;
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballX - (w - ballR)) / -ballDX));
            ballX -= (ballX - (w - ballR)) * 2;
        }else{
            ballX = w - ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    }
    if (ballY > h - ballR) {
        ballDY = -ballDY;
        // to show max height
        lastMaxHeight = maxHeight;
        maxHeight = Infinity;
        if (frameTravel) { // if using frame travel time
            // blur the outward traveling ball only for the time it has been traveling away
            blurSteps = Math.ceil(10 * ((ballY - (h - ballR)) / -ballDY));
            ballY -= (ballY - (h - ballR)) * 2;
        }else{
            ballY = h - ballR; // move ball to touching wall
            blurSteps = 1; // there is no outward motion
        }
    }        
    // draw the ball motion blured
    drawMotionBlur(
        ball,                    // image to draw
        ballX - ballR,             // offset radius
        ballY - ballR,
        ballDX * (blurSteps / bSteps),  // speed and adjust for bounced
        ballDY * (blurSteps / bSteps),
        blurSteps                // number of blurs
    );
    // show max height. Yes it is min but everything is upside down.
    maxHeight = Math.min(maxHeight,ballY);
    lastMaxHeight = Math.min(ballY,lastMaxHeight);

    // show max height
    ctx.font = "12px arial black";
    ctx.beginPath();
    ctx.moveTo(0,lastMaxHeight - ballR);
    ctx.lineTo(w,lastMaxHeight - ballR);
    ctx.stroke();
    ctx.fillText("Max height.",40,lastMaxHeight - ballR + 6);


    var str = ""; // display status string
    if(slowMotion){   // show left click help
        str += "10fps."
        ctx.fillText("click for 60fps.",textCenterX,43);
    }else{
        str += "60fps."
        ctx.fillText("click for 10fps.",textCenterX,43);
    }

    if(frameTravel){ // show mode and right click help
        str += " Mid frame collision.";
        ctx.fillText("Right click for Simple collision",textCenterX,55);
    }else{
        str += " Simple collision.";
        ctx.fillText("Right click for mid frame collision",textCenterX,55);
    }
    // display help text
    ctx.font = "18px arial black";  
    ctx.strokeText(str,textCenterX,30);
    ctx.fillText(str,textCenterX,28);

    if(slowMotion){
        setTimeout(update,100); // show in slow motion
    }else{
        requestAnimationFrame(update); // request next frame (1/60) seconds from now
    }

    // all done
}
update(); // to start the ball rolling
.canC { width:500px;  height:500px;}
<canvas class="canC" id="canV" width=500 height=500></canvas>

答案 1 :(得分:0)

对于沿650 x 650区域底部(Y = 0)的X轴运行的墙,我们希望:

if (player.y <= 20) {
    player.y = 20;
}

对于在650 x 650场的左侧(X = 0)沿Y轴运行的墙,我们希望:

if (player.x <= 20) {
    player.x = 20;
}

对于在650 x 650场的右侧(X = 650)沿Y轴运行的墙,我们希望:

if (player.x >= 630) {
    player.x = 630;
}

对于沿650 x 650的顶部(Y = 650)沿X轴运行的墙,我们希望:

if (player.y >= 630) {
    player.y = 630;
}

答案 2 :(得分:0)

这段代码类似于我使用的代码,如果我们将水平(h)和垂直(v)速度属性附加到玩家对象,我们可以将它们乘以负值,以便让玩家从墙上反弹将超越界限。或者,如果您希望它停止,请在墙上将它们设置为零。

//player.x+player.h gives us the future position of the player
if (player.x+player.h>630||player.x+player.h<0)
{
    player.h*=-1;//bounce
    //stop player.h=0;
}
if (player.y+player.v>500||player.y+player.v<0)
{
   player.v*=-1;
   //stop player.v=0;
}
//new player coordinates
player.x+=player.h;
player.y+=player.v;

希望这有帮助。