• 2D shoot 'em up
SDL2 Santa game tutorial 🎅
SDL2 Shooter 3 tutorial
The Legend of Edgar 1.36
SDL2 map editor tutorial [UPDATED]
TBFTSS: The Pandoran War - Amiga OS4 Port
— Simple 2D map editor —
We're almost feature-complete with our basic map editor. There are a few more things that will be nice for us to add, so in this part we'll be expanding the size of the map, to allow for scrolling, and also adding in the ability to "pick" objects and move them around, rather than deleting and recreating them.
Extract the archive, run cmake CMakeLists.txt, followed by make, and then use ./editor04 -edit example to run the editor. You will see a window open, displaying a scene like the one above. The controls from the previous tutorial apply. The view can now be scrolled around, by using the WASD control scheme. The scene will stop scrolling when at the limits of the map. Users can also press 3 on the keyboard to enter "Pick" mode. In this mode, an entity can be clicked on with the left mouse button, and moved to a new position. Clicking the left mouse button again will drop it in the new place. Once you're finished editing, press Space to save the map. If there are errors, these will be logged to the terminal. With your map saved, close the window to exit. As before, you can play the map by using ./editor04 -map example.
Inspecting the code
Once again, the focus of this update is mostly on editor.c (with a single visit to map.c). Adding in our entity picking and map scrolling is a very simple affair, since we've already done much of the ground work. Once again, we'll be snipping away blocks of logic we're not interested in, so we can focus on the changes and additions that are most relevant.
The first thing we've done is update our MODE enum:
We've added a new entry: MODE_PICK. When in this mode, we'll be able to pick entities, and move them around the map.
Now onto initEditor:
We've added in a variable in editor.c called moveTimer. This is a variable that will control the speed of our scrolling, when moving around the map. We'll see this in use when we come to to keyboard controls.
First, let's look at the updates to `logic`:
Just one new line here - we're updating the value of moveTimer, reducing it and limiting the result to 0.
There's nothing more to discuss here, so look at the updates to doMouse:
We've added in an else block to our mode testing. If we're not in MODE_TILES or MODE_ENTIITES, we're going to work with the picking mode. In this mode, we're only supporting the left mouse button; the right button and the scroll wheel will do nothing. When the left mouse button is pressed, we'll test if currentEntity is NULL. If so, this means that we currently don't have an entity picked, so we'll call pickEntity, a new function. We'll come to this one in a moment. Otherwise, we'll set currentEntity to NULL. As we've seen before, currentEntity's `x` and `y` values are updated with the mouse position, so the picked entity will follow the mouse around. Therefore, simply setting currentEntity to NULL will result in the entity being relocated.
Nice and easy. Now for pickEntity:
There are probably no surprises here - when picking an entity, we simply loop through all the entities in the stage, looking for one that intersects the mouse pointer (with help from the `collision` function, camera position included). We then set currentEntity to `e`, the entity in our iteration, and return from the function. This means that if two entities are overlapping, we'll select the first one we find, so some micromanagement might be required by the user when it comes to grabbing the correct object (as with most other editors..!).
Nothing taxing so far! Now onto doKeyboard:
Okay, so there's a few new things happening here, since we're handling the scrolling and the option to enter picking mode. First off, we're testing whether moveTimer has now hit 0, and, if so, we'll start testing our movement keys. We set two variables called `dx` and `dy` to 0, as our control variables. We then test our WASD control scheme. If A or D are pressed, we're going to set `dx` to -MAP_TILE_SIZE or MAP_TILE_SIZE, depending on the key. We next test W and S, and set `dy` to -MAP_TILE_SIZE or MAP_TILE_SIZE, again depending on the key.
Next, we test if `dx` or `dy` is a non-zero value. If so, we're going to add `dx` to Stage's camera's `x` value, limiting the resulting value to the bounds of our map, albeit with some overscan (SCROLL_OVERSCAN). Adding in this overscan allows us to move beyond the bounds of the map by a certain amount (SCROLL_OVERSCAN is defined as MAP_TILE_SIZE * 8). This makes for a more comfortable editing experience, as we don't need to reach into the corners and edges, just to update the tiles at the extremities of our stage. We do the same for `dy`, adding it to Stage's camera's `y` value, and limiting the result to stay within the map bounds. With that done, we set moveTimer to 3, to prevent us from scrolling again too soon; we don't want the screen to scroll too fast (and it'll be faster at high frame rates!).
That's our movement done. Next, we've added in a test for pressing 3 on the keyboard. If so, we're going to clear the key, and set `mode` to MODE_PICK. We're also setting currentEntity to NULL, to ensure we pick a fresh entity (we've also updated the other mode selections with similar logic).
That's all there is to the keyboard handling updates. Now we just need to have a look at the updates to the rendering. These are simply tweaks and won't take long.
Starting with drawTopBar:
We've added an else clause to our existing if-checks when it comes to rendering the position ("Pos"). Now, if currentEntity is NULL, we're rendering a string to indicate an empty or unknown position. This will happen in picking mode when we're not handling an entity. The rest of the function remains the same.
The last function we need to update is drawBottomBar:
The only change we've made here is to test if we're not in picking mode. If we're not, we'll be rendering the currently selected tile / entity rectangle and arrow. When in pick mode, we don't want to do this, as tiles and entity aren't being selected.
That's everything for editor.c. Before we close, let's look at map.c quickly. You will have noticed while scrolling around that when outside the bounds of our map (in the overscan areas) a checkerboard pattern appears. This is done in map.c. We'll quickly look at how we're doing this.
Starting with initMap:
We're loading an AtlasImage here called checkerboard.png, and assigning it to a variable called `checkerboard`.
Now, to see how it's used, let's look at drawMap:
When drawing our tiles, we're first calling isInsideMap before calling blitAtlasImage. Notice the else-if clause that follows. We're testing to see if we're in editor mode (isEditor). If so, we're calling blitAtlasImage, using the `checkboard` image. So, whenever we're in editor mode and move outside the bounds of our map, we'll see that pattern being rendered.
And that's it! Hurrah! Another part down! We've very nearly finished our little editor. A few more nice-to-haves and we'll be done. So, in the final part, we'll add in a mini map, that will display the overall look of the map we're working on, and also update the UI to display a message when we save.
The source code for all parts of this tutorial (including assets) is available for purchase:
It is also available as part of the SDL2 tutorial bundle: