darcmagik

Tilemap rendering 2D RPG

Recommended Posts

Good evening all, so it has been a while since I've posted but I am back to working on some stuff now that I have time.  I'm working out some issues with a game I'm working on which is an old school 2D top down style JRPG.  I'm working out the code for rendering the map to the screen which I know is something that has been done a million times and believe me I have read through close to hundreds of articles and tried so many changes to my code to find out what is going on and what is causing the error I'm having and I still can't track down what I'm doing wrong so I have decided that it is time for a fresh set of eyes to look at my code.

Let me set the stage for what I'm doing I have my map setup so that I can have multiple layers to the map, to give the world some depth, and I am using a Vector to hold my array of int values that are used to decide the tiles to render to the screen. 

The issue I am having is somewhere between my rendering and my update code if I try to move the map at all on the screen I immediately get a Vector index out of bounds error and I can't track down what I'm doing wrong.  I know it is probably something simple but I can't see it.

Here is my update method:

void Layer::UpdateLayer(float deltaTime)
{
	// update the draw position based on scroll speed, velocity and the change in time from frame to frame
	m_currentDrawPosition.x += (m_scrollSpeed.x * m_velocity.x) * deltaTime;
	m_currentDrawPosition.y += (m_scrollSpeed.y * m_velocity.y) * deltaTime;

	// check to see that the draw position isn't less than 0
	if (m_currentDrawPosition.x < 0)
	{
		m_currentDrawPosition.x = 0;
	}

	// check to see that the draw position isn't greater than the size of the map
	if ( (m_mapDimensions.x >= RenderManager::GetInstance().GetGameWidth()) && 
		m_currentDrawPosition.x > m_mapDimensions.x - RenderManager::GetInstance().GetGameWidth())
	{
		m_currentDrawPosition.x = m_mapDimensions.x - RenderManager::GetInstance().GetGameWidth();
	}

	// check to see that the draw position isn't less than 0
	if (m_currentDrawPosition.y < 0)
	{
		m_currentDrawPosition.y = 0;
	}

	// check to see that the draw position isn't greater than the size of the map
	if ( (m_mapDimensions.y >= RenderManager::GetInstance().GetGameHeight()) &&
		m_currentDrawPosition.y > m_mapDimensions.y - RenderManager::GetInstance().GetGameHeight())
	{
		m_currentDrawPosition.y = m_mapDimensions.y - RenderManager::GetInstance().GetGameHeight();
	}
}

 

Here is my Render Method:

void Layer::RenderLayer()
{
	int screenRows = 0;		// int that represents the number of rows on the screen
	int screenColumns = 0;	// int that represents the number of columns on the screen
	int tileX = 0;			// int that represents x position on screen for a tile
	int tileY = 0;			// int that represents y position on screen for a tile
	int tileValue = 0;		// value of tile in tilemap at the position
	int textureColumns = 0;	// how many columns in the texture

							// only calculate and do rendering if this is a graphical layer
	if (m_layerType == graphicLayer)
	{
		// calculate certain values for rendering
		screenRows = RenderManager::GetInstance().GetGameHeight() / (int)(m_tileDimensions.y);
		screenColumns = RenderManager::GetInstance().GetGameWidth() / (int)(m_tileDimensions.x);

		if (screenColumns > m_layerColumns)
		{
			screenColumns = m_layerColumns;
		}

		if (screenRows > m_layerRows)
		{
			screenRows = m_layerRows;
		}

		tileX = (int)(m_currentDrawPosition.x / m_tileDimensions.x);
		tileY = (int)(m_currentDrawPosition.y / m_tileDimensions.y);

		textureColumns = (int)(RenderManager::GetInstance().GetTextureDesc(m_textureName).Width / m_tileDimensions.x);

		// traverse through the number of rows
		for (int row = 0; row < m_layerRows; row++)
		{
			// traverse through the number of columns
			for (int column = 0; column < m_layerColumns; column++)
			{
				// grab value from tilemap
				tileValue = m_tileMap[((tileY + row) * m_layerColumns + (tileX + column))];

				// calculate the source rectangle
				m_sourceRectangle.left = (long)((tileValue % textureColumns) * m_tileDimensions.x);
				m_sourceRectangle.top = (long)((tileValue / textureColumns) * m_tileDimensions.y);
				m_sourceRectangle.right = (long)(m_sourceRectangle.left + m_tileDimensions.x);
				m_sourceRectangle.bottom = (long)(m_sourceRectangle.top + m_tileDimensions.y);

				// calculate the destination rectangle
				m_destinationRectangle.left = (long)(column * m_tileDimensions.x);
				m_destinationRectangle.top = (long)(row * m_tileDimensions.y);
				m_destinationRectangle.right = (long)(m_destinationRectangle.left + m_tileDimensions.x);
				m_destinationRectangle.bottom = (long)(m_destinationRectangle.top + m_tileDimensions.y);

				// draw the tile to the screen at the correct position
				RenderManager::GetInstance().RenderObject(m_textureName, m_sourceRectangle, m_destinationRectangle);
			}
		}
	}
}

So although I'm not looking for somebody to write my code for me I'm looking for some help in figuring out what I'm doing wrong that is causing the error's I'm getting.  Please help me with this if you can.  Also let me know if you need any more info on how I'm doing things.

Share this post


Link to post
Share on other sites

If you use the debugger while that exception occurs you should look at the values you pass as index into your m_tileMap.

 

You loop over the full(!) map, but apply tileX,tileY offset as well. Obviously that will get out of bounds once tileX or tileY are not zero.

Share this post


Link to post
Share on other sites

As Endurion mentioned, the problem lies in your index:

On 11/13/2017 at 3:42 AM, darcmagik said:

// grab value from tilemap

tileValue = m_tileMap[((tileY + row) * m_layerColumns + (tileX + column))];

Once you move your tileX or tileY - it goes out of bounds.
You should maybe instead use something like:

for (int y = 0; y < m_layerRows; y++)
{
     for (int x = 0; x < m_layerColumns; x++)
	 {
			tileValue = m_tileMap[(y * m_layerColumns) + x];
			...
	 }
}

And use another method to translate your map, maybe even something like:

m_sourceRectangle.left = (int)(m_currentDrawPosition.x + (long)((tileValue % textureColumns) * m_tileDimensions.x);

Im not sure what you are using to do your rendering, but if its opengl or directx or using shaders, maybe just use the modelview to translate the whole map.

Id get a whole map working and moving as you want first, and then maybe think of optimizing it to only display the parts you want when you need to, it may never come up :)
 

Also apologizes if i just misunderstood what you wanted, i honestly just had a min to scan over the code

Share this post


Link to post
Share on other sites

Hmm ok so I looked at what you both were talking about and I figured out how to fix the issue somewhat... so it is no longer going out of bounds and it does scroll all around the map like it is supposed to which is great but I do have one problem left to solve which is now when it is scrolling up down left and right I get some weird abnormality which I think has to do with my sourceRectangle and DestinationRectangle code.  I have included a picture to show what it is doing:  I realize again this is probably something simple that I'm missing and I am going to keep looking at my own code to see if I can find it but if anybody has any ideas as to what is causing this please help.  I'm currently working off of very little sleep so any help is appreciated on this project.

Also somebody asked me about how I am rendering things just an FYI that might help in understanding I am using DirectX 11 but I'm using the DirectXTK in order to make doing 2D rendering easier which from what I can see there is no translate option for 2D in DirectXTK so...  And as a reminder this is how my look that render's the map to the screen looks right now.

 

		// traverse through the number of rows
		for (int row = 0; row < screenRows; row++)
		{
			// traverse through the number of columns
			for (int column = 0; column < screenColumns; column++)
			{
				// grab value from tilemap
				tileValue = m_tileMap[((tileY + row) * m_layerColumns + (tileX + column))];
				//tileValue = m_tileMap[(column * m_layerColumns) + row];

				// calculate the source rectangle
				//m_sourceRectangle.left = (long)((tileValue % textureColumns) * m_tileDimensions.x);
				m_sourceRectangle.left = (long)(m_currentDrawPosition.x + (long)((tileValue % textureColumns) * m_tileDimensions.x));
				m_sourceRectangle.top = (long)(m_currentDrawPosition.y + (tileValue / textureColumns) * m_tileDimensions.y);
				m_sourceRectangle.right = (long)(m_sourceRectangle.left + m_tileDimensions.x);
				m_sourceRectangle.bottom = (long)(m_sourceRectangle.top + m_tileDimensions.y);

				// calculate the destination rectangle
				m_destinationRectangle.left = (long)(column * m_tileDimensions.x);
				m_destinationRectangle.top = (long)(row * m_tileDimensions.y);
				m_destinationRectangle.right = (long)(m_destinationRectangle.left + m_tileDimensions.x);
				m_destinationRectangle.bottom = (long)(m_destinationRectangle.top + m_tileDimensions.y);

				// draw the tile to the screen at the correct position
				RenderManager::GetInstance().RenderObject(m_textureName, m_sourceRectangle, m_destinationRectangle);
			}
		}

 

WeirdIssue.thumb.png.d76966ef4f5a7d194a035a81e8b372e8.png

Share this post


Link to post
Share on other sites

You should assign your indexer to an int, and step through it to ensure it is getting what you expect, there isint much information in what you showed - but i suspect its your m_layerColumns

2 hours ago, darcmagik said:

m_tileMap[((tileY + row) * m_layerColumns + (tileX + column))];

id suspect that this is accessing the wrong value, depending on what its value actually is ( isint shown )

Share this post


Link to post
Share on other sites
4 minutes ago, McGrane said:

You should assign your indexer to an int, and step through it to ensure it is getting what you expect, there isint much information in what you showed - but i suspect its your m_layerColumns

id suspect that this is accessing the wrong value, depending on what its value actually is ( isint shown )

Hey thanks for responding here is the code that I use to define the layerColumns, and layerRows:

// calculate certain values for rendering
screenRows = (RenderManager::GetInstance().GetGameHeight() / (int)(m_tileDimensions.y) + 1);
screenColumns = (RenderManager::GetInstance().GetGameWidth() / (int)(m_tileDimensions.x) + 1);

Yes I will look at what you mentioned.

Share this post


Link to post
Share on other sites
3 hours ago, darcmagik said:

Now to speed up the scrolling speed...

Your approach of making this many rendering this many tiny draw calls may be a performance issue as your game grows.

Looks you've got 16x30, or 480 render calls for the ground. You'll likely have another 200 or so when you add items to your world, plus another bunch when you add UI.  That is a potential problem.

The details are hidden inside the RenderObject() function, but unless you're batching the calls, making instances of the tiles, rendering tiles as a polygon soup from a texture atlas, or otherwise taking steps to minimize draw calls, you'll quite likely run into performance problems with command buffer sizes, context switches, and other problems related to sending a large number of instructions with a small amount of data.  

Today's graphics cards are designed around a large number of polygons rendered as batches with a small number of calls.

Share this post


Link to post
Share on other sites

I see what you are talking about with the rendering I'm not sure how to fix this yet I'm going to do some research.  The challenge is with using Direct X 11 + DirectXTK the way DirectXTK does rendering for 2D is using an implementation of a sprite batch which I currently have things setup so that every system in my engine that is currently active that needs to render does a render call between the SpriteBatch Begin call and the SpriteBatch End call.

But I have been trying to make this DirectXTK stuff work for a while now maybe its time to consider another way of rendering.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now