Castle Raider/DevelopmentDiary

From Retrosoftware

(Difference between revisions)
Jump to: navigation, search
Line 573: Line 573:
The next thing to do is to write some code for the two outcomes.
The next thing to do is to write some code for the two outcomes.
-
==== Finding the Finishing Point (2014-09-30) ====
+
==== Plugging the Gaps (2014-09-30) ====
The free space I have left at this point is in the so-called working area in pages B and C. We will have to use what remains of this space to have any chance of including a reasonable ending to the game. To start with, I included some text to print when the player has lost their last life - nothing terribly inspiring - and appended it to the title screen text that already gets copied down into the working area when the game's loader runs. The next thing to do will be to free up some more space in the main game code, so that we can actually call any new code we write, then start to figure out what that code will contain.
The free space I have left at this point is in the so-called working area in pages B and C. We will have to use what remains of this space to have any chance of including a reasonable ending to the game. To start with, I included some text to print when the player has lost their last life - nothing terribly inspiring - and appended it to the title screen text that already gets copied down into the working area when the game's loader runs. The next thing to do will be to free up some more space in the main game code, so that we can actually call any new code we write, then start to figure out what that code will contain.

Revision as of 22:35, 30 September 2014

Contents

Castle Raider Development Diary

by David Boddie

See the main Castle Raider page for information about the game itself.

Laying Foundations (2012-01-07, 2012-01-08)

Before I started writing Jungle Journey, I had been looking at writing some sort of game for the Electron that used scrolling. Games with scrolling scenery were released for the Electron despite the lack of hardware support for it, but everything needed to be done in software. The results vary in quality, but some games stand out: Firetrack has excellent vertical scrolling, Perplexity has good horizontal and vertical scrolling, Ravenskull has reasonable horizontal and vertical scrolling, Moon Raider has fast, smooth horizontal scrolling, Ballistix has variable speed vertical scrolling, and Predator has parallax horizontal scrolling. Although not quite like the others, Thrust also features smooth, multi-directional scrolling.

Of all these games, Moon Raider is the one that inspired the scrolling routine I decided to use. My impression is that the game only plots the differences in the scenery that are needed as it scrolls from right to left. The objects - missiles, fuel dumps, guns - require additional work, but there are at most one of these per column of scenery, so there isn't too much to plot every frame. The scrolling occurs in steps of two pixels and, because the ground is a single colour, you never notice that "real" scrolling is not occurring.

Initially, I thought about doing something very close to what I think Moon Raider does, plotting over existing graphics with EOR operations to update scenery, but I also wanted to have more complex scenery, not just a uniform ground that slips past the eyes. In theory, Moon Raider could have had a dithered pattern 2 pixels wide for the ground, but perhaps it wouldn't be as convincing to look at. I wanted to have 2 pixel scrolling steps, but use tiles that were wider than two pixels. The problem with this is that, if you have a 4 pixel wide tile and want to scroll by 2 pixels, you need to update all of the tile, and that means redrawing every tile in a span of identical tiles.

The solution I decided to use was to use two screen banks: one to show scenery at scroll offsets 0, 4, 8, and so on, and another to show scenery at scroll offsets 2, 6, 10, and so on. Each bank is scrolled by 4 pixels, to take advantage of the trick of only redrawing the edges of spans of identical tiles. To scroll by 2 pixels, we simply switch banks to show the pre-scrolled scenery.

Close up of sprites plotted on bank 1. Close up of sprites plotted on bank 2.
Spot the difference: Close ups of sprites plotted on bank 1 (left) and on bank 2 (right).


Background on Foreground (2012-01-15)

The version shown in the YouTube video shows the scrolling at work, but this version has one important hidden limitation: the way the level data is stored limits the number of tile types to 8. This is because the initial level format uses a single byte to describe the tile type for a given span of tiles on a given row (theoretically 256 types of tile), but required a convenient way to describe the join between two spans with different tile types for plotting on bank 2. The compromise solution involved storing a byte in which the top 5 bits described a "merged" tile (for bank 2) and the bottom 3 bits described the tile used for the span itself. This meant generating a collection of "merged" tiles based on the adjacent tiles found when level data is generated. With a possible set of 56 "merged" tiles from an initial set of 8 tiles, there was also the chance that the final level would be too complex for the 5 bits allocated in the level format.

After some discussion on the forums, I thought that 8 tile types was a bit poor. After all, once you have blank spaces, ground, grass, bricks, bridges and doors, there's not much room for creativity. Expanding the range of available tiles was possible but required a different, less convenient approach to plotting "merged" tiles, involving constructing them when plotting spans of tiles on screen. In the end, the trade off was made, hopefully without much of a performance impact.

The next question was that of how the level might change while the game is played. Even though the level format doesn't store every tile - only spans of tiles - there won't be room in memory to have another unmodified set of level data that can be used to restore modifications to the level. The approach I am taking instead is to store actions that modify parts of the level data when the player's character interacts with "triggers" in the level.

Close up of level scenery.
Some of the different tiles in use at the moment. The player is about to step on a visible trigger tile to open a door.


Taking a Detour (2012-02-16)

As I write this, I realise that it's been over a month since my last proper update. During that time I started to add support for collectable objects, extending the use of trigger tiles to cover that case as well. I'm not really happy with the result - it's too much of an abuse of the trigger mechanism to do things like make tiles remove themselves and things like that. In addition, I'm a bit worried that having a potentially sparse table of actions and an even larger collection of level modifications might take up too much memory before I've even designed basic levels. I'm torn between leaving the code in there, removing only the hackery required to support objects embedded in the scenery, and removing all the trigger code completely. Perhaps I'll create a branch without triggers and explore other gameplay options.

Before I got sidetracked looking at Electron hardware, I started to try and improve the jumping motion of the main character in the game. Initial efforts led to some odd game freezing behaviour and loading problems, so it's possible that I'm overwriting code with other code, or something. I think this is what I'll finish next because I'm not at all satisfied with the current jumping motion.

A Change of Plan (2012-02-19)

Before even starting to look at the jumping motion, I ripped out the trigger/action code and replaced it with something a bit simpler. Each span of tiles will have a special type number that controls its visibility, and these will be set to suitable values at the start of the game. Most tile spans will be type 0, which will be fixed so that they are always visible. Other types will be toggled when the player does certain things.

While messing around with sprites and level layouts, I encountered some disturbing mishandling of tile spans, possibly caused by span lengths getting out of bounds. This is something I'll have to keep an eye on. Speaking of bug fixing, the crashing problem I mentioned earlier turned out to be the game code overwriting the game loader code, so that's one less thing to worry about.

Jumping and Falling (2012-03-04)

I felt that the main character's jumping motion was a bit abrupt, with vertical motion in steps of one or more map rows, so I decided to at least have some half row movement in there. It doesn't make a huge different, but hopefully the motion is a little smoother than before. I also made a little change to the falling motion - the character doesn't follow a parabolic curve when jumping and falling, but it's good enough for now. He/she no longer runs in mid-air, either.

Meanwhile, the problems with tile spans have appeared again. While very annoying, at least I now have the chance to deal with the bug properly.

Back on Track (2012-03-10)

I isolated the span rendering problem by plotting only certain rows in the playing area and focusing on just one screen bank. When plotting occurs on multiple rows, it means that a counter has gone out of range. In this case, the row counter exceeds an 8-bit value when the sum of the existing value and the size of the current span are greater than 255. Previously, I only checked for a value of 40, exiting the plotting loop for that row if the counter equals or exceeds it, but the value could be between 255 and 296 and fail to satisfy this test. Fortunately, we only need to check whether the carry flag is set to catch this case. In other words, we first check whether the new value is greater than 255 and, if not, check if it is 40 or greater.

Other time was spent looking at how the screen should be laid out, with a panel above the playing area. A certain amount of time was also spent trying to figure out why the game can't be loaded again after Break is pressed - it seems to me that a bug like this would have been noticeable for lots of other software.

Climbing Stairs (2012-03-11)

A couple of small tweaks today. The first was to enable tile visibility to be configured when the level is created instead of setting all the special tiles to be invisible when the game starts. The second makes the game nicer to play: the character now runs up stairs automatically, as can be seen in this video of the current gameplay. My plan now is to start looking at adding other objects/enemies.

Falling Through the Gaps (2012-03-12)

Just to liven things up a bit, the player's character can now fall off the bottom of the level, losing a life in the process. I added some code to scroll back to the last known "safe" position in such an event. Just what the "safe" position is has yet to be defined.

Construction Kit (2012-03-17)

I've been experimenting with jumping motion again, trying to make it a bit smoother, but it still looks a bit sluggish. Meanwhile, stepping back a bit, I decided to put together a simple level editor for desktop environments to help make it less tedious to make changes to the game's map. It's a bit basic at the moment but it beats editing a text file. Speaking of which, the level data is written out as text files, so it's still possible to edit it the old fashioned way.

Desktop version of a level editor. Screenshot of a level produced in the desktop level editor.


As usual, messing with level design exposes problems in the code with level painting, this time related to initial rendering of the level. That's been on my mental to-do list for a while, so I suppose that will be the next thing to tackle.

Procrastination (2012-04-27)

Since I'm still trying to get inspired enough to work on the gameplay, other infrastructural issues, like the level painting bug mentioned in the last entry, are left unfixed while I mess around with the editor and various level designs. At some point, I'm going to have to implement collectable objects just so that I can experiment further with level design. At that point, things should start moving again but, right now, I'm still trying to commit to the single, horizontal scrollable level format.

Picking Things Up (2012-05-04)

Before talking about collectable objects, there's something to say about the way special tiles were defined in the level format until now. Each span of tiles was defined using a pair of bytes, defined in binary like this:

 00000001 00000004
 type byte  number of tiles (minus 1)

So this example would give us a span of tiles of type 1, 5 tiles in length. We store the number minus 1 because that lets us use 0 as a usable value - we never need to store a span of zero length. The type byte is split into two halves:

 sssstttt
 00000001 (s = 0; t = 1)

The bottom four bits (t) are the tile number in the case where the top four bits (s) is zero. This is just a normal tile. If s > 0 then we have a special tile. Previously, the value of s was the special tile number, an index into a visibility table, and the value of t meant the same as for normal tiles. Using this approach, you could mark lots of tiles with a particular value of s and turn them "on" or "off". The editor I wrote, however, linked each value of s with a particular value of t, meaning that you could only one type of tile in a door, for example. Given that limitation, I started thinking about have more types of special tile, using them for collectable objects, and the limitation of only 15 special tile numbers was too restrictive (special type 0 is implicitly used for normal tiles).

Now, when s > 0, the whole byte is used as an offset into a visibility table and, to decouple the type value from the type of tile shown, it is also used as an offset into a tile type table.

 sssstttt
 00010110 (0x16) -> visibility
                          -> tile type

This means that we still restrict the tile type shown for each kind of special tile, but we can pick arbitrary tile types to show for it. We can also define up to 240 special tiles if we want, though for practical purposes we will only allocate enough memory for 32, the first 16 of which will be scenery tiles and the rest will be collectable objects.

Collectable objects are treated specially in the routine that checks whether a player can move. For ordinary tiles, anything other than a blank span prevents the player from walking, falling or jumping into the part of the screen occupied by that span. For special tiles, the visibility of the span is taken into account: invisible spans are treated the same as blank space, but visible spans are impassable. Collectable items are an exception to this rule: when the player tries to cross one, it is marked as invisible and the player can continue to move.

Picking up keys.
Adding collectable items to the map involved some changes to the level data and some "register" management.


The above image shows some key objects added to the level. These are collectable but do nothing except cause their image to be added to the panel at the top of the screen. Of course, the images have to be plotted on both screen banks - we don't want to see flickering keys at the top of the screen!

The code to plot collected items in the panel was a problem to start with, causing unexpected side effects. It turned out that my use of zero-page addresses was a bit careless, with $70 to $73 being used for plotting while they were also used for other things. Some juggling of addresses solved the problem but I need to be more disciplined with address usage from now on.

Doors and Treasure (2012-05-05)

Doors are made up of non-collectable special tiles that are visible by default. To open a door, you need to pick up the corresponding key - when the player encounters a door tile, we add 16 to the special tile number to get the corresponding number for the key and check its visibility. If the key has been collected then we can make the door invisible.

Treasure is made up of pairs of collectable special tiles that are also visible by default. Since these tiles will appear next to each other, and we want them both to be collected at the same time, we check if the special tile number is in a certain range and remove both tiles in the pair.

Approaching a door with the appropriate key. Collecting treasure.
Doors can be opened with the right keys. Treasure is made up of pairs of collectable items.


Looking at the defined special tiles so far, it seems that we might encounter some problems with partitioning the scenery tiles and the collectable tiles equally, especially since I'm considering using 4 tiles for treasure and 6 tiles for keys. This leaves 6 remaining tiles for objects that could have some effect or another, but the use of 4 treasure tiles effectively leaves 4 scenery tiles without a way to be modified by the player.

Special Tiles in General (2012-05-06)

I felt that the hard-coded handling of special tiles was unsatisfactory and wanted something a bit more general that could use all the available special tiles, deciding what kind of objects they should be on a tile-by-tile basis. To this end, I modified the values in the visibility table to contain collections of flags instead of just 0 or 1. Now they look something like this:

 vcdt0000

Where the visibility flag is the most significant bit. This makes it as easy (and quick) to check for visibility when plotting but lets us store flags for collectable objects, doors and treasure in the same byte. There are a few more operations required when checking for certain kinds of tiles, but it should be about as fast as before.

First Demo (2012-05-06)

Exploring a cave.
The player's quest currently involves cave exploration.


For those of you who are interested to see how the game plays so far, the first demo of Castle Raider is now available: CastleRaider-demo1.zip. It is known to work in Elkulator, but may not work properly in BeebEm.

Thinking about Monsters (2012-05-07)

Castle Raider isn't supposed to be a pure puzzle game. I want it to be something more than a treasure hunt with a few awkwardly-placed platforms to jump between; just how much more remains to be seen. To this end, I have started to think about the possible enemies that the player will face and the sprite routines that I will have to confront. At the moment, I'm just sketching out the code for these and trying to overcome the mental barrier that comes with plumbing them into the game. Writing small pieces of code and keeping expectations low appears to be a non-intimidating way to do this.

More about Monsters (2012-05-08)

Started adding code to plot monsters. At the moment, monsters are aligned vertically with each row of scenery on the screen, but half-row alignment will also be added. Right now, if the routine is called, all it does is plot any monsters that are defined. They don't move or get scrolled when the player moves horizontally.

Monster Movement (2012-05-10)

I started to look into how monsters should be plotted and moved when the player scrolls the screen. Aside from some inadvertent breakage of the sprite addresses generated by the build script, things went more-or-less as expected. Further work will be required to make sure that monster sprites can be plotted at any point in the playing area, and plotting at the edge will also need to be considered.

Testing monster plotting.
Testing monster plotting and movement.


There will also need to be some cleaning up of the checks around scrolling to make it easier to decide when to redraw monsters, especially if the player doesn't scroll the screen.

Interruptions (2012-05-14)

For one reason or another I didn't get to do much this last weekend, and when I got back into coding I just wanted to play with some ideas instead of getting back to business. I have always found the idea of writing interrupt routines on the Electron a bit intimidating, yet I like the ones you get on the BBC where music plays while loading or, as with Skirmish/Joust, you get something arguably more impressive. Since the restrictions of the Electron's ULA apparently makes interrupt-driven music impossible, I was convinced that any kind of interrupt-driven entertainment was out of the question on the Electron.

Recently, I was told that it should be possible to do interrupt-driven stuff while loading from cassette, so I decided to look into it. I've seen things like loading timers before, but the usual experience with these is that they are typically wrong because of all the rewind action that loading games usually entails. Instead, my plan was to do a proper progress bar to show how much of the game has loaded. The way I do this is to assemble the loader with information about a few addresses that will be overwritten when data/code loads and record the values that will appear there. In the loader, I initialise those addresses with bit inverted values of those expected values, and wait until each of them changes to the correct value. As this occurs, I plot a bit more of the progress bar on the screen. It was fairly simple to implement and is quite a nice touch. At some later point, I'll tart it up a bit.

Monster Movement and Titles (2012-06-04)

I did a bit more work on plotting monsters and started to add logic to them, but things stalled again. In the meantime, I started chasing another idea that I've been thinking about for a while: an animated title screen.

Work in progress title screen.
Work in progress title screen.


The title screen is basically a waving flag - not all that exciting, but quite a nice effect. As with the scrolling, we're only plotting the changes to each column in the flag, so there isn't all that much to plot at any given time. The aim is to minimise all calculations and plotting so, no doubt, there is still much to be improved.

Cutting up Code (2012-06-05)

Broke the animation code into pieces to help fit it into the program flow of the interrupt routine used while loading from cassette occurs. Once basic initialisation has occurred, a pointer is stored which points to the next part of the routine to execute. Each part of the animation routine updates the pointer before returning, so this lets us split the animation into chunks that are hopefully short enough to avoid causing problems with the loading system.

Now with wavy flag loading.
Now with wavy flag loading.


Before loading starts, the routine runs without this overhead, so it's almost as quick as the unadulterated version. The speed of the animation during loading is nothing to write home about, but that's not really important, given that it was only done to show that it is possible.

Second Demo (2012-07-29)

In preparation for the upcoming Homebrew Gaming Weekend, I've created a second demo of Castle Raider: CastleRaider-demo2.zip. Hopefully someone will be able to transfer the files onto cassette/disk in time for next weekend. In the meantime, those of you who are interested to see the title screen in action can download it and try it out. Have fun!

Up and Running Again (2013-04-08)

In an attempt to try to get back into writing the game, even with various other things on the go, I played with the level designer a bit this evening. The level needs to be simplified a lot to get nice fluid scrolling, so bits of fancy decoration got thrown out. I also disliked what I'd done with putting keys and doors in places where the player would have to walk back and forth a lot, so I started taking much of that out. When I find a bit where the player has to stumble over minor obstacles, I smooth the landscape a bit, making it possible to just walk onto them or necessary to jump over them. I also modified the player sprites a bit to add a touch more animation - it's not entirely satisfying, but it's a work in progress.

Looking at Drawing Errors (2013-04-14)

Something that's been at the back of my mind for a while has been a drawing error I encountered a while ago. It doesn't occur in the default level I'm working on, so I can pretend it doesn't happen, but sooner or later I know I'll have to face it. I decided that sooner was probably better.

The images below show the problem: the initial view of the level looks fine but, when the character moves right by one step, the second screen bank is shown and the level is displayed incorrectly. Since the whole screen is drawn in full when the gameplay starts, there must be something wrong with the routine to do this. There may also be bugs in the routines to incrementally change the second screen bank, but that's not what we need to solve here.

Correctly drawn bank 1 screen. Incorrectly drawn bank 2 screen.
The start of a level correctly drawn on bank 1 (left), but incorrectly drawn on bank 2 (right).


So, this is the next challenge. I aim to dig into it in the next day or so.

Update

There are two routines to plot the initial view onto the level. The first is fairly simple because it can just plot spans of tiles into corresponding cells on the screen. The second is a bit more complicated because each tile needs to be shifted right by two pixels. This means that each cell on the screen contains one of two combinations of tiles:

  • The end of the previous span (the right edge of a tile on the left) and the start of a new span (the left edge of a tile on the right). A "merged" tile.
  • The continuation of a span (a tile "rotated" so that its right edge is on the left and its left edge is on the right).

Initially, I had thought that cells in the first column on the screen should only contain continuations of spans, but hadn't considered the situation where the initial span on the screen is only one tile wide. This meant that it was being skipped without being plotted in any way at all. The solution is to always plot a merged tile, even if there is no previous span to consider.

So that's a nagging problem solved, at least for the time being. While fixing this, I also changed the brick tile to give the platforms flat edges. This means that I can discard the half-brick tiles at a later date, freeing up two tiles for other things.

Next time, I want to look at jumping again.

Jumping and Falling Again (2013-04-15)

I'm not terribly happy with the jumping motion in the game at the moment. I managed to simplify it a bit this evening but it probably needs a bit more work. At least I managed to strip out some unnecessary code. The player's character should still be able to jump as high and as far as before, but we no longer need to perform loops to check that they are not going to jump through platforms.

Another character out for a test run.
Another character out for a test run.


I'm starting to think about giving the player a choice of characters, so I'm experimenting with an alternative set of character sprites. In the cassette version, at least, this choice will be made during loading, as it was in Superior Software's Citadel. Other versions, if they eventually appear may allow the player to choose before each new game.

Changing Identities (2013-04-16)

I briefly thought about writing code for the loader to allow the user to choose the character they want to play with. The idea of updating the loader, writing a selection routine, and moving data around in memory was a bit much so I decided to include both sets of sprites in the game. To save space, I only need to include the left-facing sprites in the sprite data file, though I also include a set of default right-facing sprites for convenience. The right-facing sprites are generated from the left-facing ones, meaning that you can't designing the sprites to make them do anything fancy for different directions, but we save some space, I hope. As long as the direction-flipping routine is less than 96 bytes long...

Update

The routine was too long. It's just easier to include the sprites and make room for them in memory.

Level Limitations (2013-04-22)

Thinking about a way to introduce monsters to the level design, I reviewed some of the data structures I am using for the level data. I note that, for each row, I use a single byte index into the array of spans for that row. This means that there is a hard limit of 256 spans for any given row and, once one row has reached that, the level effectively ends. To check the situation with my current design, I printed the number of spans for each row. Right now, I am using at most 144 spans - that is for the penultimate row on the screen, perhaps unsurprisingly. The level is about 27 screens wide at this point, with some fancy bits in places. We'll see how things progress from here. I don't really want to extend the span indices to more than one byte because that would complicate things. Perhaps the limitation will help to focus the design and make development less open-ended.

Redecorating (2013-04-23)

While in the process of figuring out how to place monsters on the level, I tarted up the panel at the top of the screen.

An updated panel design.
An updated panel design.


Backtracking (2013-04-29)

Changed my mind about how to add monsters again. I had thought about embedding them as tiles in the main level design, but pulling out the information about them would involve either messing with the plotting routines to see what is there, or traversing the level data for each row to see what has just appeared on screen. Changing back to the original idea of having a single list of monster-gap spans means that it should be easier to pull out the information, and we can use two indices into this list to handle monsters appearing on the left and right edges of the screen. At least there should now be the basis of a workable system to try to implement.

Background Tasks (2013-05-01)

Thought about the scheme for handling monsters a bit and managed to implement something. The way the list of spans are traversed is a bit cumbersome but it makes me think that one of the arrays in the plotting routine (the initial tile array) might actually be unnecessary if I adopt the same scheme for plotting. At the moment, the priority is to connect the monster data to routines to put monsters on the screen. I can re-examine the level plotting routines later, if I feel the need to.

Making an Entrance (2013-05-02)

Although the scrolling routines have been modified to recognise when a monster should become visible, only a basic effort was made to shove one onto the screen. This evening, I did some work on this to make their appearance a bit more tidy, refactoring the monster plotting routine to allow individual monsters to be plotted separately (though I don't use that yet) and splitting the monster span list traversal code into separate routines. These separate traversal routines make it possible to create new monsters separately from scrolling the level, and therefore can be run after the currently displayed monsters have been unplotted. This means less of an unsightly mess when they appear, though they still just pop up when summoned. I still need to make sure they're scrolled properly, get rid of them when they leave the screen, and enable support for more than one of them.

It would also be good if the creation routine respected the monster type, too. Some adjustment will be needed to bring the data format used for this in the level description in line with the format used by the plotting routines.

Keeping Track of Monsters (2013-05-05)

The monster I had added to the level data as a test subject had a nasty habit of appearing in different places on screen when the player ran past a certain point in the level. It turned out that I wasn't properly updating all the parameters related to the left edge of the screen as it traverses the monster data. Ensuring that the span index is decremented as the offset into the span reaches zero (corresponding to being incremented as we step past the start of a span when we scroll right) results in the proper behaviour. The monster is no longer recreated in a strange position on screen.

The problem now is to avoid strangeness when the player repeatedly passes the same point on the level. Some kind of corruption occurs in the on-screen monster array. I'll need to ensure that the array is being managed correctly, and also ensure that monsters are removed from the array when they leave the screen.

Landscaping and the Monsters' Revenge (2013-05-12)

The monsters were appearing in unexpected places, even when the monster data wasn't supposed to contain any monsters! As a result, I switched them off for a while and tried to look at creating a basic map. At the moment, I'm only using a few items, such as keys to open doors and items that do pretty much the same for door-like obstacles. Following that, I thought a bit more about the misbehaving monsters and now I understand their motivation a little better. The map needs monsters to make it more interesting - running up and down hills is only fun for a while - so that's where the focus will be next.

Part of the updated map.
Part of the updated map.


Monster Taming Part 1 (2013-05-16)

I realised that the vertical positions of the monsters weren't taking the extra 8 rows above the 16 map rows into account, so I modified the way monsters are stored in the monster data to allow for values up to 23. It occurs to me that there's currently no way to define monsters on the map that could take advantage of this.

After a detour in which the new encoding played pretty badly with the existing way active monsters are represented in the game, I realised that I didn't need to have exactly the same representation for monsters in the span data and active monsters in the monster table. It would be nice if they could be quite close, as it would reduce the amount of data manipulation needed when creating new active monsters. Currently, we move the vertical position into a separate byte and expand the monster number to include an existence bit, an animation bit and a bit that represents half a horizontal cell on screen.

Two monsters show up for work.
Two monsters show up for work.


At the moment, the monsters appear fairly reliably as the player's character runs to the right, but sometimes fail to show up when returning to the left. There's probably a case that I've missed in the span traversal code. Hopefully it won't be too much work to track down.

Monster Taming Part 2 (2013-05-18)

When monsters appear on the edge of the screen, they are registered in a table that can contain four entries. Each time the player encounters a monster on the right hand side of the screen, a monster is created and added to the table, and an offset into the table is incremented. If the monster leaves the right hand side of the screen, the entry in the table is cleared and the offset is decremented. Similarly, we use another offset to track monsters entering and leaving the left hand side of the screen. This means that we can keep track of monsters entering and leaving the screen as the player moves around on the map.

Uh oh, what's that?
Uh oh, what's that?


One problem we have to deal with is ensuring that monsters are created before the player encounters them so that they can be plotted partially as they enter the screen, and delete them after they have completely left the screen. This is all fairly easy to imagine in principle, but slightly more work to put into practice. The right hand side of the screen is easy to deal with - when we set up the scrolling routines, we can pretend that the screen is slightly wider than 40 columns and calculate the appropriate span index and offsets to this edge. The left hand side is slightly more difficult to handle because we can't position the initial location of the edge in a span that precedes the first monster span in the level - it doesn't exist! Following some attempts to find a complex solution to this problem, the simple solution presented itself: just insert a two column initial span into the monster span data! This caused some quirks - the indices into the on-screen monster table need to be set to appropriate values to work around the initial empty span - but seems to work as expected.

I've updated the monster plotting routines to take the screen edges into account. The code is probably a lot more complicated than it needs to be, but it can probably be simplified and streamlined later if required.

Moving Monsters (2013-05-19)

Now that monsters enter and leave the screen cleanly, it is time to make them move again. Early test code for monsters allowed them to move up and down, so that seemed like a good place to start. I did this in stages: first, making them bounce between the top and bottom of the playing area, then making them take the scenery into account. Finally, I added some basic checks for the player when they move.

A New Platform and a New Demo (2013-05-20)

Since the scrolling code relied on the Electron's ULA, it wouldn't run on the BBC Micro. I wondered how much work it would be to do the same bank switching trick on the Beeb and it turned out to be easy to swap out the Electron code for some to access the 6845 registers via the SHEILA memory-mapped addresses. The game runs faster and more smoothly on the Beeb, as you might expect, but not that much faster. A diligent programmer would use the facilities provided by the 6845 in a more effective way for a Beeb version, of course.

To demonstrate the current state of the game, I've made a new demo for people to try out: CastleRaider-demo3.zip. It contains UEF files for both the Electron and BBC Micro versions of the game. I may merge these versions at some point in the future and do some run-time checks to see which machine the user is running the game on. We'll see.

Misbehaving Monsters (2013-05-23)

I laid the groundwork for horizontally moving monsters last night but was too tired to fill in the final details - basically, just a case of making the monster-tile checking routine able to check for tiles horizontally adjacent to monsters as well as above or below them. Now, the monsters can move left and right as well, though I've currently only enabled this for spiders. As I write more and more code to deal with things like this, I keep having to increase the memory allocated to code. At some point, I'm going to have to perform a spring clean of the code, removing all sorts of unnecessary stuff.

So, some monsters can move horizontally, and they run into scenery and change direction. In the future, it would be good to let them fall off things as well. The trouble is that they don't always change direction. Some work is needed to investigate just why they flaunt the rules like this!

Opening Doors (2013-09-22)

Inspired and motivated to work on this again after seeing other Retro releases, I started thinking about ways to make the gameplay a bit less linear. A game in which you run from left to right, potentially backtracking a number of times to get items, is probably about as tedious than any of the other left-to-right run and jump games that already exist. I'd like to give the player the opportunity to explore a bit, and that isn't possible if the whole world is designed to fit on 16 screen rows.

Ignoring the monsters for now, I started to investigate the possibility of adding portals to the level, making it possible to jump between areas instead of having to run to them. Once the data structures were sorted out - portal tiles are stored as regular tiles with values from 48 to 63, and are invisible - some initial experiments proved fairly successful in scrolling between portals. However, we want to give the impression that portals are in distinct places on a larger map, with several logical levels, so the idea is to simply redraw the screen at the new location. This is done by performing all the operations on data structures that are done normally while scrolling, but not actually redrawing everything. Ideally, we would perform calculations on the data that describes the scrolling for each row instead of brute forcing our way through the level data, but the routines for scrolling are already written and memory is getting tight.

Predictably, the monsters are not very happy when scrolling occurs like this. After testing the basic transition routine a bit more, I'll start investigating why they seem to go missing.

Watch Your Step (2013-09-28)

As if I don't have my hands full with keeping track of monsters and putting together levels, I realised that I could implement breakable tiles fairly easily. Actually, it turned out to be more complicated than expected, and is the first feature of the game which causes the actual level data to be modified during the game, requiring it to be reset before each new game. Code already existed to check whether the tile the character is standing on is breakable, so it should have been easy to redraw tiles that change. The hardest part was doing the actual drawing - fortunately, we can rely on some assumptions about the extent of the tile spans that the character is standing on because we encode breakable tiles in groups of 1 or 2. The result is that, when the character jumps or falls onto a breakable tile, it breaks a little. If it is already broken then it disappears, with predictable consequences.

Be careful what you step on.
Be careful what you step on.


I also wanted to implement an additional feature with the portals, so that different areas feature slightly different colour palettes, like I did for each screen in Jungle Journey. Since I've already implemented machine-specific routines for bank switching, I've now implemented a palette switching routine for the Electron version; the BBC routine will have to wait for the time being.

As usual, memory constraints are imposing themselves on the game. I've had to disable some features for now, but it looks like some consolidation and simplification of code is required anyway.

Housekeeping (2013-09-29)

The latest features are good to have, as long as there's a convenient way to add them to the game's map. While working on them it was enough to modify the text file containing the level data, but navigating the rows of tiles in a text editor is something I abandoned in favour of a custom editor a long time ago. The fundamental changes to the way the map is navigated by the player meant that an overhaul of parts of the editor was needed, adding support for portals and mapping their destinations to distinct logical levels. Support for breakable tiles and palette changes on entering a level were also nice features to have.

Exploring a cellar.
Exploring a cellar.


The game now involves exploration of different levels which are accessed from the main game map. Each of these might also lead to other levels, or back to the main map, or to parts of the same level. This should result in a less linear structure to the gameplay.

There are a couple of obvious bugs that need to be fixed, plus the memory saving work mentioned in the previous entry, but some effort also needs to be put into map making now. I don't yet have the big picture of how it will all fit together, so hopefully things will become clear over the next few days.

Expanding Horizons (2013-10-06)

I spent some time creating new levels, simplifying old ones, and trying to incorporate some gameplay that suits an old-style platform game. This usually involves designing a new level, making it really fancy, realising it's not fun to play, then finally stripping out the fancy bits. In doing this, it turned out that the monsters were exhibiting more strange behaviour. Sometimes, one would collide with a wall and change into a different type! As usual, this was just a case of being careless with registers (memory locations) with routines overwriting temporary values stored in zero page. It's typically either that or not clearing the carry flag.

The monsters also had an unfortunate habit of opening doors on their own. This was caused by sharing the tile checking routine between the player's character and the monsters. As a quick fix (which will no doubt end up as a permanent fix) I added a flag that is set when the routine is called to check monster movement; this flag is used to return early from the routine, before any tiles are modified.

After changing some sprites to make the basement level look nice, I looked at a dungeon level I'd already created and found that the change wasn't an improvement there. Ideally, I thought, there would be more than one wall sprite to choose from. It turned out that I'd allocated space for a door sprite that I hadn't used at all, so out it went, to be replaced by another wall sprite.

The dungeon under the castle.
The dungeon under the castle.


Looking at monsters again, it seemed like a good idea to get them to move towards the player's character when they appear on screen. This turned out to be fairly easy to implement, as was a change to the editor to allow the axis of motion to be set on a per-monster basis; the latter was already supported by the underlying level data structure. The monsters still have one problematic feature to resolve, involving their habit of moving through scenery when just off the left hand side of the screen. This will require a more careful look at the tile checking routine, but more memory savings will be required before that can happen.

Taking Turns (2013-10-27)

I haven't done much about the monster movement problem; just trying to make time to concentrate on writing the solution. In the meantime, I've removed the monster movement loop and made it so that each monster gets updated in turn every couple of frames, just like in Jungle Journey. This should make it less taxing to handle alongside all the other work the game has to do.

Interior decoration.
Interior decoration.


I've also played around a bit with colour schemes and the use of tiles in certain levels. Unfortunately, there aren't many colour combinations that work very well with the fixed black, red and yellow palette that the game uses, but magenta can be used with red to create a darker purple that looks fairly good, if you like that kind of colour. Further experimentation will have to occur, though I'm not too hopeful that I'll find any really good combinations.

Just Out of Sight (2014-02-06)

To get back into development, I picked what I considered to be the easiest item on the "to do" list: the bug involving monsters moving through scenery when just off-screen. Conceptually, the solution is easy: just check the spans of tiles the monster is trying to move through. However, the data structures are really oriented around putting things on the screen, starting with the left edge; we aren't usually concerned with what happens beyond that edge. The implementation of the solution involves measuring the x coordinate of the monster relative to the "previous" span that starts off-screen before the "current" span. If the updated x coordinate becomes positive when adjusted like this, we can say that we have found the span the monster wants to move into. If it is still negative, we check the previous span in the list, and so on. For the purposes of this routine, negative coordinates are anything in the range from 128 to 255.

Monsters don't seem to sneak through ceilings and floors now, but vigilance is still required. At least I'll know which part of the code is broken if they escape again!

Meanwhile, I'm once again considering what features the game needs. However, this time I'm thinking about removing features. In particular, the breakable tile feature is nice, and doesn't involve all that much code, but storing the individual tiles in the level data is expensive for what you get, and I don't really like the idea of modifying the level data itself during the game. I'm not ripping the code out right away, but it's going on the "nice to have" list of features that will have to go when I run out of memory.

Still Out of Sight (2014-02-22)

The previous fix for monsters sneaking past ceiling and floors didn't work. Horizontally moving monsters flaunted their ability to move into and past scenery when skulking around off the left-hand edge of the screen. The flaw in the algorithm was my usual forgetfulness when it comes to preserving the accumulator - one of the downsides of working with a processor with only three useful registers and at the assembly level. Hopefully, the last change fixes this by simply storing the span-relative coordinate while doing things like updating the pointer to data (which obviously "corrupts" the accumulator).

I took the opportunity to expand the range of x coordinates that the monsters can occupy so that the player can't just "lose" monsters by making them go off-screen. This may yet cause problems if there are monsters with overlapping horizontal ranges, and may also reduce the number of monsters that will be seen together in high density areas if other monsters have already been placed. There is, after all, a limited number of monsters that can be active at any given time.

Since I recently added support for palette changing on the Beeb, I've created a new demo to show the current state of the game: CastleRaider-demo4.zip.

Making Things Coherent (2014-03-01)

I started to organise the levels into something more than a few disconnected locations. I'm quite happy with the layouts of some of them: the dungeons and caves, for example. However, others were really trying too hard to fit the name and theme of the game at the expense of gameplay, so they were either deleted or revised. No doubt I'll revisit the levels later and change my mind over some of the decisions I've made.

There You Are (2014-03-06)

When the game begins, the player's character is alone and they only encounter monsters as they scroll onto the screen. This was the result of a limitation in the way monsters are created that is linked to the way they are stored alongside the map data. In principle, it should have been possible to show the monsters that should appear on the initial screen by simply examining the monster spans that lie on the current screen, but it seemed like a lot of effort. However, when the character enters and leaves portals, any monsters that should have been there are obviously missing. This meant that a routine had to be written to handle that case. As an aside, it should only have been necessary to write a routine that shows the monsters on the initial screen in the game since we should be able to track all the monsters' comings and goings as we scroll about the level (which is all entering portals does) but, for some reason, the scrolling routines fail to do this correctly when navigating between portals.

Anyway, I wrote a routine to show the monsters at the current location, taking into account that the monster spans are displaced left from the map spans by two columns. Initial testing has been mixed: it generally works well but, sometimes, returning to a location where a monster can appear when the monster has already been created causes another to appear, which probably means that I'm not filling the visible monster table correctly. I'll be fixing that next.

Marauding Monsters (2014-03-16)

There was much tweaking of visible monster offsets as I tried to find a way to marshal monsters onto the screen while monitoring them as they went out of range, but there was always either a problem with missing monsters or duplicates, created because the current visible offset happened to be pointing to an empty slot when it was time to scroll a new monster onto the screen. In the end, I did what I knew needed to be done: I added an extra field to the visible monster array that records the corresponding index into the monster data. This means that we now track which monsters in the map data have been created and avoid creating them twice. This appears to work reasonably well.

The character selection screen.
Who are you? The character selection screen in the loader.


While this was going on, I started to tighten the focus of the game, removing unnecessary features, fixing minor issues and adding some user friendly niceties, like the character selection screen. The game itself won't have the space for both sets of character sprites, so the player will have to make a choice before the game is loaded.

Dodging monsters in a tricky descent.
Dodging monsters in a tricky descent. I'm fairly happy with the levels, but some further improvements will doubtless occur.


I also tried to get the levels closer to their final state, putting keys and doors in appropriate places. Although the game could support quite a few collectable objects, we're not going to use so many, so I cut it down to a minimal five keys and two items of treasure. I've been play-testing the levels and not hating them, so maybe I'm lining up to start on the last set of features. These will include visual feedback on the number of "lives" the player has left, sound effects and a minimal title screen.

Counting Down (2014-03-18)

Although we are already keeping track of the number of lives the character has left, we didn't show anything in the top panel. Implementing something for that was easy but required a bit of housekeeping first. Using common routines and routine exit points saved a bit of memory, but it was a struggle to find bytes here and there. If we want to add anything more to the game then more space saving measures will need to be taken.

The panel now shows the number of lives left.
The panel now shows the number of lives left.


Spending Time to Save Memory (2014-03-21)

With little free memory left, I started to investigate ways to put data in the screen memory in a way that was not distracting before giving up and trying more conventional ways to save memory. The idea was to put code in memory, but only use the bottom four bits of each byte. This would require twice the amount of memory for each piece of code, but would ensure that only red and black pixels would be shown. After trying to write a routine to unpack code stored this way and execute it, I gave up.

Instead, I looked at the zero page memory map in issue 2 of the Advanced User Guide and decided to repurpose the Econet and filing system workspaces for some global variables. As a result, many operations became shorter by a byte each, saving about 150 bytes in total. With this windfall, it was possible to implement basic joystick support and a simple title screen. There is some space left, but perhaps not enough to implement everything we still need. We'll see how it goes.

Some additional savings were made by finding duplicate data structures and writing code to use shared structures instead. It may be the case that some of the lookup tables still contain features like this.

Titles, Collisions and Deoptimisations (2014-03-22)

Not being a hardcore 6502 hacker, I often write code that's a bit too verbose, clearing flags a lot and not taking advantage of the flag setting features of the various instructions. I realised while reading the NMOS 6502 Opcodes tutorial/summary that I could discard a lot of comparison instructions in the code, and especially those that are comparing the accumulator with zero (CMP #0). I went through the code discarding as many of these as I could find and saved even more memory. I also found comparisons where the value could be zero and optimised many of these away, too. Unfortunately, there were side effects to this - some of the LDA <value> BEQ <target> sequences that resulted from this didn't behave in quite the way I expected, so some reversions were necessary.

Using the memory saved from code simplification, I implemented a simple title screen for the main game so that it doesn't immediately start when it loads. At the moment, it's just showing some text and waiting for the Space bar or a fire button to be pressed to start the game.

The collision checks between the main character and monsters have also been a bit fuzzy. While reverting a change to comparison instructions, I realised that I was not comparing the character and monsters' vertical and horizontal positions with half row or half column resolution. That has now hopefully been fixed, making it slightly harder to duck past monsters at various places.

A Musical Interlude (2014-03-30)

I started to think about providing some nice music in either the loader or the end of game sequence. The Electron isn't well blessed with musical capabilities, though there are some impressive compositions given its limitations. I've experimented a bit with direct ULA access to try and achieve some nice effects, but the results aren't impressive and I was a bit disappointed.

Coming back to the game itself, I've added some simple sound effects and pause/unpause key support. The game also waits when the character dies to break up the gameplay a bit. Since the game now makes noises, I also added support for turning sound off and on.

Meanwhile, the fight to reclaim memory continues. I started the day with about 173 bytes free and finished with 36, and that's only after some work to reduce code duplication.

Name of the Game (2014-04-05)

On reading the Electron Advanced User Guide (version 2), I discovered that various low memory pages can be used, hopefully without bad side effects. This means that various working data structures can be located there, saving memory in the higher pages for program code. In addition, we can perform the usual tricks of loading data in the loader program and copying it into the working area as long as we don't locate the working area in the cassette buffer (page A). The current approach is to use the pages containing soft key definitions and user defined characters (pages B and C). We need to avoid page D because that's used by the Plus 1 on the Electron, and we support analogue joysticks plugged into the Plus 1.

The in-game title screen.
The in-game title screen.


I initially wanted to create a title screen that used normal level data so that I could reuse existing code to display it, and maybe even scroll it around. However, my design couldn't be stored efficiently using the level data structures, so I resorted to writing a new routine to display it. This retrieves the data stored in the working area, unpacking bytes as groups of four tiles and reusing the tile plotting routine for bank 2 to display them on the screen. The title text, having been copied into the working area immediately after the title data, is pulled out and fed to the VDU system for printing.

Disks and Demos (2014-04-24)

Before the Easter break, I started to focus on getting a demo together for the Wakefield show. Ideally, if someone wants to load up a demo on a real machine, they'll need something other than a UEF file, so I started looking at disk images again. Previously, when I created disk images for Jungle Journey, I created them by hand, loading files from UEF files in an emulator and saving them to pre-existing disk images. Since I don't want to do this for Castle Raider, I wanted a way to automate this process. As a result, I took some code from my ADFSlib Python module and started to create a single-purpose solution for creating images. However, after managing to create an ADFS image, I realised that the game won't run from a regular ADFS file system because PAGE is too high. A temporary solution is to create a DFS image that runs under from a PAGE E00 DFS file system and make people use DFS instead!

The disk image is available here. If anyone wants to try it out on a real or emulated Electron then please feel free to do so. It won't work on a BBC Micro because the bank switching code is machine specific. When I get closer to finishing the game, I'll create versions for the BBC and Electron for people to try.

Feedback (2014-05-11)

It looks like the recent demo got tested a bit at Wakefield (photos by BeebMaster and flibble) but I haven't yet heard any reactions to the game apart from a brief mention in a show report on the stardot forums.

In the meantime, my brother played the demo and gave me some useful feedback about various parts of the game, some of which has already been addressed - making some levels more forgiving in places where it's easy to miss a jump. I also need to revisit the dungeon and wasteland levels and make them a bit more balanced. While performing some level tweaks, I realised that I was wasting quite a bit of space by having separate Start and Ending levels in the level data. Since it doesn't really matter if they're separate or not, merging them meant that I could remove some blank tiles from the end of the Start level and start the Ending level with bricks. This freed up a few more bytes that I will spend on further level improvements.

Rearranging the Furniture (2014-05-17)

I was quite happy with the final jumping puzzle in the game but felt that it was facing the wrong direction. Since I haven't looked at the game for a while, I thought I'd just fix this and tidy up a few other bits of the map. This led to a reassignment of some of the keys and doors to make it slightly more work to find the key for the castle's exit, but also to make the player re-explore parts of the map.

I also tidied up the BBC Micro-specific code to save a few bytes, making it possible to build tape and disk images for that machine again.

Finding the Finishing Point (2014-09-28)

I fired up the game to do some play testing and found that the panel wasn't cleared properly when a new game was started. As usual, the carry flag was the culprit.

The other reason to look at the game again was to try to line up for the last coding effort required to complete development. At this point, all the gameplay is done, the levels are defined and more or less finished, so we're really only looking at niceties like a "game over" screen and a "well done" screen. The first of these just needs code to be written; the second required some preparatory work. In the game, the player wins by reaching a certain point. Although there is a mechanism in the game for checking where the player is on the map, there was no check for a specific finishing point. In fact, there was no defined finishing point to check for. I added a check into the main loop and added some supporting code to the supporting tools for defining a finishing point. After some tests, the game ends when the player reaches a certain point.

The next thing to do is to write some code for the two outcomes.

Plugging the Gaps (2014-09-30)

The free space I have left at this point is in the so-called working area in pages B and C. We will have to use what remains of this space to have any chance of including a reasonable ending to the game. To start with, I included some text to print when the player has lost their last life - nothing terribly inspiring - and appended it to the title screen text that already gets copied down into the working area when the game's loader runs. The next thing to do will be to free up some more space in the main game code, so that we can actually call any new code we write, then start to figure out what that code will contain.