In 2D scrolling games (and some 3D games), you’ll often need to show the player the location of a target that is off-screen, whether it’s an enemy, an ally, or a game objective. Many games use an arrow that floats close to the edge of the screen to indicate which direction the target lies in. In this tutorial, I’ll explain a method that uses simple algebra to find where to place such an indicator arrow.
Slope Intercept Form
The slope intercept form is a way to describe a straight line in 2D with linear algebra. It uses a slope, which normally uses the symbol m
, that defines the steepness of the line, and an offset or intercept, which uses the symbol b
, that defines where the line crosses the y-axis.
\[y = mx + b\]
Thanks to this relationship, if we have one value we can use the general equation to easily calculate the other value, both conceptually and mathematically.
Since we are finding the position relative to the screen – a flat surface – we do all calculations in 2D, even if the game is in 3D.
Tip: If you are working in 3D, you’ll need to transform the world location to the screen location of your 3D object. Most mainstream engines have built in functions for doing this; consult your engine’s documentation for more.
If we can find a line on screen describing which direction the object we are targeting is in, we can determine the point where it crosses any given edge, and then use a little trial and error to find out which side of the screen it will be attached to.
Making Assumptions
If we imagine that our screen is on a grid, and that the origin point (0, 0)
is right at the center of the screen, then it’s easy to calculate the values that describe the line.
Since the line will go through the center, we know that our intercept, b
, must be zero. And if the grid is placed like this, we can very easily calculate the slope, m
: it’s simply the target’s y
/x
. (In the image above, our target is the mouse cursor.)
Once we have the slope, we can use substitution to calculate where the line would cross the borders of the screen. For example, if we want to find what the y-value is at the point where the line crosses the edge of the screen, then we use the original form y = mx
, where x
is set to the edge of the screen. If we wanted to find where it crosses the top or bottom of the screen, we divide both sides by m
so that the equation becomes: x = y/m
– then we just set y
to the edge of the screen.
While the grid is placed in this manner, the edge of the screen would be half the screen’s width, negative for left and positive for right. For the vertical axis, similarly, the edge of the screen is at half its height, but whether up is positive or negative might vary between engines.
So, a 800x600px game will have its screen edges at x = -400px
, x = +400px
, y = -300px
, and y = +300px
.
Coordinate Space
The above would be fine if the origin for the coordinate system was the center of the screen, but that is rarely the case. Most engines have the origin in either the upper left, or lower left corner.
Before we do our calculations, we need to shift our coordinate space so all of our values are relative to the center of the screen, rather than the default origin our engine uses.
Sound complex? Not really. We just need to find out how much we want to move the coordinate space, and subtract that from our target position. So if we want to move our grid up half the screen width, we subtract half the screen width from the target’s y
value.
Here’s One I Prepared Earlier
In the example above, the screen size is 800x600px, with the coordinate space shifted so that (0, 0)
is at the center of the monitor. The off-screen target is at (800, 400)
using the same coordinate space.
Since the target’s y-coordinate is positive (and, in this engine, the y-axis points upwards), we know it will not be on the bottom edge of the screen, so we initially find its position along the top edge of the screen, which is (600, 300)
.
We can mathematically tell that this point is still off-screen because its x-coordinate (600
) is greater than half of the width (800/2 = 400
), so we then move onto finding its position on the side of the screen.
Again, we only need to check one side of the screen, because if our x-coordinate is positive then the point has to be on the right side of the screen. (If it were negative, it would have to be on the left side.)
Once we find the point on the right side of the screen – (400, 200)
– we know that it must be correct, since we have ruled out every other side of the screen through a process of elimination.
Add a Small Dash of Trigonometry
As well as positioning the indicator, you might want to rotate it as well for extra effect, especially if it’s an arrow. There’s a handy function that is part of most math classes that solves this problem rather easily: atan2()
.
The atan2()
function takes two parameters: an x-coordinate and a y-coordinate. It returns an angle that indicates the direction from (0, 0)
to (x, y)
.
rotation = Math.atan2(centerMouse.top, centerMouse.left); rotation = rotation * 180/Math.PI; //convert radians to degrees
There are a couple of things to keep in mind with atan2()
that can vary betweens languages and engines. Firstly, the arguments are often atan2(y, x)
, whereas most other math functions take the x-coordinate first. Also, the rotation is often returned in radians rather of degrees.
Tip: I won’t go into the differences between radians and degrees, except to say that converting from one to the other is easy: you just multiply the radians by (180/Pi)
to turn them into degrees, and multiply them by (Pi/180)
if you want to change them back.
Last Checks
There is one last thing we need to check before making an off screen indicator, and that is whether our target is actually off screen, since it doesn’t make much sense to point in our target’s direction if we can already see our target. Again, we’re going to use pretty simple math to work this out.
Since our screen is an unrotated rectangle, we don’t need to do anything with angles, we just need to check whether our target point is lower than the top, higher than the bottom, to the left of the right edge, and to the right of the left edge of the screen.
var screen = {width:200; height:100} //dummy values alert(isTargetOnScreen({left:50; top:60})); //True alert(isTargetOnScreen({left:250; top:10})); //False function isTargetOnScreen(target){ if(target.top > 0 && target.top < screen.height && target.left < screen.width && target.left > 0){ //the target is on-screen, use an overlay, or do nothing. return true; }else{ //the target is off-screen, find indicator position. return false; } }
Using the dummy values above, we find that the target is on-screen. These values can come from anywhere that stores information about the object you are tracking.
Note that the code above assumes we are in the coordinate space where (0, 0)
is in the corner of the screen, like most engines will have by default. Therefore, this step should be done before shifting the coordinate space to the center like we do when calculating the indicator position.
Putting It All Together
Here’s a quick demo to show these concepts in action:
Let’s walk through the code:
- Firstly, we check if the target is actually off-screen; if it’s on-screen we already know where to place the cursor, if we wish to do so.
- We change the coordinate space so the origin is at the center of the screen.
- Looking at the mouse position, we can easily tell whether it’s on the top or bottom half of the screen.
- Using that information, and algebraic substitution, we calculate where that point would be on either the top or bottom of the screen.
- We look at the point we’ve just calculated, and check whether it’s actually a position on screen, or if it’s too far off to the left or right.
- If the point is off-screen, we calculate a new point on the side of the screen, instead of the top or bottom.
- We should now have the right point in the wrong coordinate space, so we do the opposite of what we did in the first step to bring it back into the right coordinate system.
Conclusion
There you have it: a handy code snippet to add to your game’s user interface. Now that you can point the player in the direction of a target, consider how you might display the distance as well.