AI跟随并避免与障碍物碰撞

3

我正在制作一个基于极坐标的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>


1
角度是棘手的。如果可以的话,总是使用向量来工作。 - meowgoesthedog
@meowgoesthedog 我曾经通过获取演员和玩家之间的大小,然后将其朝向玩家移动并远离障碍物(如果足够接近)来实现这一点。可能更有效率,因为它不使用Math.atan2、cos和sin。但我遇到了问题,因为由于障碍物给出的负力,演员的速度会减慢。 - Will Pierce
如果一个问题可以在极坐标系下解决,那么它也可以在笛卡尔坐标系下解决(甚至更容易);使用笛卡尔坐标系的主要优势是你不必处理在正负180度处的坐标奇点(即向量数学具有旋转不变性)。请发布您原来基于向量的代码。 - meowgoesthedog
嘿!我在这里发帖只是因为这很酷,而且由于你��在研究 AI 导航,所以应该了解这存在。虚幻引擎有 unreal.js。您可以只使用 JavaScript 使用 Unreal 及其 AI 系统以及所有其他强大功能: https://github.com/ncsoft/Unreal.js/ - Max Baldwin
2个回答

0
如果玩家和障碍物之间的角度相同,则我们继续前进,因为变量会互相抵消。
    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

我喜欢这个诊断,但对解决方案的随机性并不感到热情。将moveAngle旋转一定角度远离angleObstacle怎么样?当它们完全相等时,我承认需要抛硬币,但只需要进行小的角度变化即可。所以,类似以下的代码(假设0-360度角度):delta = 15; cross = Math.sin(moveAngle-angleObstacle); if (cross>0) moveAngle += delta; if (cross<0) moveAngle -= delta if (cross==0) { if (random(2)==1) moveAngle += delta; else moveAngle -= delta; } - jwimberley

0
问题确实是当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>


好的想法。我认为OP甚至不需要使用极坐标,因为可以使用位移向量在笛卡尔坐标系中完成。但这本身并不能解决墙壁碰撞问题。 - Jason Stackhouse
我认为原帖并没有真正使用通常定义的极坐标,而只是使用角度来参数化单位向量,我认为这完全没问题。当非常靠近原点时,真正的极坐标会成为一个问题,我同意这一点。 - jwimberley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接