# Proper 2D isometric tile depth sorting?

I'm using Raylib, and I'm attempting to place tiles on a level, and the algorithm for placing the tiles is working.

The problem is that the player will display over the walls on the bottom right hand side of the isometric grid.

What am I doing wrong, and does this involve the painter's algorithm?

Here is the code:

#include "raylib.h"
#include <stdint.h>

//Defines
#define COLUMNS 10
#define ROWS 10

//Player
typedef struct Player
{
int dirX;
int dirY;
Texture2D playerTexture;
Vector2 position;
struct Player* player;
}Player;

//Change from 2D to Isometric
Vector2 twoDtoIso(Vector2 tilePosition);

void movePlayer(Player* player, int8_t newX, int8_t newY);

int main()
{
// Initialization
//-------------------------------------------------------------------------

const uint8_t TILE_WIDTH    =     32;
const uint8_t TILE_HEIGHT   =     32;
const uint8_t PLAYER_OFFSET =     32;
const float SCREEN_WIDTH    = 640.0f;
const float SCREEN_HEIGHT   = 352.0f;
const float SCREEN_CENTER_X = 288.0f;
const float SCREEN_CENTER_Y =   0.0f;

InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Isometric Tile placement");

//These images are loaded from a folder named "images" with
//isometric cubes that are 64 pixels by 64 pixels

Player player = {0, 0,LoadTexture("images/player.png"),(Vector2){ SCREEN_CENTER_X, PLAYER_OFFSET }};

uint8_t levelData[COLUMNS][ROWS] =
{
{1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,2,1}, //<-----The player is designated as 2, ground as 0, and wall tiles as 1
{1,1,1,1,1,1,1,1,1,1}
};

static uint8_t tileType;
const uint8_t  NUM_COLUMNS = 10;
const uint8_t  NUM_ROWS    = 10;
const int8_t   newX        = 32;
const int8_t   newY        = 16;

Vector2 coords;
Vector2 tilePosition;

SetTargetFPS(30);
//------------------------End Initialization-------------------------------

// Main game loop
while (!WindowShouldClose())
{
// Update
//---------------------------------------------------------------------

//Player input/movement

movePlayer(&player, newX, newY);

//--------------------------End Update---------------------------------

// Draw
//---------------------------------------------------------------------
BeginDrawing();

ClearBackground(BLACK);

//place tiles
for (uint8_t i = 0; i < NUM_ROWS; i++)
{
for (uint8_t j = 0; j < NUM_COLUMNS ; j++)
{
tilePosition = (Vector2){(float)j * TILE_WIDTH, (float)i * TILE_HEIGHT};
tileType = levelData[i][j];
coords = twoDtoIso(tilePosition);

if(tileType == 0) //place ground tile
{
DrawTexture(groundTexture, coords.x + SCREEN_CENTER_X, coords.y + SCREEN_CENTER_Y, WHITE);
}
else if(tileType == 1) //place wall tile
{
DrawTexture(wallTexture, coords.x + SCREEN_CENTER_X, coords.y + SCREEN_CENTER_Y, WHITE);
}
else //otherwise place the player
{
DrawTexture(player.playerTexture, player.position.x, player.position.y, WHITE);
}
}
}//end for loop

EndDrawing();
//---------------------------------------------------------------------
}

// De-Initialization
//-------------------------------------------------------------------------

CloseWindow();                // Close window and OpenGL context
//-------------------------------------------------------------------------

return 0;
}

Vector2 twoDtoIso(Vector2 tilePosition)
{
Vector2 tempPoint;
tempPoint.x =  tilePosition.x - tilePosition.y;
tempPoint.y = (tilePosition.x + tilePosition.y)/ 2;
return(tempPoint);
}

void movePlayer(Player* player, int8_t newX, int8_t newY)
{
if (IsKeyPressed(KEY_LEFT))
{
player->position = (Vector2){ player->position.x - newX, player->position.y + newY };
}
if (IsKeyPressed(KEY_RIGHT))
{
player->position = (Vector2){ player->position.x + newX, player->position.y - newY };
}
if (IsKeyPressed(KEY_UP))
{
player->position = (Vector2){ player->position.x - newX, player->position.y - newY };
}
if (IsKeyPressed(KEY_DOWN))
{
player->position = (Vector2){ player->position.x + newX, player->position.y + newY };
}
}

$$$$


You are painting your tiles in the wrong order. Yes, you would use the painters algorithm, which just means, that you draw from back to forward. In your image (I can't really see, where you 0,0 is in the image. 0,0 should be in the top middle of the screen and then like so:

if you "rotate" to change the direction you are viewing you need another draw order. It's normally solved by just inverting a loop from 0->n to n->0 or something (depends on where you 0,0 is on the image)

• Thank you so much. I will start working on this now. Commented Dec 1, 2019 at 17:41

Here's how I got it working. The player tile can be moved with WASD, and some tiles allow the player to move to tiles that are a height of less than 2. The player can collide with tiles that are designated as collision tiles. Make sure you have a folder named "images" with your tiles, where the .c file is located. Using raylib v4.6-dev:

#include <raylib.h>
#include <raymath.h>

#define GRID_WIDTH 8
#define GRID_HEIGHT 8
#define TILE_SIZE 64
#define PLAYER_SPEED 1
#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480

// Define tile types (add more as needed)
enum TileType {
TILE_FLOOR = 0,
TILE_WALL,
TILE_TREE,
TILE_WATER,
TILE_ELEVATION, // New tile type for elevation
TILE_GRASS,
};

int playerX = 0;
int playerY = 0;

// 3D array representing the isometric grid with tile types and elevation
int grid[GRID_WIDTH][GRID_HEIGHT][2] = {0}; // Initialize with all floor tiles at elevation 0

// Function to convert grid coordinates to screen coordinates
Vector2 GridToScreen(int x, int y, int elevation) {
Vector2 screenPos;
screenPos.x = (x - y) * (TILE_SIZE / 2);
screenPos.y = (x + y) * (TILE_SIZE / 4) - elevation * (TILE_SIZE / 2);
return screenPos;
}

// Function to check if the player can move to the specified grid position
bool CanMoveTo(int x, int y) {
if (x < 0 || x >= GRID_WIDTH || y < 0 || y >= GRID_HEIGHT) {
// Out of bounds
return false;
}

// Check the tile type and elevation at the specified grid position
int tileType = grid[x][y][0];
int elevation = grid[x][y][1];

// Check if the target tile has an elevation of 1 or is flat ground (elevation 0)
if (elevation == 1 || elevation == 0) {
// If the tile is a wall or tree, return false
if (tileType == TILE_WALL || tileType == TILE_TREE) {
return false;
}

return true; // Otherwise, the player can move to this tile
}

// Check if the player is moving from elevation 1 to elevation 2 or vice versa
int currentElevation = grid[playerX][playerY][1];
if ((currentElevation == 1 && elevation == 2) || (currentElevation == 2 && elevation == 1)){
return true;
}

return false; // Elevation is 2 or higher, and it's not a valid move from elevation 0
}

int main() {
InitWindow(SCREEN_WIDTH, SCREEN_HEIGHT, "Isometric Grid with Raylib");
SetTargetFPS(60);

// Place wall tiles at specific locations with elevation 1 (you can change these as needed)
grid[2][5][0] = TILE_WALL;
grid[3][1][0] = TILE_WALL;
grid[4][3][0] = TILE_WALL;
grid[2][4][0] = TILE_WALL;
grid[3][3][0] = TILE_WALL;

// Add other tile types (e.g., tree, water, grass) at specific locations and elevations
grid[1][3][0] = TILE_TREE;
grid[6][4][0] = TILE_TREE;
grid[4][1][0] = TILE_WATER;
grid[0][7][0] = TILE_ELEVATION; // Elevation at (0, 7) represented by the rock tile
grid[7][0][0] = TILE_GRASS;

// Set the elevation value for the tile representing the rock elevation
grid[0][7][1] = 1; // Elevation level of 1 for the rock tile
grid[1][7][1] = 2; // Elevation level of 1 for the rock tile

Texture2D elevationTexture = LoadTexture("images/rockTile.png"); // Texture for elevation (rock tile)

while (!WindowShouldClose()) {
// Handle player movement
if (IsKeyPressed(KEY_W) && CanMoveTo(playerX, playerY - 1)) {
playerY -= PLAYER_SPEED;
}
if (IsKeyPressed(KEY_A) && CanMoveTo(playerX - 1, playerY)) {
playerX -= PLAYER_SPEED;
}
if (IsKeyPressed(KEY_S) && CanMoveTo(playerX, playerY + 1)) {
playerY += PLAYER_SPEED;
}
if (IsKeyPressed(KEY_D) && CanMoveTo(playerX + 1, playerY)) {
playerX += PLAYER_SPEED;
}

// Ensure the player stays within the grid boundaries
playerX = Clamp(playerX, 0, GRID_WIDTH - 1);
playerY = Clamp(playerY, 0, GRID_HEIGHT - 1);

BeginDrawing();
ClearBackground(BLACK);

// Render tiles from back to front (lower y-axis first)
for (int y = 0; y < GRID_HEIGHT; y++) {
for (int x = 0; x < GRID_WIDTH; x++) {
Vector2 screenPos = GridToScreen(x, y, grid[x][y][1]); // Use the elevation value

// Render the tile at grid[x][y] at (screenPos.x, screenPos.y)
switch (grid[x][y][0]) {
case TILE_WALL:
DrawTexture(wallTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
break;
case TILE_TREE:
DrawTexture(treeTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
break;
case TILE_WATER:
DrawTexture(waterTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
break;
case TILE_ELEVATION:
DrawTexture(elevationTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
break;
case TILE_GRASS:
DrawTexture(grassTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
break;
default:
DrawTexture(floorTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
break;
}

// Render the player
if (x == playerX && y == playerY) {
DrawTexture(playerTexture, screenPos.x + SCREEN_WIDTH / 2, screenPos.y, WHITE);
}
}
}

EndDrawing();
}

CloseWindow();

return 0;
}
$$$$

• Remember to click the checkmark icon at the top-left of an answer to mark it as "Accepted" if it successfully solved your problem. Commented Jul 29, 2023 at 12:13