Advertisement

Level loading causing lag/stutter

Started by August 16, 2016 08:05 PM
7 comments, last by Servant of the Lord 8 years, 3 months ago

So I am loading a tiled map file using TinyXML but the only problem is, if I load the files in update the map is unplayable due to lag, yet if I wanted multiple levels, this is the only possible way to do it, I can't load the files in setup because it won't update to the new map. Any ideas on how to successfully do this?

Level.cpp


bool Level::LoadFromFile(std::string filename)
{
    TiXmlDocument levelFile(filename.c_str());

    if (!levelFile.LoadFile())
    {
        std::cout << "Loading level \"" << filename << "\" failed." << std::endl;
        return false;
    }

    //Map element. This is the root element for the whole file.
    TiXmlElement *map;
    map = levelFile.FirstChildElement("map");

    //Set up misc map properties.
    width = atoi(map->Attribute("width"));
    height = atoi(map->Attribute("height"));
    tileWidth = atoi(map->Attribute("tilewidth"));
    tileHeight = atoi(map->Attribute("tileheight"));

    //Tileset stuff
    TiXmlElement *tilesetElement;
    tilesetElement = map->FirstChildElement("tileset");
    firstTileID = atoi(tilesetElement->Attribute("firstgid"));

    //Tileset image
    TiXmlElement *image;
    image = tilesetElement->FirstChildElement("image");
    std::string imagepath = image->Attribute("source");

    if (!tilesetImage.LoadFromFile(imagepath))//Load the tileset image
    {
        std::cout << "Failed to load tile sheet." << std::endl;
        return false;
    }

    tilesetImage.CreateMaskFromColor(sf::Color(255, 0, 255));
    tilesetImage.SetSmooth(false);

    //Columns and rows (of tileset image)
    int columns = tilesetImage.GetWidth() / tileWidth;
    int rows = tilesetImage.GetHeight() / tileHeight;

    std::vector <sf::Rect<int> > subRects;//container of subrects (to divide the tilesheet image up)

    //tiles/subrects are counted from 0, left to right, top to bottom
    for (int y = 0; y < rows; y++)
    {
        for (int x = 0; x < columns; x++)
        {
            sf::Rect <int> rect;
            rect.Top = y * tileHeight;
            rect.Bottom = y * tileHeight + tileHeight;
            rect.Left = x * tileWidth;
            rect.Right = x * tileWidth + tileWidth;
            subRects.push_back(rect);
        }
    }

    //Layers
    TiXmlElement *layerElement;
    layerElement = map->FirstChildElement("layer");
    while (layerElement)
    {
        Layer layer;
        if (layerElement->Attribute("opacity") != NULL)//check if opacity attribute exists
        {
            float opacity = strtod(layerElement->Attribute("opacity"), NULL);//convert the (string) opacity element to float
            layer.opacity = 255 * opacity;
        }
        else
        {
            layer.opacity = 255;//if the attribute doesnt exist, default to full opacity
        }

        //Tiles
        TiXmlElement *layerDataElement;
        layerDataElement = layerElement->FirstChildElement("data");

        if (layerDataElement == NULL)
        {
            std::cout << "Bad map. No layer information found." << std::endl;
        }

        TiXmlElement *tileElement;
        tileElement = layerDataElement->FirstChildElement("tile");

        if (tileElement == NULL)
        {
            std::cout << "Bad map. No tile information found." << std::endl;
            return false;
        }

        int x = 0;
        int y = 0;

        while (tileElement)
        {
            int tileGID = atoi(tileElement->Attribute("gid"));
            int subRectToUse = tileGID - firstTileID;//Work out the subrect ID to 'chop up' the tilesheet image.
            if (subRectToUse >= 0)//we only need to (and only can) create a sprite/tile if there is one to display
            {
                sf::Sprite sprite;//sprite for the tile
                sprite.SetImage(tilesetImage);
                sprite.SetSubRect(subRects[subRectToUse]);
                sprite.SetPosition(x * tileWidth, y * tileHeight);

                sprite.SetColor(sf::Color(255, 255, 255, layer.opacity));//Set opacity of the tile.

                //add tile to layer
                layer.tiles.push_back(sprite);
            }

            tileElement = tileElement->NextSiblingElement("tile");

            //increment x, y
            x++;
            if (x >= width)//if x has "hit" the end (right) of the map, reset it to the start (left)
            {
                x = 0;
                y++;
                if (y >= height)
                {
                    y = 0;
                }
            }
        }

        layers.push_back(layer);

        layerElement = layerElement->NextSiblingElement("layer");
    }

    //Objects
    TiXmlElement *objectGroupElement;
    if (map->FirstChildElement("objectgroup") != NULL)//Check that there is atleast one object layer
    {
        objectGroupElement = map->FirstChildElement("objectgroup");
        while (objectGroupElement)//loop through object layers
        {
            TiXmlElement *objectElement;
            objectElement = objectGroupElement->FirstChildElement("object");
            while (objectElement)//loop through objects
            {
                std::string objectType;
                if (objectElement->Attribute("type") != NULL)
                {
                    objectType = objectElement->Attribute("type");
                }
                std::string objectName;
                if (objectElement->Attribute("name") != NULL)
                {
                    objectName = objectElement->Attribute("name");
                }
                int x = atoi(objectElement->Attribute("x"));
                int y = atoi(objectElement->Attribute("y"));
                int width = atoi(objectElement->Attribute("width"));
                int height = atoi(objectElement->Attribute("height"));

                Object object;
                object.name = objectName;
                object.type = objectType;

                sf::Rect <int> objectRect;
                objectRect.Top = y;
                objectRect.Left = x;
                objectRect.Bottom = y + height;
                objectRect.Right = x + width;

                if (objectType == "solid")
                {
                    solidObjects.push_back(objectRect);
                }

                object.rect = objectRect;

                TiXmlElement *properties;
                properties = objectElement->FirstChildElement("properties");
                if (properties != NULL)
                {
                    TiXmlElement *prop;
                    prop = properties->FirstChildElement("property");
                    if (prop != NULL)
                    {
                        while(prop)
                        {
                            std::string propertyName = prop->Attribute("name");
                            std::string propertyValue = prop->Attribute("value");

                            object.properties[propertyName] = propertyValue;

                            prop = prop->NextSiblingElement("property");
                        }
                    }
                }

                objects.push_back(object);

                objectElement = objectElement->NextSiblingElement("object");
            }
            objectGroupElement = objectGroupElement->NextSiblingElement("objectgroup");
        }
    }
    else
    {
        std::cout << "No object layers found..." << std::endl;
    }

    return true;
}

Without more information on what kind of game it is, and how the level data is used, it's hard to answer, but I'll take a stab.

If you have to load levels on the fly (ie, you can't have a "loading" screen) where you're still actively controlling your character, then you can look at loading them in a separate worker thread. However, you have to do this well before the user will need the new level data to ensure you don't try and display the level before it's completed loading (ie, the user is a screen away from needing the level data). Once you've loaded the level, you'll want to notify the main thread it's completed loading and it can be used.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

Advertisement

Without more information on what kind of game it is, and how the level data is used, it's hard to answer, but I'll take a stab.

If you have to load levels on the fly (ie, you can't have a "loading" screen) where you're still actively controlling your character, then you can look at loading them in a separate worker thread. However, you have to do this well before the user will need the new level data to ensure you don't try and display the level before it's completed loading (ie, the user is a screen away from needing the level data). Once you've loaded the level, you'll want to notify the main thread it's completed loading and it can be used.

I'll try to be more specific, and yes most likely I will have a loading screen(I also tried to load the maps using the loading screen state but that doesn't allow my collision to work). So after level 1 is finished, I would like to then load level 2 but because the setup already calls level 1 to begin with I cant set the next level as its not updating, wouldn't I need to handle the level changing in the update rather than the setup function? However doing this method in the Update function lags my game as it is constantly loading the LoadFromFile(std::string filename) function over and over.

This isn't SFML related, so SFML shouldn't be in the title of the thread. It's level-loading related, but more specifically, where you load your level.

You should not load your level every frame, you should only load your level when you reach a new level (unless you are streaming a large level, in which case the same logic applies at a different scale: You shouldn't load the same level segment every frame, you should only load a new level segment when you reach the new level segment).

Ignoring how you load your level, can you post the code that shows *where* you load you level? That is to say, can you post the entire function that calls "LoadFromFile()" and the entire function that calls that function, and the entire function that calls that function, all the way back to main()?

I need to see the complete code for the entire callstack of LoadFromFile(), because that's almost certainly where your problem is.

I heavily disagree that you should look into multithreading your program - that'd be way too much over-complication at your level.

if I load the files in update the map is unplayable due to lag, yet if I wanted multiple levels, this is the only possible way to do it, I can't load the files in setup because it won't update to the new map.

If you can't load a new map in setup, then your setup is in the wrong place. Perhaps you need two setup stages, one at the start of the application for the shared data, and one for each time you load a level, just for the data relevant to that level.

I'll try to be more specific, and yes most likely I will have a loading screen(I also tried to load the maps using the loading screen state but that doesn't allow my collision to work). So after level 1 is finished, I would like to then load level 2 but because the setup already calls level 1 to begin with I cant set the next level as its not updating, wouldn't I need to handle the level changing in the update rather than the setup function? However doing this method in the Update function lags my game as it is constantly loading the LoadFromFile(std::string filename) function over and over.

I was under the impression you were trying to load on the fly. Since it's at an obvious stopping point, I don't understand why you can't move to a loading state, throw up a "loading level" screen and load everything you need right then. When the level is loaded, you can go back to the playing state and start on the new level. Multi-threading is definitely not needed in this situation.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

Advertisement

Just wanted to feedback to this thread that I managed to fix my issue, thanks everyone for the help as it allowed me to tackle my problem from another angle. What I decided to do was to load my level file from the load screen state and then I changed how my collision worked. So now it all works without lag. Many thanks everyone for the help!

marked for tracking

marked for tracking

ef829aa834.png

This topic is closed to new replies.

Advertisement