gpt4 book ai didi

javascript - 碰撞检测不应该使物体传送

转载 作者:搜寻专家 更新时间:2023-11-01 04:35:16 28 4
gpt4 key购买 nike

已解决,最终算法见帖子底部

背景:我正在使用 JS 和 HTML Canvas 元素开发 2D 平台游戏。关卡 map 是基于图块的,但玩家不会被夹在图块上。我正在使用 "Tiny Platformer" on Code inComplete 中概述的碰撞检测算法.除了一种边缘情况(或“壁架”情况)外,它在很大程度上有效。

问题:

gif of ledge issue

玩家正在跌倒并向右移动,进入墙壁。当它下落时,它会传送到壁架的高度。相反,玩家应该在没有传送的情况下正常坠落。

有没有办法改变算法来防止这种行为?如果没有,你能建议一个替代的碰撞检测算法吗? 理想情况下,任何修复都不会假设玩家总是摔倒,因为在游戏中玩家的摔倒方向会在上/下/左/右之间切换。

算法:

  • 假设没有碰撞,计算玩家的新位置。 (以下代码中未显示)
  • 一个名为 getBorderTiles 的函数获取一个对象(玩家)并返回接触玩家 4 个 Angular 落中的每个 Angular 落的瓷砖。由于玩家不比瓷砖大,那些边界瓷砖必然是玩家接触的唯一瓷砖。请注意,其中一些图块可能相同。例如,如果玩家只占据一列,则左上/右上图块将相同,左下/右下图块也是如此。如果发生这种情况,getBorderTiles仍然返回所有四个图块,但有些是相同的。
  • 它会检查关卡 map (一个 2D 数组)中的这些边界图块以查看它们是否是实心的。如果瓷砖是实心的,则对象正在与该瓷砖发生碰撞。
  • 它测试上/下/左/右碰撞。如果玩家向下移动并与向下的瓷砖发生碰撞,但没有与相应的向上的瓷砖发生碰撞,则玩家正在向下碰撞。如果玩家向左移动并与左侧瓷砖发生碰撞,但没有与相应的右侧瓷砖发生碰撞,则它是在向左碰撞。等等。在左/右检查之前执行上/下检查。如果在执行左/右检查之前存在上/下碰撞,则将调整存储边界图块的变量。例如,如果玩家向下碰撞,它将被插入向上的瓷砖,因此 BL/BR 瓷砖现在与 TL/TR 瓷砖相同。
  • 玩家的 x、y 和速度根据它碰撞的方向进行调整。

  • 算法失败的原因:

    See this image.

    右下 Angular 的瓷砖是实心的,但右上 Angular 不是,所以(第 4 步)玩家向下碰撞,(第 5 步)它被向上推。此外,它与 BR 瓷砖碰撞,但不与 BL 碰撞,因此它向右碰撞并被向左推。最后,玩家被渲染在壁架的正上方和左侧。实际上它被传送了。

    尝试解决:我试图解决这个问题,但它只会产生另一个问题。我添加了一个检查,以便玩家仅在该图块内有一定距离(例如 3px)时才与该图块发生碰撞。如果玩家仅在 BR 区块中,则算法不会记录下碰撞,因此玩家不会向上传送。但是,如果玩家在另一种情况下跌倒在地,则直到玩家落入地面很远时,它才会确认碰撞。当它掉到地上时,玩家会颤抖,被推回地面,再次跌倒,等等。

    感谢您阅读到这里。我非常感谢您的反馈。

    当前算法代码:

    var borderTiles = getBorderTiles(object), //returns 0 (a falsy value) for a tile if it does not fall within the level
    tileTL = borderTiles.topLeft,
    tileTR = borderTiles.topRight,
    tileBL = borderTiles.bottomLeft,
    tileBR = borderTiles.bottomRight,
    coordsBR = getTopLeftXYCoordinateOfTile(tileBR), //(x, y) coordinates refer to top left corner of tile
    xRight = coordsBR.x, //x of the right tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
    yBottom = coordsBR.y, //y of the bottom tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
    typeTL = tileTL ? level.map[tileTL.row][tileTL.col] : -1, //if tileTL is in the level, gets its type, otherwise -1
    typeTR = tileTR ? level.map[tileTR.row][tileTR.col] : -1,
    typeBL = tileBL ? level.map[tileBL.row][tileBL.col] : -1,
    typeBR = tileBR ? level.map[tileBR.row][tileBR.col] : -1,
    collidesTL = typeTL == TILETYPE.SOLID, //true if the tile is solid
    collidesTR = typeTR == TILETYPE.SOLID,
    collidesBL = typeBL == TILETYPE.SOLID,
    collidesBR = typeBR == TILETYPE.SOLID,
    collidesUp = false,
    collidesDown = false,
    collidesLeft = false,
    collidesRight = false;

    //down and up
    if (object.vy < 0 && ((collidesTL && !collidesBL) || (collidesTR && !collidesBR))) {
    collidesUp = true;
    /*The object is pushed out of the bottom row, so the bottom row is now the top row. Change the collides__
    variables as this affects collision testing, but is it not necessary to change the tile__ variables. */
    collidesTL = collidesBL;
    collidesTR = collidesBR;
    } else if (object.vy > 0 && ((collidesBL && !collidesTL) || (collidesBR && !collidesTR))) {
    collidesDown = true;
    /*The object is pushed out of the bottom row, so the bottom row is now the top row. Change the collides__
    variables as this affects collision testing, but is it not necessary to change the tile__ variables. */
    collidesBL = collidesTL;
    collidesBR = collidesTR;
    }

    //left and right
    if (object.vx < 0 && ((collidesTL && !collidesTR) || (collidesBL && !collidesBR))) {
    collidesLeft = true;
    } else if (object.vx > 0 && ((collidesTR && !collidesTL) || (collidesBR && !collidesBL))) {
    collidesRight = true;
    }

    if (collidesUp) {
    object.vy = 0;
    object.y = yBottom;
    }
    if (collidesDown) {
    object.vy = 0;
    object.y = yBottom - object.height;
    }
    if (collidesLeft) {
    object.vx = 0;
    object.x = xRight;
    }
    if (collidesRight) {
    object.vx = 0;
    object.x = xRight - object.width;
    }


    更新:用马拉卡的解决方案解决。算法如下。基本上它测试(x 然后 y)并解决冲突,然后它测试(y 然后 x)并以这种方式解决冲突。无论哪种测试导致玩家移动更短的距离,最终都会被使用。

    有趣的是,当玩家同时在顶部和左侧发生碰撞时,它需要一个特殊情况。也许这与玩家的 (x, y) 坐标在其左上 Angular 有关。在这种情况下,应该使用导致玩家移动更长距离的测试。在这个 gif 中很清楚:

    gif showing why special case is needed

    玩家是黑框,黄框代表玩家在使用其他测试(导致玩家移动更远距离的测试)时所处的位置。理想情况下,玩家不应移入墙壁,而应移至黄色框所在的位置。因此,在这种情况下,应该使用更远距离的测试。

    这是快速而肮脏的实现。它根本没有优化,但希望它非常清楚地显示了算法的步骤。

    function handleCollision(object) {
    var borderTiles = getBorderTiles(object), //returns 0 (a falsy value) for a tile if it does not fall within the level
    tileTL = borderTiles.topLeft,
    tileTR = borderTiles.topRight,
    tileBL = borderTiles.bottomLeft,
    tileBR = borderTiles.bottomRight,
    coordsBR = getTopLeftXYCoordinateOfTile(tileBR), //(x, y) coordinates refer to top left corner of tile
    xRight = coordsBR.x, //x of the right tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
    yBottom = coordsBR.y, //y of the bottom tile(s) (useful for adjusting object's position since it falls in middle of 4 tiles)
    typeTL = tileTL ? level.map[tileTL.row][tileTL.col] : -1, //if tileTL is in the level, gets its type, otherwise -1
    typeTR = tileTR ? level.map[tileTR.row][tileTR.col] : -1,
    typeBL = tileBL ? level.map[tileBL.row][tileBL.col] : -1,
    typeBR = tileBR ? level.map[tileBR.row][tileBR.col] : -1,
    collidesTL = typeTL == TILETYPE.SOLID, //true if the tile is solid
    collidesTR = typeTR == TILETYPE.SOLID,
    collidesBL = typeBL == TILETYPE.SOLID,
    collidesBR = typeBR == TILETYPE.SOLID,
    collidesUp = false,
    collidesDown = false,
    collidesLeft = false,
    collidesRight = false,
    originalX = object.x, //the object's coordinates have already been adjusted according to its velocity, but not according to collisions
    originalY = object.y,
    px1 = originalX,
    px2 = originalX,
    py1 = originalY,
    py2 = originalY,
    vx1 = object.vx,
    vx2 = object.vx,
    vy1 = object.vy,
    vy2 = object.vy,
    d1 = 0,
    d2 = 0,
    conflict1 = false,
    conflict2 = false,
    tempCollidesTL = collidesTL,
    tempCollidesTR = collidesTR,
    tempCollidesBL = collidesBL,
    tempCollidesBR = collidesBR;

    //left and right
    //step 1.1
    if (object.vx > 0) {
    if (collidesTR || collidesBR) {
    vx1 = 0;
    px1 = xRight - object.width;
    conflict1 = true;
    tempCollidesTR = false;
    tempCollidesBR = false;
    }
    }
    if (object.vx < 0) {
    if (collidesTL || collidesBL) {
    vx1 = 0;
    px1 = xRight;
    conflict1 = true;
    tempCollidesTL = false;
    tempCollidesBL = false;
    collidesLeft = true;
    }
    }
    //step 2.1
    if (object.vy > 0) {
    if (tempCollidesBL || tempCollidesBR) {
    vy1 = 0;
    py1 = yBottom - object.height;
    }
    }
    if (object.vy < 0) {
    if (tempCollidesTL || tempCollidesTR) {
    vy1 = 0;
    py1 = yBottom;
    collidesUp = true;
    }
    }
    //step 3.1
    if (conflict1) {
    d1 = Math.abs(px1 - originalX) + Math.abs(py1 - originalY);
    } else {
    object.x = px1;
    object.y = py1;
    object.vx = vx1;
    object.vy = vy1;
    return; //(the player's x and y position already correspond to its non-colliding values)
    }

    //reset the tempCollides variables for another runthrough
    tempCollidesTL = collidesTL;
    tempCollidesTR = collidesTR;
    tempCollidesBL = collidesBL;
    tempCollidesBR = collidesBR;

    //step 1.2
    if (object.vy > 0) {
    if (collidesBL || collidesBR) {
    vy2 = 0;
    py2 = yBottom - object.height;
    conflict2 = true;
    tempCollidesBL = false;
    tempCollidesBR = false;
    }
    }
    if (object.vy < 0) {
    if (collidesTL || collidesTR) {
    vy2 = 0;
    py2 = yBottom;
    conflict2 = true;
    tempCollidesTL = false;
    tempCollidesTR = false;
    }
    }
    //step 2.2
    if (object.vx > 0) {
    if (tempCollidesTR || tempCollidesBR) {
    vx2 = 0;
    px2 = xRight - object.width;
    conflict2 = true;
    }
    }
    if (object.vx < 0) {
    if (tempCollidesTL || tempCollidesTL) {
    vx2 = 0;
    px2 = xRight;
    conflict2 = true;
    }
    }
    //step 3.2
    if (conflict2) {
    d2 = Math.abs(px2 - originalX) + Math.abs(py2 - originalY);
    console.log("d1: " + d1 + "; d2: " + d2);
    } else {
    object.x = px1;
    object.y = py1;
    object.vx = vx1;
    object.vy = vy1;
    return;
    }

    //step 5
    //special case: when colliding with the ceiling and left side (in which case the top right and bottom left tiles are solid)
    if (collidesTR && collidesBL) {
    if (d1 <= d2) {
    object.x = px2;
    object.y = py2;
    object.vx = vx2;
    object.vy = vy2;
    } else {
    object.x = px1;
    object.y = py1;
    object.vx = vx1;
    object.vy = vy1;
    }
    return;
    }
    if (d1 <= d2) {
    object.x = px1;
    object.y = py1;
    object.vx = vx1;
    object.vy = vy1;
    } else {
    object.x = px2;
    object.y = py2;
    object.vx = vx2;
    object.vy = vy2;
    }
    }

    最佳答案

    发生这种情况是因为您首先检测两个方向的碰撞,然后调整位置。 “向上/向下”首先更新(重力方向)。首先调整“左/右”只会使问题变得更糟(每次跌倒后,您可能会被向右或向左传送)。

    我能想到的唯一快速而肮脏的解决方法(重力不变):

  • 计算两个相关点在一个方向上的碰撞(例如,当向左行驶时,只有左边的两个点很重要)。然后在那个方向调整速度和位置。
  • 计算两个(调整后的)相关点在另一个方向上的碰撞。调整碰撞方向的位置和速度。
  • 如果步骤 1 中没有冲突,那么您可以保留更改并返回。否则计算与步骤 1 之前的原始位置相比的距离 dx + dy。
  • 重复步骤 1. 到 3. 但这次你先从另一个方向开始。
  • 以较小的距离进行更改(除非您已经在第 3 步中找到了很好的更改。)。

  • 编辑:示例
    sizes: sTile = 50, sPlayer = 20
    old position (fine, top-left corner): oX = 27, oY = 35
    speeds: vX = 7, vY = 10
    new position: x = oX + vX = 34, y = oY + vY = 45 => (34, 45)
    solid: tile at (50, 50)

    1.1. Checking x-direction, relevant points for positive vX are the ones to the right:
    (54, 45) and (54, 65). The latter gives a conflict and we need to correct the
    position to p1 = (30, 45) and speed v1 = (0, 10).

    2.1. Checking y-direction based on previous position, relevant points: (30, 65) and
    (50, 65). There is no conflict, p1 and v1 remain unchanged.

    3.1. There was a conflict in step 1.1. so we cannot return the current result
    immediately and have to calculate the distance d1 = 4 + 0 = 4.

    1.2. Checking y-direction first this time, relevant points: (34, 65) and (54, 65).
    Because the latter gives a conflict we calculate p2 = (34, 30) and v2 = (7, 0).

    2.2. Checking x-direction based on step 1.2., relevant points: (54, 30) and (54, 50).
    There is no conflict, p2 and v2 remain unchanged.

    3.2. Because there was a conflict in step 1.2. we calculate the distance d2 = 15.

    5. Change position and speed to p1 and v1 because d1 is smaller than d2.

    关于javascript - 碰撞检测不应该使物体传送,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44191923/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com