A$$emble IT - Programming tutorial

Project Housekeeping, Custom Sprite Moving, Screen Scrolling with Additional Game Projects

Project Housekeeping / Custom sprite movement / Directional Detection Movement / Game 8: Granny's Teeth (Craptastic version) / Recorded Path Movement / Timed Control Movement
/ Background + Character Animation and Star Fields / Horizontal Scrolling / GAME 9: Blastopia
/ Vertical Scrolling /Scrolling Upwards / Scrolling Downwards / GAME 10: Super Toboggan Challenge

Project Housekeeping

Probably the most boring subject in the Assemble It feature, but it can be a good option for you to learn from. Although there is nothing to download from this little chapter. There are some example games which also use the housekeeping method anyway. So how helpful is this part of the chapter supposed to be for you?. Let us put things this way. If you were to create a bigger game project, and use just one assembly file to program your game. There could be very long strings of code, and you can easily get yourself lost. (Like you probably are already in this part of the chapter).

Let us say for example, you are writing a new C64 game, it has a title screen, the main game, and fully animated end sequence. You should create SEPARATE assembly files in which should be linked to your project. Let us take for example, Missile Blasta (from the previous chapter) for example. The game should be split into separate assembly files: The main project code (to assemble to should be MISSILEBLASTA.ASM TITLESCREEN.ASM, GAMECODE.ASM, ENDING.ASM. (To create your own assembly source code, simply do the same as you did before when you created a new project. (ADD, NEW ASSEMBLY FILE, enter name of your assembly code). It will be placed in the project.

Now what if you wanted to link the entire source together, which you already done - placed into separate files?

Simple:

You enter !source "assemblyfile.asm"

An example for setting a Missile Blasta V2, project with additional ASM files would sort of look something like this:

;Missile Blasta - Remastered V2
;by Richard Bayliss
;For Assemble It 2018

    !to "MISSILEBLASTA.PRG",CBM

;setup basic / sys start address (since nothing
    ;is overlapping memory $0801)

    *=$0801
    !basic 2018,$3800

    *=$0c00
attribs
    !bin "bin/attributes.bin"


    ;insert the sprites
    *=$2000
    !bin "bin/sprites.bin"

    ;insert the status screen (pre-built from older build)
    *=$2700
    !bin "bin/status.prg",,2

    ;insert the game binary charset
    *=$2800
    !bin "bin/charset.bin"

    ;insert the game screen binary (made from charpad)
    *=$3000
gamematrix
    ;insert the game screen matrix (made from charpad)
    !bin "bin/screen.bin"

    *=$3400
titlematrix
    ;Insert the title screen matrix (made from charpad)               
    !bin "bin/titlescreen.bin"

;Main game code
    *=$3800
    !source "GAMECODE.ASM"
;
Insert title screen code
    !source "TITLECODE.ASM"
;
Insert end sequence code
    !source "ENDCODE.ASM"

scorebackup
    !bin "bin/status.prg",,2
    ;insert the music (,,2 prg)
    *=$8000
    !bin "bin/music.prg",,2
    ;goat tracker custom sound effects table

So remember, any time you create a new C64 project (Which you will discover in the next few chapters on this page), always organize your project into separate parts. It isn't all that hard.

BACK TO TOP



Custom Sprite Movement

There are several different ways in which you can make a custom based movement of an object. Let us say for example rather than a sprite going one specific direction, you can alter directions for each sprite.

The directional detection movement

Example: Honey Bee



There is also another method to sprite movement. Let us take for example you are writing a game, such as Honey Bee, Balloonacy or any sort of single screen dodge, collect and platform game. You would want to have objects moving in a fixed direction, then flip from one direction to another. This is probably the most simplest approach to custom sprite movement. A subroutine is called to first check the value of a pointer, for example ObjDir is set as the object direction. If the value of ObjDir is set to a specific value, called, for example 0 = Up, 1 = Down, 2 = Left, 3 = Right then the code should call the sprite object to move that specific direction. That is of course until it reaches the set limited position, from either a fixed value, or a value from a custom pointer. Then the code forces the object to change direction. The code snipped below shows an example of how to move a single sprite object using that specific method.

;Move sprite, according to direction snippet

TestObjectDirection
    lda ObjDir
    cmp #UP
    bne NotUp
    jmp MoveUp

NotUp
    cmp #DOWN
    bne NotDown
    jmp MoveDown

NotDown
    cmp #LEFT
    bne NotLeft
    jmp MoveLeft

NotLeft
    cmp #RIGHT
    bne NotRight
    jmp MoveRight

;Move sprite UP, then once set at limited position,
;switch the direction to DOWN.

MoveUp
    lda ObjPos+1    ;Grab position of object Y
    sec
    sbc #2          ;Movement speed backwards
    cmp #$32        ;Stopping position
    bcs UpdateUp    ;Not reached destination yet
    lda #DOWN       ;Force DOWN to pointer ObjDir
    sta ObjDir
    rts
UpdateUp            ;Store new position to object Y
    sta ObjPos+1
    rts

;Move sprite DOWN, then once set at limited position,
;switch the direction to UP.

MoveDown
    lda Objpos+1    ;As before, grab position of object Y
    clc
    adc #2          ;Movement speed forwards
    cmp #$f2        ;Has sprite reached the bottom?
    bcc UpdateDown  ;Not reached destination yet
    lda #UP         ;Force UP to pointer ObjDir
    sta ObjDir
    rts
UpdateDown          ;Store new position to object Y
    sta ObjPos+1
    rts

;Move sprite LEFT, then once set at limited position,
;switch the direction to RIGHT.

MoveLeft
    lda ObjPos      ;This time we are using X
    sec
    sbc #1          ;Movement speed backwards
    cmp #$0c        ;Has sprite reached left most limit
    bcs UpdateLeft  ;Not reached destination yet
    lda #RIGHT      ;Force RIGHT to pointer ObjDir
    sta ObjDir
    rts
UpdateLeft          ;Store new position to object X
    sta ObjPos
    rts

;Move sprite RIGHT, then once set at limited position
;set the direction to LEFT.

MoveRight
    lda ObjPos       ;Grab current position of sprite
    clc
    adc #1           ;Movement speed forwards
    cmp #$a2         ;Has the sprite reached right most limit?
    bcc UpdateRight  ;Not reached destination yet
    lda #LEFT        ;Force direction LEFT to pointer ObjDir
    sta ObjDir
    rts
UpdateRight           ;Store now position to object X
    sta ObjPos
    rts

This code snippet above shows only an example of moving one sprite back and forth, depending on which direction you have set the pointers. But what if you wanted to do this to ALL 8 sprites? Well, it is possible to move each sprite individually using multiple routines and macros. Simply define some pointers for direction for each sprite, also define macros correctly and call a few subroutines to test each sprite movement. Also have a play around with the example source snippet and see what you can make from it.



View source code



BACK TO TOP

  

GAME 8: Granny's Teeth (The original 4K Craptastic Compo version)

Although I have showed you an example of the code above to allow all 8 sprites move according to path/direction. We are going to show you an example game, which uses a similar approach, although it is quite an old game. Back in 2016 I entered the C64 craptastic game making compo, which was to create and develop a game that squeezed into 4K (after compression through the ALZ64 compressor). It was a platform game, inspired by one of those games creator games, from back in the early-mid 1980's. This game was made to look a bit like one of those Games Creator/Creations games, only just for fun. However, to prove that this game was NOT made with the games creator. A complete project binary+source has been provided to accompany this chapter on fixed sprite movement and changing directions.

So what's this game all about?. You play the role of Granny, who has tucked her grandchildren into bed. Locked all of the doors, and placed her teeth safely into her room and went to sleep. The next morning, Granny wakes up to find her teeth had gone missing. Also, she finds that her house has been ransacked. The floorboard had collapsed revealing that her house was built over a swimming pool. Her cat and dog are on the loose, a bird has flown in, and who the heck let that spider in?. If all was that bad? Her teeth had gone missing. The kids were playing with her teeth and thrown them into the fishbowl. It is now up to you to try and fish your teeth out.

Granny must jump from platform, to platform, avoiding contact with any tacks strewn on the floor. She cannot swim either. She has to avoid any moving objects or pets in sight and pick up her teeth. You will score bonus points and move on to the next level for every time teeth have been picked up.

This game also features an implementation of Achim Volker's useful Sprite/Background collision which calculates the X, Y position of the player sprite, and checks whether or not the lower part of the sprite is touching the pixel of the background (When using software sprite/background based collision, X and Y position of the player sprite must be accurately set to the pixel which the player object lands. This game also uses the directional control of the sprite movement, similar to very first example in this category. A software sprite/sprite collision detection is used. The project also uses the project housekeeping method which helps organize the program files and code. The active assembly file is of course Granny.asm. So this file should be marked as active source when compiling the binary data and code.



Download the full C64 Studio binary + source code to Granny's Teeth



BACK TO TOP




You are writing a space shoot 'em up. You have a player which is controlled by a joystick. The player can fire, but there are also enemies that can move around. There are two different ways in which you could create an attack movement pattern for an enemy sprite. They are as follows:

Recorded Path Movement

Example: X-Force



The recorded path movement is simply created utility based. The utility based path movement, is where you record and create the movement, according to the position of a sprite you set it. A maximum of 256 units is used for making your own alien path movement. One particular tool which can produce custom object movements, based on sprite position is the TND Alien Movement Maker V1.0+ (Available in the utilities section). This tool allows you set a starting position to a sprite and then record its movement. Be very careful when using this. You'll need to remember the screen size which you plan your game project.  I first created my own source code to do this for the game X-Force (pictured above). A small code example for moving an object based on table movement would look something like this:

;Example path movement code (based on reading the table)

MoveEnemy1
    ldx Enemy1MovePointer     ;Pointer to set position of enemy
    lda PositionTable,x       ;Read PositionTableX to grab a X position
    sta EnemySpritePosX       ;Write the position to the sprite X position
    lda PositionTable+$100,x  ;Read PositionTableY to grab a Y position
    sta EnemySpritePosY       ;Write the position to the sprite Y position
    inx                       ;Increment value of pointer loop for moving object
    beq RemoveEnemy1          ;Until all 256 byts are read
    inc Enemy1MovePointer     ;Increment EnemySpritePos X+Y table by 1 byte
    rts                       
RemoveEnemy1                  ;All 256 bytes from X+Y position table complete.
    ldx #0                    ;Reset the pointer of the enemy movement
    stx Enemy1MovePointer
    jsr SetNextTable          ;Call a subroutine to move the next table.
    rts

;Example binary file (inside project)

    *=$7000 ;Or where to put the movement data
PositionTable
    !bin "movement.prg",,2

Please also note that this method uses slightly more memory. Alternatively, if you cannot wait all that long .... You can check out issue 27 of Scene World, and check out the source code for Zap Zone 




Alternatively, if you want to learn an even bigger/advanced phase in game programming. You could always try 
Star Toast from issue 28 of Scene World magazine. Please note that both games were coded in 64ASM, which means that you will need to alter some of the pseudo commands, should you wish to port those games to C64STUDIO, and tweak the code for fun. (Or make your own game from it).



BACK TO TOP



The Timed Control  Movement



Example: Starfysh

There is also another example, which is slightly trickier, compared to the option (above). Especially if you want to have objects using timed movement, based on behaviour patterns. This is because you need to use MORE pointers, although it does use up less memory. The trick does work a treat. You first set the starting X, Y position of a sprite outside the border. Set the pointer/delay and then another pointer read, to read the table position, also your read the speed table of the sprite. After the time of one position has expired, the table read pointer increments to the next position on the table, recording the next X,Y speed (direction) of the object. Then store it to the sprite position. Sometimes this can result into awkward consequences, but eventually you can get some great enemy movement patterns - and not just going one straight direction. Of course, you will need to create/generate a table of bytes that set the time value for each movement, X-Speed, Y-Speed of moving sprite, and of course some other bits. You will be able to see a full implementation of enemy movement code in GAME 9 - BLASTOPIA. Which is also available on this chapter.

;Example timed speed object movement

MoveEnemy1
   

        lda objpos+4
        clc
        adc Alien1XSpeedStore
   
        sta objpos+4
        lda objpos+5
        clc
        adc Alien1YSpeedStore
        sta objpos+5
       
        ;Calculate flip properties - So that
        ;the movement can be triggered to change

        ;direction
        jsr Alien1FlipTest           
        rts

;Actual flip test for alien 1

Alien1FlipTest

        lda Alien1FlipDelay    ;Alien flip delay test ... Counter
        cmp Alien1_FlipTime
        beq Alien1SwitchToNextMotion ;Switch to next motion from table
        inc Alien1FlipDelay
        rts

        ;Switch alien motion

Alien1SwitchToNextMotion
       
        lda #$00                ;Reset alien flip delay
        sta Alien1FlipDelay
        ldx Alien1FlipPointer   ;Read pointer
        lda Alien1_XSpeed,x     ;Read selected X direction/speed from table
        sta Alien1XSpeedStore   ;Store it to alien speed X (Alien1SpeedStore)
        lda Alien1_YSpeed,x     ;Read selected Y direction/speed from table
        sta Alien1YSpeedStore   ;Store it to alien speed Y (Alien1SpeedStore)
        inx
        cpx #$04                ;Has the table reached the last byte on each speed table?
        beq Alien1SpeedReset    ;Yes ... Reset the speed table.
        inc Alien1FlipPointer   ;Then move to next table setup (To be Self-modified)
        rts

        ;Reset flip table and delay for alien 1

Alien1SpeedReset           
        ldx #$00
        stx Alien1FlipPointer
        lda #$00
        sta Alien1FlipDelay
        rts
       
        ;Time alien 1's movement. Has it reach its movement deadline
        ;if so. Switch over to the next set of properties for the next
        ;alien. Then spawn the next alien object.

TimeAlien1Movement
        lda Alien1MovementPointer   
        cmp Alien1_MoveTime
        beq Alien1WaveComplete
        inc Alien1MovementPointer
        rts
Alien1WaveComplete       
        lda #$00
        sta Alien1MovementPointer
        lda #1
        sta Alien1Offset
        rts

BACK TO TOP



Background Character Animation and Starfields

When producing a game or a demo, it is possible to generate animation through character sets. There are different methods in which you can animate a character. You could scroll a character, (or a set of characters) to form an effect, or maybe roll through a set of characters one after another by using frames. Here are a few examples to try in C64Studio.

The scrolling character

The most easiest way to scroll a character would be to scroll it left/right. Or alternatively you could scroll the character up or down. The concept would be to pick out the address of the 8 bytes of a character you wish to scroll and produce a simple loop to roll the character along. Scrolling characters left/right are the most simplest way to move them. However if you wanted to scroll characters UP/DOWN then you would need to generate some pointers for those.

A small example of scrolling a char left:

;Small example of scrolling a character left

    !to "scrollcharleft.prg",,2

    *=$0801
    !basic 2064    ;SYS2064
    *=$0810

    lda #$18     ;Set character mode to $2000-$2800
    sta $d018
   
    ;Fill the entire screen with zeroed character

    ldx #$00
FillScreenWithChar
    lda #$00 ;Very first char (@)
    sta $0400,x
    lda #1
    sta $d800,x
    inx
    bne FillScreenWithChar

TestLoop
    lda #$80
    cmp $d012
    bne *-3
    jsr ScrollCharsLeft
    jmp TestLoop

ScrollCharsLeft

    ;Scroll the very first character
    ;to the left.

    ldx #$00
ScrollLeft
    lda $2000,x    ;Each char = 8 bytes.
    asl            ;
    rol $2000,x    ;Rotate char to the left by one byte
    inx
    cpx #8         ;8 bytes read
    bne ScrollLeft
    rts

    ;Let's make a custom character for char 0 (Alternatively
    ;as a shortcut, you can import a charset using the !bin command
    *=$2000
    !byte %11111111
    !byte %10000001
    !byte %10111101
    !byte %10100101
    !byte %10100101
    !byte %10111101
    !byte %10000001
    !byte %11111111

    ;End of program

Without changing the additional source in GREEN. Copy the code and change the YELLOW example to this next example below.

A small example of scrolling a character to the right

 ScrollCharRight

    ldx #$00
CharRight:
    lda $2000,x    ;Each char = 8 bytes.
    lsr
    ror $2000,x    ;Rotate char to the right
    inx
    cpx #8
    bne CharRight
    rts

Rotating the characters left/right are much easy to perform. A word of warning however. If you try and scroll multicolour chars, they will change colour. In order to solve this problem. You could add and extra LSR ROR $CHAR, or ASL ROL $CHAR. This should prevent colour shifts of each character.

Moving Charsets Up / Down

What if you wanted to move characters up or down? It works completely differently. You would need to make a temp byte to store the current character byte to. Scroll the character in a forward or backward loop (depending on the direction your character is scrolling) then store the stored loop to either the FIRST or LAST byte of the character that is being read.

A small example of scrolling a character up

ScrollCharUp

    lda $2000
    sta CharByteStore
    ldx #$00
ShiftCharUp
    lda $2001,x ;Fetch last char
    sta $2000,x ;move to the next char
    inx
    cpx #8
    bne ShiftCharUp
    lda CharByteStore
    sta $2007 ;Grab last character
    rts

CharbyteStore !byte 0

A small example of scrolling a character down

ScrollCharDown

    lda $2007
    sta CharByteStore    ;We just reverse the above
    ldx #$07             ;process
ShiftCharDown
    lda $2000-1,x
    sta $2000,x
    dex
    bpl ShiftCharDown
    lda CharByteStore
    sta $2000
    rts

Swapping Frames

It is also possible to animate charsets by swapping their own frames. You will want to work out how many frames your animation charset should use and how to come about with them. We simply copy 8 bytes of the first character, and paste it to the last character. Then call a simple loop which pulls each of the 8 bytes of each character frame. So that there is an actual character animation. This works like this:

AnimChars
    lda AnimDelay
    cmp #4
    beq AnimOK
    inc AnimDelay
    rts
AnimOK
    lda #0
    sta AnimDelay
    ldx #$00
CopyAndPaste1
    lda $2000,x
    sta $2040,x
    inx
    cpx #8 ;8 bytes = 1 char
    bne CopyAndPaste1
    ldx #$00
CopyAndPaste2
    lda $2008,x
    sta $2000,x
    inx
    cpx #$40 ;Max, 8 charaacter frames!
    bne CopyAndPaste2
    rts
AnimDelay !byte 0

Star Fields

Generating char-based star fields can be quite awkward, but are actually VERY EASY to implement. All you need is to know the memory address of where the frames for your char star field is. Then roll each byte forward or backwards and follow on, for the other charsets. Here is a small example on how a star field animation is formed (Using different layers). The example code below uses a star field that consists of four characters in the char set. $2000-$2020. A pixel skip is placed in the code, in order to rotate the same byte of the whole 4 characters. It is possible to increase the size of the star field. Simply by generating additional characters.

;Character based wrap-around animation. Produce
;a starfield, by rolling the bytes of the
;selected layers to be animated at different speeds

StarField
        jsr AnimPixel1
        jsr AnimPixel2
        jsr AnimPixel2
        jsr AnimPixel3
        jsr AnimPixel3
        jsr AnimPixel3
        jsr AnimPixel4
        jsr AnimPixel4
        rts
       
;Animate the first chosen pixel on the
;current charset - Scroll it to the left.

AnimPixel1
        lda CHARSET+1
        asl
        rol CHARSET+1+24
        rol CHARSET+1+16
        rol CHARSET+1+8
        rol CHARSET+1
        rts
       
AnimPixel2
        lda CHARSET+3
        asl
        rol CHARSET+3+24
        rol CHARSET+3+16
        rol CHARSET+3+8
        rol CHARSET+3
        rts
       
AnimPixel3
        lda CHARSET+5
        asl
        rol CHARSET+5+24
        rol CHARSET+5+16
        rol CHARSET+5+8
        rol CHARSET+5
        rts
       
AnimPixel4
        lda CHARSET+7
        asl
        rol CHARSET+7+24
        rol CHARSET+7+16
        rol CHARSET+7+8
        rol CHARSET+7
        rts

That covers this section on character animation.

BACK TO TOP

       

SCREEN HORIZONTAL SCROLLING



In an earlier chapter, I showed you how to scroll text messages. This time round, I am going to show you how to scroll more than just text. Let us assume you are writing a new C64 game, and you want to scroll a background for the first time, without having to generate blocks/maps. Well, it is possible. What you have to do is set a speed of a scrolling pointer. Then we pull characters back through a loop. Once that has been done, a memory read of the map area is then placed as last character for each row. Then we update the code to increment to the next LO-BYTE of the map column address on the picked row. A code snippet below shows this simple example.


;Simple Screen Horizontal Scrolling
;by Richard Bayliss

        !to "horizscreenscrl.prg",cbm


SCRN = $0400

        jsr InitMapPosition
TestLoop
        lda #$f8
        cmp $d012
        bne *-3
        jsr ScrollMap
        lda SCROLLX
        sta $d016
        jmp TestLoop  
           
;Start by setting the scroll speed.
ScrollExit
        rts
ScrollMap
        lda SCROLLX
        sec
        sbc #MAPSCROLLSPEED
        and #$07
        sta SCROLLX
        bcs ScrollExit
       
;Shift a byte from each character on screen
;then move it to the next character on screen.
;Split this into four different loops. (5 rows per
;loop)

        ldx #00 ;Grab last value of char - then add it
MoveRow1
        lda SCRN+1,x  ;Grab one screen column higher
        sta SCRN,x        ;pull it to the previous column
        lda SCRN+41,x
        sta SCRN+40,x
        lda SCRN+81,x
        sta SCRN+80,x
        lda SCRN+121,x
        sta SCRN+120,x
        lda SCRN+161,x
        sta SCRN+160,x
        inx
        cpx #$28
        bne MoveRow1
       
        ldx #00 ;Do the same thing for the next set of rows
MoveRow2
        lda SCRN+201,x
        sta SCRN+200,x
        lda SCRN+241,x
        sta SCRN+240,x
        lda SCRN+281,x
        sta SCRN+280,x
        lda SCRN+321,x
        sta SCRN+320,x
        lda SCRN+361,x
        sta SCRN+360,x
        inx
        cpx #$28
        bne MoveRow2
       
        ldx #00 ;Once again do the same for the next set of rows
MoveRow3
        lda SCRN+401,x
        sta SCRN+400,x
        lda SCRN+441,x
        sta SCRN+440,x
        lda SCRN+481,x
        sta SCRN+480,x
        lda SCRN+521,x
        sta SCRN+520,x
        lda SCRN+561,x
        sta SCRN+560,x
        inx
        cpx #$28
        bne MoveRow3
       
        ldx #00 ;Finally for the last 5 rows
MoveRow4
        lda SCRN+601,x
        sta SCRN+600,x
        lda SCRN+641,x
        sta SCRN+640,x
        lda SCRN+681,x
        sta SCRN+680,x
        lda SCRN+721,x
        sta SCRN+720,x
        lda SCRN+761,x
        sta SCRN+760,x
        inx
        cpx #$28
        bne MoveRow4
       
       
;Now place the self-mod stored character on to the
;last byte of each row.

mapSM1
        lda MAPROW1
        sta SCRN+39
mapSM2
        lda MAPROW2
        sta SCRN+79
mapSM3
        lda MAPROW3
        sta SCRN+119
mapSM4
        lda MAPROW4
        sta SCRN+159
mapSM5
        lda MAPROW5
        sta SCRN+199
mapSM6
        lda MAPROW6
        sta SCRN+239
mapSM7
        lda MAPROW7
        sta SCRN+279
mapSM8
        lda MAPROW8
        sta SCRN+319
mapSM9
        lda MAPROW9
        sta SCRN+359
mapSM10
        lda MAPROW10
        sta SCRN+399
mapSM11
        lda MAPROW11
        sta SCRN+439
mapSM12
        lda MAPROW12
        sta SCRN+479
mapSM13
        lda MAPROW13
        sta SCRN+519
mapSM14
        lda MAPROW14
        sta SCRN+559
mapSM15
        lda MAPROW15
        sta SCRN+599
mapSM16
        lda MAPROW16
        sta SCRN+639
mapSM17
        lda MAPROW17
        sta SCRN+679
mapSM18
        lda MAPROW18
        sta SCRN+719
mapSM19
        lda MAPROW19
        sta SCRN+759
mapSM20
        lda MAPROW20
        sta SCRN+799
       
;Now to get a new character fetched from the map, increment
;the lo/byte of the self-modifying addresses in order to
;allow all 256 bytes to update the last character that was
;used after every frame scrolled.

        inc mapSM1+1
        inc mapSM2+1
        inc mapSM3+1
        inc mapSM4+1
        inc mapSM5+1
        inc mapSM6+1
        inc mapSM7+1
        inc mapSM8+1
        inc mapSM9+1
        inc mapSM10+1
        inc mapSM11+1
        inc mapSM12+1
        inc mapSM13+1
        inc mapSM14+1
        inc mapSM15+1
        inc mapSM16+1
        inc mapSM17+1
        inc mapSM18+1
        inc mapSM19+1
        inc mapSM20+1
        lda mapSM1+1
        bne InitMapPosition
        rts


Now, if you wanted to initialise the map so it starts again you would need to LOAD the LO-BYTE of the map address, and then STORE it to self-mod code. For example, if MAPROW address of map to be read = $4000. We place the low byte of MAPROW to the self mod one byte after the address where the label pointer mapSM1 is. Also we place the hi byte of MAPROW to the self mod address 2 bytes after the address where the label pointer mapSM1+1 is. To get the whole 20 rows cycling like the first MAPROW, we select every 256th address of low and hibyte to set up the map row / column.

InitMapPosition
        lda #<MAPROW
        sta mapSM1+1
        lda #>MAPROW
        sta mapSM2+2
        lda #<MAPROW+$100
        sta mapSM2+1
        lda #>MAPROW+$100
        sta mapSM2+2
        lda #<MAPROW+$200
        sta mapSM3+1
        lda #>MAPROW+$300
        sta mapSM3+2   


VIEW COMPLETE SOURCE
but don't forget to use charpad to draw your own test charset and map (Export your test project as charset, and map). For this example, your map needs to be 20 rows by 256 columns with tiles disabled.
   
Now, should you wish to try and build scrolling background using BLOCKS+MAPS, instead. I strongly recommend that you check out Achim Volker's chapter on column map extraction, on Codebase64.

BACK TO TOP

     
GAME 10 - BLASTOPIA



Now I have covered a lot of tips in the previous few sub chapters that are in this chapter. I have decided to come up with a brand new game project, for us to play with. As a fan of the retro blasting shoot 'em up genre. I have come up with a new shoot 'em up specially for this project. If you want to just play the original game, without the source, you will find it on the TND games page, under BLASTOPIA :). This awesome game features a short front end and end screen, 8 fast paced levels of alien blasting mayhem (all controlled by pointers), 24 different alien sprites, camels (which you must rescue during play), sprite/sprite collision, sprite/background collision (Based on Achim Volker's codebase example). There's also plenty of thumping trance music and sound effects. Hopefully you should have loads of fun playing this game (or maybe exploring through the code). Full instructions on how to play the game, can be found in the RELEASE folder. Which also consists with a finally compiled disk and .tap version (with tape master if you want to write this game to a real tape).

About the project:

THE SPRITES

These are the game sprites, that I will be using for Blastopia. There are 4 frames for each sprite, except for the bullet and the explosion animation. The player ship sprite animation are the first 4 sprites, a bullet is the second. The explosion sprites, followed by all of the aliens. The last 4 sprites are the camels which the player will have to rescue. Each sprite has its own colour and animation scheme.



THE GRAPHICS

This is the complete character set for BLASTOPIA. It represents all of the characters that builds each level map screen. If you load in one of the example level files into charpad and then use IMPORT RAW/BIN FILE / CHARSET, select the folder FILES and select gamecharset.bin. You should get this set of characters.  The characters 0 - 3 have been chosen to be animated as a star field (More on this in the tutorial). Character 42 has been chosen as the lazer gate character. Characters 33 and 38 represent the flashing lights for the landing pad. The rest of the graphics (Except for the letters, symbol and numbers) are deadly characters - The landing pad characters 35-40 (except for character 38) are special characters. 



Below, you can see the whole map of LEVEL 1, which consists of 256 characters per row and 20 rows.  There are also additional charpad level files where each map is built by 5 rows and 64 columns of 4x4 tiles. Disabling the tile mode gives you tiles. The last 5 rows are saved for a blank gap, and the game's status panel.



LEVEL MAP CRUNCHING / ADDING A BACKWARDS DECRUNCHER

In the files DIR are all of the example maps (Un-Compressed). These maps have been extracted as RAW binary format. However, if you're making a full game, you'll want to consider crunching each level map. So that they will fit in the game code. To crunch each exported map. You can use the EXOMIZER to crunch the memory down, and use a DECRUNCHER source program to extract the data. The command used as an example:

exomizer mem -l $b400 level1map.bin,$9ffe -o level1map_cr.prg -P0

Please also note, if you are not using Exomizer V3, but V2 instead you won't need to -P0  prefix

$b400 has been set for the address where to place the crunched data.
$a000 is where the data should decrunch to ($9ffe is 2 bytes offset before the actual decrunch address for raw files. PRG source files can use $a000 instead of $9ffe).


The -P0 is a prefix for using V2.0 level compression. As V3 hasn't got an actual decrunch source yet. For the decruncher, you will need DASM cross assembler, and edit WRAP2.s in notepad to set the address of the decruncher. Then in the command prompt enter

DASM wrap2.s -oleveldecruncher.prg

I edited the wrap2.s to set the address of the exomizer decruncher at $c400. Once assembled through DASM to leveldecruncher.prg, the file gets copied over to the FILES folder, along with the other binary data, including sprites, alien sprite formation data, charset, uncrunched maps, etc.

SETTING UP SPRITE FORMATION



Also provided with the binary and source code. I have provided an exclusively modified version of my Alien Formation Maker V1.0+, which has been made for BLASTOPIA. So you can have a go at creating your own alien formation, and then import it into the game code later on (if you want to that is). The aliens should have a selected movement during the game. While using the editor press F1, F3, F5, F7 to alter the formation speed. Select inside the red border (where the cursor cannot be seen) by placing the cursor as the start position. Then hold down the fire button to record the path movement by playing around with the joystick. You have 256 units to use up per formation. These will be used quite quickly with slower movements. Watch you don't make too any mistakes. 256 bytes of the alien formation are stored for X ($3000-$30ff) and Y ($3100-31ff). If you are finished with an alien formation and you still have units spare - make sure it ends inside the red border. Otherwise the alien formation will look very odd indeeed :)

THE GAME MUSIC

I have chosen three tunes crammed to one file, done in Goat Tracker V2.7. For this example. The theme is once again, like with a few of my latest space shoot 'em ups, TRANCE themed. The game is intending to be a fast-paced game, and the music is set to be fast and very trance like. The game also will feature SFX, based on the instrument editor made in Goat Tracker. The three tunes picked are title music, in game music and end screen music. Game Over has sound effects. 

NOW LET'S CREATE THE GAME

Click on image to download BLASTOPIA binary and source

Download the complete source (or view each source separately in TXT format by clicking on the link, corresponding to the code. If you choose to download the code and the source. Load the project in to C64Studio V5.8, or higher. The filename is BLASTOPIA.S64. The project folder contains multiple files which are as follows You are also required EXOMIZER V2 or higher, in c:\EXOMIZER\WIN32 directory, otherwise delete the postbuild from blastopia.asm and manually compress the program with a cross platform or native C64 packer/cruncher of your own choice  :).

blastiopa.asm - This is the main file that needs to be assembled
variables.asm - A list of variables, that has been linked to the code
titlescreen.asm - Quick code for the front end
gamecode.asm - The main game code
pointers.asm - Series of pointers and text that the game code uses for varied tasks

OUTSIDE BLASTOPIA.ASM
compressmap.asm - This is a command, which was set to import a charpad map, and in post-build properties. A command to Exomizer is used to level crunch the imported map. (exomizer -l mem $xxxx loadname.prg -o packedname.prg).

The project also has a series of binary and PRG files which have also been included in the ZIP archive. These are part of the game project. The selected binary/program files are read in blastopia.asm

I won't go through the entire source individually, as the code is all there is self-detailed. I'll just give you a brief explanation about each source file. Simply click on the source file to view the code in .txt format (Or go all the way to the bottom of this chapter and download the entire C64Studio project.

blastopia.asm
This source consists of importing/linking the binary data files and possibly additional source code. First a starting level is set in the code (Default is 0). We then generate the program file to assemble to called "BLASTOPIA.PRG".  Then link the source variables.asm to the source code.

CHARSET MEMORY

$0800-$0fff is the address where the game graphics charset data is being imported to.

FORMATION TABLE WRITING

$1000-$1200 has been reserved for the copied set level X and Y alien formation data. This is self-modified in the game's code for every time an alien formation is complete, a new one gets copied over to that specific address. $1000 = X formation, and $1100 = Y formation

SPRITE DATA

$2000-$3c00 is being used for all of the sprites. I'm using default memory BANK $03 for the screen memory.

STARTING MAP (Plain Stars)

$3C00 inserts the game's starting map (The plain starfield), which is used for the title screen, the main game starting point, level complete and also the game's ending.

TITLE CODE, GAME CODE, TABLES AND POINTERS + ALIEN FORMATION DATA

$4000 - $8EFF consists of the main code and pointers. The address starts by switching the KERNAL mode, to be allowed to use addresses exceeding $9000, and before $D000. The game will be using standard KERNAL interrupts. Also we call one time code that checks the C64's system (PAL/NTSC) and also checks for a hidden cheat. Which if detected BIT out the decrementing LIVES counter. Also imported into the code area (as additional pointers) are all 24 different alien formation tables (extracted from a .D64 which consists of the BLASTOPIA ALIEN FORMATION MAKER).  Also the low/hi byte of the title screen code is set to the RESTORE pointers. So that every time the RESTORE key is pressed, the title screen restarts.

MUSIC DATA

$8F00 - $9fff is the Goat Tracker music player. You may use any other music player, but the SFX won't work, and will probably crash the player. Zeropage for GoatTracker tunes is best set to $fd-$ff.

DECOMPRESSED MAP (Full Level Map)
$a000-$b3ff is the game map (after it has been decompressed by Exomizer's decrunch routine). The LEVELSTART+LEVELEND labels represent the starting and ending position of the compressed map data. If you look at C64Studio V5.8, and assemble the program. The specific addresses of where the data/code lies is displayed on the left of the screen.

EXOMIZER COMPRESSED LEVEL MAPS
$b400-$c3ff contains all of the crunched level maps. Each level map has labels with END. Which is used as the low/hi-byte address for decrunching the level data.

EXOMIZER DECRUNCHER SOURCE
$c400-$c5ff is the Exomizer's level backwards decruncher source code. 

STATUS /SCORE PANEL SCREEN
$c600-$c6c8 is the game status panel / scoring matrix

TITLE SCREEN LOGO
$c700-$c900 is the title screen logo matrix

variables.asm

A list of variables/labels set for specific addresses, values and zeropage pointers

gamecode.asm

First of all the source code imports the titlescreen.asm code, which indicates the code for the main front end for the game. More on this later. After the title screen code has finished running. GAMESTART is called which does a fresh start of the game.

GAMESTART sets up all of the graphics data, resets the quota, score and other bits, should a new game start. Test background multi colours are set - although for the game start that isn't really needed, since a table of colours is normally read. Level pointers are set, and the score is zeroed (Probably yet again not required, since the titlescreen code does all that).

NEWLEVEL initialises any existing interrupts in the background. This basically kills all IRQs while trying to set up new levels . The shield count is reset to 50 on the player's shield counter.  The alien X and Y formation table is cleaned up with padded zeroes. Specific pointers are reset, so that the correct aliens and formation are used when starting a brand new game or level. Then we call a loop, which prepares the level. LEVELPOINTER cycles through the level properties. If the level is OVER the last level, then the code automatically jumps to the end screen code. Otherwise, all low and high bytes of the MAP DECRUNCH END ADDRESSES are stored to the self-mod area of the EXOMIZER decrunch routine. The LOW+HI BYTE values of the colour table are read and stored to specific colour areas - so that each level has its own set colour. LEVEL SELECT TABLES, set up the selection of aliens and their formation according to level.  Finally CAMELQUOTADIGITLEFT and CAMELQUOTADIGITRIGHT will set up the camel rescue quota according to level.

After the level selection code process has been made. The code then writes the new LEVEL ALIEN SELECT table, to the ACTUAL alien select table. There are 24 bytes stored since each level uses 24 attack formations.  The SID chip is initialised, so during the map decrunch process everything is silent. The level map is then decrunched.

GAMECOMPLETE redraws the status panel and the starfield, which will afterwards display the game complete text. It will also prompt the player to press fire. Then records the final score and checks if it is a new high score. If the player's score is a new high score, then the title screen will display the new high score. The end screen also resets the game interrupts and set the END MUSIC to initialise and play. Also if after pressing fire to exit the well done screen. If the cheat mode has been detected then the player will NOT have a new high score. The cheat mode is then disabled and the code will run back to the title screen.

DECRUNCHLEVELMAP will decrunch the level map data which the end low/hi byte address has been set to decrunch the program backwards until the decruncher reaches the load address of the file. The map is extracted in full to $a000-$b400.  

The game code continues by initialising the starting position of ALL ROWS AND COLUMNS of the map. Also alien properties are reset for the chosen level. The default starting position for sprites are set, along with the colour settings . Additional pointers for indicating the player dead, lazer time and lazer on are also initialised. The game IRQs are set up initialising the in game music and playing it. The frame of the player's sprite is set by default.

MAINGAMESTART disables the launch pad, also draws the starfield starting map, and also sets the game char colour  redraws the status panel.

GAMELOOP Synchronizes the game timer and expands the screen position so that sprites can use the full screen area.. A pause key (CONTROL) is checked to see whether on not the key is pressed. If CONTROL is pressed. The game is paused. If FIRE on joystick in port 2 is pressed then the game can continue to run its main body of the code.

SCROLLMAP - Scrolls the game map. This is made by pulling 20 rows of characters from the last character position to the previous character position. The map code then positions the current pointer position of the map to the very last character. Then the code increments the position of the low byte of each character. Basically after one full draw, the map will move on to the next map draw column. Then resets after 256 bytes has been read. After all a single byte can be from 0 - 255.

STARFIELD - Animates the starfield. Basically it scrolls the selected bytes of pixels over each character, depending on which byte has been chosen. ASL, ROL will roll bytes to the left, LSR ROR will roll bytes to the right.

TESTLAZER - Checks whether or not the lazer character is switched on or off. If the lazer is off, then a blank char replaces the lazer char. Otherwise if it is on, the char is switched on. The lazer is controlled by a looping timer and two logic switches (LAZERON = 0 represents OFF, LAZERON = 1 represents ON)

ANIMSPRITES - Creates animation frames from sprite value tables and stores those to custom pointers - The speed of the animation is controlled by a delay. Max of 4 bytes is read for the sprite animation.

PLAYERPROPERTIES - Sets up the properties of the player. It checks whether or not the player is alive or dead. If the player is dead, then the explosion animation takes place. Otherwise if the player is alive, the player can move, and animate. There's also code for bullet properties included in the PLAYERPROPERTIES, which allows the player to shoot fast bullets unless one bullet sprite is on screen. If the bullet is offset, then it can be fired again.

TESTALIENPROPERTIES - Tests alien properties. Macro code is created to check for each alien property, control its formation, etc. If the alien is alive (ALIENDEAD = 0) and ALIENOFFSET = 0 and ALIENSPAWNED = 1. This means that the alien is able to move around the screen. ALIENDEAD will trigger the explosion of the alien (if enabled) then resets the alien to ALIENOFFSET mode. The macro code is LINKED to all 5 aliens that are being blasted. If all aliens are OFFSET. A spawn timer is reset, then after the timer expired, spawns the aliens on to the screen with their own attack formation. 

TESTCAMELPROPERTIES - Tests whether or not a complete fleet of aliens have been destroyed. If so, then the camel is released from the last alien shot.

SPRITETOSPRITE - This tests the sprite to sprite collision. The values of the collision co-ordinates (from variables) are set from the player and bullet. Then some macros are made to check each alien collision. For example if ALIEN1DEAD = 1, then no collision should take place. If the alien hits the player, then the shield should decrease (If the player is not invulnerable). If an alien hits a bullet and is not dead. The bullet is removed, and then the alien is triggered to be dead. Where TESTALIENPROPERTIES will then force the alien to explode. The alien shot is stored to a pointer, which the camel gets released from if all 5 aliens of the same fleet have been shot.

SPRITETOBACKGROUND - This tests the position of the player. The player collision position has been set to the central area of the ship (1 single character). If that area of the player's hit hits any background while the player is invulnerable. Nothing should happen. However, if the player hits the background, and it isn't a specific character. Like with the sprite to sprite collision, the background drains the player's shield. Only chars 0-3 are excluded from the deadly background, and aware classed as safe chars. The landing pad 34-40 (excluding 38) is checked - If PADENABLED = 1 then the landing pad is NOT a deadly background. It is used to detect that the level is complete (Where the background flash and level complete message is triggered). The lazer character (42) is only deadly when the pointer LAZERON = 1. Otherwise if LAZERON=0, the lazer is removed and collision with that specific char is ignored.

ANIMATEPAD- Checks whether or not the landing pad is enabled or not. If the landing pad is enabled, it calls out a timed animation to set the lights flashing (Simply by swapping two char animation frames from two addresses.).

CHECKMATCHINGQUOTAS - Checks to see whether or not the camel pickup quota has been reached. If it has, then the camels cannot be released, and also the code will trigger the landing pad for availability. The camel quota check code will check fro a direct match between CAMELQUOTA and PICKEDQUOTA. The landing pad will not be enabled quota doesn't match. Therefore the player has to keep on blasting.

pointers.asm consists of all pointers that support the code in the entire game project. For example sprite animation frames, level settings, etc. A lot of the stuff in the code and pointers have its own explanation. So there's no need for me to go through it all again :)

titlescreen.asm


Initialises the interrupts, displays the credits and also shows the previous and high score. The status panel is then reset to zero

And there you go. A quick and brief explanation of some of the source code. I recommend you explore it, as you'll probably won't grasp it straight away, but at least you'll know how to code a basic horizontal scrolling shoot 'em up with its own limitations.

BACK TO TOP


VERTICAL SCREEN SCROLLING

In this part of the chapter. We are going to learn a little bit about vertical scrolling. Unlike using horizontal scrolling, the trick to making vertical scrolling is slightly more trickier. Should you already know how to scroll a background horizontally. Vertical scrolling works in a pretty much similar way. but instead of controlling $D016 (Horizontal Screen Position (HSP)). You need to control the value $D011, (Vertical Scroll Position(VSP)). A smooth vertical scroller is controlled in a similar way as a horizontal scroller would be controlled. You can basically create a hard scroller going up, or scrolling down. First the control of a VSP pointer is calculated. Should the value of the counter reach the 8th byte, it should reset and then call a subroutine to perform a hard scroll. In C64 terms, a vertical hardscroll is formed by fetching a full row (or a row of a chosen number of characters), and then pasting it to the next row. Then you perform the row pulling forwards or backwards.


SCROLLING A SCREEN UPWARDS

For a start off, we are going to make a simple program which can scroll a map upwards:

;Simple full screen upscroll by Richard Bayliss
;2019 - Assemble It tutorial
;http://tnd64.unikat.sk

row = $0400
colr = $d800

row0 = row
row1 = row+40
row2 = row+80
row3 = row+120
row4 = row+160
row5 = row+200
row6 = row+240
row7 = row+280
row8 = row+320
row9 = row+360
row10 = row+400
row11 = row+440
row12 = row+480
row13 = row+520
row14 = row+560
row15 = row+600
row16 = row+640
row17 = row+680
row18 = row+720
row19 = row+760
row20 = row+800
row21 = row+840
row22 = row+880
row23 = row+920
row24 = row+960

;Please feel free to use this source should you wish to.

                !to "upscroll.prg",cbm
               
                *=$0801 ;BASIC SYS 2064 call
                !basic 2064
                *=$0810
               
                sei
               
                ;Black out screen,
                lda #$00
                sta $d020
                sta $d021
                lda #$18
                sta $d018
               
                ;fill the screen with the spacebar character
                ;and colour white.
               
                ldx #$00
fillscreen
                lda #$20
                sta row,x
                sta row+$100,x
                sta row+$200,x
                sta row+$2e8,x
                lda #$01
                sta colr,x
                sta colr+$100,x
                sta colr+$200,x
                sta colr+$2e8,x
                inx
                bne fillscreen
               
                ;Setup an IRQ Raster interrupt for code.
               
                ldx #<irq
                ldy #>irq
                lda #$00
                stx $0314
                sty $0315
                sta $d012
                lda #$7f
                sta $dc0d
                lda #$1b
                sta $d011
                lda #$01
                sta $d01a
                cli
               
                ;Perform a synchronized timer before performing
                ;an upward scroller.
               
LOOP            lda #0
                sta SYNCTIMER
                lda SYNCTIMER
                cmp SYNCTIMER
                beq *-3
                jsr UpScroll        ;Call upward scroller
                jmp LOOP
                       

               
;Perform Upscroll
               
UpScroll
                lda YPOS ;Read pointer YPOS
                sec
                sbc #$01 ;Speed of YPOS scroll
                and #7   ;Values can only be forced to 0 - 7 to control smoothness
                sta YPOS ;Update the position of YPOS.
                bcs NoScroll ;YPOS=0, read do main scroll, if over - don't do hard scroll yet
                jmp HardScroll ;Call hardscroll routine
NoScroll
                rts
       
;The main hard scroll - this is how NOT to
;implement an up scroll                           
               
HardScroll
            jsr DoHardScroll
               
;Pick the screen map location, and then write it to the last row on screen.

                ldx #$27
MapRead lda Map,x
                sta row24,x
                dex
                bpl MapRead
               
;Now calculate the position of the 40 column map.

                lda MapRead+1
                clc
                adc #$28         ;Shift to the next row
                sta MapRead+1
                lda MapRead+2    ;After all rows of map memory read
                adc #$00         ;shift to the next hibyte address. (i.e. $1000 = $2000)
                sta MapRead+2
                lda MapRead+2
                cmp #>MapEnd ;Reached end of map?
                bcc SkipEnd

;Re-initalise the map position.

ResetMap
                lda #<Map
                sta MapRead+1
                lda #>Map
                sta MapRead+2
SkipEnd
                rts                
               
;Hard Scroll screen layer 1 (Read one char, and shift it up)
DoHardScroll
                ldx #$27 ;Calculate loops backwards, saves cycles
HardLoop1
                lda row1,x     ;Fetch a bottom row of characters on screen
                sta row0,x     ;Shift them up one row.
                lda row2,x
                sta row1,x
                lda row3,x
                sta row2,x
                lda row4,x
                sta row3,x
                lda row5,x
                sta row4,x
                lda row6,x
                sta row5,x
                lda row7,x
                sta row6,x
                lda row8,x
                sta row7,x
                lda row9,x
                sta row8,x
                lda row10,x
                sta row9,x
                lda row11,x
                sta row10,x
                dex
                bpl HardLoop1
               
                ldx #$27
HardLoop2
                lda row12,x
                sta row11,x
                lda row13,x
                sta row12,x
                lda row14,x
                sta row13,x
                lda row15,x
                sta row14,x
                lda row16,x
                sta row15,x
                lda row17,x
                sta row16,x
                lda row18,x
                sta row17,x
                lda row19,x
                sta row18,x
                lda row20,x
                sta row19,x
                lda row21,x
                sta row20,x
                lda row22,x
                sta row21,x
                lda row23,x
                sta row22,x
                lda row24,x
                sta row23,x
                dex
                bpl HardLoop2
                rts
               
;Multiple IRQ raster interrupts (Synchronize timer, and
;also setup the blank screen position    at the screen bottom area
               
irq            inc $d019
                lda #$00    ;Raster position set
                sta $d012
                lda #$70    ;Stable illegal opcode, to make a black line
                sta $d011 ;cover a row
                lda #1        ;Force sync timer to switch on.
                sta SYNCTIMER
                ldx #<irq2;Move to next interrupt
                ldy #>irq2
                stx $0314
                sty $0315
                jmp $ea7e
               
;IRQ Raster interrupt 2 - The main smooth vertical scroller
               
irq2        inc $d019
                lda #$fa
                sta $d012
                lda YPOS ;GRAB position Y
                ora #$10 ;Output mode to screen on
                sta $d011
                ldx #<irq ;
                ldy #>irq
                stx $0314
                sty $0315
                jmp $ea7e
               
SYNCTIMER !byte 0
YPOS    !byte 0
               
                *=$2000 ;experimental custom charset
                !bin "charset.bin" (Or if C64 charset !bin "charset.prg",,2)
                *=$3000
                !bin "map.bin" (Or if C64 charset !bin "map.prg",,2
Map
MapEnd               
       
My test scroller looks like this:

I know, it looks really basic and naff, but this is only a test map :)

SCROLLING DOWNWARDS

Scrolling a screen upwards is usually a simple way to go. Especially if you wanted to do something like an upwards scroll for moving text messages for an intro or demo parts, or maybe something like a dowhill skiing simulator, etc. This isn't really all that feasible for writing many scrolling games, unless you wanted to do something like a platform jumper, where a player is contuously falling, until reaching a platform, which pushes the player upwards. Usually a lot of C64 games, for example SEUCK games, and great commercial titles in C64's history use a down scroller. Let us take Slap Fight, Dragon Spirit, Light Force, Denarius (I'd better be careful how I pronounce it and not mistake it for Katakis) and the original SEUCK, etc. as an example. Unlike the upscroller example (above), the process has to be reversed in order to do a downwards scroll. We add the value of a position of a soft scroll instead of subtract a position, but there is more than meets the eye. Using the same map and example above. We try to do a simple downwards smooth scroll:

;Simple full screen down by Richard Bayliss
;2019 - Assemble It tutorial
;http://tnd64.unikat.sk

row = $0400    ;Declare screen RAM starting row.

row0 = row     ;Each row is set manually where the screen line for a 1x1 charset
row1 = row+40  ;is rowN = row + y*40. For example row7 = 280, which is (row7 = row + 7 * 40 = 280)
row2 = row+80
row3 = row+120 ;We deal with 24 rows for this scroller. The last row will be
row4 = row+160 ;reseved
row5 = row+200
row6 = row+240
row7 = row+280
row8 = row+320
row9 = row+360
row10 = row+400
row11 = row+440
row12 = row+480
row13 = row+520
row14 = row+560
row15 = row+600
row16 = row+640
row17 = row+680
row18 = row+720
row19 = row+760
row20 = row+800
row21 = row+840
row22 = row+880
row23 = row+920
row24 = row+960

;Please feel free to use this source should you wish to.

                !to "downscroll.prg",cbm
                
                *=$0801 ;BASIC SYS 2064 call
                !basic 2064
                *=$0810
                
                sei
           
                ;Blackout the border and screen

                lda #$00
                sta $d020
                sta $d021

                ;Setup charset @ $2000
                lda #$18
                sta $d018
                
                ;Pad the screen with the spacebar character
                ;and colour white.
                ldx #$00
fillscreen
                lda #$20
                sta row,x
                sta row+$100,x
                sta row+$200,x
                sta row+$2e8,x
                lda #$01
                sta colr,x
                sta colr+$100,x
                sta colr+$200,x
                sta colr+$2e8,x
                inx
                bne fillscreen
                
                ;Setup a synchronized IRQ Raster interrupt for the smooth downscroller
                ;routines.
                
                ldx #<irq
                ldy #>irq
                lda #$00
                stx $0314
                sty $0315
                sta $d012
                lda #$7f
                sta $dc0d
                lda #$1b
                sta $d011
                lda #$01
                sta $d01a
                cli
                
                ;Perform a synchronized timer before performing
                ;an upward scroller.
                
LOOP            lda #0
                sta SYNCTIMER
                lda SYNCTIMER
                cmp SYNCTIMER
                beq *-3
                jsr DownScroll        ;Call upward scroller
                jmp LOOP
                        

                
;Perform down scroller
DownScroll        
                lda YPOS
                clc
                adc #$01
                sta YPOS
                lda YPOS
                and #$07 ;Value of YPOS should read only $00-$07
                sta YPOS+1 ;Store to an additional pointer YPOS+1
                cmp #$00   ;Check if value of YPOS has reset to 0
                beq HardScroll ;Yes, we can do the hard scroll
                rts            ;otherwise stop
HardScroll
                    
                jsr DoHardScroll

;After performing the hardscroll (Where each row is shifted)
;Pick the screen memory map location and then
;write it to the very first column

                ldx #$27
MapRead         lda MapEnd-40,x
                sta row0,x
                dex
                bpl MapRead
                
;Now calculate the position of the 40 column map.

                lda MapRead+1    ;Grab current row of map data
                sec
                sbc #$28         ;Shift to next row by 40 columns (1 whole 1x1 char row)
                sta MapRead+1
                lda MapRead+2    ;After all rows of map memory are read
                sbc #$00         ;shift to the next hibyte address. (i.e. $1000 = $2000)
                sta MapRead+2
                lda MapRead+2      
                cmp #>Map        ;Have we reached end of map? (The very top)
                bcs SkipEnd      ;no. Skip end...   Otherwise ...

;Re-initalise the map position.

ResetMap
                lda #<MapEnd-40
                sta MapRead+1
                lda #>MapEnd-40
                sta MapRead+2
SkipEnd
                rts                 
                
;The main hard scroll. This will fetch a chosen row on screen, and then
;shift it one row below. We simply start from the second last row
;of the character and place it one row down to the last row. We work each
;row copy all the way to the very first row.

DoHardScroll

                ldx #$27 ;Calculate loops backwards, saves cycles
HardLoop1
                lda row23,x
                sta row24,x
                lda row22,x
                sta row23,x
                lda row21,x
                sta row22,x
                lda row20,x
                sta row21,x
                lda row19,x
                sta row20,x
                lda row18,x
                sta row19,x
                lda row17,x
                sta row18,x
                lda row16,x
                sta row17,x
                lda row15,x
                sta row16,x
                lda row14,x
                sta row15,x
                dex
                bpl HardLoop1
                
                ldx #$27
HardLoop2
                lda row13,x
                sta row14,x
                lda row12,x
                sta row13,x
                lda row11,x
                sta row12,x
                lda row10,x
                sta row11,x
                lda row9,x
                sta row10,x
                lda row8,x
                sta row9,x
                lda row7,x
                sta row8,x
                lda row6,x
                sta row7,x
                lda row5,x
                sta row6,x
                lda row4,x
                sta row5,x
                lda row3,x
                sta row4,x
                lda row2,x
                sta row3,x
                lda row1,x
                sta row2,x
                lda row0,x
                sta row1,x
                dex
                bpl HardLoop2
                rts
   
                
                
                
                
;Multiple IRQ raster interrupts (Synchronize timer, and
;also setup the blank screen position    at the screen bottom area
                
irq             inc $d019
                lda #$ff    ;Raster position set
                sta $d012
                lda #1        ;Force synchronised timer to switch on.
                sta SYNCTIMER
                lda #$70      ;Stable opcode value, to make a black line
                sta $d011     ;cover a row or two
            
                ldx #<irq2    ;Move to next interrupt
                ldy #>irq2
                stx $0314
                sty $0315
                jmp $ea7e
                
;IRQ Raster interrupt 2 - The main smooth vertical scroller
                
irq2            inc $d019
                lda #$e2
                sta $d012
            
                lda YPOS+1   ;GRAB position Y
                ora #$10     ;Output mode to screen on (Because if we didn't use
                sta $d011    ;ORA #$10 the screen would have been switched off completely)
                ldx #<irq ;
                ldy #>irq
                stx $0314
                sty $0315
        
                jmp $ea7e
                
SYNCTIMER !byte 0
YPOS    !byte 0,0

                                
                *=$2000 ;experimental custom charset
                !bin "charset.bin" (Or use !bin "charset.prg",,2

                *=$3000 ;Screen map data
Map             
                !bin "testmap.bin" (Or use !bin "testmap.prg",,2
MapEnd                

You can also view the complete source code

If you assemble and run the program above in C64Studio or ACME cross assembler. You will notice that this scroll will work smoothly on PAL machines. However on NTSC machines, the first row will flicker like hell. If you were to do a scroller like this for PAL machines, you'd be okay. However NTSC machines are pretty much more difficult to handle, if you are more used to PAL. Let's patch the scroll to work on NTSC. Simply alter the routine, to the hard scroll below and also add a new pointer called ROWTEMP. Simply go to the label DoHardScroll and replace it with this handy routine:

DoHardScroll    ;(PAL+NTSC COMPATIBLE)

                ldx #$27 ;Calculate loops backwards, saves cycles
HardLoop1
                lda row10,x ;Grab the 11th row
                sta ROWTEMP,x  ;Store to a self-modyfying pointer ROWTEMP.
                lda row9,x  ;Grab 10th row,
                sta row10,x ;Store to 11th row.
                lda row8,x  ;and so on, all the way to the very first row (row0)
                sta row9,x
                lda row7,x
                sta row8,x
                lda row6,x
                sta row7,x
                lda row5,x
                sta row6,x
                lda row4,x
                sta row5,x
                lda row3,x
                sta row4,x
                lda row2,x
                sta row3,x
                lda row1,x
                sta row2,x
                lda row0,x
                sta row1,x
                dex
                bpl HardLoop1
               
                ldx #$27
HardLoop2
                lda row23,x         ;In the second loop work from the second bottom, and
                sta row24,x         ;paste to the next row - working the row calculation
                lda row22,x         ;up 1 row at a time.
                sta row23,x
                lda row21,x
                sta row22,x
                lda row20,x
                sta row21,x
                lda row19,x
                sta row20,x
                lda row18,x
                sta row19,x
                lda row17,x
                sta row18,x
                lda row16,x
                sta row17,x
                lda row15,x
                sta row16,x
                lda row14,x
                sta row15,x
                lda row13,x
                sta row14,x
                lda row12,x
                sta row13,x
                lda row11,x
                sta row12,x
                lda ROWTEMP,x        ;Grab stored row from ROWTEMP (Copied from the 11th row)
                sta row11,x          ;store it to the 12th Screen Row.
                dex
                bpl HardLoop2
                rts
               
ROWTEMP         !fill $28,$00        ;Self-Modifying consisting of 40 pointers (We filled this are with 40 @'s exclusively)

View downscroll example  listing

Okay, so by now, by looking at the routines, you probably might now know a bit on how a downscroll works. (We did about upward scrolling in chapter 3). The examples which I showed you are quite practical, but why don't we do something more interesting with an upscroll ...   Well look below, because it's GAME MAKING TIME :)


GAME 10 - SUPER TOBOGGAN CHALLENGE





CLICK DISK ABOVE TO DOWNLOAD COMPLETE SOURCE + BINARY DATA

The complete package comes with the game binary data, charpad work graphics and also the complete game project source, which can be loaded into C64Studio. There is also a RELEASE folder, which contains the public D64 and TAP release of the game which has respectively been put together.

We are going to make a little game. Also for a change, we are not going to make another of those space blasters. Instead, I came up with an alternative idea. We are going to create a winter sports game. I remember reading a book many years ago, while I was a teenager, called Commodore Omnibus, which I used to borrow at the local library for type in games. It had a type in game called Toboggan Run (I think that might have been the name) which was all in BASIC. For this edition of Assemble It, I have decided to create my own toboggan run, but nothing like in the book. First of all it isn't a BASIC type in game. The second thing is that it has a few additional ideas I came up with. So there you go. Anyway, what's this game going to be all about? ... Read on to find out more.

Super Toboggan Challenge is a smooth vertical scrolling dodge and collect game, in the mode of winter sports. This is a score attack challenge in which you have to guide your tobaggon through an infinite course. You must pick up flags as you slide throughout the course. For every flag picked up, you will score 150 points. However, should you reach near the end of the course, you will be given a bonus of 1000 points added to your score. This may sound like an easy challenge, but not exactly. The course has been poorly maintained. Parts of the slope are broken, and additional random objects are placed on the slope, such as barriers, rocky debris and tree logs. If you crash into any obstacles or hit the water, you will lose the challenge. Try to score as many points as you possibly can, before losing. Also try and challenge friends to see who can get a high score, while playing the game.

Controls:

LEFT and RIGHT on Joystick port 2 to move your tobaggon.

So there you go. Do you accept the challenge?

THE GRAPHICS/ASSETS

Before we start building this game. We want some graphics. First of all, because of the size of my map. I decided not to show a snapshot of the entire map on this page. However, here are the game charsets, which sort of represent the map. If you take a look at the filename, ST_Graphics,  in Charpad V2.0 The first 20 chars (0 - 19) will be representing the safe chars, which the player is allowed to move across. Character 179 represents the collectible flag, and the rest of the characters all are classed as deadly chars. The deadly chars are there to force the player in losing the Super Toboggan Challenge (As you may have guessed).



I also provided two different map types in this tutorial. The first (ST_Graphics.ctm) represents the map, which consists of a map made with 5x5 tiles (Similar size to what you get with SEUCK). However, since we don't want to program the game using tiles, but using chars only. Select the option 'Disable Tiles'. This will then remove the tiles, and replace them with single chars. I have also done a second file (ST_Graphics_No_Tiles.ctm).

There's a BIN folder, which consist of the following attributes for the game code:

gamecharset.bin - The game's charset data
gamemap.bin - The game's map data (charset based, not tiled based)

The second set of graphics, (ST_Title_Logo.ctm) represents the front end and score panel.



There are 3 special chars in which are being used for the logo's scrolling effect. The chars are 129,130,131. These all represent the scrolling inside the TOBOGGAN logo.

The logo looks a bit like THIS:



The chars between 64 and 110 represent the score panel characters. This has been made in a slightly different way, compared to a normal 1x1 char. A subroutine in masking the score panel will show how 2 sides of a score char is formed. More on that later. Here's how the score panel should show up as:



The exported filenames for the title screen charset, and score panel screens are:

textcharset.bin (Title screen, logo + panel charset)
logomatrix.bin (Title screen logo screen data)
stauspanel.bin (Score panel screen data)

The Game Sprites:

The game sprites look something like this:



Sprites 0 and 1 - represent animation of the player's toboggan
Sprites 2 - 9, represent the animation of the explosion.
Sprites 10,11 and 12 are the Ready, Set Go lights for the start of the game
Sprites 13 - 20 are the GAME OVER word sprites
Sprites 21 - 28 are the title screen sprites, where the toboggan sprites rotate.

The Music:

The music file was done in Goat Tracker, and it consists of 7 different tracks. These are as follows:

TRACK 0 - In game music
TRACK 1 - LIGHT Red, Yellow
TRACK 2 - LIGHT Green
TRACK 3 - Crash
TRACK 4 - Game Over jingle
TRACK 5 - Get Ready jingle
TRACK 6 - Front End music (Title Screen)

The filename of course is music.prg

The code:

First of all if you wish:

DOWNLOAD THE C64STUDIO SOURCE + BINARY DATA

Extract it to a directory of your choice (as usual). Load in C64Studio V5.9 (or whichever version you have installed on your HDD on your PD). then open the filename Super_Toboggan_Challenge_-_Assemble_It

You will notice something different regarding this project. I have highlighted toboggan.asm as the main target assembly source. This is done in order to allow this file to be assembled FIRST before any other linked files. This saves me having to click tabs before re-compiling the data and source. The game has 3 different source files (Which you can also view by clicking on the links to the files below). As usual, the code is self-explainable.

toboggan.asm - The main target file to be assembled, including importing of binary files.
titlecode.asm - Complete code for the title screen/front end

gamecode.asm - Complete code for the main game and main game pointers]

Right, let's divide everything to each section for this game project:

toboggan.asm

There isn't much by the way of code to explain about here. However, let me tell you what is in this source file.

First of all, as well as the introductory comments, we have some variables:

Hardware screen based variables:

split1 - split4 = (value) represents the set position of where raster splits will be placed inside multiple IRQ interruptys/
screen = $0400 is the starting VIDEO RAM area in which screen data should be displayed to. It is also the default screen RAM position
rowtemp = $0100 is where a self-modifying row read from the map data is placed.
ScoreMask = $0771 is the screen position where the player's score is placed.
HiScoreMask = $078b is the screen position, where the High Score is placed.
Status Message = $0756 is the screen position, where we place WELL DONE, or BAD LUCK after game over takes place.
row0 = screen to row24 = screen +19*40 represents the screen row position where the scrolling map is stored to.

Software charset based variables:

SafeArea = 20 represents the safe char area, in which is based on the software based sprite/background collision detection routine
Flag  = 179 - As indicated earlier on in this game tutorial, char 179 represents the flag, which the player has to pick up.

Sprite based variables:

LeftStopPosition = $0e is the minimum area which the player can move to when moving to the left of the screen.
RightStopPosition = $9e is the maximum are which the player can move to when moving to the right of the screen
zp = $70 is the zeropage based value, which corresponds to the software based sprite/background collision

Game screen based variables:

ypos = $05 is the zeropage set for controlling the smooth down scroll inside a raster split in order to perform a soft scroll while cycling through the game's map and storing it to the screen.

GameMapStartPosition = MapEnd-25*40 represents the starting position of where the map is on screen. Since after all when scrolling through a map using a down scroll, the map scrolls from the bottom position, all the way to the top of the end point.

GameMapDefaultPosition  = MapEnd-26*40 represents the default starting position of the map on screen.

Music based variables:

GameMusic = 0 is the value of the in game music
LightRed = 1 is the Get Ready light sound (Low beep)
LightGreen = 2 is the Green light sound (Long high beep)
Bang = 3 is the sound for the player smashing into an obstacle.
GameOverTune = 4 is the game over jingle
GetReadyTune = 5 is the get ready tune
TitleMusic = 6 is the title / front end music

MusicInit = $1000 is the address, which the music initialise/restarts during programming
MusicPlay = $1003 is the address which the music plays (Usually inside an IRQ raster interrupt.

At $0800, we import the game charset graphics data, then $1000 import the music data. $2000 has sprites imported. followed by the status panel and title screen charset at $2800. Following that, a status/score panel charset is inserted into spare memory $2d00 (Since the title screen charset isn't all that huge in size).

At $3000. A one-time code which is run to test between PAL and NTSC machines.

Titlecode.asm, is then added to the source code. Followed by gamecode.asm. Then 256 bytes are filled to make a clean space before importing the title screen code. (Which titlecode.asm then gets added).

Next the game map data is imported. MapStart = $5000, and MapEnd = where the map ends after importing.

Finally we complete the main project file by importing the main title screen logo.

titlecode.asm

This is the main code for the front end. First off, we kill off all of the existing interrupts. Then we disable any existing sound that is playing, switch off the the screen and initialise pointers for example ANIMDELAY and ANIMPOINTER. These two pointers are for the front end and in game sprite animation.  We also initialise the COLOURBARDELAY and COLOURBARPOINTER so that it runs from the start. Also we do a reset of the colour bar, by calling the subroutine, which does the colour bar swap. So that at the start of the front end the scrolling logo multi colours are light blue and cyan

Next the title screen graphics are set up. Simply the charset for the front end (Read from $2800) is poked to the $0400-$07e7 screen RAM and also the border and background is set to black. The screen is cleared using the usual filling $0400-$07e7 with spacebar character loop. The scroll text is initialise (Simply by storing the low and high bytes of ScrollText to the self modifying loop value MessRead, which by now you should already know by heart. The sprite positions (Well, the virtual object positions, also known as ObjPos) are all initialized. Object positions and values for the two toboggans (which are set to spin on the title screen) are put in place, and formed, painted red. Only two sprites are enabled 0 and 1. Also we disable all sprite expansions and priorities. This is just in case a program linked to this program has sprites which expand or go behind the background, and not yet been initialized on an intro, loader or something similar.

Now we draw the main title screen, by reading from the LogoMatrix, row by row, until it has been drawn to the 12th row (row11) using a loop of 40 characters per row. Then we draw the credits from the text pointers line1 to line3 to the screen RAM. Finally before setting up any interrupts, we draw the masked score panel and place to the bottom area of the title screen. The FIREBUTTON trigger pointer is then initialized. After all, after pressing fire on a game over screen, we need to control the fire pressing. The sensitivity of the fire button can be very high, even when you do a simple tap. The trigger is there to prevent the sensitivity of the fire button.

We now setup the multiple IRQ interrupts (Well, initialise those), and the title music, before calling a synchronized loop for the main title code, that calls 6 subroutines. The 6 subroutines expands the sprite area and stores virtual sprite position to actual hardware sprite X,Y positions. Other routines call the a score conversion subroutine, the title screen scroll text, scrolling colour bar behind the logo, animate/rotate the title sprites and then   A fire press check is also in place. If fire is pressed, but not let go, the synchronized loop (TitleLoop) will continue running. Otherwise, leaves the title screen loop and runs the start of the game code (gamecode.asm) by calling jmp GameCode.

The Title Screen looks something like this:



gamecode.asm

This is the main code for the game. Like with the front end, we start off by initializing everything. We set the $01 value to $36 in order to allow memory $a000-$bfff to be read as well as memory $0100-$9fff and $c000-$cfff. The VIC2 registers for border, background, multi colours, charset mode, and horizontal screen default position are set. Also we initialise the pointer LIGHTMODE to 0. This is used to time, and then select which get ready light should be displayed before the player can race off on the track. More on this shortly. A loop is also called to reset the player's score and draw the main starting screen, read from GameMapStartPosition The score panel is re-drawn and initialized, along with some unnecessary screen junk being cleared away. game sprite defaults (frame, colour and positions) are set. The game map is initialized .

A multiple interrupt routine is set up, initializing the GET READY theme tune. We clear the IRQ flag, and then create a synchronized timed loop. So that the music will play, and control which starting light should commence before allowing the main game to start. We use 3 different lights. Red, Amber and Green. Once all 3 have lit up, the game can the progress on to the next synchronized loop.



Now we start the game from StartTheGame this will set up the main in game music. Then call a new synchronized loop, which will expand the sprite screen area (ExpandMSB), scroll the screen (using the vertical scrolling hard+soft scroll subroutine (Scroller), similar to the one in the previous sub chapter). Also the player control (PlayerControl) is called, in order to allow use of joystick in port 2 left/right. So that the player can move the toboggan. Followed by animations on the player (AnimatePlayer), which uses a two sprite frame to look as if ice is being sprayed from the course. Followed by background animation (AnimateWaterChar), which picks the chosen characters that scrolls the water char continuously. An additional subroutine RemoveLights, simply scroll the lights off screen to the right until out. Then there is the score masking code (ScoreConvert) that converts the scoring value pointers into the status panel characters, like in the main front end.

A lot of the source is pretty much straight forward, so I need not explain everything, but of course if you look at the subroutine SPRBGRCollision, which indicates the player's sprite/background collision. You will notice something slightly different here. I have done two different calculations, and called the CollisionTest subroutine twice. The reason for this is because the collision for the toboggan should take place at the top middle, and direct central sprite area. Collision should not take place elsewhere. It is important to start with a collision = good char (even if it is over the safe char range). That is because rest assured, we get a fair non-deadly collision (if a char is mixed with deadly characters). Then a range of deadly chars should be read lastly. If we did this the other way round. There would be a bug in the collision code, and the player will lose if it tries to pick up a flag. And this is what would happen:



Yes, that's right. The player will die, and the GAME OVER screen will run and play the GAME OVER jingle.

Scoring:

For scoring, I have based it in two different ways. First of all, if a flag is picked up, a score subroutine inside a loop (Value will calculate amount of points to be added to the panel) is read. Also after the map has restarted after the GOAL memory position has been read. The score routine will give 1000 points to the player as a bonus, before the player has to go through the entire course again. The way to calculate a score is simply like this:

ldy #ScoreValue (x*10 points-1)
ForceScore jsr AddScore
dey
bpl ForceScore

or

ldx #$00
ForceScore jsr AddScore}
inx
cpx #ScoreValue (x*10 points)
bne ForceScore
rts

Okay. I can now leave it to you to check out the code and explore it. Oh, and try to make your own cool games using the existing source. If you do manage to make something from it, please feel free to share it with me. If you'd like it to be published on the TND web site. I might consider to add something to the contributors page for you.

If you are feeling ambitious and would like to try using tiles + maps approach to your up/down scrolling programs. I strongly recommend that you check out Achim Voilker's excellent tile/map decoding tutorial on CodeBase64. 

Coming soon:

CHAPTER 7 - Mastering your final production to different physical/digital media + tips