Today I’m going to show how I integrated tilemaps into the engine and used them to show a basic map in game.
But before that I need to highlight a few issues that emerged during the process of implementation.
The first issue was related to what we see on screen. The player could move for now, but I had to implement some kind of camera movement so we can explore different areas of any loaded tilemap.
Because I’m developing the engine simultaneously with the game (meanwhile tinkering with dependent libraries) I usually code things according to my current needs. That is why I didn’t implement any camera class or anything like that.
It seems a bit too complex, especially when all I need is simple camera movement. To be honest I don’t really find a place for it, at least for now.
What I did instead, was introducing the
ViewPort - as in, the currently visible part of the world on screen - as an instance of
Bramble.Core.Rect structure, accessible from a
It technically is what the name states - it’s a rectangle of given size placed in some arbitrary position.
So, how would we move the camera now in this kind of implementation?
Well, it’s quite simple - we just have to change the ViewPort so the rectangle is placed in different position.
Let’s take a look how operating on the viewport looks like.
Here we have 2 methods. One that centers the viewport to a given position (for example a player, so we center the camera on him), which changes our viewport accordingly.
It’s worth to note that the rectangle’s position is placed in upper left corner so we have to subtract half of the terminal size to get it properly.
The other method is used to move our viewport relatively.
This kind of method is useful, when we move our player by X units, and we want the camera to keep on being centered on the player.
So now inside our
PlayerMovementHandler component, right after applying the movement vector we also move our viewport.
But there is another issue related to this.
ViewPort units vs World units
Terminal accepts viewport units. Which are restricted to be non-negative points within the viewport, and if we now keep moving our player, his position’s X or Y may be bigger than the one restricted by terminal size.
Which means that current
Render method of
Player class needs to be changed.
As you can see we do not take the game’s viewport into the account. The method would try to render the player’s world position as viewport position.
To fix that, we simply need to subtract the
ViewPortOffset from the player’s position, like so:
Excellent, now our rendering works properly!
But this example shows a different problem, related to the current architecture.
We would have to act the same way in all other
Render methods of all other game objects. And that’s definitely not convenient. It smells, and violates the DRY principle.
But it’s not the topic of this post so let’s leave it for now like it is and pretend everything is good.
Having resolved the issues at hand, let’s move on to the actual tilemaps integration.
I have decided to leave the
Praedium.Engine namespace untouched by the external libraries - such as the
TiledSharp which I used to parse the .tmx file structure.
Instead, the engine now contains it’s own abstractions used to represent such data structures, and the first one is the
Level is basically a scene that contains multiple layers of tiles, ordered by their Z-Index.
Rendering the level means rendering every tile from every tile layer one by one. Here we handle the rendering and complying with the game’s viewport offset.
The next abstraction is the
TileLayer. TileLayer is a structure that has a certain Z-Index, by which it’s rendering order is decided and it also contains a collection of tiles.
The final and most basic structure is the
Tile. A tile is represented by a
Character structure, a position in world space units and - for now - a simple boolean flag to handle collisions in order to restrict player movement.
If you don’t remember, the
Character structure is a set of a character code in given encoding, a foreground and background, which is all we need to render it on screen.
Adding level support to
To allow our game to handle the levels, I added a
Level property that defines the current level and a
LoadLevel method that handles callbacks for the levels and takes care of cleaning up the entities assigned by current level.
Other than that I had to call
Render on the level before rendering the game objects.
Having defined all these classes and structures let’s look onto an example.
The example will be a tilemap representing the farm. It’s not a finished map but it’s good enough for a sandbox.
So, how do we use those classes from the engine?
Well, it’s quite simple, all we need to do is inherit from the
Level class and within the appropriate callback (
OnLoad) we load our map from resources.
The code - thanks to
TiledSharp library is quite straightforward.
Firstly, we load the map from the file, which we can access from resources that are copied over to the output directory.
Secondly, we get the tileset which contains all meta-information for specific tiles and then we iterate through every layer to create a new one.
GetTile helper method is where we process all information needed to create instances of the
After parsing every tile of every layer we take care of tilemap-defined objects.
For now there is only one - a Spawn, which is the place where our character should be instantiated at.
Having created our player, and set his position to the one defined by spawn, we can center our viewport to focus our player.
After implementing our class for specific level we then need to make changes in the application code that sets up our game inside
Because of implementing the
Level class we do not need to manually instantiate level-specific game objects such as the
Player. The level itself takes care of that now.
Previously mentioned GIF capture of working example.
Wrapping it up
There is still a lot of things I need to cover such as level transitions, keeping certain objects not destroyed between transitioning and so on but it’s a starting point and it works.
I have also mentioned the rendering issue, which I’ll be tackling in the next post by implementing components that handle strictly the rendering code.
And that’s basically it for this post. If you have any suggestions, feel free to drop a comment below.
See you next time!