0
\$\begingroup\$

I'm new to Monogame and I'm having some perfomance issues, I'm loading a matrix of 9 maps (3x3) each with 50x50 tiles of 32x32 pixels each. I store that in a list of maps from 0 to 8 and call them by their filename from [0,0] ( [X,Y] ) up to [2,2] completing the 3x3 matrix in order to know what and where to load each new map.

Whenever my player goes to the border of the "playzone" [1,1] (the middle map) I load the respective three maps that follow, in this case I implemented the right loading, when the player heads to the right, over to [1,2] I need to load [0,3][1,3][2,3] and I unload [0,0][0,1][0,2] but whenever my game does this it stutters for a second. I might have overcomplicated things a bit and I'd love to be pointed in the right direction to optimize this. Thank you!

GameScreen class

namespace Game1.Screens
{
    public class GameplayScreen : GameScreen
    {
        Player player;
        List<Map> maps;
        float chunkWidth;
        Vector2 viewChunk;
        MapConfig mapConfig;
        XmlManager<Map> mapLoader;
        XmlManager<MapConfig> mapConfigLoader;
        bool loadingMaps;

        public GameplayScreen()
        {
            chunkWidth = 0;
            viewChunk = new Vector2(0, 0);
            mapLoader = new XmlManager<Map>();
            mapConfigLoader = new XmlManager<MapConfig>();
            mapConfig = mapConfigLoader.Load("Load/Gameplay/Maps/MapConfig.xml");
            loadingMaps = false;
        }

        public override void LoadContent()
        {
            base.LoadContent();
            XmlManager<Player> playerLoader = new XmlManager<Player>();
            player = playerLoader.Load("Load/Gameplay/Player.xml");
            player.LoadContent();
            LoadMaps(new Vector2(0,0));
            if (maps.Count > 0)
                chunkWidth = maps[0].Layer[0].TiledMap.Row.Count * maps[0].TileDimensions.X;
        }

        public void LoadMaps(Vector2 startingPosition)
        {
            maps = new List<Map>();
            int mapX = 0;
            int mapY = 0;
            int currentMapX = (int)(Math.Floor(player.CurrentPos.X / mapConfig.TileDimensions.X / mapConfig.ColumnCount));
            int currentMapY = (int)(Math.Floor(player.CurrentPos.Y / mapConfig.TileDimensions.Y / mapConfig.RowCount));
            for (int i = 0; i < mapConfig.ChunkMatrixAmount.Y; i++)
            {
                mapY = currentMapY + (i - 1);
                for (int j = 0; j < mapConfig.ChunkMatrixAmount.X; j++)
                {
                    mapX = currentMapX + (j - 1);
                    maps.Add(mapLoader.Load("Load/Gameplay/Maps/Map" + mapX + mapY + ".xml"));
                }
            }
            for (int i = 0; i <= maps.Count - 1; i++)
                maps[i].LoadContent(i, startingPosition);
            loadingMaps = false;
        }

        public override void UnloadContent()
        {
            base.UnloadContent();
            player.UnloadContent();
            foreach (Map map in maps)
                map.UnloadContent();
            maps = new List<Map>();
        }

        public override void Update(GameTime gameTime)
        {
            Vector2 endingMatrixPos = new Vector2(0, 0);
            Vector2 currentMatrixPos = new Vector2
            {   
                X = (float)(Math.Floor(player.CurrentPos.X / mapConfig.TileDimensions.X / mapConfig.ColumnCount)),
                Y = (float)(Math.Floor(player.CurrentPos.Y / mapConfig.TileDimensions.Y / mapConfig.RowCount))
            };

            //Load a new RIGHT and unload LEFT
            if (currentMatrixPos.X >= Math.Floor(maps[5].Layer[0].Image.Position.X / mapConfig.TileDimensions.X / mapConfig.ColumnCount) &&
                maps[8].Layer[0].Image.Position.X != 0 && 
                !loadingMaps)
            {
                loadingMaps = true;
                foreach (Map map in maps)
                    map.UnloadContent();
                endingMatrixPos.X = maps[0].Layer[0].Image.Position.X + mapConfig.TileDimensions.X;
                endingMatrixPos.Y = (maps[0].Layer[0].Image.Position.Y + mapConfig.TileDimensions.Y) - (mapConfig.TileDimensions.Y * mapConfig.RowCount);
                LoadMaps(endingMatrixPos);
            }

            base.Update(gameTime);
            player.Update(gameTime);
            foreach (Map map in maps)
                map.Update(gameTime, ref player);
            if (player.CameraFollow)
            {
                Camera.IsFollowingObject = true;
                Camera.Pos = -player.CurrentPos;
            }
            Camera.Update();
        }

        public override void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, null, null, null, Camera.Transform);
            base.Draw(spriteBatch);
            foreach (Map map in maps)
                map.Draw(spriteBatch, "Underlay");
            player.Draw(spriteBatch);
            foreach (Map map in maps)
                map.Draw(spriteBatch, "Overlay");
            spriteBatch.End();
        }
    }
}

If you need more information I'll provide it when possible! Thank you for your help again!

\$\endgroup\$
  • \$\begingroup\$ Whenever it comes to optimizing you need to ask yourself "what are you optimizing for?". Generally, optimization comes at a cost to something else. Code complexity typically goes up as well as one of the other resources (e.g. memory, speed, load times, etc). To solve these problems code complexity goes up again. My advice: don't optimize until you have a problem worth optimizing. 9 maps of 50x50 tiles doesn't seem like a lot. It might be worth just loading them all into memory up front. \$\endgroup\$ – craftworkgames Sep 7 '17 at 3:09
  • \$\begingroup\$ I load them all into memory, the problem is when I want to load more, in my code I remake the list and remove and rerender all 9 chunks, this causes a half a second stutter on the game when moving from chunk to chunk, theres gotta be something im doing wrong since its just reloading 9 chunks per boundary switch, maybe if I only load the upcoming 3 instead of everything? But still, I have found no way of doing that so far... \$\endgroup\$ – Amirgem Sep 7 '17 at 4:21
  • \$\begingroup\$ Should I load a, say, 30x30 matrix and just set a loding gif on the bottom right when loading new zones? Or is an open world possible with what I'm intending? Maybe having one enormous 15000x15000 map and just loading whats in the camera? \$\endgroup\$ – Amirgem Sep 7 '17 at 4:41
  • \$\begingroup\$ First of all, why do you load in every file every time? Some of them are already loaded and you throw them away. Second, you shouldn't just open a file every time you need to load a map. The memory should be enough to store hundreds of maps (my project can load in 10,000 32*32 maps and still have 9/10 of the memory left (and due to culling, a buttersmooth fps). Third, if you need to load something from the harddrive, then use a separate thread. \$\endgroup\$ – Bálint Sep 7 '17 at 5:10
  • \$\begingroup\$ @Bálint This looks like an answer, maybe you could post it as such :) \$\endgroup\$ – Vaillancourt Sep 7 '17 at 10:14
0
\$\begingroup\$

First, don't load every file in every time. If you load in a 3x3 square when you leave the center square, then you'll throw away 6 or 4 tilemaps based on the player's direction. That's a huge waste.

Second, 3x3 is very small. In fact, if your map isn't literally thousands of tiles in each direction, then the Memory should be able to handle it. Yes, memory is expensive, but if a 2011 PC could handle a big chunk of Skyrim's world at once, then it should be able to work with a tiled map.For example, my MonoGame project could load in 10,000 different tiled maps in a very inefficient format, each having 32 x 32 tiles without even remotely maxing out my 4GBs of memory (that's 10,240,000 integer if you're curious, which is around 40,960,000 bytes or around 39 MB). Just apply some sort of culling so only the visible maps are drawn.

And last, if you need to do any kind of IO do it in a separate thread. There's no reason to make the player wait for

\$\endgroup\$
  • \$\begingroup\$ Thank you, so basically I should be loading pretty much all my maps for the "overworld" in a separate thread and just load what's in the camera? Couldn't that cause my player to go into an unloaded place if he's fast enough and a map hasn't loaded? Or should I make my player wait a bit while a good amount is loaded and then throw him in? And once he's in check if the map he's going to is already loaded? \$\endgroup\$ – Amirgem Sep 7 '17 at 12:13
  • \$\begingroup\$ @Amirgem Basically loading screen while you load the map, then during gameplay you only draw what's visible. The areas should be all loaded into the memory. And when you need to enter a building or different overworld, then use a loading screen again. You only need threading, if you need to load stuff during loadtime. \$\endgroup\$ – Bálint Sep 7 '17 at 13:01
  • \$\begingroup\$ Perfect! Thank you so much! One last question, if changes are made to the map I should write them into their files in the thread or should I wait and set an auto save every x amount of time? \$\endgroup\$ – Amirgem Sep 7 '17 at 14:29
  • \$\begingroup\$ @Amirgem You should save it while you save the whole game \$\endgroup\$ – Bálint Sep 7 '17 at 14:40
  • \$\begingroup\$ Perfect, I will give it a shot, thanks for this Bálint! One step closer to knowing how it all works haha \$\endgroup\$ – Amirgem Sep 7 '17 at 14:48

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.