Ledge Grabs - whaaaaaat?!1!?cos(0)!!?!
This is insane :D Hats off to you sir.
The game has been going a little slower than usual, I've spent a lot of time optimising code and trying to fix bugs. I also rewrote my level sprites code as I wasn't happy with its performance. So let's begin with some info about that!
The current plan is to use map tiles for pickups such as keys,collectable tech pieces (these are required to complete a level with 100% for replay value) and also for the doors. The reasoning behind this is two fold. Firstly this takes a lot of strain off the multiplexer which is always good when trying to scroll in full color at speed, and secondly it reduces the number of level objects that need to be scrolled in, out and around the screen using sprites. As we will see the second point is very important for performance. The only thing we need to put into the level object list is the enemy sprites.
In order to scroll enemy sprites onto the screen as Doc travels around the map we need to check his current position against all the level objects to see which ones are ready to become "active". An active level object is one that is still alive (has not been killed), and is either on screen or off-screen but at the edges (I use an off-screen margin of 48 pixels around the screen). As the levels are quite large, I've reserved up to 256 level objects per level. This means that up to 256 objects need checking to see if they should appear.
Obviously doing range checks against the current map position for 256 objects per frame is far too much and impractical, so instead I check 16 per frame and cycle through the list omitting anything that's already become active or anything that's been killed and also looping as soon as the end of the list has been reached. This means in the worst case scenario it will take 16 frames (0.32s on PAL) for an object to spawn, this is where having a 48 pixel margin around the screen helps as it helps prevent pop in.
Once a level object is made active it is added to the sprite list and then the map scroll subroutines can begin to scroll it along with the map. If these sub routines at any point scroll the object off-screen again then the object is marked as inactive and removed from the sprite list. Active objects will also fire an update every frame which is responsible for controlling sprite frames for animation and executing enemy behaviours (a topic for another log). The following describes a level object entry in the current system:
|0-1||Map X position LSB/MSB. Object coordinates are in map pixels from the top left of the map.|
|2-3||Map Y position LSB/MSB.|
|4||Object Type. Used to determine which colors, sprites and animations to use. Plus other enemy data such as behaviours, score, hit points etc. This system allows for creation of enemy definitions that can be pointed to by a single byte, great for saving space. When this value is 0 it means the object no longer exists (i.e. killed)|
|5||Sprite hardware ID. This value always starts as 255 so that the code knows the object is inactive. When the object is moved to active then the index of the multiplexer sprite used for it is stored here for reference so that any enemy behaviours know which sprite to affect.|
|6-7||Dynamic memory allocation vector - When an enemies behaviour runs for the first time (this vector points to $0000) it will allocate the extra memory it requires for things like hit points or animation counters, this vector gets stored here.|
Originally I used 16 bytes, but in an effort to try and save some space I reduced this down to 8. By using a dynamic memory allocation table I can store additional data just by pointing to it using bytes 6 & 7, some behaviours may need only 1 or 2 bytes of memory in which case they can utilise bytes 6 & 7 as storage rather than a memory allocation pointer.
I've spent a lot of time in the past few weeks playing various games and tweaking Doc's controls to get the game to feel as good as I can. There's still a lot of work to do, I always say that the player controls is the single most important part of the game and so its important to get it right and constantly iterate as you develop. The following things have been added recently:
A very common effect in many games on many platforms (even if not so much the C64) is to have the player create small dust bursts under their feet when they land. With this in mind I've added this effect to the modern timeline. It was a simple effect to create with a hires 4 frame sprite animation using sprite #0 (Will be used for door animations but otherwise free. In the old timeline it's used for Doc's sword). It definitely adds a little polish to the player movement.
Having Doc's weapon switch out to a sword in the old timeline adds another nice timeline twist and creates more mechanics that can be used for puzzles or enemy fights. While I'm not happy with the current animation I do like the idea of it and it will stay. Soon I will add enemy "stagger back" when they are hit which should make using the sword feel even more natural still.
This was something I really wanted to add from the beginning and now it's implemented I'm incredibly happy with it. By allowing Doc to use his gun while grabbing a ledge it creates more strategy options for eliminating enemies, and by combining it with a timeline shift creates a 3rd longer jump (You can jump as far as an old timeline jump but grab a ledge at the far side by switching at the right time, allowing you to climb up slightly higher). I also added a small delay to the "jump up" from the ledge which if you line up several ledge grabs working up the screen has the effect of making it feel like Doc is slingshotting himself from ledge to ledge, it feels great!
The player collision is pretty simple as detailed in my last blog, using the screen directly to discern what is going on around the player. However this felt really awkward for the enemies as once they reached the edge of the screen they either had to stop moving or make assumptions as to what was around them by extrapolating the data at the edges of the screen. It also meant that the enemies would often just disappear once they had been scrolled off-screen and could not be scrolled back on. So instead of taking this approach I decided to take the CPU hit and have the enemy behaviours actually check tile data directly in the map rather than the screen. The result is enemies behave correctly when off-screen and don't just disappear. As Doc is going to be going back and forth over areas relatively often, I felt this was an important feature to have.
There's still a huge amount of stuff to do. Firstly I want to tighten up the existing code, squashing as many bugs as I can while continuing to tweak the controls here and there as necessary. I also want to spend a little bit of time reviewing my code and adding documentation where needed, it's becoming a large project and I really want to keep it maintainable.
Once I'm happy with the code I currently have and its quality I want to start looking at creating the multi-loader system. At the moment everything is loaded into memory once at the start of the game and is done so using a gmod2 cartridge format. I want to change this to build two versions, one cartridge and one disk, and to start to create the systems required to load different game sections. Only when this is done can I start looking into other game systems such as boss fights, save games, intro screens and a few other things I'll be keeping secret for now ;)
In the meantime here is the latest gameplay video, bugs and all! Bear in mind Doc is still invulnerable at this point and has infinite energy for his timeline device, this will not be available on release, sorry :p
Log in with itch.io to leave a comment.
Ledge Grabs - whaaaaaat?!1!?cos(0)!!?!
This is insane :D Hats off to you sir.
That looks fantastic. It's amazing how that little touch of adding the dust makes the movement look more dynamic, and the ledge grab is a very cool addition.
Looks great! Nice additions. Can’t wait!