JS贪吃蛇游戏中的碰撞检测bug

Collision detection bug in JS snake game

我正在尝试编写 Snake 的变体,其中蛇 "bounces" 离开了墙。

它大部分时间都有效,但偶尔会出现蛇 "escapes",我不明白为什么。最初我将碰撞检测函数中的不等式严格设置为 <>,我认为这是问题的原因,但我已将它们更改为 <=>= 问题仍然存在。

谁能解释一下为什么会这样? (一般要玩一分钟左右蛇才会逃走...)

<canvas id="canvas" width=500 height=500 style="display: block; border: 1px solid green; margin: auto;"></canvas>
<script>
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = '30px Arial';

var HEIGHT = 500;
var WIDTH = 500;
var SEGMENT_WIDTH = 30;

var snakeVelocity = {
    i: 1,
    j: 0
};
var snakeArray = createSnake();

function createSnake() {
    var snakeArray = [];
    var length = 5; // Initial length of snake
    for (var i = 0; i < length; i++) {
        snakeArray.push({
            x: i + 1, 
            y: 1
        });
    }
    return snakeArray;
}

function moveSnake(arr) {
    var head = arr.slice(-1)[0];
    var tail = arr[0];
    var newHead = arr.shift();

    // check for wall collision, which also updates velocity if needed
    snakeWallCollision(head);

    newHead.x = head.x + snakeVelocity.i;
    newHead.y = head.y +  + snakeVelocity.j;

    arr.push(newHead);
    return arr;
}

function snakeWallCollision(obj) {
    var collision = false;
    if (obj.x >= WIDTH / SEGMENT_WIDTH || obj.x <= 0) {
        snakeVelocity.i *= -1;
        collision = true;
    }
    if (obj.y >= HEIGHT / SEGMENT_WIDTH || obj.y <= 0) {
        snakeVelocity.j *= -1;
        collision = true;
    }
    return collision;
}

function drawSnake() {
    console.log(snakeArray[0]);
    for (var i = 0; i < snakeArray.length; i++) {
        var segment = snakeArray[i];
        ctx.fillText('S', segment.x * SEGMENT_WIDTH, segment.y * SEGMENT_WIDTH + 30);
    }
}

function update() {
    ctx.clearRect(0, 0, WIDTH, HEIGHT);
    moveSnake(snakeArray);
    drawSnake();
}

function checkKey(e) {

    e = e || window.event;

    if ([38, 40, 37, 39].includes(e.keyCode)) {
        e.preventDefault();
    }

    if (e.keyCode == '38') {
        snakeVelocity = {
            i: 0,
            j: -1
        };
    } else if (e.keyCode == '40') {
        snakeVelocity = {
            i: 0,
            j: 1
        };
    } else if (e.keyCode == '37') {
        snakeVelocity = {
            i: -1,
            j: 0
        };
    } else if (e.keyCode == '39') {
        snakeVelocity = {
            i: 1,
            j: 0
        };
    }
}
document.onkeydown = checkKey;
setInterval(update, 1000 / 20);
drawSnake();
</script>

如果你交换这些行会发生什么:

newHead.x = head.x + snakeVelocity.i;
newHead.y = head.y +  + snakeVelocity.j;

// check for wall collision, which also updates velocity if needed
snakeWallCollision(head);

问题似乎是,如果用户按与您同步的键 collision check,方向会改变两次,蛇就会越过墙。

也许这个可以帮到你(我添加了一个测试变量 USER_ACTION 来检查用户是否按下了键,如果是,请不要执行 collision check):

var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = '30px Arial';

var HEIGHT = 500;
var WIDTH = 500;
var SEGMENT_WIDTH = 30;
var USER_ACTION = false;

var snakeVelocity = {
    i: 1,
    j: 0
};
var snakeArray = createSnake();

function createSnake() {
    var snakeArray = [];
    var length = 5; // Initial length of snake
    for (var i = 0; i < length; i++) {
        snakeArray.push({
            x: i + 1,
            y: 1
        });
    }
    return snakeArray;
}

function moveSnake(arr) {
    var head = arr.slice(-1)[0];
    var tail = arr[0];
    var newHead = arr.shift();

    // check for wall collision, which also updates velocity if needed
    snakeWallCollision(head);

    newHead.x = head.x + snakeVelocity.i;
    newHead.y = head.y + +snakeVelocity.j;

    arr.push(newHead);
    return arr;
}

function snakeWallCollision(obj) {
    if (!USER_ACTION) {
        var collision = false;
        if (obj.x >= WIDTH / SEGMENT_WIDTH || obj.x <= 0) {
            snakeVelocity.i *= -1;
            collision = true;
        }
        if (obj.y >= HEIGHT / SEGMENT_WIDTH || obj.y <= 0) {
            snakeVelocity.j *= -1;
            collision = true;
        }
    } else {
        USER_ACTION = false;
    }
    return collision;
}

function drawSnake() {
    console.log(snakeArray[0]);
    for (var i = 0; i < snakeArray.length; i++) {
        var segment = snakeArray[i];
        ctx.fillText('S', segment.x * SEGMENT_WIDTH, segment.y * SEGMENT_WIDTH + 30);
    }
}

function update() {
    ctx.clearRect(0, 0, WIDTH, HEIGHT);
    moveSnake(snakeArray);
    drawSnake();
}

function checkKey(e) {
    USER_ACTION = true;
    e = e || window.event;

    if ([38, 40, 37, 39].includes(e.keyCode)) {
        e.preventDefault();
    }

    if (e.keyCode == '38') {
        snakeVelocity = {
            i: 0,
            j: -1
        };
    } else if (e.keyCode == '40') {
        snakeVelocity = {
            i: 0,
            j: 1
        };
    } else if (e.keyCode == '37') {
        snakeVelocity = {
            i: -1,
            j: 0
        };
    } else if (e.keyCode == '39') {
        snakeVelocity = {
            i: 1,
            j: 0
        };
    }
}
document.onkeydown = checkKey;
setInterval(update, 1000 / 20);
drawSnake();

如果您在撞到墙壁之前改变方向离开墙壁,就会出现此问题。

举例来说,蛇的头刚刚移动到 x = 0 并在下一个更新帧之前向左移动和用户向右箭头键。现在 snakeVelocity.i 设置为离墙 1 远。

你接着试墙

if (obj.x >= WIDTH / SEGMENT_WIDTH || obj.x <= 0) {
    snakeVelocity.i *= -1; // negate the direction
                           // but the direction is already away from the
                           // wall due to user input. This will turn it back
                           // onto the wall
}

上下同样如此。

您需要让碰撞测试知道蛇的前进方向,然后根据该测试该移动是否会导致碰撞。

更改测试函数以查找如果允许蛇头按照其当前状态指示移动的下一个位置。仅当该移动导致头部超出游戏范围时,您才改变方向。

function snakeWallCollision(head) {
    var x = head.x + snakeVelocity.i; // find out where the head will be 
    var y = head.y + snakeVelocity.j; // next frame
    if (x > WIDTH / SEGMENT_WIDTH || x < 0) {
        snakeVelocity.i *= -1;
        return true;
    }
    if (y > HEIGHT / SEGMENT_WIDTH || y < 0) {
        snakeVelocity.j *= -1;
        return true;
    }
    return false;
}