15
\$\begingroup\$

Consider a game who's default resolution is 800x600. Objects with collision masks are placed in a game world of size 800x600. The collision masks can detect when the mouse collides with them.

Now consider we scale the game up to 1024x768 (assume we scale the graphics by simply rendering everything to a layer and then scaling the entire layer up at once). We have two options for making the collisions with the mouse work properly in this new resolution:

A.) Scale up the world to 1024x768 and scale every object's collision mask accordingly.

B.) "Map" the mouse position onto the original world (800x600).

By "map" I mean simply scale the mouse position onto the original 800x600 world. So for example, if the mouse position on the screen is (1024, 768), then the mouse position in the world is (800, 600).

Now obviously, option B requires way less computation and is probably less prone to geometric errors, but it also feels kind of "hackish" to me, like there are unforeseen consequences of going with this method that will be hell to fix later on.

Which method should I go with: A, B, or something else?

\$\endgroup\$
  • 2
    \$\begingroup\$ As pointed out by Classic Thunder, display coordinates should not be the same as world coordinates, or object coordinates. Typically you want to have transforms (basically, as you say mappings, but typically done with matrix math). \$\endgroup\$ – Dan Apr 29 '16 at 20:24
38
\$\begingroup\$

Typically (even for 2d games) there is a separate coordinate system for the game that is independent of the resolution, this is typically referred to as the world space. This allows you to scale to an arbitrary resolution.

The easiest way to achieve this is use a 2D Camera, which is essentially a matrix that defines the transition from world space (your arbitrary units) to screen space (the pixels on the screen). This makes dealing with the math trivial.

Take a look at the below section in XNA 2d Camera Scrolling - why use matrix transform?

It makes it really easy to convert between coordinate system definitions

To go from screen to world space simply use Vector2.Transform. This is commonly used to get the location of the mouse in the world for object picking.

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

To go from world to screen space simply do the opposite.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

There is no drawbacks to using a matrix other than it takes a little learning.

\$\endgroup\$
  • \$\begingroup\$ ...and indeed, generally speaking, modern hardware is designed for matrix operations through vectorisation architectures like SSE, NEON, etc. So it is the smart choice - you will save CPU cycles over non-vectorised approaches. \$\endgroup\$ – Engineer Apr 28 '16 at 18:49
  • 1
    \$\begingroup\$ Mild disclaimer: XNA was discontinued by microsoft, but Monogame uses the same API. \$\endgroup\$ – Pharap Apr 29 '16 at 4:47
  • 1
    \$\begingroup\$ Sure, it's smart to use matrices to translate between the two coordinate systems. But how does that answer the question? You must still decide, if you want to map from system A to system B or from B to A and what the consequences are, if there are any. \$\endgroup\$ – mastov Apr 29 '16 at 15:35
  • \$\begingroup\$ "Is it a bad idea to “map” the mouse position on the screen so that collision detection works regardless of resolution?" is answered by "it's smart to use matrices to translate between the two coordinate systems"... \$\endgroup\$ – ClassicThunder Apr 29 '16 at 15:44
  • \$\begingroup\$ I also answer "You must still decide, if you want to map from system A to system B or from B to A and what the consequences are, if there are any." by saying you should use an arbitrary one not tied to resolution and scale from it. So C -> A or C -> B depending on if A or B is the resolution. Of course you can have C equal a base resolution you design for and scale from there. Point is all math happens in the same coordinate system and you only scale for the rendering. \$\endgroup\$ – ClassicThunder Apr 29 '16 at 15:47
2
\$\begingroup\$

Another option would be: On each input mouse movement event, move the in-game mouse cursor by the number of game pixels corresponding to the number of pixels in the mouse event. This comes naturally for 3D games that lock the real mouse pointer to the center and rotate the aim direction by an amount corresponding to the input mouse movement, but you could do it the same way by moving a sprite representing the in-game mouse cursor.

You obviously have to ignore any input mouse movement events caused by warping it to the center, and if your game has any input lag at all the cursor unresponsivity will be the most jarring and noticeable aspect.

Partially, what solution you use depends on how important the mouse position is to gameplay. If this is an RTS and the player is simply clicking to select units, you can probably simply go with whichever of your A or B is easier. If it's a top-down shooter and the mouse directly controls the character's movements, then you will probably want a more in-depth solution that limits the amount of variability in how the character can move based on not only resolution but also things like mouse movement speed. If the mouse controls targeting direction instead, then you'll want a different solution, etc.

\$\endgroup\$
0
\$\begingroup\$

ClassicThunder's answer is correct, but I'd like to provide an example of an alternative/simpler means of achieving the desired effect. This is a simpler solution for rapid prototyping, cases where you don'thave access to a fully featured library, or for cases where you don't have access to a GPU (e.g. in embedded systems).

To perform said mapping, you can use the following function (assume it to be defined in the static class Helper):

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

(You didn't specify a language, but I see you have some knowledge of C#, so my example is in C#.)

You can then use this function like so:

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);

Where camera.Bounds is a rectangle representing the area of the world the camera can see (i.e. the area being projected onto the screen).

If you have a Vector or Point class, you could simplify this process further by creating a 2D equivalent of the map function, like so:

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;
}

Which would make your mapping code a simple one liner:

Vector mousePosInWorld = Map(Mouse.Pos, Screen.Bounds, camera.Bounds);
\$\endgroup\$
-1
\$\begingroup\$

Option (C): change the screen resolution back to 800x600.

Even if you don't do this, consider it as a thought experiment. In that case, it's the responsibility of the display to resize the graphics to fit its physical size, and then the responsibility of the operating system to give you pointer events at an 800x600 resolution.

I think it all depends on whether your graphics are bitmap or vector. If they're bitmap, and you're rendering to an 800x600 buffer, then yes it's much easier to remap the mouse into screen space and ignore the real screen resolution. However the big downside of this is that the upscaling can look ugly, especially if you're doing blocky "8-bit" style graphics.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.