Wow loads of help. Thank you!
TLDR - I’ve solved it.
As @MarkusR suggested, I decided to try to use maths to solve the problem rather than relying on a special tile. The advantage is that I can change the tile dimensions in my graphics editor and don’t have to remember to create a new “mouse map” image.
I think the suggestion by @Eric_Williams would likely work and is smart. Thanks @anon20074439 for the explanation of what was going wrong.
For those that are interested in hex maps what I did, is as follows:
First the screen is internally carved up into MouseCell
s (drawn in red):
The coordinates in red at the top of the red cells are the “mouse map” (row, column
). The black coordinates in the centre of the hexes are actual in-world (row, column
) coordinates.
We can see from the picture that if we know the row
and column
of a MouseCell
we can compute the in-world tile index by adding a delta
value to the MouseCell.Row
and MouseCell.Column
. The delta to add depends which part of the MouseCell
the cursor is in:
First thing we need to do is convert the X,Y coordinates in the mouse map to local coordinates within a cell. We do this by using Mod
with either column * cellWidth
(for the X
) or row * cellHeight
(for Y
). Incidentally, Mod
is a great tool for wrapping a value into a range. Once we have X and Y for the cell (localX
and localY
) we can start determine which region of the cell that point is in.
Each MouseCell
can be thought of as a hexagon with four surrounding regions. We need to determine which area (1 to 4) each point in the cell is in. There are four triangles and two rectangles to check:
In order to check if our local point is within one of the triangles or lower rectangles, we need the vertices (points):
Fortunately the vertices are easy to compute:
Var v1 As New Point(cellWidth / 2, 0)
Var v2 As New Point(cellWidth, tileHeight * 0.25)
Var v3 As New Point(cellWidth, tileHeight * 0.75)
Var v4 As New Point(cellWidth / 2, tileHeight)
Var v5 As New Point(0, tileHeight * 0.75)
Var v6 As New Point(0, tileHeight * 0.25)
Var va As New Point(0, 0)
Var vb As New Point(cellWidth, 0)
Var vc As New Point(cellWidth, tileHeight)
Var vd As New Point(0, tileHeight)
Now we can loop through every point in the mouse cell and determine which region it is and, therefore, what the delta to apply is:
// Loop through every point within the mouse map cell and determine
// which region it's in.
For x As Integer = 0 To cellWidth - 1
For y As Integer = 0 To cellHeight - 1
Var p As New Point(x, y)
// We must check the triangles first, then the centre and then
// the rectangles.
If PointInTriangle(p, va, v1, v6) Then
// Top left triangle (4).
mMouseMap(x, y) = New Delta(-1, -1)
ElseIf PointInTriangle(p, v1, vb, v2) Then
// Top right triangle (1).
mMouseMap(x, y) = New Delta(-1, 0)
ElseIf PointInTriangle(p, v4, v3, vc) Then
// Bottom right triangle (2).
mMouseMap(x, y) = New Delta(1, 0)
ElseIf PointInTriangle(p, v4, v5, vd) Then
// Bottom left triangle (3).
mMouseMap(x, y) = New Delta(1, -1)
ElseIf y <= tileHeight Then
// Centre.
mMouseMap(x, y) = New Delta(0, 0)
ElseIf x <= cellWidth / 2 Then
// Bottom left rectangle (3).
mMouseMap(x, y) = New Delta(1, -1)
Else
// It's in the bottom right rectangle (2).
mMouseMap(x, y) = New Delta(1, 0)
End If
Next y
Next x
Before you ask, mMouseMap()
is a two dimensional array (mMouseMap(localX, localY)
) where the value of each element is the delta to apply for both the row and the column. This is stored in the class Delta
:
Class Delta
Property Row As Integer
Property Column As Integer
Sub Constructor(row As Integer, column As Integer)
Self.Row = row
Self.Column = column
End Sub
End Class
The PointInTriangle
method determines if a point is within a triangle by checking which side of the half-plane created by the edges the point is:
Function PointInTriangle() As Boolean
Var d1, d2, d3 As Double
Var has_neg, has_pos As Boolean
d1 = Sign(p, v1, v2)
d2 = Sign(p, v2, v3)
d3 = Sign(p, v3, v1)
has_neg = (d1 < 0) Or (d2 < 0) Or (d3 < 0)
has_pos = (d1 > 0) Or (d2 > 0) Or (d3 > 0)
Return Not (has_neg And has_pos)
End Function
The Sign()
function is a little helper:
Function Sign(p1 As Point, p2 As Point, p3 As Point) As Double
Return (p1.X - p3.X) * (p2.Y - p3.Y) - (p2.X - p3.X) * (p1.Y - p3.Y)
End Function
At this point, we now have an array that we can quickly look up a local mouse cell coordinate and get the delta to apply. I create the mouse map array once when the game loads and that saves loads of maths on every mouse move.