Drawing Tilemaps Help

I've got this config file below that I am trying to load, however I am not finding a very concrete guide anywhere on it. I've tried simply using orxObject_CreateFromConfig, however nothing appears. Does anyone have any advice, or can tell me where in the config I've messed up?

[TileGraphicbeginning]
Texture    = "SnowSides.png"
Pivot  = top left
TextureSize    = (32,32,0)

[beginning0@TileGraphicbeginning]
TextureOrigin  = (0,0,0)

[beginning1@TileGraphicbeginning]
TextureOrigin  = (32,0,0)

[beginning2@TileGraphicbeginning]
TextureOrigin  = (64,0,0)

[beginning3@TileGraphicbeginning]
TextureOrigin  = (96,0,0)

[beginning4@TileGraphicbeginning]
TextureOrigin  = (128,0,0)

[beginning5@TileGraphicbeginning]
TextureOrigin  = (160,0,0)

[beginning6@TileGraphicbeginning]
TextureOrigin  = (192,0,0)

[beginning7@TileGraphicbeginning]
TextureOrigin  = (224,0,0)

[beginning8@TileGraphicbeginning]
TextureOrigin  = (256,0,0)

[beginning9@TileGraphicbeginning]
TextureOrigin  = (288,0,0)

[beginning10@TileGraphicbeginning]
TextureOrigin  = (0,32,0)

[beginning11@TileGraphicbeginning]
TextureOrigin  = (32,32,0)

[beginning12@TileGraphicbeginning]
TextureOrigin  = (64,32,0)

[beginning13@TileGraphicbeginning]
TextureOrigin  = (96,32,0)

[beginning14@TileGraphicbeginning]
TextureOrigin  = (128,32,0)

[beginning15@TileGraphicbeginning]
TextureOrigin  = (160,32,0)

[beginning16@TileGraphicbeginning]
TextureOrigin  = (192,32,0)

[beginning17@TileGraphicbeginning]
TextureOrigin  = (224,32,0)

[beginning18@TileGraphicbeginning]
TextureOrigin  = (256,32,0)

[beginning19@TileGraphicbeginning]
TextureOrigin  = (288,32,0)

[beginning20@TileGraphicbeginning]
TextureOrigin  = (0,64,0)

[beginning21@TileGraphicbeginning]
TextureOrigin  = (32,64,0)

[beginning22@TileGraphicbeginning]
TextureOrigin  = (64,64,0)

[beginning23@TileGraphicbeginning]
TextureOrigin  = (96,64,0)

[beginning24@TileGraphicbeginning]
TextureOrigin  = (128,64,0)

[beginning25@TileGraphicbeginning]
TextureOrigin  = (160,64,0)

[beginning26@TileGraphicbeginning]
TextureOrigin  = (192,64,0)

[beginning27@TileGraphicbeginning]
TextureOrigin  = (224,64,0)

[beginning28@TileGraphicbeginning]
TextureOrigin  = (256,64,0)

[beginning29@TileGraphicbeginning]
TextureOrigin  = (288,64,0)

[beginning30@TileGraphicbeginning]
TextureOrigin  = (0,96,0)

[beginning31@TileGraphicbeginning]
TextureOrigin  = (32,96,0)

[beginning32@TileGraphicbeginning]
TextureOrigin  = (64,96,0)

[beginning33@TileGraphicbeginning]
TextureOrigin  = (96,96,0)

[beginning34@TileGraphicbeginning]
TextureOrigin  = (128,96,0)

[beginning35@TileGraphicbeginning]
TextureOrigin  = (160,96,0)

[beginning36@TileGraphicbeginning]
TextureOrigin  = (192,96,0)

[beginning37@TileGraphicbeginning]
TextureOrigin  = (224,96,0)

[beginning38@TileGraphicbeginning]
TextureOrigin  = (256,96,0)

[beginning39@TileGraphicbeginning]
TextureOrigin  = (288,96,0)

[beginning40@TileGraphicbeginning]
TextureOrigin  = (0,128,0)

[beginning41@TileGraphicbeginning]
TextureOrigin  = (32,128,0)

[beginning42@TileGraphicbeginning]
TextureOrigin  = (64,128,0)

[beginning43@TileGraphicbeginning]
TextureOrigin  = (96,128,0)

[beginning44@TileGraphicbeginning]
TextureOrigin  = (128,128,0)

[beginning45@TileGraphicbeginning]
TextureOrigin  = (160,128,0)

[beginning46@TileGraphicbeginning]
TextureOrigin  = (192,128,0)

[beginning47@TileGraphicbeginning]
TextureOrigin  = (224,128,0)

[beginning48@TileGraphicbeginning]
TextureOrigin  = (256,128,0)

[beginning49@TileGraphicbeginning]
TextureOrigin  = (288,128,0)

[TilesMap]
Map    = beginning43 #beginning43 #beginning43 #beginning43 #beginning43 #beginning43 #beginning43 #beginning11 #beginning10 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 #beginning2 

Comments

  • Hi Sacked, you still need to create code to loop through and paint in place. There is a small guide here that should be able to help: https://wiki.orx-project.org/en/tutorials/community/sausage/semi-dynamic_objects_and_level_mapping

    Let me know if you have any issues with it.

  • edited May 2020

    Thanks for the help. I did have a bit of a hard time understanding what the code was doing given how poor at programming I am so I added some comments to it, I'll post it in case it helps someone.

    bool PaintTiles(int tileIndexPosition){
    
        int mapLength = sizeof(map) / sizeof(map[0]);
            orxLOG("Length %d map[0] %d map %d", mapLength, sizeof(map[0]), sizeof(map)    );
        if (tileIndexPosition >= mapLength)
            return false;
    
        for (int x=0; x<TILES_ACROSS; x++){
            int i = x + tileIndexPosition;
    
            if (i > mapLength-1){
                return false; //out of tiles, no more drawing
            }
    
            //Create String in format: Tile{Number}, for example "tile8".
            orxCHAR buffer[6];
            orxString_Print(buffer, "tile%d", map[i]);
    
            //Create Tile{Number} object from config file, utilizing string created above.
            //Note our tiles are stored as "[tile8@TilesGraphic]" in our config file. 
            orxGRAPHIC *graphic;
            graphic = orxGraphic_CreateFromConfig(buffer);
    
            //Create Default Tile object, which holds our fullsize image.
            orxOBJECT *tile;
            tile = orxObject_CreateFromConfig("TilesObject");
    
            //Link our Tile{Number} config to our fullsize image.
            //The Tile{Number} config contains the portion of our fullsize tilemap image that we want to display.
            orxObject_LinkStructure(tile, orxSTRUCTURE(graphic));
    
            //Position our tile on the screen.
            orxVECTOR tilePos;
            tilePos.fX = (80 * x);
            tilePos.fY = 160;
            tilePos.fZ = 0;
            orxObject_SetPosition(tile, &tilePos);
    
        }
    
        return true;
    }
    
  • Oops I have some hard coded numbers in there instead of working out the tile positions from the width and height of a tile :) But it's ok for demonstration purposes.

    Good comments.

  • edited May 2020

    Just a quick comment on that code, the following part is not safe:

            //Create String in format: Tile{Number}, for example "tile8".
            orxCHAR buffer[6];
            orxString_Print(buffer, "tile%d", map[i]);
    

    buffer can only hold tile + 1 single digit + string terminal character.
    If map[i] is > 9, this will result in a buffer overflow.

    A safer version would be, for example:

            //Create String in format: Tile{Number}, for example "tile8".
            orxCHAR buffer[16] = {};
            orxString_NPrint(buffer, sizeof(buffer) - 1, "tile%d", map[i]);
    
  • Fixed both in the wiki. Thanks guys.

  • Thanks!

  • edited May 2020

    I was thinking of another way to do this, would there be a way to dynamically load objects based on proximity to an object? Perhaps adding an "existence" parameter that activates an object when they are N coordinates away from another object, perhaps tied to some sort of broadphase for collision detection?

    It could be useful in other areas as well, like dynamically loading levels as a player progresses.

  • You'd have to write your own system to handle world streaming. I'd recommend starting with something simple, maybe attached to a clock with a lower frequency. Such a system could be based on a regular grid with cells being object hierarchies that would be created/deleted based on the anchor(s) positions.

    I'd actually like to demonstrate such a system as you can see in my video sample todo list, Trello card at https://trello.com/c/ee9GBOlD/39-world-streaming.

    That being said, if you're only interested in rendering background tiles, I'd recommend using a shader-based approach where all the tile data is stored inside a texture and the rendering is entirely GPU-based. You can find an example at: https://github.com/iarwain/tilemap

  • I'm doing some testing to see what I can do, is there a max size for config file arrays?

    I've got 30k characters in a config file which make up a map, however its crashing with an error "No value for key <#24#25#26#27#28#29...". I dont see any reference to any maximum, and it seems like I can draw 40k tiles using random values, it seems to just be the config file.

  • There are indeed two limits.

    The first one is the size of the buffer we're using for streaming config content from file, which is 16kb, so a full key = value pair needs to fit inside that limit. One way to get passed that limit is to modify the content's of the config at runtime, using the orxConfig API. The buffer limit used there is 512kb.

    The second limit is the number of entries in a single config list: there's a maximum of 65535 entries per list.

    Regarding your map's storage, you have multiple options as well:

    • use a hierarchical approach, the map is an object which will have children for the subdivisions (you can have multiple levels of subdivisions as well
    • use a naming convention approach with separate sections, and iterate through them in code until you don't find anymore (a bit cumbersome)
    • separate the map in multiple data structures:
      • a tile index texture for rendering (using the shader-based approach mentioned earlier)
      • an object hierarchy for everything that will be moving and/or have a physical/animated presence
  • I'm having another very strange problem, it seems the tiles draw with this line commented out:
    "string mapGraphic = _map + "Graphic";", but with it in it doesnt draw even though I'm not using it. Is anyone able to help me figure out whats going on here?

    bool PaintTiles(string _map){
        //Pull map into array map[]
        string mapName = _map + "Map";
        orxConfig_PushSection((orxCHAR*)mapName.c_str());
        int mapsize = orxConfig_GetListCount("map");
        int map[mapsize];
        for(int i=0;i<mapsize;i++){
            map[i] = orxConfig_GetListU32("map",i);
        }
        orxConfig_PopSection();
        //Get tile width/height
        string mapGraphic = _map + "Graphic";
        orxConfig_PushSection("beginningGraphic");
        orxVECTOR tileDimension;
        orxConfig_GetVector("TextureSize", &tileDimension);
        int tileWidth = tileDimension.fX;
        int tileHeight = tileDimension.fY;
        orxConfig_PopSection();
        //Get Maps column count
        orxConfig_PushSection("beginningMap");
        int TILES_ACROSS = orxConfig_GetU32("columnCount");
        orxConfig_PopSection();
    
        //Get Position Vector from Camera
        orxConfig_PushSection("MainCamera");
        orxVECTOR camPosition;
        orxConfig_GetVector("Position", &camPosition);
        orxConfig_PopSection();
        //Get Display Size
        orxConfig_PushSection("Display");
        int screenWidth = orxConfig_GetU32("ScreenWidth");
        orxConfig_PushSection("Display");
        int screenHeight = orxConfig_GetU32("ScreenHeight");
        orxConfig_PopSection();
        //Get camera relative tile position, to cut stuff out of view
        int camStartTilePositionX = floor((camPosition.fX-screenWidth/2) / tileWidth);
        int camEndTilePositionX = ceil((camPosition.fX+screenWidth/2) / tileWidth);
        int camStartTilePositionY = floor((camPosition.fY-screenHeight/2) / tileHeight);
        int camEndTilePositionY = ceil((camPosition.fY+screenHeight/2) / tileHeight);
    
        //Draw tiles in view
        int mapLength = sizeof(map) / sizeof(map[0]);
        int x, y = 0;
        for (int i=0; i<mapsize; i++) {
            //Dont draw if its out of range of camera
            if((x >= camStartTilePositionX+3) && (x  <= camEndTilePositionX-3)) {
                if((y >= camStartTilePositionY+3) &&  (y <= camEndTilePositionY-3)) {
                    //Create String in format: Tile{Number}, for example "tile8".
                    orxCHAR buffer[16] = {};
                    orxString_NPrint(buffer, sizeof(buffer) - 1, "beginning%d", map[i]);
    
                    //Create Tile{Number} object from config file, utilizing string created above.
                    //Note our tiles are stored as "[tile8@TilesGraphic]" in our config file. 
                    orxGRAPHIC *graphic;
                    graphic = orxGraphic_CreateFromConfig(buffer);
    
                    //Create Default Tile object, which holds our fullsize image.
                    orxOBJECT *tile;
                    tile = orxObject_CreateFromConfig("TilesObject");
    
                    //Link our Tile{Number} config to our fullsize image.
                    //The Tile{Number} config contains the portion of our fullsize tilemap image that we want to display.
                    orxObject_LinkStructure(tile, orxSTRUCTURE(graphic));
    
                    //Position our tile on the screen.
                    orxVECTOR tilePos;
                    tilePos.fX = (32 * x);
                    tilePos.fY = (32 * y);
                    tilePos.fZ = 0;
                    orxObject_SetPosition(tile, &tilePos);
    
                }
            }
            //Iterate next position (translates 1d map array into 2d)
            x =  x + 1;
            if(x == TILES_ACROSS){
                x = 0;
                y++;
            }
        }
        return true;
    }
    
  • Hi Sacked, hard to see what effect you're getting. The line you mentioned isn't commented out.

    Is it possible your objects are painting outside of the frustum area?

  • edited June 2020

    Sure, heres what it looks like with that line commented out, and then with it left in. The only thing I've modified is adding a // in front. I'm trying to attach the code as well if its possible to just compile it.

    ;Display

    [Viewport]
    Camera = MainCamera
    BackgroundColor = (0, 0, 255)

    [Display]
    ScreenWidth = 800
    ScreenHeight = 600
    Title = spookyparty
    FullScreen = false
    Smoothing = false
    VSync = false
    ShowFPS = true

    [MainCamera]
    FrustumWidth = 800 ;frustum is the actual size of the map? (screen size can be different)
    FrustumHeight = 600
    FrustumFar = 2.0
    FrustumNear = 0.0
    Position = (300, 300.0, -1.0)

    [render]
    ShowFPS = true

  • Hi @sacked,

    I'll have a look at the archive you posted tomorrow night unless you or @sausage find the issue before then!

  • edited June 2020

    @sacked I just got a blank blue screen with the line commented out or not.

    Are you doing clean/rebuilds?

    I noticed that:

        //Dont draw if its out of range of camera
        if((x >= camStartTilePositionX+3) && (x  <= camEndTilePositionX-3)) {
    

    was never reached.

    Is it possible just to start with a simple paint routine to place the tiles first, then slowly put your camera logic back in?

  • edited June 2020

    On my side, I can see the tiles after fixing two code issues that would prevent me from running the program with VS 2019:

    • First:
        //Draw tiles in view
        int mapLength = mapsize;
        int x, y = 0;
        for (int i=0; i<mapsize; i++) {
            //Dont draw if its out of range of camera
            if((x >= camStartTilePositionX+3) && (x  <= camEndTilePositionX-3)) {
    

    Here x hasn't been initialized before use. Fix:

    int x = 0, y = 0;
    
    • Second:
            //Move camera
            orxConfig_PushSection("MainCamera");
            orxVECTOR camPosition;
            orxConfig_GetVector("Position", &camPosition);
            char array[10];
            sprintf(array, "%f", camPosition.fX);
            orxLOG(array);
    

    Here the sprintf call will result in a buffer overflow and corrupt the stack. Fix:

            char array[16];
            orxString_NPrint(array, sizeof(array) - 1, "%f", camPosition.fX);
            orxLOG(array);
    

    Let us know if you still encounter your issue with the two above fixes. I haven't looked closely into the actual logic itself but I can do a pass later if need be.

  • Wow, so it was the X not being initialized, I'm so confused how it worked depending on the second use of '_map' to "graphic". I really appreciate the help, I am slowly improving my my newbie knowledge of C++.

  • No worries!

    The issue with uninitialized stack-allocated variables like this is that you get whatever value was left on the stack by previous function calls and it's really hard to predict as it depends on many factors, including the compiler version, the compiler flags, etc...

    You might want to modify the level of warnings of the compiler you're using to help catch this kind of issues (or use a static code analysis tool like cppcheck or clang-analyzer).

Sign In or Register to comment.