2
\$\begingroup\$

I have a group of soldiers, and each has a vector describing their position in the squad. When the group rotates, I do this math to calculate the desired position of each soldier.

var unitPositions : Array<CGPoint> = []

let cosRadian = cos(rotation)
let sinRadian = sin(rotation)

for var index = 0; index < self.positions.count; ++index {

    // Find the vector for this unit, translate it, then store it
    let targetVector = self.getVectorAtPosition(index) - translate
    let newX  = targetVector.dx * cosRadian - targetVector.dy * sinRadian
    let newY = targetVector.dx * sinRadian + targetVector.dy * cosRadian

    unitPositions.append(CGPoint(x:newX, y:newY))
}

This produces the following result:

Units after normal matrix rotation

The problem with this is that units in an isometric world facing south east should match up with the reference grid. So the next step was to add a multiplier. In the loop above, I changed it to:

    let isoX = newX * 1.414213562373095;
    let isoY = newY

    unitPositions.append(CGPoint(x:isoX, y:isoY))

This produced closer results:

Positions after multiplier on X

But, it's still not 100% accurate. So, right now I'm going through and trying to get my fundamentals of math, matrices, vectors, etc all figured out in hopes of producing this desired result:

desired unit positions

I would love any pointers on what I should be searching for, as right now it is all a bit overwhelming. Thanks much!

UPDATE

Due to the awesome comment by @DMGregory, here are my findings:

If I want to "hack" this and continue to use screen coordinates, I can do so using 1.414213 (the magic number found here) combined with your true isometric projection.

let isoX = newX * 1.414213562373095
let isoY = newY * (1.414213562373095 * 1/sqrt(3))

That will produce:

enter image description here

That approach will work, but won’t solve everything else you mentioned (i.e: units will move faster when going certain directions). To handle that, creating the original formation with isometric vectors, and then just rotating that matrix will produce the same results. So, it looks like I have a lot of work converting my game calculations and coords to default as isometric coordinates. Thanks again!

\$\endgroup\$
2
\$\begingroup\$

The trick here is conversion between world space and screen space.

World space is the coordinate system you use for your game logic - calculations of pathing, movement, formations, etc. Your original formation code is correct for world space.

Screen space is the coordinate system in which items are displayed. Because you're using an axonometric projection here, your vertical/depth axis is foreshortened. So a distance of "one meter" forward spans fewer pixels on screen than "one meter" to the right. This also means that right angles in world space can become acute or obtuse on screen - which is why your right-angled unit formation (which is correct for world space) doesn't match your isometric grid.

You generally won't want to do game calculations in screen space - otherwise units moving "North" will have a higher effective speed relative to your isometric grid than units moving "East" (and units will be able to shoot further if they attack from North/South etc.)

3D games typically store only the world coordinates of each entity. They generate a "View-Projection" matrix, based on the position/orientation/FoV of a virtual camera, which converts world coordinates into screen coordinates on the GPU as the content is being rendered. A lot of "2D" games do this too, just with an orthographic matrix or constrained camera angle.

Judging by your example though, it looks like you're manually positioning your units with a formula, so I'll demonstrate that style.

The details will vary based on your coordinate setup, so I'll make some assumptions you might need to modify.

Let's say your world axes run like this, to align nicely with your isometric grid: coordinates

Here's some pseudocode (uv is just a temporary label for notational convenience)

// First, we recenter our worldspace position
// By changing center.xy, you can pan the "camera" around the map
uv.xy = worldPosition.xy - center.xy

// Then we convert to pixel offsets from the center of the screen
screenPosition.x = (uv.x - uv.y) * TileWidth * 0.5    
screenPosition.y = (uv.x + uv.y) * TileHeight * 0.5

// Depending on your drawing environment, you might need to apply an offset to this
// eg. if your pixel coordinates are measured from bottom-left rather than from the center

Where TileWidth is the number of pixels across one grid square from left to right.

If you're using a true isometric projection,

TileHeight = TileWidth * 1/sqrt(3)

But note that many games use an "almost isometric" ratio of 1:2 to avoid irrationals, in which case

TileHeight = TileWidth * 0.5
\$\endgroup\$
  • \$\begingroup\$ Accepting this as answer. Updated my original question with my findings. Thanks again for your help. \$\endgroup\$ – Ryan C Feb 16 '15 at 14:59
  • \$\begingroup\$ In case you're wondering what the "magic" is behind 1.414, it's because you've scaled the lengths of the sides of your diamonds to be 1. The diagonal across the diamond (in either direction, before we squash it vertically for the isometric look) then has length sqrt(2) = 1.414... by Pythagorean theorem. \$\endgroup\$ – DMGregory Feb 16 '15 at 16:12
  • \$\begingroup\$ Ahh, makes sense. \$\endgroup\$ – Ryan C Feb 16 '15 at 16:17

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.