1

We are building a basic game engine with a friend of mine as part of a course, and we are now in the process of implementing a grid-map. The grid map will act as a dev-tool for checking the movement filtering logic that is based on tile type i.e., SOLID, EMPTY, etc.

We are using the Tiled editor to extract our tile sets and our map info in the form of .csv and .json files. We have managed to load, construct and render the tile map successfully while also using memoization for the tile indices. Next up, we must load, construct and render a grid map (4×4 per tile) based on the tile indices that we got from Tiled.

I'm trying to figure out the logic for this process, and here is what I have so far. Please correct me if I'm wrong:

  1. Somehow, determine the various tile types dynamically for the various tile sets that the user/dev may want to use with the engine
  2. While parsing the tile indices and constructing the tile map, generate the 4×4 grid element for each tile. While doing this, keep track of each grid element's associated tile index, its position within the tile & its type.
  3. Generate a generic texture for the “solid” tiles that will be rendered on top of the actual tile of the tile map (i.e., red outline and transparent center)
  4. Render the tiles from (3) on top of the current tile map.

Here come the questions:

— Is there a setting in Tiled that I'm not aware of that can generate the tile types based on their index so that we can achieve (1) dynamically and allow the user/dev to provide any tile set? If not, is the only other way to manually review the tile set and determine the mappings? For example, if we have 30 unique tiles, would the only option be to manually write a map in order to store the tile's Index and its respective type?

— Regarding (2), in our lectures we saw a naive example of how we can use the grid elements (16 per tile) to detect collisions and filter movement. That example included parsing each pixel of each tile, extracting the color percentages and determining if that segment of the tile is a solid or not (i.e., mostly white means empty). Just want to confirm my assumption that this seems like an awful solution for determining which parts of a tile should be collide-able, right?

— For the rendering part, assuming that we have already rendered the tile map on the window's surface, should we use SDL_RenderCopy to draw the grid element outlines over the solid tiles?

Here's our tile map parsing (based on the .csv file) & memoization logic: TileMap class:

#define MAX_WIDTH 192
#define MAX_HEIGHT 336

typedef unsigned short Dim;
typedef short Index;
typedef unsigned char byte;

typedef struct {
    Dim x, y;
} Tile;

class TileMap {
public:
    explicit TileMap(const std::string& mapPath);

    std::vector<std::string> tilesetPaths;
    std::vector<std::string> csvLayers;
    std::map <Index, std::pair <unsigned short, unsigned  short> > preCachedIndices;
    std::vector<Index> indices;

    unsigned short tilesetHeight{}, tilesetWidth{}; // number of tile rows/columns
    unsigned short tileHeight, tileWidth{};
    unsigned short mapWidth{}, mapHeight{};

    Tile GetTile(Dim row, Dim col);

    void WriteTextMap(const Json::Value& data, const std::string& fp);

    bool preCacheIndices(const std::string &inputMapPath);

    void renderMap(SDL_Window *window, SDL_Rect &cameraOffset);

    void printMapInfo();

    void initMapInfo(const std::string& path);
};

parsing & memoization logic:

 bool TileMap::preCacheIndices(const std::string &inputMapPath) {
    std::ifstream file;
    std::string line;

    file.open(inputMapPath.c_str(), std::ios::in);

    if (!file) {
        std::cerr << "Error opening file: " << std::strerror(errno) << "\n";
        return false;
    }

    while (std::getline(file, line)) {
        std::stringstream ss(line);
        std::string item;

        while (std::getline(ss, item, ',')) {
            Index index = std::stoi(item);
            indices.push_back(index);

            // apply memoization to speed the process up (O(1) access time for already existing keys)
            if (preCachedIndices.find(index) == preCachedIndices.end()) { // index has not been cached yet
                unsigned short row = index / tilesetWidth;
                unsigned short col = index % tilesetWidth;
                std::pair<unsigned short, unsigned short> p = std::pair<unsigned short, unsigned short>(row, col);
                preCachedIndices.insert(std::pair<Index, std::pair<unsigned short, unsigned short>>{index, p});
            } else {
                continue;
            }
        }
    }

    file.close();

    return true;
}

rendering logic for the tile map:

void TileMap::renderMap(SDL_Window *window, SDL_Rect &cameraOffset) {
    SDL_Surface *screenSurface, *tileset;

    screenSurface = SDL_GetWindowSurface(window);
    if (!screenSurface) {
        printf("Could not initialize screen surface. Error message: %s", SDL_GetError());
    }

    tileset = IMG_Load(tilesetPaths.at(0).c_str()); // load tileset bitmap

    SDL_FillRect(screenSurface, nullptr, SDL_MapRGB(screenSurface->format, 0xFF, 0xFF, 0xFF));

    int row = 0;
    int col = 0;

    for (const auto &index: indices) {
        SDL_Rect src, dest;

        src.x = preCachedIndices.at(index).second * tileWidth;
        src.y = preCachedIndices.at(index).first * tileHeight;
        src.w = tileWidth;  //tileManager.tilemapWidth;
        src.h = tileHeight; //tileManager.tilemapHeight;

        dest.x = col * tileWidth + cameraOffset.x;
        dest.y = row * tileHeight - cameraOffset.y;
        dest.w = tileWidth;
        dest.h = tileHeight;

        SDL_BlitSurface(tileset, &src, screenSurface, &dest);

        if (col == mapWidth - 1) {
            col = 0;
            row++;
        } else {
            col++;
        }

    }

    SDL_FreeSurface(tileset);
    SDL_UpdateWindowSurface(window);
}

Grid map class:

typedef enum {
    ThinAir,
    LeftSolid,
    RightSolid,
    TopSolid,
    BottomSolid,
    Ground,
    Floating,
} Masks;

typedef struct {
    int x, y;
} Position;

//Operator overloading sto std::map can understand how to handle Position structs
inline bool operator<(Position const &a, Position const &b) {
    return std::tie(a.x, a.y) < std::tie(b.x, b.y);
}

typedef struct {
    Index index;
    Masks mask;
} GridTile;

using GridIndex = byte;

class GridMap {
private:
   TileMap *mapManager;
   std::map<Position, GridTile> gridTiles;
   unsigned short gridTileWidth, gridTileHeight;

public:
    explicit GridMap(TileMap *mapManager);

    void printGridTiles();

    void precacheGridTiles();

};

The calculation for the grid tile width and height is done as follows:

gridTileHeight = mapManager->tileHeight / 4;
gridTileWidth = mapManager->tileWidth / 4;

My friend's approach to generating the grid map tiles so far:

void GridMap::precacheGridTiles() {
    SDL_Surface *tileset;

    //todo: This should change for layering (at(0) part)
    tileset = IMG_Load(mapManager->tilesetPaths.at(0).c_str());

    Index i = 0;

    int row = 0, col = 0;
    for (const auto &index: mapManager->indices) {
        for (int j = 0; j < gridTileWidth * gridTileHeight; j++) {
            GridTile gridTile;
            gridTile.index = i;

            //todo: Find way to determine mask
            if(index == 61) {
               //todo:sp check based on current position if the tile is top,left,right,bottom side

            } else {
                gridTile.mask = ThinAir;
            }

            Position position;
            position.x = col + (j * gridTileWidth);
            position.y = row + (j * gridTileHeight);

            gridTiles[position] = gridTile;
        }

        if (col < mapManager->mapWidth) {
            col += gridTileWidth;
        } else {
            col = 0;
            row += gridTileHeight;
        }
    }
}

Any pointers for direction would be greatly appreciated. We're looking to confirm/adjust our logic and hopefully find an answer to our questions, but don't want an answer that solves the problem for us please.

genpfault
  • 51,148
  • 11
  • 85
  • 139
Stelios Papamichail
  • 955
  • 2
  • 19
  • 57

0 Answers0