3
\$\begingroup\$

I have implemented some basic wall collisions where I project the velocity vector onto the wall the player is colliding with so they slide across the wall. The problem I am having is when you slide into the corner of a wall the player goes through the wall. What am I missing? Am I using the wrong method for this type of collision.

You can see the code at it's original location or below:

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var lastTime = Date.now();
var deltaTime = 0;
var sector = {
    walls: [
        {
            start: {
                x: 150,
                y: 100
            },
            end: {
                x: 300,
                y: 100
            }
        },
        {
            start: {
                x: 300,
                y: 100
            },
            end: {
                x: 350,
                y: 150
            }
        },
        {
            start: {
                x: 350,
                y: 150
            },
            end: {
                x: 350,
                y: 300
            }
        },
        {
            start: {
                x: 350,
                y: 300
            },
            end: {
                x: 300,
                y: 350
            }
        },
        {
            start: {
                x: 300,
                y: 350
            },
            end: {
                x: 150,
                y: 350
            }
        },
        {
            start: {
                x: 150,
                y: 350
            },
            end: {
                x: 100,
                y: 300
            }
        },
        {
            start: {
                x: 100,
                y: 300
            },
            end: {
                x: 100,
                y: 150
            }
        },
        {
            start: {
                x: 100,
                y: 150
            },
            end: {
                x: 150,
                y: 100
            }
        }
        
    ]
}
var player = {
    rotation: 180,
    radius: 20,
    moveSpeed: 1,
    turnSpeed: 90,
    maxSpeed: 1,
    friction: 2,
    speed: {
        x: 0,
        y: 0,
        z: 0
    },
    position: {
        x: 200,
        y: 200,
        z: 0
    }
}
var keys = {
    backspace:      8,
    tab:            9,
    enter:          13,
    shift:          16,
    ctrl:           17,
    alt:            18,
    pause:          19,
    capslock:       20,
    escape:         27,
    space:          32,
    pageUp:         33,
    pageDown:       34,
    home:           36,
    end:            35,
    left:           37,
    up:             38,
    right:          39,
    down:           40,
    insert:         45,
    del:            46,
    zero:           48,
    one:            49,
    two:            50,
    three:          51,
    four:           52,
    five:           53,
    six:            54,
    seven:          55,
    eight:          56,
    nine:           57,
    a:              65,
    b:              66,
    c:              67,
    d:              68,
    e:              69,
    f:              70,
    g:              71,
    h:              72,
    i:              73,
    j:              74,
    k:              75,
    l:              76,
    m:              77,
    n:              78,
    o:              79,
    p:              80,
    q:              81,
    r:              82,
    s:              83,
    t:              84,
    u:              85,
    v:              86,
    w:              87,
    x:              88,
    y:              89,
    z:              90,
    select:         93,
    numpad0:        96,
    numpad1:        97,
    numpad2:        98,
    numpad3:        99,
    numpad4:        100,
    numpad5:        101,
    numpad6:        102,
    numpad7:        103,
    numpad8:        104,
    numpad9:        105,
    multiply:       106,
    add:            107,
    subtract:       109,
    decimal:        110,
    divide:         111,
    f1:             112,
    f2:             113,
    f3:             114,
    f4:             115,
    f5:             116,
    f6:             117,
    f7:             118,
    f8:             119,
    f9:             120,
    f10:            121,
    f11:            122,
    f12:            123,
    numlock:        144,
    scrollLock:     145,
    semicolon:      186,
    equal:          187,
    comma:          188,
    dash:           189,
    period:         190,
    forwardSlash:   191,
    graveAccent:    192,
    openBracket:    219,
    backSlash:      220,
    closeBraket:    221,
    singleQuote:    222
}
var key = {
    up: false,
    down: false,
    left: false,
    right: false,
    down: [],
    next: []
}

/*==================================================
    CORE CODE
==================================================*/
function toRadians(degrees){
    return degrees * Math.PI / 180;
}

function update(){
    updateTime();
    updateKeyboard();
    updatePlayer();
    clearCanvas();
    drawPlayer();
    drawSector();

    window.requestAnimationFrame(update);
}

window.requestAnimationFrame(update);

function clearCanvas(){
    context.clearRect(0, 0, canvas.width, canvas.height);
}

function updateTime(){
    var currentTime = Date.now();
    deltaTime = (currentTime - lastTime) / 1000.0;
    lastTime = currentTime;
}

function lineIntersection(a, b, c, d){
    var denominator = ((b.x - a.x) * (d.y - c.y)) - ((b.y - a.y) * (d.x - c.x));
    var numerator1 = ((a.y - c.y) * (d.x - c.x)) - ((a.x - c.x) * (d.y - c.y));
    var numerator2 = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y));

    if (denominator == 0) return numerator1 == 0 && numerator2 == 0;

    var r = numerator1 / denominator;
    var s = numerator2 / denominator;

    return (r >= 0 && r <= 1) && (s >= 0 && s <= 1);
}


/*==================================================
    PLAYER CODE
==================================================*/
function updatePlayer(){
    if(key.down[keys.up]){
        player.speed.y = Math.max(-player.maxSpeed, player.speed.y - player.moveSpeed * deltaTime);
    }
    else if(key.down[keys.down]){
        player.speed.y = Math.min(player.maxSpeed, player.speed.y + player.moveSpeed * deltaTime);
    }
    else {
        friction();
    }

    if(key.down[keys.left]){
        player.rotation = player.rotation - player.turnSpeed * deltaTime;
    }
    else if(key.down[keys.right]){
        player.rotation = player.rotation + player.turnSpeed * deltaTime;
    }

    var radians = toRadians(player.rotation);
    var move = {
        x: Math.cos(radians) * player.speed.y,
        y: Math.sin(radians) * player.speed.y
    }

    var remainder = moveRemainder(move);

    player.position.x += remainder.x;
    player.position.y += remainder.y;
}

function moveRemainder(move){
    var remainder = move;

    var currentPosition = {
        x: player.position.x,
        y: player.position.y
    }
    var nextPosition = {
        x: player.position.x + move.x,
        y: player.position.y + move.y
    }

    var checkWalls = function(index){
        for(var i=0; i<sector.walls.length; i++){
            var wall = sector.walls[i];
            if(lineIntersection(currentPosition, nextPosition, wall.start, wall.end)){
                var xLength = Math.max(wall.start.x, wall.end.x) - Math.min(wall.start.x, wall.end.x);
                var yLength = Math.max(wall.start.y, wall.end.y) - Math.min(wall.start.y, wall.end.y);
                var equation = (move.x*xLength + move.y*yLength) / (xLength*xLength + yLength*yLength);

                remainder.x = xLength * equation;
                remainder.y = yLength * equation;
                if(index == 0){
                    checkWalls(1);
                }
                return;
            }
        }
    }
    checkWalls(0);

    return remainder;
}

function drawPlayer(){
    context.beginPath();
    context.fillStyle = 'red';
    context.arc(player.position.x, player.position.y, 3, 0, Math.PI * 2);
    context.fill();
    context.closePath();
}

function friction(){
    if(player.speed.x < 0){
        player.speed.x = Math.min(0, player.speed.x * deltaTime);
    }
    else if(player.speed.x > 0){
        player.speed.x = Math.max(0, player.speed.x * deltaTime);
    }
    if(player.speed.y < 0){
        player.speed.y = Math.min(0, player.speed.y + player.friction * deltaTime);
    }
    else if(player.speed.y > 0){
        player.speed.y = Math.max(0, player.speed.y - player.friction * deltaTime);
    }
}

/*==================================================
    SECTOR CODE
==================================================*/
function drawSector(){
    context.strokeStyle = 'black';
    sector.walls.forEach(function(wall){
        context.beginPath();
        context.moveTo(wall.start.x, wall.start.y);
        context.lineTo(wall.end.x, wall.end.y);
        context.stroke();
        context.closePath();
    })
}

/*==================================================
    INPUT CODE
==================================================*/
function updateKeyboard(){
    key.down = key.next;
}

window.addEventListener('keydown', function(e){
    key.next[e.keyCode] = true;
})

window.addEventListener('keyup', function(e){
    key.next[e.keyCode] = false;
})
<!doctype html>
<html>
    <head>
        <title>Retro3d proof of concept</title>
        <style>
            body {
                margin: 0;
                background: #f4f6f8;
            }
            #canvas {
                margin: 20px auto;
                display: block;
                background: #fff;
                border-radius: 2px;
                box-shadow: 0 0 0 1px rgba(63,63,68,0.05), 0 1px 3px 0 rgba(63,63,68,0.15);
            }
        </style>
    </head>
    <body>
        <canvas width="600" height="600" id="canvas"></canvas>
        <script src="main.js"></script>
    </body>
</html>

\$\endgroup\$
  • \$\begingroup\$ Have you been able to solve your problem? \$\endgroup\$ – Christoph Apr 12 '18 at 13:27
0
\$\begingroup\$

I saw a couple problems in your checkWalls function:

Once lineIntersection triggers for one wall, you return - this means once the check is positive for one wall you don't check for the second wall that would be needed for the corner check - dirty fix by moving the return outside the loop.

Without putting too much brainpower into fully understanding the math in lineIntersection, you seem to run into an "edge"(haha)-case that the velocity vector and the second line actually do NOT intersect - fixed by having the lines overlap.

https://jsfiddle.net/x0s0ef0b/41/

This is not bulletproof since I was able to have the player escape with those two fixes in place but it should hint you in the right direction. Also there is now some optimization to be done (you are now always checking all wall, which is not necessary). Instead of intersection you might want to check for "being on the outside of a wall" (e.g. if you always construct the levels clockwise, being on the left side of the wall).

Also I commented out the

if(index == 0){
  //checkWalls(1);
}

part, because I did not understand why you would want to run the check twice (and it still seemed to work).

\$\endgroup\$
0
\$\begingroup\$

Finally figured out how to get this to work. I added a circle collision to the player. Example can be found here jsfiddle.net/CalinR/3augx250 What I'm doing is projecting the player position onto the wall and if the distance between the projection and the player is less than the player radius I get the normal of the wall and multiply it by the player radius to get the new position of the player

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.