First off, I know exactly what my drawing problem is, and I have various ideas on how to approach solving it. I am here to figure out how to adjust which frame is drawn so that the isometric "illusion" is maintained.
I am writing a 2D, birds-eye view game that takes place in space. I am trying to use isometric graphics in a world where Up is North, no rotating of the game world like in traditional isometric games (remember, the game takes place in space, no terrain). Here is an example sprite I am trying to use: http://cell.stat-life.com/images/stories/AsteroidOutpost/Spaceship64.png Rotated 64 times about it's own vertical axis with a viewing angle of 35 degrees (aka, iso). The image was generated, so this can be changed.
For the sake of clarity, I have dictated that North (Up) is 0 degrees, and East (Right) is 90.
My problem is that my sprite doesn't always look like it's facing where the game thinks it is due to a difference in the 3D planes used. Not sure if I'm using the terminology correctly, but my spaceship was rotated on one plane, and the view plane is expecting things to be rotated on its own plane. Here's a depiction of the problem:
http://cell.stat-life.com/images/stories/AsteroidOutpost/ProjectionIssue.png On the left, I have side views, on the right I have top-down views. On the top, I have the space ship/sprite sheet view of the world. On the bottom, I have what the sprite looks like when it is drawn to the screen.
In the top right is a simplified version of how my spaceship was rotated (even degree separations between each frame). In the bottom right is the angle that the sprite looks like it's facing when a particular angle is drawn on the screen. The problem is most evident at around 45 degrees. Here is an image that is overlaid with a "screen plane" line that is pointed in the direction that the ship is supposed to be facing: http://cell.stat-life.com/images/stories/AsteroidOutpost/Spaceship64RedLine.png The red line should always be pointed in the exact same direction as the ship, but as you can see, the projection issue is causing a problem. I hope I am describing the issue well enough.
EDIT: The red line is rotated on the screen plane, while the space ship is rotated on the "ship plane", hence the large difference between the ship angle and the screen angle when it is drawn. END EDIT
It looks rather odd when this ship is shooting a laser beam at a target that is off to one side, when it should be shooting straight ahead.
So... the solutions that I have come up with are:
- Stop trying to fake an isometric view, go big or go home. Rotate my world already. (PS: this will fix my problem, right?)
- Alter my sprite sheet (somehow) to have frames that represent the "screen angle" that the model will be viewed at. So when I ask for a 45 degree image, it will actually look like it's facing 45 degrees on the screen.
- Alter my sprite rendering system to grab a different frame from the sprite sheet (somehow), knowing that grabbing the "normal" frame will look funny. So instead of grabbing the 45 degree frame, it will grab the ... 33 degree frame (just guessing) so that it looks correct to the user.
- Just use 3D! It's so much easier man
For one: Which solution would you recommend? I'm leaning toward 2, even though I know it's wrong. Remember that I have full access to the sprite generation tool. Method 3 would be my second choice. Both of these methods simply require that I apply some math to the angle(s) to get my desired result. Btw, I added #4 in there as a joke, I don't want to move to 3D.
For two: Using methods 2 or 3, how would I adjust the input or output angles? I'm not all that good with 3D projections, and 3D matrices (let alone 2D matrices), and whatever other math might be used to achieve me desired result.
Thinking out loud here: if I take a unit vector facing in the direction that I want the ship to look (on the screen plane), then project that onto the ship plane, then figure out what the angle between that projected line and the plane's north is, I should have the angle of the ship graphic that I want to display. Right? (solution for #3?) Man... I can't work that out.
EDIT:
I know the problem is hard to visualize with static images, so I have complied my Sprite Library Visual Tester to display the issue: http://cell.stat-life.com/downloads/SpriteLibVisualTest.zip It requires XNA 4.0 This application is simply rotating the sprite, and drawing a line from its centre point outward at the angle the game thinks the ship should be facing.
EDIT Sept 8
Continuing from my "thinking out loud" paragraph: Instead of using a projection (because it looks hard) I'm thinking I could take a north facing unit vector (0x,1y,0z), use a matrix to rotate it to the angle the sprite is actually facing, then rotate it again from the "viewing plane" outward to the "sprite plane" around the X axis, then... flatten? the vector (essentially project it) back onto the viewing plane by only using the X and Y (ignoring Z). Then using that new flattened vector, I could determine its angle and use that to... something something. I'll think more later.
EDIT Sept 8 (2)
I tried following my idea from above with some success, yay! So... to get a red line to look like it is coming straight out of my spaceship (testing purposes only), I did the following:
Vector3 vect = new Vector3(0, 1, 0);
vect = Vector3.Transform(vect, Matrix.CreateRotationZ(rotation));
vect = Vector3.Transform(vect, Matrix.CreateRotationY((float)((Math.PI / 2) - MathHelper.ToRadians(AngleFromHorizon))));
Vector2 flattenedVect = new Vector2(vect.X, vect.Y);
float adjustedRotation = (float)(getAngle(flattenedVect.X, flattenedVect.Y));
Where rotation
is the screen angle and adjustedRotation
is now that screen angle looks on the sprite plane. I don't know why I needed to rotate Y instead of X, but it's probably something to do with 3D that I don't know about.
So, now I have an additional piece of information, but how do I use this to pick a more appropriate frame? Maybe instead of rotating from the view plane into the sprite plane, then projecting back, I should try to do it the other way around. Here's my sprite with an adjusted red line, but what I really want to do is to adjust the model, not the line: http://cell.stat-life.com/images/stories/AsteroidOutpost/Spaceship64RedLineCorrected.png
EDIT Sept 9
HORAY! It's fixed! I will describe what I did to fix it:
I followed eBusiness's advice and "squished" by world coordinate system (or stretched...?). This is essentially solution #1, although I was under the impression that I would have to rotate my world coordinate system in relation to the screen coord system. Not true! Up is still +y, and Right is still +x. I modified my WorldToScreen and ScreenToWorld methods to properly convert between the world and screen coordinates. These methods may not be optimal, but here are the only two methods in my codebase that had to change.
public Vector2 ScreenToWorld(float x, float y)
{
float deltaX = x - (size.Width / 2f);
float deltaY = y - (size.Height / 2f);
deltaX = deltaX * scaleFactor / (float)Math.Sqrt(3);
deltaY = deltaY * scaleFactor;
return new Vector2(focusWorldPoint.X + deltaX, focusWorldPoint.Y + deltaY);
}
public Vector2 WorldToScreen(float x, float y)
{
float deltaX = x - focusWorldPoint.X;
float deltaY = y - focusWorldPoint.Y;
deltaX = deltaX / scaleFactor * (float)Math.Sqrt(3);
deltaY = deltaY / scaleFactor;
return new Vector2(size.Width / 2f + deltaX, size.Height / 2f + deltaY);
}
That was it! Changing those two methods affected everything else: what I was looking at, where the objects where drawn, where I was clicking, everything. My spaceship now looks directly at the target it is shooting, and everything is falling into place. I will be experimenting with the orthographic projection, see if it makes everything seem more 'real'.