Game Picture

Hi! Above is a visual of the problem I am having. My game has two different coordinate systems. You have the client, or the canvas, where the game is rendered - a standard coordinate plane; however, I also have the game coordinate plane where it is rotated due to the isometric nature of the game.

Note: The server only feeds chunks of tiles to the game at a time, which makes it impossible for me to just translate the canvas. I have to calculate the drag amount and in what direction, and request the new chunk of tiles to be displayed from the server.

My problem: I can't figure out how to calculate the # of tiles moved (so I can send that data to the server), because I don't know how to determine how many tiles and in what direction the user moves the map. Because if they move to the left, it's not only change the game's x position, but also the game's y position because both new columns and rows would have to be loaded due to the nature of the isometric map.

I hope this made sense, I tried my best to explain the problem and I'm not too sure I explained it correctly.

  • \$\begingroup\$ Are you asking how to convert between the two coordinate systems? \$\endgroup\$ – Anko Jan 30 '15 at 12:05
  • \$\begingroup\$ @Anko No. I was trying to say that I can't figure out how to calculate the # of tiles moved (so I can send that data to the server), because I don't know how to determine how many tiles and in what direction the user moves the map. Because if they move to the left, it's not only change the game's x position, but also the game's y position because both new columns and rows would have to be loaded due to the nature of the isometric map. I don't know why I am having such troubles explaining this problem. \$\endgroup\$ – X33 Jan 31 '15 at 7:34
  • \$\begingroup\$ @X33 is there anything more you need than a function to translate between screen-coordinates and world-coordinates? \$\endgroup\$ – Philipp Feb 1 '15 at 0:08

I hope this is the answer to your problem, not something obvious you already know. If it is, sorry!

You can keep the track of the last loaded tiles in each direction in blue dimensions and the movement of camera in red ones. For example, when the user starts, if it starts from the center, mathematically, the tiles will be loaded based on this formula:

|y| = n - |x| 

Now, you can get the dimensions of the display port. If there are variations in screen sizes, does the objects get scaled up in bigger screens or the bigger screens can see more tiles all at once? based on the answer, whether you have to pass a fixed number or a dynamic formula that gets the actual space a tile takes on the screen (in your screenshot each tile has taken 12% x 10.5% of your display) It's isometric display, and 45 degrees so it's quite easy (only depends on your camera's vertical angles) when you had this "projected" height and width of the tiles in pixels, if the player moved the camera more than the projected width or height of a tile, a new row or column of tiles would be requested, again based on tiles' dimensions on the screen.

  • \$\begingroup\$ Hi, and firstly thank you the reply! This solution doesn't seem like it would solve my problem. For instance, if I drag only to the left on the client horizontal axis, even though there was no change in the vertical axis for the client, there WOULD be a change in the game (blue) axis. Because going left on the map would actually be decreasing the game (blue) X and Y at the same time. It doesn't seem like this solution accounts for that? Again thanks! \$\endgroup\$ – X33 Jan 29 '15 at 0:44
  • \$\begingroup\$ When the movement on the red axis gets more than the projected size of a tile, a request for a new row or column of tiles (matching the size of screen) could be sent to server. In fact, this will work at its best when the movement is only horizontal or vertical on red axis. \$\endgroup\$ – aarman Jan 30 '15 at 5:18
  • \$\begingroup\$ My point is that you should call rows and columns of tiles (with the formula and depending on what quarter the player is on) based on camera movement. On blue coordinates, these rows and columns would have a 45 degree angle with the blue axes but on the screen they seem straight. Does that make sense? \$\endgroup\$ – aarman Jan 30 '15 at 5:24

I agree that its a big optimization to avoid attempting to draw tiles that aren't visible to the client. I suggest drawing the client screen area using a staggered approach. Refer to the image you've provided in your question to make sense of the following algorithm. It has a nicely visible grid to figure out the row and column movements in the server tilemap data structure. I assume you want as much of the client screen area covered as possible.

Here are some output examples for the code I've provided.

Staggered Isometric Drawing (Engine of Evil)

  • The top-left image shows how the top left corner of the server's tilemap gets drawn in a staggered fashion without adequate border padding tiles. The black spaces can be seen along the outer edges of the client screen area.
  • The bottom-left shows the same inadequate padding near the center of the server's tilemap data.
  • The top-right and bottom-right images show staggered isometric drawing with betting border padding tiles to allow for the illusion of a more continuous area.

Note that I removed the grass tiles from the "insufficient padding" example on the left to highlight the missing tiles against the back background. The orange square is my programmer art for my player sprite.

Here is the general algorithm:

  1. Get the top left point of the visible window, meaning the position of the camera in server space.
  2. Convert this point to Cartesian coordinates on the assumption that its already an isometric coordinate, even if you never explicitly converted the camera movement or position to isometric. Here is a useful link for coordinate conversion and isometric tilemaps in general
  3. Calculate the tilemap cell (aka tile) using that Cartesian point, meaning the index in your tilemap array. I use a 2D array, so I directly index tiles by row and column.
  4. Decrease the row by 1 or 2 to find a cell that will start drawing at a wider point beyond the client screen area.
  5. Continue from there by querying and drawing tiles "diagonally" through the server tilemap data structure. This will result in a visibly horizontal line of tiles across the client screen area. Meaning, from the staring [row][column] go to [row++][column--].
  6. The number of cells to draw across is just the ratio of isometric cell width to client screen area width. EG: screenWidth == 800, isometric cell width == 64, so the number of cells to draw horizontally is (int)(800 / 64) == 12 loops. You'll probably have to add some extra loops, say + 5, depending on whatever offset your floor tile images have.
  7. Once a line is complete, toggle an odd/even line boolean, meaning if its false make it true, and vis versa.
  8. If odd/even boolean is true, increment the starting column, then set your indexing row and column back the the starting row and column (using the modified starting column).
  9. If odd/even boolean is false, increment the starting row, then set your indexing row and column back the the starting row and column (using the modified starting row).
  10. Continue in this odd/even staggered movement down the client screen area according to twice the ratio of isometric cell height to client screen area height. EG: screenHeight == 600, isometric cell height == 32, so the number of horizontal lines to draw is (2 * (int)(600 / 32)) == 37 loops. Again, you'll probably have to add some extra loops, say + 5, to ensure the client screen area is fully covered.

My Solution

I am using SDL and C++ but the principles are the same for Java. Note that my tile origins have been pre-calculated and saved as isometric values. I am aware that the following code may be difficult to understand without knowing what all the function calls I'm making are; however, the idea here is that you should look into staggered isometric drawing instead of diamond isometric drawing because it maps much better to client screen area (even if it is more confusing than diamond drawing methods) and the relative draw order is preserved.

void eMap::Draw() {
    // DEBUG: these constants assume a cell is square, and that its isometric projection
    // is twice as wide as it is tall, the invIsoCellHeight is halved to account for the
    // staggered isometric cell alignment
    static const float invIsoCellWidth = 1.0f / (float)(tileMap.CellWidth() << 1);
    static const float invIsoCellHeight = 1.0f / (float)(tileMap.CellHeight() >> 1);

    // DEBUG: the extra '+ 5' is on both of these to ensure enough tiles are drawn to cover the screen
    int maxHorizCells = (int)(game.GetRenderer().ViewArea().w * invIsoCellWidth) + 5; 
    int maxVertCells = (int)(game.GetRenderer().ViewArea().h * invIsoCellHeight) + 5;   

    eVec2 camTopLeft = eVec2(game.GetCamera().GetAbsBounds().x, game.GetCamera().GetAbsBounds().y);
    eMath::IsometricToCartesian(camTopLeft.x, camTopLeft.y);

    int startRow, startCol;
    int row, column;
    tileMap.Index(camTopLeft, startRow, startCol);      // DEBUG: this tile has isometric coordinates
    // DEBUG: also note that the startRow and startCol may be negative if the top left corner of the camera is negative.

    startRow -= 2;                                      // DEBUG: ensure enough rows cover the screen area
    row = startRow;
    column = startCol;

    bool oddLine = false;
    for (int vertCount = 0; vertCount < maxVertCells; vertCount++) {
        for (int horizCount = 0; horizCount < maxHorizCells; horizCount++) {
            // DEBUG: ensure the tilemap row and column about to be queried are valid
            if (row < tileMap.Rows() && row >= 0 && column < tileMap.Columns() && column >= 0) {
                eTile & tile = tileMap.Index(row, column);
                eVec2 screenPoint = eVec2(
                    eMath::NearestFloat(tile.Origin().x - game.GetCamera().GetAbsBounds().x),
                    eMath::NearestFloat(tile.Origin().y - game.GetCamera().GetAbsBounds().y)
                game.GetRenderer().AddToRenderQueue(renderImage_t(screenPoint, tile.Image(), tile.Layer()));
            row++; column--;
        oddLine = !oddLine;
        oddLine ? startCol++ : startRow++;
        row = startRow;
        column = startCol;

I hope all this gets you thinking and ideally helps solve some problems. Here's another useful answer to staggered drawing


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.