After creating the base of my new game engine (see my previous news post on procedural generation), it was time to start to bring the game world to life. This meant taking the data I had generated for my dungeon and turning it into instances in a room so that the player actually had something to interact with. So, today I'll be explaining how I went about this, as well as talking about some of the challenges I faced and the techniques I used for optimising things.
Let There Be Walls!
At this point in development I had my dungeon generator creating a DS grid, the cells of which were being flagged with different values that I'd assigned to macros - currently g_wall (0), g_empty (-4), g_door (1) and g_corridor (2) (using macros in this way helps keep "magic numbers" out the code base and makes the code clearer to read, so don't forget to use them!). This setup meant that all I really have to do is a couple of "for" loops to go through the grid a cell at a time checking it's grid value and then creating an instance relative to the grid position that's been flagged.
After adding in some placeholder graphics, I was getting something like this:
So far so, good!
Adding in the doors was also easy to do using the same loop that parses the DS grid, but what about the player? And Obviously, I'll need some kind of exit too, otherwise how will the game progress?
More Random Stuff
Given that my dungeon generator scripts store the number of "rooms" created, and given that I also have an array of values corresponding to the actual dimensions of each "room", I can pick any one of them randomly and then place the player at a random position within them... and if I store the player room number, I can repeat the process for the exit, only making sure to not choose the player room. This is actually a pretty satisfactory solution, as due to the way the splits are created by the BSP scripts, you never know which room area is going to be beside which other room area, so it doesn't really matter if the start and end points are randomised.
So, I whipped up a script to do just that and on testing, it worked really well - I didn't even have to worry about the exit room being available to the player, as I'd already done the pathfinding between room areas when I created the DS grid, so I know there is a clear path between them. Note that, as before for the walls, I didn't actually create the player straight away, but rather flagged a grid square in the DS map as being the player or the exit (more macro values!), and then added another case to the "switch" I had in the object create script. Keeping the whole dungeon creation code in a "virtual" environment (ie: the DS grid) meant that I could later go back and tweak things easily, as no instances would be involved, and it also meant I could further tweak things when the instance itself is created, especially as I'd be creating them all at once.
The final thing I wanted to add in at this point were some enemy placeholders, as well as placeholders for a few other typical ARPG items - chests, potions and gold - more so I could get an idea of how it'll look than anything else, as I wasn't really ready to start working on those details. So, once again I went back to the script that flagged the player/exit cells, and added another set of conditions:
- if the room area is not the player room, flag some grid cells as enemies randomly within the area
- if the grid cell is marked as a corridor, spawn some gold
- if the grid cell is empty and has a wall on at least one side, then there is a 1/20 chance of spawning a chest or a potion
Some pretty simple rules to start with, but then, I'm only really playing with the generator code at this point to get an idea of it's potential, but nonetheless, I'm getting some pretty cool results:
At this point I felt that I needed a rest from the engine itself, so I added some basic movement and collisions to the player and set the view to follow it around too. All worked well, and I felt that I could call this section of my development plan "complete" and move on to something else.
Time For A Break
Whenever I start to get bored by a peice of code, or have an issue that I can't resolve or am not happy with, I take that as a cue to start working on something else and then come back to what I was doing later. In this case, I was actually happy with the base engine, but felt that tinkering with it further at this point would not be beneficial - after all, I need a context for that tinkering and so far the game is still a bit of a "cloud concept" that needs refining. It's time to take a break from it and to start working on the graphics then!
As a one-man-dev-team I have to do everything for myself (which is one of the main reasons I love game dev so much!), or out-source and pay for someone else to do it. In this case, since I wanted a "low-res" look to the game, I felt confident that I could produce the required art myself and so set about creating some base graphics for the walls and the floor. These would form the backdrop to the action, and so in a large part they would be responsible for the ambience and feel that the final game has.
At first I started working on a 47 tile tileset for the walls - 47 is the number of unique tiles required to cover all eventual combinations of walls. I started with this method because I have an old script that I've been using since the start to "autowall" levels. I created it for my Gauntlet Revisited game and I haven't touched it since (it's now on the Marketplace too), although it's definitely a more brute-force than elegant solution since it uses a massive set of nested "if... then... else" to do the job... but if ain't broke, don't fix it and the script works so well that I have no intention of changing anything.
However, as I looked back at my Gauntlet game, I realised that this was an incredible waste of time! I had imagined for my game at least 4 or 5 different areas, so I'd have to sit down and create a whopping 235 tiles at least... which is when I had my revelation. I only needed one tile, and then 47 overlay tiles to give the relief effect. So, I fired up Photoshop and set about creating a light/shadow map for each tile, then created a couple of wall tiles with simple (but different) designs on them for different levels.
Note that I made the images grey-scale. With this game, I'm going for maximum variety and maximum re-usability since the game is going to be procedural. Grey-scale images like these will permit me to colour them "on the fly" when they are added dynamically to the room, which means I can add variety at very little cost. The floor tiles I created the same way too - 8x8, grey-scale tiles - and I planned to use the background_blend array to colour them. With that done, it was back to GameMaker:Studio...
Making It Beautiful
After adding the tiles to GameMaker and importing my Autowall script, it was time to have the walls add the tiles and colour them. In all my games I have got into the habit of creating a number of "init" scripts that initialise all the global variables the game will require, so I opened that and created a couple of globals that I knew I'd need: global.Level and global.Colour. I wanted the blend colour to be different per level, and decided at this time to have it change every five levels, so levels 1 - 5 would be blue, levels 6 - 10 would be green, etc.... which was easily achieved using div and a switch.
Next I went into the BSP controller object and added an alarm call of one step to the Create Event, after all the BSP code has been run to generate the maze. In the alarm I added my autowall script to get the overlay sprite (the light/shadows) then had the wall objects spawn a basic wall tile, colour it and then add the overlay tile on top, giving an effect like this:
If you look carefully at the image, you can see that the top left corner has a floor tile which is a different colour to others. This was another idea I had to make the whole game room have a bit more variety at practically no cost... My BSP scripts create an array with the room area positions in it, so I figured "why not use that to add tiles only on the internal rooms?". This meant that room areas could have coloured floors with different tiles, while all the rest of the room (basically the corridors at this point) would have the "base" background tile.
However, something wasn't right... the graphics that I licensed for using as the character and enemy sprites are side on, and this was most definitely a top down look! I needed to add perspective of some kind to the walls, otherwise I'd have mismatched graphics or I'd have to source out some proper top-down fantasy sprites (which at such a low resolution wasn't going to be easy). the solution was something that I'd done previously in my game Pixoban - slide all the wall tiles UP a few pixels and add "fronts" to them.
The front tile is another grey-scale image that is 8x6 pixels in size, and I created a number of "ornaments" that can be placed (randomly, of course) over the top of them to give variety. I also took a moment to create a door place-holder sprite (which you can see in the image above) so I could get a better idea of how things would look in the final game. After testing these changes under different circumstances and with different colours, I was exceptionally pleased with the result and though that it would work well with the side-on sprites I had for the characters.
There was one task I had left to do before moving on to something else - optimisation! You may not think that at this early stage in development there is much that can be done, but I like to revise what I'm doing when I reach any point that I think a section is "finished" and see if indeed I can optimise and improve things a bit more. I like the "modular" approach to things, and try to compartmentalise my code as much as possible as it makes re-factoring easier, it makes bug finding easier, and it also means that when you are finished with the current section you rarely have to go back and revise it again.
The obvious bottleneck in my current setup was the sheer quantity of walls that were being created. In any one room I could have upwards of 700 wall instances which essentially will be doing nothing in the game except taking up step time. I had already made them invisible, which removes any GPU time, but they were still being needlessly processed. I had to optimise this somehow! I wasn't sure what type of movement and AI I was going to use, so I didn't want to remove them completely yet (although it was a possibility for the future), but I had to cut down on their number. The solution was a simple one: have each wall check around on the horizontal or vertical for other walls and then if it finds any, destroy itself and make the other stretch to fit the "gap" it leaves.
The gif above is from a slightly later iteration of the project, but it illustrates just how the script optimises the walls - the red lines indicate the bounding box of all the wall objects in the room - and you can imagine the saving this was giving me. Going from 700+ instances in some rooms down to maybe 120 was a big optimisation (note that, as with the Autowall script, this one is also available on the Marketplace).
The Game Is Afoot!
My little artistic break had paid dividends and I was exceptionally happy with the results. I now had a system to generate walls and floors based on the current level - giving them colour and extra adornments to spice things up a a bit - and had also optimised things quite nicely. Which means it was time to get back to coding! My next task was to add in the Player and decide on controls and movement, which is the subject of my next news post.