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\$
1
  • \$\begingroup\$ Have you been able to solve your problem? \$\endgroup\$
    – Christoph
    Apr 12, 2018 at 13:27

2 Answers 2

2
\$\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\$
1
1
\$\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\$
4
  • \$\begingroup\$ Very interesting, I'm trying to implement this also, but how did you manage to get the normal of the wall itself? PS this code and idea is brilliant BTW, exactly what I was looking for for a while \$\endgroup\$ Apr 26, 2020 at 4:24
  • \$\begingroup\$ Yo I was looking into this and do you know by any chanec how to get it to collide smoothly on the outside of the walls as well? \$\endgroup\$ Apr 30, 2020 at 7:20
  • \$\begingroup\$ notice how if the - and + for normal.x and normal.y at lines 271-272 are switched, then it jitters at the corner jsfiddle.net/ujfe6Lc7/1 \$\endgroup\$ Apr 30, 2020 at 7:23
  • \$\begingroup\$ yo hope ur welljust coming back to this, how did u manage to get it to not stop at the corners if a wall is isolated? \$\endgroup\$ Jul 5, 2022 at 12:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .