ИИ следуй и избегай столкновения с препятствием - PullRequest
0 голосов
/ 30 октября 2018

Я делаю навигационную систему AI на основе полярных координат. Цель состоит в том, чтобы переместить актера в положение, одновременно удаляясь от возможного препятствия на его пути.

Код работает идеально большую часть времени, но после тестирования я обнаружил следующее: когда игрок, препятствие и актер все идеально выровнены в направлении X или Y или по диагонали, актер застревает в препятствии. Это особенно заметно, когда игрок «обнимает» стену, потому что вектор движения актера обрезается стенами, выравнивая их.

Нажмите кнопки во фрагменте, чтобы увидеть, о чем я.

Есть ли способ предотвратить это?

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let playerX = 100;
let playerY = 200;

let obstacleX = 200;
let obstacleY = 200;

let actorX = 300;
let actorY = 201;


function loop() {
    // Wall clipping
    if (actorX > 490) {
        actorX = 490;
    } else if (actorX < 10) {
        actorX = 10;
    }

    if (actorY > 490) {
        actorY = 490;
    } else if (actorY < 10) {
        actorY = 10;
    }

    // Vector between player and actor
    let vectorPlayerX = playerX - actorX;
    let vectorPlayerY = playerY - actorY;

    // Vector between obstacle and actor
    let vectorObstacleX = obstacleX - actorX;
    let vectorObstacleY = obstacleY - actorY;

    // Where to move, defaults to player's position
    const anglePlayer = Math.atan2(vectorPlayerY, vectorPlayerX);
    let moveAngle = anglePlayer;

    // If near obstacle, adjust course and try to avoid it
    if (Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY) < 50) {
        const angleObstacle = Math.atan2(vectorObstacleY, vectorObstacleX);
        moveAngle += anglePlayer - angleObstacle;
    }

    // Move the vector to desired location
    actorX += Math.cos(moveAngle);
    actorY += Math.sin(moveAngle);

    //Drawing
    ctx.clearRect(0, 0, 500, 500);

    ctx.beginPath();
    ctx.fillStyle = "gray";
    ctx.arc(actorX, actorY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.fillStyle = "orange";
    ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.fillStyle = "blue";
    ctx.arc(playerX, playerY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    requestAnimationFrame(loop);
}

requestAnimationFrame(loop);


function nonAligned() {
    playerX = 100;
    playerY = 200;

    obstacleX = 200;
    obstacleY = 200;

    actorX = 300;
    actorY = 201;
}

function alignedY() {
    playerX = 100;
    playerY = 490;

    obstacleX = 200;
    obstacleY = 490;

    actorX = 300;
    actorY = 490;
}

function alignedBoth() {
    playerX = 200;
    playerY = 200;

    obstacleX = 300;
    obstacleY = 300;

    actorX = 400;
    actorY = 400;
}
#options {
    position: fixed;
    top: 5px;
    left: 5px;
}
<!DOCTYPE html>
<html>
<body>
    <canvas id="canvas" width="500" height="500"></canvas>
<div id="options">
    <button onclick="nonAligned()">Spawn non-aligned</button>
    <button onclick="alignedY()">Spawn Y aligned</button>
    <button onclick="alignedBoth()">Spawn diagonally aligned</button>
</div>
</body>
</html>

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

Проблема действительно в том, что moveAngle не изменяется, когда он направлен прямо на препятствие. Небольшая модификация проверяет, находится ли moveAngle по часовой стрелке или против часовой стрелки от препятствия, и отклоняется еще дальше (примечание: мой код нарушает объятия стены и ведет себя плохо в случае "выровненного по Y" по этой причине, которая исправима, но я пофиг):

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

let playerX = 100;
let playerY = 200;

let obstacleX = 200;
let obstacleY = 200;

let actorX = 300;
let actorY = 201;


function loop() {
    // Wall clipping
    if (actorX > 490) {
        actorX = 490;
    } else if (actorX < 10) {
        actorX = 10;
    }

    if (actorY > 490) {
        actorY = 490;
    } else if (actorY < 10) {
        actorY = 10;
    }

    // Vector between player and actor
    let vectorPlayerX = playerX - actorX;
    let vectorPlayerY = playerY - actorY;

    // Vector between obstacle and actor
    let vectorObstacleX = obstacleX - actorX;
    let vectorObstacleY = obstacleY - actorY;

    // Where to move, defaults to player's position
    const anglePlayer = Math.atan2(vectorPlayerY, vectorPlayerX);
    let moveAngle = anglePlayer;

    // If near obstacle, adjust course and try to avoid it
    obs_distance = Math.sqrt(vectorObstacleX * vectorObstacleX + vectorObstacleY * vectorObstacleY);
    if (obs_distance < 100) {
        const angleObstacle = Math.atan2(vectorObstacleY, vectorObstacleX);
        delta = Math.PI/2.0;
        if (obs_distance > 100.0/32.0) { delta = (100.0/32.0)*Math.PI/obs_distance; }
        cross = Math.sin(moveAngle-angleObstacle);
        if (cross>0) { moveAngle += delta; }
        if (cross<0) { moveAngle -= delta; }
        if (cross==0) {
           if (Math.random(2)==1) {
             moveAngle += delta;
           } else {
             moveAngle -= delta;
           }
        }
    }

    // Move the vector to desired location
    actorX += Math.cos(moveAngle);
    actorY += Math.sin(moveAngle);

    //Drawing
    ctx.clearRect(0, 0, 500, 500);

    ctx.beginPath();
    ctx.fillStyle = "gray";
    ctx.arc(actorX, actorY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.fillStyle = "orange";
    ctx.arc(obstacleX, obstacleY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    ctx.beginPath();
    ctx.fillStyle = "blue";
    ctx.arc(playerX, playerY, 10, 0, Math.PI * 2, true);
    ctx.fill();

    requestAnimationFrame(loop);
}

requestAnimationFrame(loop);


function nonAligned() {
    playerX = 100;
    playerY = 200;

    obstacleX = 200;
    obstacleY = 200;

    actorX = 300;
    actorY = 201;
}

function alignedY() {
    playerX = 100;
    playerY = 490;

    obstacleX = 200;
    obstacleY = 490;

    actorX = 300;
    actorY = 490;
}

function alignedBoth() {
    playerX = 200;
    playerY = 200;

    obstacleX = 300;
    obstacleY = 300;

    actorX = 400;
    actorY = 400;
}
#options {
    position: fixed;
    top: 5px;
    left: 5px;
}
<!DOCTYPE html>
<html>
<body>
    <canvas id="canvas" width="500" height="500"></canvas>
<div id="options">
    <button onclick="nonAligned()">Spawn non-aligned</button>
    <button onclick="alignedY()">Spawn Y aligned</button>
    <button onclick="alignedBoth()">Spawn diagonally aligned</button>
</div>
</body>
</html>
0 голосов
/ 30 октября 2018

Если угол наклона игрока и препятствия одинаков, то мы продолжаем курс, так как переменные компенсируют друг друга.

    moveAngle += anglePlayer - angleObstacle;

Если anglePlayer равен 117, а angleObstacle равен 117, а ваш moveAngle равен 117, вы получите

   117 + 117 -117 = 117 ...

Возможно, вы захотите что-то вроде этого (псевдокод)

    moveAngle += anglePlayer + random(90)-45;

Или, если вы столкнулись с препятствием, двигайтесь влево или вправо

    moveAngle += anglePlayer - 90;
    if (random(2)==1 moveAngle += 180
...