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 = []
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:
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:
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:
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:
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!
Answer
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:
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
No comments:
Post a Comment