Sunday, October 26, 2014

Getting stuff done...

So I got a lot done this weekend (not counting the work I did on my back porch, but this isn't a home improvement blog, so....)

  • I got the basics of the scripting system working for cutscenes in Robo-Ninja. I reworked my original design slightly (to put the entire script in a single file, instead of a different script for each "puppet" in the scene). I still need to flesh out a number of the script commands, but I have a very ugly basic cutscene working, where Robo-Ninja stands there as Prof. Treeblot walks away from him, and then a buggy half-broken Dr Squidbrain shows up.

  • On Atari Anguna, I did some thinking, and realized the way I had written 3 completely separate kernels was dumb. I managed to rework it so that the the smaller, simpler kernels were just subsets of the bigger more complicated kernel. They are a couple cycles slower, as I had to add a branch to possibly jump out of the kernel loop for the smaller kernels, but I managed to squeeze the branch into a spot where the timing wasn't as critical. So that saved me a good 500 bytes (1/8 of a bank, or 1/16 of my total rom space). If nothing else, that frees up space for at least 6 more room layouts. (to allow for more total number of rooms, I'll reuse the layout data across multiple rooms in the game)

  • I also got my bankswitching scheme all set up, so that the main game logic happens in one bank, and the display kernel and graphics data lives in the other bank. This was a little messy because I originally divided my code into separate files, based on what sort of game element each file was about. So I had a file for enemies, a file for backgrounds, a file for the main player, etc. But by default, the assembler just lays out everything sequentially in memory. So I had to do a little bit of rearranging to make sure that everything was declared in the right bank. (There was one small set of data, used for positioning sprites, that I ended up having to include in both banks, so I hardcoded its location, so that it was in the same place in both banks).  The fact that I mostly use macros to break up my code (instead of actual subroutines) make it a bit easier, as the macros get substituted inline where you call them, instead of existing where you declare them.

The big thing that annoyed me this weekend is realizing, once again, how slow development is, when you don't have much time to put into it. Often I've only got about 30-60 minutes of useful brain function left in my spare time in the evenings, before I get too tired to do anything useful. It's just hard to get anything done in that time. (which is partially why I like blogging about it -- it takes less brainpower, so I can do this during that weird gap of time where I'm not quite ready for bed, but my brain refuses to produce more code.) I guess if I only had 1 project instead of 2, it would move faster. Although I've really enjoying the fact that I have 2 options. If I get bored of one project, I just swap to the other.

Ok, my brain is fully shutting down now....

Tuesday, October 21, 2014

Kernels, enemies dying, and bankswitching

So for the past couple weeks, I've been putting off working on Atari Anguna because I've been up against a bug that sounded difficult and boring to solve: whenever I killed an enemy, it messed up my kernel logic, and everything went haywire.

What's supposed to happen, is that every frame, I sort my enemies by Y position, and then compare all their Y values, select the right kernel mode, then draw them in order. If an enemy is dead, I'd sort it to the end, and select kernel modes that ignored that enemy. But it wasn't working.

I sat down to tackle this today, and happily, it only took about 10 minutes. It was a simple logic error in my sorting routine, which occasionally caused a dead enemy to get sorted to the front of the list.

(Tangent time! I ended up using a *gasp* Bubble Sort for sorting these enemies. Why? Because I realized that for N=3, which is my case, Bubble Sort only requires 3 comparisons (a/b, b/c, a/b again). So it's a really simple sort to implement, and if I'm guaranteed constant time, then hey, why not. The bug I ran into was that if the enemy in the first position of a comparison was dead, I'd always swap, but I forgot to check if the enemy in the 2nd position was dead, and refuse to swap in that case.)

So tonight I also realized that my kernel was working for 3 enemies as long as 2 of them overlapped, but I never had gotten around to implementing my kernel mode for 3 completely separate enemies. That's really simple and straightforward, but requires a good bit of compiled-code, as I'm using macros instead of subroutines (I can't afford the 6 cycles for the JSR and 6 more cycles for the RTS during my kernels!), so in the compiled code, there's tons of repetition. Well, this created a new problem: I've now almost used up my entire 4K of ROM.

Now the geniuses back in the 80's solved this problem with bankswitching, which is what I now have to figure out. Basically, there are 2 entirely separate blocks of ROM that you can switch in and out of actual ROM. This gives you double the space, but gets tricky. For example, as soon as you give the command to switch banks, it immediately switches. Meaning the instruction pointer is now pointing to the memory location of the next command, but in the other bank. So you have to make sure to line up your code just right, so that when you toggle banks, the code in the new bank picks up and runs where you left off. (And when you power on the Atari, you don't know which bank you're in. So both banks have to have identical initialization routines, so you can get yourself into a known state!)

I think I've got the basics working now:
In both banks, I have an almost-identical routine where I manually load a target address (the target address of a routine in the other bank that I want to execute) onto the stack, simulating what would happen if I had jumped to a subroutine with JSR. Then I toggle banks, and immediately RTS, which makes the Atari try to return from the subroutine, and jump to the target routine in the new bank:

    ORG $D009    ;Bank 0 uses a "pretend" address of $D000
    RORG $F009   ;but a "real" address of $F000
                 ;(The actual Atari ROM is always $F000) 

JumpToOtherBank
    LDA BANK_2   ;Reading this register switches banks
    RTS          ;and then jump to the location on the stack

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    ORG $E009    ;Now the same thing for Bank 1
    RORG $F009    

JumpToOtherBank
    LDA BANK_1   
    RTS          

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; And then a macro to make it easier to set up
; and call the JumpToOtherBank routine

        MAC BankJump 
            LDA #>{1}   ;push the target's MSB onto the stack
            PHA
            LDA #<{1}-1 ;push the LSB (minus one*) onto the stack
            PHA
            JMP JumpToOtherBank ;then call the magic routine

        ENDM


* I spent a good while fighting this code until I realized I needed the "minus one" on the least-significant-bit. Turns out JSR pushes one less than the address of the next command, and then RTS pulls that from the stack, and increments it. So I was 1 byte off when I'd try to RTS, which was breaking everything horribly.

I still need to actually break my code up into the different banks now. I'm hoping to fit the kernels and graphics data in bank 1, and everything else in bank 0. But we'll see.

And I still need to actually test my full kernel for 3 separate enemies. And do the enemy-wall collisions that I've been planning for weeks. But bank switching is more fun.....

Thursday, October 9, 2014

Jetpack and death counter

Tonight was back to Robo-Ninja. First, to make Aaron happy, I went ahead and created the jetpack item. He's been wishing for that non-stop for months, so I thought it would be a fun surprise to add for him. Plus, it was one of those few times that something was really easy. It helped that it's so similar to the double-jump item in function.

Then, I felt like doing something different (the scripting stuff, while interesting, required some serious thought and design work, which I didn't feel up to tonight), so I added something I've been planning for awhile: the death counter. What good is a hard game if it doesn't regularly remind you about how bad you are at the game?

With Anguna, when I released it, I had a little contest, where the first person to win the game with 100% of the hidden items being found, won a free copy of the game. It was a fun way to drum up some interest and attention. (and was fun from my end -- the 2nd place winner was only a few hours behind the real winner!)

I'm thinking about doing something similar with Robo-Ninja if I ever finish. To have a contest to see who can win with the fewest deaths. I'm not sure what the winner will get, but hopefully it will tempt a few crazy people into actually finishing the game. We'll see. (Realistically, I'll be lucky if I can get more than 5 or 10 people to actually notice and download the game. But hey, whatever).

Monday, October 6, 2014

3 enemy kernel (getting close!)

Spent awhile tonight working on trying to get my 3-enemy kernel working for the atari game. It basically amounted to a ton of branches, checking to see which enemies were close, which frame I was in, etc.

Basically, I have to see which enemies are close to each other vertically, pick the appropriate kernel (whether to render 1, 2, or all 3 enemies), then swap around the enemies in the enemy array to make sure that the correct enemies are rendered for this frame (since enemies that overlap a row only get rendered on alternating frames).

So tonight was my first pass at it. It works in a few of the cases, but completely freaks out in a few of the arrangements. So now I get to start the fun job of isolating which cases fail, and debugging. This should be fun.

Wednesday, October 1, 2014

Robo-Ninja Puppets

So now on Robo-Ninja I have the fun little diversion of figuring out how to do the scripted sequences for the beginning and ending stories. It will consist of little more than a handful of characters (Robo-Ninja, Prof. Treeblot, and Dr ?, maybe a robot guard) moving around and having word bubbles over their heads.

But I need a nice way of scripting it.

So that's what I'm currently working on, which is fun because it's a different sort of thing. I decided to not go all out and embed a lua interpreter in my game, but I AM making a super-simple script language that I'll run for each character.

I've extended my GameCharacter class (which handles drawing characters, animated them, detecting collisions with other characters) to create a new class that follows a script. Now I just need to actually implement the script system (and parser!)

I haven't finished planning it out fully yet, but I'm envisioning something like:

2.5   moveto 132,234
1.5   say    This is text that he will say!
1.5   wait
2.5   moveto 132,234
Where the first number is the amount of time to spend doing the action, the next token (separated by whitespace) is the action to take (I'm not sure I need much more than those 3 actions, unless I need some way to specify additional animation frames that a character should do (ie facial expressions?)). The moveto command should be smart enough to move at the right speed to take the amount of time specified on the left. And be smart enough to make sure the character is facing the right direction, showing the "walk" animation, etc.

Now I just need to sit back and think about whether I'm missing anything important. (And I'm still trying to decide if instead of a "wait" command I should include absolute start times on each command. That would be more complicated for me if I had to rearrange and re-time everything, but it might be easier for keeping multiple character's script information in sync. I'll still have to think on that.)

In other news, I got my Atari enemy display kernel working with 2 enemies right now. (No wall collision detection yet though). I'll see if I can get that 3rd enemy working, then try to tackle the collision detection.


NES Anguna

Well, I had a little bit of time still, while Frankengraphics is finishing up her game Project Blue, to have a little downtime on Halcyon, s...