Good news! Tileson can now handle "collection of images" tile maps. At least, my version does. I've yet to share the changes back. Either way, it means we can get to work.

 Click here to watch the video on Odysee.

Performance Warning

The code for this part is hideously inefficient. For each tile it looks up the image's file name in an unordered map. It has to calculate the string's hash, and look it up in the map. Modern computers are fast enough that you can get away with this. At least, for this 2D challenge.

Nevertheless, it's a lot of overhead compared to storing a pointer directly to the tile's texture. The code isn't production ready, and should you try to do anything ambitious, you may find the overhead gets too much, and you have to optimize the code.

With that out of the way, let's get started...

Parallax Scrolling

The initial code was displaying the foreground correctly, but not the background. That's because parallax calculations weren't implemented yet. Parallax scrolling is a pseudo 3D effect, where distant background layers move slowly as the foreground scrolls past the screen. it's similar to what you see when you look out the side car window.

Displaying Tile Map Worlds Time 0 00 1304

The tile-map contains two values per layer:

  • A parallax origin - controls the reference point in the tile-map for parallax calculations
  • A parallax scale factor - the scale factor that's multiplied by the camera position to determine how much the layer is scrolled

Tiled's documentation explains how to calculate the offsets. Here's the code:

auto pFactorTson = layer.getParallax();
auto pOriginTson = layer.getMap()->getParallaxOrigin();
raylib::Vector2 parallaxFactor(pFactorTson.x, pFactorTson.y);
raylib::Vector2 parallaxOrigin(pOriginTson.x, pOriginTson.y);

auto layerOffsetTson = layer.getOffset();
raylib::Vector2 layerOffset(layerOffsetTson.x, layerOffsetTson.y);
auto cameraOffset = camera.GetWorldToScreen(parallaxOrigin) + 
	raylib::Vector2(-GetScreenWidth() / 2, -GetScreenHeight() / 2);

raylib::Vector2 newOffset = offset + layerOffset -
	cameraOffset * (raylib::Vector2(1.0f, 1.0f) - parallaxFactor);

Notice that the camera offset needs to be adjusted to the middle of the screen. Otherwise the calculations won't match what you see in Tiled. Designing worlds gets very frustrating if the editor and the in-game visuals don't match.

That's parallax scrolling done. On to the next problem...

Tiling the Image Layers

The world still doesn't look right:

Displaying Tile Map Worlds Time 0 05 0115The problem? Image tiling wasn't implemented. Getting this working properly took some trial and error, because RayLib's DrawTextureTiled() function isn't designed for the task.

Here's the final code:

bool repeatX = layer.hasRepeatX();
bool repeatY = layer.hasRepeatY();

if(repeatX || repeatY) {
	auto texWidth = texture->GetWidth();
	auto texHeight = texture->GetHeight();
	raylib::Rectangle sourceRec(
		repeatX ? -offset.x : 0, 
		repeatY ? -offset.y : 0, 
		texWidth, texHeight); 
	raylib::Rectangle destRec(
		repeatX ? 0 : offset.x, 
		repeatY ? 0 : offset.y,
		repeatX ? GetScreenWidth() : texWidth, 
		repeatY ? GetScreenHeight() : texHeight);
	raylib::Vector2 origin = raylib::Vector2(0,0);
	texture->DrawTiled(sourceRec, destRec, origin);
} else {
	texture->Draw(offset, tint);

With that code in place, the in-game scene matches the level as it's displayed by Tiled:

Displaying Tile Map Worlds Time 0 07 0725

Displaying Tile Map Worlds Time 0 07 0511

What's Next

Next time I'll add a tracking camera that will follow Scarfy around the world. I was hoping to do that today, but getting the tile-map displaying properly sucked up too much time.

Click here for part 7.

Source Code

Click here to download the source code.