“映射”鼠标在屏幕上的位置,以便无论分辨率如何都能进行碰撞检测,是否是个坏主意?


16

考虑一个默认分辨率为800x600的游戏。带有防碰撞罩的对象放置在大小为800x600的游戏世界中。防撞罩可以检测鼠标何时与它们碰撞。

现在考虑我们将游戏缩放到1024x768(假设我们通过简单地将所有内容渲染到一个图层然后一次放大整个图层来缩放图形)。在此新分辨率下,我们有两个选项可以使与鼠标的碰撞正常进行:

A.)将世界缩放到1024x768,并相应缩放每个对象的碰撞蒙版。

B.)将鼠标位置“映射”到原始世界(800x600)。

通过“地图”,我的意思是简单地将鼠标位置缩放到原始800x600的世界上。因此,例如,如果鼠标在屏幕上的位置是(1024,768),则鼠标在世界上的位置是(800,600)。

现在,显然,选项B所需的计算方式更少,并且可能更不容易出现几何错误,但是对我来说,这也感觉有些“骇人听闻”,就像使用此方法会带来无法预料的后果一样,以后将很难解决。

我应该使用哪种方法:A,B或其他?


2
正如Classic Thunder指出的那样,显示坐标不应与世界坐标或对象坐标相同。通常,您需要进行转换(基本上,正如您所说的mappings,但通常是通过矩阵数学完成的)。
丹丹

Answers:


38

通常(即使是2D游戏)也有独立于分辨率的游戏坐标系,这通常称为世界空间。这使您可以缩放到任意分辨率。

实现此目的的最简单方法是使用2D相机,该相机本质上是一个矩阵,用于定义从世界空间(任意单位)到屏幕空间(屏幕上的像素)的过渡。这使得处理数学变得微不足道。

看看XNA 2d Camera Scrolling中的以下部分-为什么使用矩阵变换?

它使在坐标系定义之间转换变得非常容易

要从屏幕进入世界空间,只需使用Vector2.Transform。这通常用于获取鼠标在世界上的位置以进行对象拾取。

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));

要从世界进入屏幕空间,要做相反的事情。

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

除了需要一点点学习之外,使用矩阵没有任何缺点。


……实际上,一般而言,现代硬件是通过SSE,NEON等矢量化体系结构为矩阵运算而设计的。因此,这是明智的选择-与非矢量化方法相比,您可以节省CPU周期。
工程师

1
轻度免责声明:XNA被Microsoft终止,但Monogame使用相同的API。
法拉普

1
当然,使用矩阵在两个坐标系之间平移是很明智的。但这如何回答这个问题?如果要从系统A映射到系统B还是从B映射到A,您仍然必须决定后果如何,如果有的话。
mastov '16

“在屏幕上“映射”鼠标位置,以便无论分辨率如何都能进行碰撞检测,这是一个坏主意吗?” 回答“使用矩阵在两个坐标系之间平移是明智的” ...
ClassicThunder

我还要回答:“如果要从系统A映射到系统B,还是要从B映射到A,还必须决定,如果有的话,后果是什么。” 通过说应该使用任意一个不受分辨率限制和扩展的工具。所以C-> A或C-> B取决于分辨率是A还是B。当然,您可以让C等于您为其设计的基本分辨率,并从那里开始缩放。关键是所有数学运算都在同一坐标系中进行,并且只为渲染缩放。
ClassicThunder

2

另一个选择是:在每个输入的鼠标移动事件上,将游戏内鼠标光标移动与鼠标事件中像素数相对应的游戏像素数。这对于将真实鼠标指针锁定在中心并按照与输入鼠标移动相对应的量旋转目标方向的3D游戏来说很自然,但是您可以通过移动代表游戏内鼠标光标的精灵来以相同的方式进行操作。

显然,您必须忽略由于将鼠标翘曲到中心而导致的任何输入鼠标移动事件,并且如果您的游戏在所有输入方面都存在输入滞后,则无响应性将是最刺耳且最明显的方面。

在某种程度上,您使用哪种解决方案取决于鼠标位置对游戏玩法的重要性。如果这是RTS,而玩家只是单击以选择单位,那么您可能可以轻松选择A或B中的任意一个。如果是自上而下的射击游戏,并且鼠标直接控制角色的移动,那么您可能需要更深入的解决方案,该解决方案不仅限制分辨率而且还限制鼠标移动之类的限制,从而限制角色移动的可变性速度。如果鼠标改为控制目标方向,那么您将需要其他解决方案,等等。


0

ClassicThunder的答案是正确的,但我想提供一个示例来实现所需效果的替代/简单方法。这是用于快速原型制作,您无法访问功能齐全的库或无法访问GPU(例如在嵌入式系统中)的情况的简单解决方案。

要执行上述映射,可以使用以下函数(假设它在static class中定义Helper):

static float Map(float value, float fromLow, float fromHigh, float toLow, float toHigh)
{
    return ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow)) + toLow;
}

(您没有指定语言,但是我看到您对C#有所了解,所以我的示例在C#中。)

然后,您可以像下面这样使用此功能:

float mouseXInWorld = Helper.Map(Mouse.X, 0, Screen.Width - 1, camera.Bounds.X, camera.Bounds.X + camera.Bounds.Width - 1);
float mouseYInWorld = Helper.Map(Mouse.Y, 0, Screen.Height - 1, camera.Bounds.Y, camera.Bounds.Y + camera.Bounds.Height - 1);

哪里camera.Bounds是矩形,代表相机可以看到的世界区域(即投影到屏幕上的区域)。

如果有VectorPoint类,则可以通过创建等效于map函数的2D来进一步简化此过程,如下所示:

static Vector Map(Vector value, Rectangle fromArea, Rectangle toArea)
{
    Vector result = new Vector();
    result.X = Map(value.X, fromArea.X, fromArea.X + fromArea.Width - 1, toArea.X, toArea.X + toArea.Width - 1);
    result.Y = Map(value.Y, fromArea.Y, fromArea.Y + fromArea.Height - 1, toArea.Y, toArea.Y + toArea.Height - 1);
    return result;
}

这将使您的映射代码变得简单:

Vector mousePosInWorld = Map(Mouse.Pos, Screen.Bounds, camera.Bounds);

-1

选项(C):将屏幕分辨率更改回800x600。

即使您不这样做,也可以将其视为思想实验。在这种情况下,显示器有责任调整图形大小以适合其物理尺寸,然后由操作系统负责以800x600分辨率为您提供指针事件。

我认为这完全取决于您的图形是位图还是矢量。如果它们是位图,并且要渲染到800x600缓冲区,则可以,将鼠标重新映射到屏幕空间并忽略实际屏幕分辨率要容易得多。但是,这样做的最大缺点是,升级看起来很难看,尤其是当您正在制作块状的“ 8位”样式的图形时。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.