【问题标题】:How to sort objects in 2d game?如何在 2d 游戏中对对象进行排序?
【发布时间】:2019-01-19 00:06:11
【问题描述】:

我正在用javascript开发一种基于透视的2d/3d游戏

我有一个 X 轴和一个 Y 轴,如下图所示。


我的问题:我的地图上有一堆对象(标有“1”和“2”),其属性如下:

  • 位置X / 位置Y
  • sizeX / sizeY

在图像中,对象“1”得到坐标x:3, y:2,对象“2”得到坐标x:5, y:4。两个对象的 SizeX 和 sizeY 都是 w:1, h:1

我想用这个信息做的是按升序(基于对象的位置和大小)对所有对象进行排序,以便在 3d 中了解哪些对象是另一个对象稍后绘制的所有对象进入我的画布(前景/背景中的对象=“图层”)。


注意:相机必须固定位置 - 假设相机具有相同的 X 和 Y 值,因此在计算 CameraX = CameraY 时不得使用相机位置。

到目前为止我已经尝试过:

let objects = [
  {
    name: "objectA",
    x: 8,
    y: 12,
    w: 2,
    h: 2
  }, 
  {
    name: "objectB",
    x: 3,
    y: 5,
    w: 2,
    h: 2
  },
  {
    name: "objectC",
    x: 6,
    y: 2,
    w: 1,
    h: 3
  }
]


let sortObjects = (objects) => {
  return objects.sort((a, b)=> {
    let distanceA = Math.sqrt(a.x**2 + a.y**2);
    let distanceB = Math.sqrt(b.x**2 + b.y**2);
    return distanceA - distanceB;
  });
}


let sortedObjects = sortObjects(objects);
console.log(sortedObjects);

// NOTE in 3d: first Object drawn first, second Object drawn second and so on...

编辑上面的sn-p:

我尝试根据对象的 x/y 坐标对对象进行排序,但似乎在计算时也必须使用 width 和 height 参数以避免错误。

如何使用宽度/高度? Tbh 我没有任何线索,所以任何帮助将不胜感激。

【问题讨论】:

  • 如果您只想按从后到前的顺序对对象进行排序以进行渲染(所谓的画家算法),他们为什么不实现 BSP 树。 BSP-tree 保证完美排序,但你需要权衡一些事情......

标签: javascript sorting math 3d 2d


【解决方案1】:

我不确定你的意思:

注意:相机必须固定位置 - 假设相机具有相同的 X 和 Y 值,因此在计算 CameraX = CameraY 时不得使用相机位置。

所以这是一个一般情况的解决方案。

您必须按对象与相机的最近距离对对象进行排序。这取决于对象的尺寸及其相对位置。

该算法可以在JS中实现如下:

// If e.g. horizontal distance > width / 2, subtract width / 2; same for vertical
let distClamp = (dim, diff) => {
    let dist = Math.abs(diff);
    return (dist > 0.5 * dim) ? (dist - 0.5 * dim) : dist;
}

// Closest distance to the camera
let closestDistance = (obj, cam) => {
    let dx = distClamp(obj.width, obj.x - cam.x);
    let dy = distClamp(obj.height, obj.y - cam.y);
    return Math.sqrt(dx * dx + dy * dy);
}

// Sort using this as the metric
let sortObject = (objects, camera) => {
    return objects.sort((a, b) => {
        return closestDistance(a, camera) - closestDistance(b, camera);
    });
}

编辑此解决方案也不起作用,因为它做出幼稚的假设,将很快更新或删除。

【讨论】:

  • 我觉得这个解决方案看起来不错。特别是因为您使用宽度和高度进行计算。我已将您的算法构建到我的代码中......长话短说:这种方法似乎也不起作用。在许多情况下,图层功能可以完美运行(即使我不得不交换最接近的距离(a,相机) - 最接近的距离(b,相机)),但也有很多例外,它不能正常工作......
  • ... 如果我站在右侧的一个对象(sizeX: 1, sizeY: 2)前面,我将被描绘为对象后面而不是前面的玩家.另一方面,它似乎没有任何问题。
  • @jonas00 能否给出玩家和物体的坐标和尺寸,以及相机的坐标?我不明白这个问题应该如何出现。
  • 当然! 玩家{x:7.16, y: 10.25, width: 0.2, height: 0.2 }对象{x: 6.66, y: 11.44, width: 0.3, height: 1.6}。我的相机位于x: 100, y: 100。我的相机在哪里不会有什么区别,因为 x 和 y 始终相同。所以x: 200, y: 200 也是有效的。
  • @jonas00 啊,我知道现在出了什么问题,抱歉。我完全忘记了像这样的简单方法是行不通的,因为它没有考虑对象平面的方向。我会重新发布或删除。
【解决方案2】:

好吧,让我们试一试吧!我们将不得不按距离而不是按障碍对这些对象进行排序。我的意思是,对于两个对象 A 和 B,A 可以明显地阻碍 B,B 可以明显地阻碍 A,或者两者都不阻碍另一个。如果 A 阻碍了 B,我们将首先绘制 B,反之亦然。为了解决这个问题,我们需要能够判断 A 是否阻碍了 B,或者相反。

这是我想出的。我只是测试这个能力有限,所以可能仍然存在缺陷,但思考过程是合理的。

步骤 1. 将每个对象映射到其边界,保存原始对象以供以后使用:

let step1 = objects.map(o => ({
  original: o,
  xmin: o.x,
  xmax: o.x + o.w,
  ymin: o.y,
  ymax: o.y + o.h
}));

第 2 步。将每个对象映射到两个角,当在它们之间画一条线时,会形成相机视野的最大障碍:

let step2 = step1.map(o => {
  const [closestX, farthestX] = [o.xmin, o.xmax].sort((a, b) => Math.abs(camera.x - a) - Math.abs(camera.x - b));
  const [closestY, farthestY] = [o.ymin, o.ymax].sort((a, b) => Math.abs(camera.y - a) - Math.abs(camera.y - b));

  return {
    original: o.original,
    x1: closestX,
    y1: o.xmin <= camera.x && camera.x <= o.xmax ? closestY : farthestY,
    x2: o.ymin <= camera.y && camera.y <= o.ymax ? closestX : farthestX,
    y2: closestY
  };
});

第 3 步。对对象进行排序。从相机到一个对象的每个端点绘制一条线段。如果另一个对象的端点之间的线段相交,则另一个对象更近,必须在后面绘制。

let step3 = step2.sort((a, b) => {
  const camSegmentA1 = {
    x1: camera.x,
    y1: camera.y,
    x2: a.x1,
    y2: a.y1
  };
  const camSegmentA2 = {
    x1: camera.x,
    y1: camera.y,
    x2: a.x2,
    y2: a.y2
  };
  const camSegmentB1 = {
    x1: camera.x,
    y1: camera.y,
    x2: b.x1,
    y2: b.y1
  };
  const camSegmentB2 = {
    x1: camera.x,
    y1: camera.y,
    x2: b.x2,
    y2: b.y2
  };

  // Intersection function taken from here: https://stackoverflow.com/a/24392281
  function intersects(seg1, seg2) {
    const a = seg1.x1, b = seg1.y1, c = seg1.x2, d = seg1.y2,
          p = seg2.x1, q = seg2.y1, r = seg2.x2, s = seg2.y2;
    const det = (c - a) * (s - q) - (r - p) * (d - b);
    if (det === 0) {
      return false;
    } else {
      lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
      gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
      return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
    }
  }

  function squaredDistance(pointA, pointB) {
    return Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2);
  }

  if (intersects(camSegmentA1, b) || intersects(camSegmentA2, b)) {
    return -1;
  } else if (intersects(camSegmentB1, a) || intersects(camSegmentB2, a)) {
    return 1;
  } else {
    return Math.max(squaredDistance(camera, {x: b.x1, y: b.y1}), squaredDistance(camera, {x: b.x2, y: b.y2})) - Math.max(squaredDistance(camera, {x: a.x1, y: a.y1}), squaredDistance(camera, {x: a.x2, y: a.y2}));
  }
});

第 4 步。最后一步——取回原始对象,按从远到近的顺序排序:

let results = step3.map(o => o.original);

现在,把它们放在一起:

results = objects.map(o => {
  const xmin = o.x,
        xmax = o.x + o.w,
        ymin = o.y,
        ymax = o.y + o.h;

  const [closestX, farthestX] = [xmin, xmax].sort((a, b) => Math.abs(camera.x - a) - Math.abs(camera.x - b));
  const [closestY, farthestY] = [ymin, ymax].sort((a, b) => Math.abs(camera.y - a) - Math.abs(camera.y - b));

  return {
    original: o,
    x1: closestX,
    y1: xmin <= camera.x && camera.x <= xmax ? closestY : farthestY,
    x2: ymin <= camera.y && camera.y <= ymax ? closestX : farthestX,
    y2: closestY
  };
}).sort((a, b) => {
  const camSegmentA1 = {
    x1: camera.x,
    y1: camera.y,
    x2: a.x1,
    y2: a.y1
  };
  const camSegmentA2 = {
    x1: camera.x,
    y1: camera.y,
    x2: a.x2,
    y2: a.y2
  };
  const camSegmentB1 = {
    x1: camera.x,
    y1: camera.y,
    x2: b.x1,
    y2: b.y1
  };
  const camSegmentB2 = {
    x1: camera.x,
    y1: camera.y,
    x2: b.x2,
    y2: b.y2
  };

  // Intersection function taken from here: https://stackoverflow.com/a/24392281
  function intersects(seg1, seg2) {
    const a = seg1.x1, b = seg1.y1, c = seg1.x2, d = seg1.y2,
          p = seg2.x1, q = seg2.y1, r = seg2.x2, s = seg2.y2;
    const det = (c - a) * (s - q) - (r - p) * (d - b);
    if (det === 0) {
      return false;
    } else {
      lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
      gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
      return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
    }
  }

  function squaredDistance(pointA, pointB) {
    return Math.pow(pointB.x - pointA.x, 2) + Math.pow(pointB.y - pointA.y, 2);
  }

  if (intersects(camSegmentA1, b) || intersects(camSegmentA2, b)) {
    return -1;
  } else if (intersects(camSegmentB1, a) || intersects(camSegmentB2, a)) {
    return 1;
  }
  return Math.max(squaredDistance(camera, {x: b.x1, y: b.y1}), squaredDistance(camera, {x: b.x2, y: b.y2})) - Math.max(squaredDistance(camera, {x: a.x1, y: a.y1}), squaredDistance(camera, {x: a.x2, y: a.y2}));
}).map(o => o.original);

让我知道这是否有效!

【讨论】:

  • @jonas00 我看到了问题:我正在测试相机线段是否与另一条线段相交,而我应该测试它是否与另一条线相交。我编辑了我的代码。现在可以用了吗?
  • @jonas00 已修复!我在寻找交叉点的新方法中有一个错误,所以我回到以前使用线段交叉点的方法,并为处理不正确的情况添加了检查。通过使用您的 JSFiddle,我还意识到对象不是以它们的坐标为中心的。坐标是左上角。我在答案中编辑了我的代码,还编辑了您的 JSFiddle:jsfiddle.net/4z9bcu7o 出于测试目的,显示了从相机到对象的线条。另请注意,您可能需要调整相机的位置。
  • @jonas00 谢谢!很高兴我能帮上忙!
  • 朱皮。该示例运行良好 - 干得好兄弟。但是现在我已经在我的游戏中测试了你的代码——它不能正常工作。无论如何感谢您的帮助 - 即使还有一些我现在刚刚注意到的错误,我也会给予您赏金。嗯。 :)
  • @jonas00 现在有哪些错误?什么物体排列画错了?我很好奇我没有预料到的场景以及如何修复我的代码。
【解决方案3】:

这里的问题是您正在使用欧几里得距离来测量对象与(0, 0) 的距离,以尝试测量与y = -x 线的距离。这不起作用,但曼哈顿距离会。

let sortObjects = (objects) => {
  return objects.sort((a, b)=> {
    let distanceA = a.x + a.y;
    let distanceB = b.x + b.y;
    return distanceA - distanceB;
  });
}

这将在您的旋转坐标系中垂直排列您的对象。

【讨论】:

  • 感谢您的回答。但我必须完全诚实:这个答案似乎并不正确。当我计算曼哈顿距离时,它部分起作用,就像我用欧几里德距离计算它一样。但是,它并非在所有情况下都有效。因此我补充说,错误的原因是我没有计算对象的宽度和高度。我仍然这么认为。我很高兴你的回答。提前谢谢你。
  • 我一定是误解了这个问题。物体可以有不同的尺寸吗?如果是这样,图像锚定在哪里(中心、左上角等)?
  • @jonas00 如果cameraX == cameraY 和(假设)所有对象都有固定的高度/宽度,我只是很难看出这在哪里不起作用
  • 是的。但是在我的旋转坐标系中,图层以某种方式连接到对象的宽度和高度。所有对象可能有不同的大小,这会破坏你/我的排序算法。而且我很确定这个解决方案不能完全按照它应该的那样工作:(
  • @jonas00 对象是否在其行中“移动”以容纳更大对象的空间?您的精灵不是以(x, y) 为中心绘制的吗?我真的不明白精灵的宽度/高度是如何改变相对定位的。在我看来,较小的精灵似乎仍会与正常大小的精灵在同一水平行中。
【解决方案4】:

在您的图表中考虑每个单元格的 x+y 值

要从上到下对单元格进行排序,您只需按x+y 的值进行排序。

【讨论】:

  • 这正是我的代码所做的,但 OP 声称它不起作用。
  • @DillonDavis:我知道...希望显示一个图表可以清楚地说明
  • 但是一个对象可以从 (x: 0, y: 0) 到 (x: 1, y: 2) 所以我不能像 @DillonDavis 那样按它们的 x/y 位置对对象进行排序,因为这是绝对错误的。 X/Y 参数代表区域的中心点。
  • @jonas00:你应该问自己的是......“任何建立在标有 3 的正方形上的东西都可以隐藏在标有 4 的正方形上吗?”...(或另一个标有 3 的)跨度>
【解决方案5】:

也许你能找到一些有用的东西here(使用 Firefox 并查看DEMO

在我的情况下,深度基本上是pos.x + pos.y [assetHelper.js -> get depth() {...] 与第一个答案描述的完全一样。然后排序就是简单的比较 [canvasRenderer -> depthSortAssets() {...]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多