Skip to main content

Destructible levels

Ok, so I've been thinking over the idea of destructible levels in AGD for a while now. Basically the idea is to have a more advanced equivalent of the 'fodder' block - this kind of block can be destroyed by the player in game. The problem with this however, is that these blocks are restored when the player returns to a room. This means we can't create levels for players to open up themselves. What we want here is the ability to preserve the blocks that have been removed so that when you exit a room and then come back in, they will not be restored - they will remain the way that the player left them. This opens up the possibility of areas of game being inaccessible to the player until they dig or blast their way through.

So, what are the considerations? The first and most obvious consideration is that we will need to store the changes made to the rooms somewhere. This could take up a lot of space quite quickly if we don't plan it properly. At the same time, it will need to be done invisibly and quickly, since the player should be able to move smoothly between rooms without being aware of whatever code is being run in the background. Finally, as with all AGD work I do, it should be accessible. It should be something that is relatively easy for others to implement, should they wish to include it in a game.

One option would be to update the AGD level data itself so that the levels are drawn with the changes made by the player. The advantage of this would be that we would not need additional storage space to make the changes, and the rooms would stay as the player left them. However, this in itself presents another question - any changes made to the levels would need to be restored on game restart. So as well as how to store the changes in game, we also need to consider how to undo those changes on game exit.

In theory, if we use the 'fodder' block type, and replace it with a 'special' block of a specific number, we could restore the levels on game start by simply scanning for that block type and swapping it out for a fodder block. However, there are sticking points with this approach.

Firstly, we would obviously want to have fodder blocks of different patterns and colours, and there would be no way for AGD to know which block had been removed. Secondly,  AGD screens are not all the same size in terms of memory use. Making changes to these screens would mean that the space taken up by each screen would increase and decrease depending on the amount of blocks that were removed by the player. And since AGD works like a layer cake, this would mean that any code or data that is above the screen data would also have to move. This data includes the scripts, and you definitely don't want to be moving those around midgame. It's a problem I've had when looking at sprite banking, and not one I want to tackle unnecessarily.

So I think updating or editing the actual screen data is a non-starter. The next option then, would be to reserve an area of memory, and  store the changes made there. This would mean that the 'original' state of the levels themselves would remain in AGD, and would be restored on game start automatically. Should we go this route, we would need to think of a way to update the levels before they were made visible to the player, i.e update them before they are shown, but we'll come to that later.

So, having decided then on storing the level data somewhere, we need to think about the most efficient way this data could be stored. We could record each change as it happens, but on the face of it this wouldn't be especially efficient, and is probably unnecessary. The best option would seem to be to analyse the screen upon exit, and then store the changes. However, as we will discover later, this actually isn't the case. Nevertheless, there are a number of ways we could do this, and it's worth looking at them.

In order for this to work, we will have to think about the number of blocks that can potentially be destroyed on screen. The average AGD screen is usually something like 30 x 20 blocks, or 600 in total. That's a fair amount to keep track off. We would need to be able record if any of these blocks had been removed, and take action accordingly.

The first, and perhaps most obvious way to do this, would be to parse the block buffer, and store the memory location of each block which has been destroyed. As this would be a 16 bit address, this would mean two bytes per change. We could perhaps set a limit to the number of destructible blocks per screen, and this would ensure we didn't go overboard with the memory.

On the face of it, it's a reasonable solution. To do this for different screens we would need to use some kind of separator to indicate the end of one particular screen, and the start of another. However, if we were to set a limit of say 100 blocks per screen, that would mean around 200 bytes of possible data, per screen. If we have 30 screens, that would be about 6k. Although this amount of data would be unlikely, it's still a lot of space to reserve. Instead we could, say, set a maximum number of destructible blocks in the entire game to something like one thousand, and that would then use up 2k. That's more reasonable.

As any 8-bit assembly coder will tell you, you are always trying to find ways of balancing how much memory a routine takes up in itself, how much memory it needs to run, how efficiently it runs, and how well it will play with the rest of the code. Striking this balance is what makes this kind of coding both a joy and, sometimes, a nightmare. Saving space is almost always a good idea, as long as you don't compromise anything else, and in this situation, we may be able to save a little space by making use of those two bytes more efficiently.

If we use a typical screen of 600 blocks, we wouldn't necessarily need to use two whole bytes to store the data. We know that the block buffer is kept at 64768, so rather than store the full address, we could perhaps store an offset - a number that could be added to the start of the buffer.  This would mean we would only need to store numbers 0 to 599, which doesn't require 16 bits - it only requires 10, because 2 to the power of 10 is 1024, which would be enough to cover even the largest screen of 768 bytes.

To explain. We need to store a memory address for each destroyed block. We know that the maximum number we require would be 767. That's enough for even the biggest screen in AGD. To store a number like that, we would need 10 bits. So if we use a 16 bit number, it would give us a further 6 bits to play with. We could potentially use those 6 bits to store the room number. 6 bits would allow us 64 possible rooms, which again, would be enough for any AGD game.

So, I'm now leaning towards this as a possible solution. 2 bytes per block which are stored consecutively. Something like this:

0 0 0 0 0 0 0 0   0 0 0 0 0 0 0 0    room number   screen address

This has a reasonable amount of potential, because when the screen loads, we can grab the current screen number, shift it across two bits, and then run some basic binary logic to match it to all the numbers in our list. As soon as we get a match, we can then read the memory address, and remove that block from the room, because we will know that it was destroyed by the player.

However, it's also worth considering how we will go about removing the blocks. AGD has a number of built in routines that would allow us to 'print' an empty block to screen at the address in question, but to use those routines, we would have to convert that address into X and Y co-ordinates, which would involve division. In Z80 coding, division by anything that is not a multiple of 2 is almost always a hassle and should be avoided whenever possible. Therefore, it would make more sense to store the X and Y co-ordinates of the block in our 16 bit number. In that way, we wouldn't need to make the calculation, and we would be able to remove the destroyed blocks more easily. Therefore the following seems a viable option:

0 0 0 0 0 0 0 0   0 0 0 0 0 0 0 0    room number   X co-ord Y co-ord
Using this method, we can store our room number in a 6 bit number (0 to 63), and our X and Y in a 5 bit number (0-32 in each case). This allows us to store any block, in any location in only 2 bytes. What this means is, as intimated earlier, it may actually be easier to store the block co-ordinates as they are destroyed, since we will have their X and Y co-ordinates at that point. We could use a 2 byte word to store the current position of our 'destruction buffer', and add to it every time a block is removed. If we know the room and both the X and Y co-ordinates, it will be a quick calculation to work out the two bytes needed, store them in our 'destruction buffer', and increase the count.

So, at this point, this would seem to be the most efficient method. What's interesting is that at the start of this blog, I had almost rejected this idea in favour of a simple compression technique, but I feel reasonably sure now that this will be a fairly balanced way of managing the data in such a way that we are always in control of how much memory we are using. It won't have to take up 2k, we can choose the number of destructible blocks we can afford to use.

In the next part of this blog, we will look at how we are going to go about loading and displaying levels in such a way that the player won't notice the rooms being updated. For now, let me know your thoughts on this first entry and whether you have other possible solutions to this challenge. In the meantime, enjoy your day wherever you are, and as always, Happy Coding!

Allan

P.S reading this back, it also strikes me that this could be used for collectable blocks as well. Those of you who are on the Facebook page or have seen my videos will know I created a collectable block type in AGDX. The code discussed here would allow blocks to be collected by the player on a screen, and, like objects, they would not be restored when the screen was reloaded.




Comments

Popular posts from this blog

Welcome

Hello there happy coders. I'm currently in a cheap hotel room in the Northeast of Bishkek, Kyrgystan. Watching the world cup, living mostly on green tea and cherries. Seems like a good time to start a code blog...

My God, it's full of stars...

Here I am once again out in the wilds of Central Asia and today, with a few hours to kill waiting for a connecting flight, I struck upon a new approach to an idea that I'd been thinking about for a while. I wanted to look at the AGD Starfield routine and see if I could enhance it in some way. I'm not going to get into the exact ins and outs of the whole routine, but essentially there are five types of particle that can be generated in AGD - a horizontal laser, an explosion pixel, a trail pixel, a horizontal scrolling pixel, and a vertical scrolling pixel. Each one has its own routine that controls its movement - for example, explosion pixels spawn at the explosion point and have a random 'outward' angle. Trail pixels are spawned at the current co-ordinate, and slowly trickle away. Horizontal 'stars' are pixels which are spawned at the right side of the screen, and move from right to left, and of course Vertical 'stars' start at the top of the screen