Jump to content





Reindeer Rescue recap II

Posted by , 14 December 2005 · 222 views

2600 Game Development Reindeer Rescue
MORE DEBRIEFING of Reindeer Rescue. It was such a whirlwind putting this together over the last month that I feel a bit wrung out right now. I'm hoping to write some of the development stuff down here to try to remember and try to collect my thoughts.

So anyway. Here's a description of the kernel and how it works.

First, a picture:
Attached Image
The kernel has four main parts. The top part is the horizon, where the sky, the sun, the snowman, the snowmobile, and the hills are drawn. The sky is the background, the hills are the playfield (duplicated, symmetrical), and the other three objects are the players.

The sun is stationary; the snowman and the snowmobile move from right to left. The sprite used for the sun is repositioned to draw the snowmobile. The real tricky part was this: the snowman/snowmobile scroll off the screen (completely) and then a new object replaces each of them on the right. I am using the whole width of the screen; how can I scroll something off/on the screen? Well, each of the two objects has a dedicated variable which is used to mask them. It was pretty tricky figuring out how to set up these variables, but the code to draw them is pretty simple:
Screen4            ;
    jsr Ret3
    jsr Ret3           ;+24    51
    lda (Temp),Y
    sta.w PF0          ;+9 60
    lda (Temp+2),Y
    sta PF1        ;+8 68
    lda (Temp+4),Y
    sta PF2        ;+8 76
    lda (MidSkyDataPtr),Y
    and MSOMask
    sta GRP0           ;+11    11
    lda (LowSkyDataPtr),Y
    and LSOMask
    sta GRP1           ;+11    22
    dey
    bpl Screen4        ;+5 27
The masking variables are MSOMask and LSOMask.
Another thing that made the kernel tricky was this: You may notice that I claim that I repositioned the sun sprite to reuse it for the snowmobile. How is there, then, no HMOVE blank? Well, because I hit HMOVE early, ending at cycle 73, that's how. Actually, every HMOVE I strobe in the entire game is hit at cycle 73. This made the kernel a little convoluted, but this gave me the most headaches when I was figuring boundaries for sprites - because hitting HMOVE early moves all sprites 8 pixels left of where they would normally end up, when a sprite had an X-position of 3 it really ended up on the right side of the screen, at pixel 155.

Anyway, moving on. After the horizon is drawn, the playfield is erased and its color is changed to match the background, and both sprites are repositioned. Player 0 is used to draw Santa, and player 1 is positioned to draw the reindeer.

I also wanted the reindeer (and other flying things) to scroll smoothly on and off the screen - but, again, you can't really do that, since if you scroll things off the left edge of the screen they just show up on the right. So what I ended up doing was using PF0 to hide the sprites when they scroll on and off. So for about 20 scanlines PF0 is drawn on the left and then for the next 20 scanlines PF2 is drawn only on the right. So the playfield priority is changed so that the playfield appears in the foreground (after being in the background behind the snowman/snowmobile).

This means that flying objects need to scroll onto the screen from the upper right and scroll off the screen in the middle left. But the effect is pretty cool. :D

After these ~45 scanlines, the reindeer-sprite is repositioned and we begin the real fun part: the scrolling playfield. There are 8 bands of scrolling playfield, of varying heights, covering the next ~60 scanlines. For the top 7 scrolling bands, only the playfield and Santa are drawn; during the bottom band those are drawn plus the abominable snowman (A. Snowman).

The scrolling playfield is stored in RAM; 8 bands, each with all six playfield registers drawn to, means 48 bytes. The Running Man demo used 10 bands with another column (of 10 bytes) which contained the data that was being scrolled onto the screen. I couldn't afford that much RAM for the playfield, so I cut it to 8 bands and rewrote the scrolling routine to keep track of how many bits had been scrolled on so I could pull the new data directly from the ROM one bit at a time.

A. Snowman moves at exactly the same speed as the scrolling playfield; in four-pixel jumps. A little jerky, but still looks ok. After that is the floor, drawn with the background, then the string of Christmas lights, the score, and the Santa hats, all drawn with a general purpose subroutine I wrote that draws 48-pixels-wide bitmaps to the screen. That subroutine wasn't real hard to write, but it made creating the title screen, the game-over screen, the game-winning screen, and this section of the main kernel much easier. Just set the pointers to the bitmap and color data, set Y for how tall the bitmap is, and call the subroutine. It also made adding the score/hiscore to the title screen, game-over screen, and game-winning screen a breeze.




I wondered how all that stuff worked. ;)

(The surprising thing is - I actually understood some of that.)

It's interesting to see what went into the game, and how that affected the way the graphics had to be made. Pretty cool stuff.

I wonder if a variation on this could be adapted into a port of Scramble? :)

Anyway, I wanted to say "congratulations" on finishing up Reindeer Rescue! I know that there were a few bugs you had to squash at the last minute, but the finished game is excellent!

Despite all of the testing I did, I still enjoy firing up the game just to play it. I think that speaks volumes as to how well it turned out.

(And here's hoping it shows up in the High Score Club... 'cause I'm ready! ;) )
  • Report
Hi there!

I wonder if a variation on this could be adapted into a port of Scramble? :)


Or Sonson or Ghosts'n'Goblins? ;)

Greetings,
Manuel
  • Report
OK, Bob. I got my cart today in the mail and I was going to wait till Christmas to open it up and play it but... the kids cajoled me into trying it right away, yeah that's it. OK, maybe not, but we all enjoyed playing the game. We fought over control actually. So great job. How enjoyable.
  • Report
Glad you enjoyed it! I got mine in the mail yesterday and I had fun with it also. :)
  • Report
Now that I have a cart and have been thoroughly enjoying it, it's nice to read how it works. Two things I'm wondering about:

1) For the scrolling playfield, how did you cope with PF1 and PF2/PF0 being read in different directions?

2) I keep reading that it is easier to write original music than to port existing songs to the VCS, and yet all the songs you did sound great. Did you use any special tricks?

p.s. Suburbia is quite a challenge. :) :D
  • Report

Now that I have a cart and have been thoroughly enjoying it, it's nice to read how it works. Two things I'm wondering about:

Thanks for the interest. I'll release the source sometime soon, I think.

1) For the scrolling playfield, how did you cope with PF1 and PF2/PF0 being read in different directions?

I'm not exactly sure what you mean - the kernel looks like this, in part:
   align 256

LoopBand3
    nop        ;+2 72
    nop        ;+2 74
BeginBand3
    sta.w GRP0
    lda (P0DataColorPtr),Y ;+5  7
    sta COLUP0     ;+3 10

    dey        ;+2 12  decrement absolute scanline value by one here
    sec        ;+2 14

;---Next 12 lines (machine cycles 15-50) draw an asymmetrical playfield---

    lda PF0Data+3  ;+3 17
    sta PF0    ;+3 20  PF0 drawn to screen at machine cycle (MC) 22.7
    lda PF1Data+3  ;+3 23
    sta PF1    ;+3 26  PF1 drawn to screen at MC 28
    lda PF2Data+3  ;+3 29
    sta PF2    ;+3 32  PF2 drawn to screen at MC 38.7
    lda PF3Data+3  ;+3 35
    sta PF0    ;+3 38  PF0 finished at MC 28, drawn to screen again at MC 49.3
    lda PF4Data+3  ;+3 41
    sta PF1    ;+3 44  PF1 finished at MC 38, drawn to screen again at MC 54.7
    lda PF5Data+3  ;+3 47
    sta PF2    ;+3 50  PF2 finished at MC 49.3, drawn to screen again at MC 65.3

;---End draw of asym playfield---

    lda #P0HEIGHT-1
    dcp SantaTemp
    bcs DoDrawBand3    ;+2/3   59/60
    lda #0         ;+2 61
    .byte $2C      ;-1 60
DoDrawBand3
    lda (P0DataPtr),Y  ;+5 65

    dex        ;+2 67
    bne LoopBand3      ;+2/3   69/70
And the scrolling routine looks (in part) like this:
ScrollRoutine
    lda NewDataCounter
    and #NEWDATA_BITS
    tay
    lda Temp-1,X
    cpy #0
    beq NoAdjustToPFData
SetupBandDataLoop
    asl
    dey
    bne SetupBandDataLoop
NoAdjustToPFData
    asl
    ror PF5Data-1,X
    rol PF4Data-1,X
    ror PF3Data-1,X
    clc
    lda #%00001000
    and PF3Data-1,X
    beq PF2Rotate
    sec
PF2Rotate
    ror PF2Data-1,X
    rol PF1Data-1,X
    ror PF0Data-1,X
    dex
    bne ScrollRoutine
That answer your question? I'm happy to explain more/better if you want. ;)

2) I keep reading that it is easier to write original music than to port existing songs to the VCS, and yet all the songs you did sound great. Did you use any special tricks?

A couple of tricks/techniques.
First, I used, extensively, Thomas Jentzsch's tune2600 program.
Second, I played around with a lot of songs; trying to find which ones sounded the best. You can get away with out-of-tune notes when 1. they are played alone (no harmony) and 2. they are reasonably in tune with the notes immediately following/preceding them. Also, if the out-of-tune notes are quick you don't notice as much.
Third, my brother helped me rewrite Silent Night a little and pointed me towards this set of notes, which are all relatively in tune with each other:
e3f3g3a3b3
c4c#4d4d#4e4f4f#4a4b4
c5d5e5f#5g5g#5a5a#5b5
c6c#6e6g6a6

These notes are a little iffy: b3, f#4, f#5, c#6
But the rest are all around +/-10 cents. Notice that you have this string of consective notes (in the C major scale): e3 to f4 plus this almost string: a4 to c6 (minus f5)

For putting together single melody lines without harmony you have a lot of options there.

p.s. Suburbia is quite a challenge. :) :D

Yeah...wait until you get to the city. :)

:)
  • Report
Thanks for sharing the code Bob. The scrolling routine is simpler than I had imagined. It had never occured to me that you can use ROL/ROR to transfer data between different bytes via the carry bit. (I ought to read others' code more often.)

When you experimented with different songs, did you try out "O Tannenbaum" (a.k.a "O Christmas Tree)? I was thinking of using that song if I ever get around to doing "X-Bert"

I'm getting good at the suburban level, and have reached the train in the city. :)
  • Report

    ror PF3Data-1,X
    clc
    lda #%00001000
    and PF3Data-1,X
    beq PF2Rotate
    sec
PF2Rotate

6+2+2+4+2+2 = 18 cycles (10 bytes)

How about
    ror PF3Data-1,X
    lda #8
    and PF3Data-1,X
    cmp #1; Carry set if NZ

6+2+4+2 = 14 cycles (8 bytes)

Another variation:
  lda PF3Data-1,X
  ror
  sta PF3Data-1,X
  and #8
  cmp #1
4+2+4+2+2 = 14 cycles (9 bytes) Not sure if there's any advantage to this version. If an "rol" wer needed instead of "ror", there'd be some 'undefined opcodes' that might be helpful, but nothing looks apropos here.
  • Report
Thanks for the suggestions, John - but a bit too late! :)

Actually, I wrote the scrolling routine over a year ago (early Dec 2004) and, since it worked, didn't mess with it too much when I picked it back up this November. I kinda knew that the method I was using there could probably be improved, but I had lots of non-working things to deal with so improving the working sections was low on the priority list. :D

...


And, actually, I just remembered that today is December 27, the day I was going to release the ROM for this. I gotta go do that!
  • Report