如何计算等角世界中鼠标下方瓦片的索引,考虑到瓦片高度的影响。

10

我有一个基于瓷砖的等角世界,并且可以使用以下计算方法计算特定(鼠标)坐标下方的瓷砖:

function isoTo2D(pt:Point):Point{
  var tempPt:Point = new Point(0, 0);
  tempPt.x = (2 * pt.y + pt.x) / 2;
  tempPt.y = (2 * pt.y - pt.x) / 2;
  return(tempPt);
}

function getTileCoordinates(pt:Point, tileHeight:Number):Point{
  var tempPt:Point = new Point(0, 0);
  tempPt.x = Math.floor(pt.x / tileHeight);
  tempPt.y = Math.floor(pt.y / tileHeight);
  return(tempPt);
}

(摘自http://gamedevelopment.tutsplus.com/tutorials/creating-isometric-worlds-a-primer-for-game-developers--gamedev-6511,此处为Flash实现,但数学原理相同)

但是,当我有不同高度的瓦片时遇到了问题:

enter image description here enter image description here

在这些情况下,由于某些具有较高高度的瓦片,它们后面的瓦片(或部分瓦片)被覆盖,不应该能够被鼠标选择,而是应该选择前面的瓦片。 如何计算考虑到瓦片高度的鼠标坐标所选的瓦片? 我正在使用JavaScript和Canvas实现。


好的,你的瓦片在一个三维平面上,而你的鼠标在一个二维平面上,因此你需要进行一些线性代数投影,可能需要使用你查看瓦片的角度。 - OneCricketeer
我没有使用3D平面 - 它是一个模拟3D平面的2D平面。 - user1094553
重点是模仿部分。假设你可以“旋转”你的世界,那么你就能看到不同的瓷砖,对吧?将角度倾斜,这样你就可以直接找到鼠标所在的瓷砖了。因此,无论是考虑倾斜和旋转角度,或者使用鼠标进入和离开事件的边界框侦听器,或者放弃鼠标,只使用键盘来选择瓷砖。 - OneCricketeer
4个回答

2
有一种技术可以在canvas上捕捉鼠标下的对象,而无需将鼠标坐标重新计算为您的“世界”坐标。这并不完美,有一些缺点和限制,但它在一些简单的情况下可以胜任。
1)在主canvas之上放置另一个canvas,并将其opacity设置为0。确保第二个canvas与主canvas具有相同的大小并重叠。
2)每当您将交互式对象绘制到主canvas时,请在第二个canvas上绘制和填充相同的对象,但是使用每个对象的唯一颜色(从#000000#ffffff)。
3)将鼠标事件处理设置为第二个canvas
4)在鼠标位置上使用第二个canvas上的getPixel来获取所单击/悬停对象的“id”。
主要优点是WYSIWYG原则,因此(如果一切都做得正确),您可以确信,在主canvas上的对象与第二个canvas上的对象处于相同的位置,因此您不需要担心canvas的调整大小或对象depth(如您的情况)计算以获取正确的对象。
主要缺点是需要“双重渲染”整个场景,但可以通过在不必要时不在第二个canvas上绘制来进行优化,例如:
  • 在“空闲”场景状态下,当交互式对象停留在其位置并等待用户操作时。

  • 在“锁定”场景状态下,当某些内容正在动画或其他方面,并且不允许用户与对象交互时。

主要限制是场景中交互式对象的最大数量(最多#ffffff或16777215个对象)。
因此...不建议用于:
  • 具有大量交互式对象的游戏。 (性能差)

  • 快节奏的游戏,其中交互式对象不断移动/创建/销毁。(性能差,重新使用id存在问题)

适用于:
  • GUI处理

  • 回合制游戏/缓慢的益智游戏。


这是一个很好的答案。这里有一个非常相似的新问题:https://dev59.com/EGEh5IYBdhLWcg3w9Xan#49216758 - Anil Vaitla

1
你的命中测试函数需要访问所有的图块以确定哪个被击中。然后,它将从最高高度开始执行测试命中。
假设你只有离散(整数)图块高度,则通用算法如下(伪代码,假定tiles是具有高程属性的对象的二维数组):
function getTile(mousePt, tiles) {
    var maxElevation = getMaxElevation(tiles);
    var minElevation = getMinElevation(tiles);
    var elevation;
    for (elevation = maxElevation; elevation >= minElevation; elevation--) {
        var pt = getTileCoordinates(mousePt, elevation);
        if (tiles[pt.x][pt.y].elevation === elevation) {
            return pt;
        }
    }
    return null; // not tile hit
}

这段代码需要根据任意高程进行调整,并且可以优化以跳过不包含任何瓷砖的高程。
请注意,我的伪代码忽略了瓷砖的垂直侧面,点击它们将选择被垂直侧面遮挡的(较低高程)瓷砖。如果需要考虑垂直瓷砖,则需要使用更通用的表面命中检测方法。您可以访问每个瓷砖(从最近到最远),并测试鼠标坐标是否在“屋顶”或查看器面向的“墙”多边形中。

这是一个有趣的方法。但对于凸起瓦片的边缘应该怎么办呢?我最初的想法是检查鼠标是否在凸起瓦片和同一地面层级的瓦片之间的矩形内,但如果前方有一个高度较低的瓦片,它会选择错误,选择后面的瓦片(高度更高)而不是正确选择前面的瓦片(高度稍低)。 - user1094553
你说得对,这种方法没有解决点击垂直(“墙”)表面的问题,只处理水平(“屋顶”)表面的点击。问题描述没有说明当点击“墙”时应该是什么行为。它应该是一个无操作还是与单击该图块的“屋顶”相同?我选择了“简单”的行为并将墙壁视为可点击穿透。;-) - Stepan Riha
1
这应该与在瓦片的顶部单击相同。 - user1094553

0

如果地图不可旋转且与您在此处发布的图片完全相同,

当您绘制多边形时,请将每个瓦片的多边形保存在一个多边形数组中。然后仅使用它们(它们的瓦片)到您的距离(最近的第一位,最远的最后一位)对数组进行一次排序,同时按瓦片索引将它们分组。

当发生点击事件时,获取鼠标的x、y坐标,并从数组的第一个元素开始执行点与多边形相交测试,直到最后一个元素。当命中时,在该元素处停止。

无论瓦片有多高,都不会隐藏任何距离您更近(甚至是相同距离)的瓦片。

点与多边形相交测试已经解决:

点与多边形相交算法

如何确定2D点是否在多边形内?

点与多边形相交

你甚至可以使用这个函数检查画布上的每一个像素,并将结果保存到一个点的二维数组vect2[x][y]中,该数组给出了从鼠标的x,y坐标到i,j索引的瓦片,然后将其用作非常快速的索引查找器。

优点:

  • 如果有数百万个瓦片,则可以使用WebWorkers进行快速并行化处理
  • 使用按距离排序的多个多边形数组可扩展到多个等角地图
  • 高程不会降低性能,因为每个瓦片最多只有3个
  • 不需要将等角转换为2D。只需在画布上的多边形的角落和鼠标在同一画布上的坐标即可。

缺点:

  • 如果您还没有每个角的坐标,则需要获取它们的坐标
  • 单击角会选择距离您最近的瓷砖,而此时它同时位于四个瓷砖上。

-2
答案很奇怪地详细写在维基百科页面的“屏幕到世界坐标的映射”部分。与其试图描述这些图形,不如读三遍该部分内容。
您需要确定使用的等距投影方式,通常通过用尺子测量屏幕上的瓷砖大小来确定。

2
如果我正确理解了这篇文章,那么我正在使用的方法正是计算瓦片位置的方法。然而,我的问题在于当一些瓦片具有较高的海拔时,瓦片的侧面以及瓦片本身必须能够被鼠标选择,而如果它被隐藏在另一个瓦片后面,则不应该能够选择到其后面的瓦片。 - user1094553
对于这个问题,没有捷径;你必须计算可能出现的所有瓦片集合,然后选择最前面的一个,通常是行列数最小的那个。要最小化的确切函数将取决于您的投影和编号方式,但将是(最小或最大)(+/-)行(+/-)列。使用屏幕上或调色板中的z值来编码瓦片号码是一种“便宜”的方法。 - Charles Merriam

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