Game Design for C64

Main.asm

At the beginning of any Commodore 64 Game Project we need to setup variables that will be used throughout the entire project building process. You can use the tab box above to learn more about them. There is also further reading down the page that explains the first setup process to the example game.

Your Content Goes Here

IncAsm "VICII.asm" ; VICII register includes 
IncAsm "macros.asm" ; macro includes
; Screen/Char Memory
SCREEN_MEM = $4000 
SCREEN1_MEM = $4000 ; Bank 1 - Screen 0 ; $4000 
SCREEN2_MEM = $4400 ; Bank 1 - Screen 1 ; $4400 
SCORE_SCREEN = $5800 ; Bank 1 - Screen 6 ; $5800 
COLOR_MEM = $D800 ; Color mem never changes 
CHAR_MEM = $4800 ; Base of character set memory (set 1) 
SPRITE_MEM = $5C00 ; Base of sprite memory 

COLOR_DIFF = COLOR_MEM - SCREEN_MEM ; difference between color and screen ram

; Animation Memory CHRADR1 = 19992 ; Rotating pole CHRADR3 = 20200 ; top water tile CHRADR4 = 20104 ; Sprite Memory

SPRITE_POINTER_BASE = SCREEN_MEM + $3f8 ; last 8 bytes of screen mem 
SPRITE_BASE = $70 ; the pointer to the first image# 
SPRITE_0_PTR = SPRITE_POINTER_BASE + 0 ; Sprite pointers 
SPRITE_1_PTR = SPRITE_POINTER_BASE + 1 
SPRITE_2_PTR = SPRITE_POINTER_BASE + 2 
SPRITE_3_PTR = SPRITE_POINTER_BASE + 3 
SPRITE_4_PTR = SPRITE_POINTER_BASE + 4 
SPRITE_5_PTR = SPRITE_POINTER_BASE + 5 
SPRITE_6_PTR = SPRITE_POINTER_BASE + 6 
SPRITE_7_PTR = SPRITE_POINTER_BASE + 7 
SPRITE_DELTA_OFFSET_X = 8 ; Offset from SPRITE coords to Delta Char coords
SPRITE_DELTA_OFFSET_Y = 11 ; approx the center of the sprite 
SPRITE_DELTA_OFFSET_Y = 14 
ENEMY_SPRITE_DELTA_OFFSET_X = 8 
ENEMY_SPRITE_DELTA_OFFSET_Y = 14 
NUMBER_OF_SPRITES_DIV_4 = 3Ā ; loads sprites and characters under IO ROM

; Levels

LEVEL_1_MAP = $E000 ;Address of level 1 tiles/charsets 
LEVEL_1_CHARS = $E800
; Parameters  
PARAM1 = $03 ; These will be used to pass parameters to routines 
PARAM2 = $04 ; when you can't use registers or other reasons 
PARAM3 = $05 
PARAM4 = $06 ; essentially, think of these as extra data registers 
PARAM5 = $07
; Timers 
TIMER = $08 ; Timers - fast and slow, updated every frame 
SLOW_TIMER = $09 
WPARAM1 = $0A ; Word length Params. Same as above only room for 2 
WPARAM2 = $0C ; bytes (or an address) 
WPARAM3 = $0E ;---------------------------- $11 - $16 available
; Temporary Pointers 
ZEROPAGE_POINTER_1 = $17 ; Similar only for pointers that hold a word long address 
ZEROPAGE_POINTER_2 = $19 
ZEROPAGE_POINTER_3 = $21 
ZEROPAGE_POINTER_4 = $23 
CURRENT_SCREEN = $25 ; Pointer to current front screen 
CURRENT_BUFFER = $27 ; Pointer to current back buffer
; Scrolling 
SCROLL_COUNT_X = $29 ; Current hardware scroll value 
SCROLL_COUNT_Y = $2A 
SCROLL_SPEED = $2B ; Scroll speed (not implemented yet) 
SCROLL_DIRECTION = $2C ; Direction we are scrolling in 
SCROLL_MOVING = $2D ; are we moving? (Set to direction of scrolling) 

; This is for resetting back to start frames 
; All data is for the top left corner of the visible map area

; Map Pointers 
MAP_POS_ADDRESS = $2E ; (2 bytes) pointer to current address in the level map 
MAP_X_POS = $30 ; Current map x position (in tiles) 
MAP_Y_POS = $31 ; Current map y position (in tiles) 
MAP_X_DELTA = $32 ; Map sub tile delta (in characters) 
MAP_Y_DELTA = $33 ; Map sub tile delta (in characters)
KICKSTART
; Sys call to start the program - 10 SYS (2064)
*=$0801 
BYTE $0E,$08,$0A,$00,$9E,$20,$28,$32,$30,$36,$34,$29,$00,$00,$00
PRG_START

– setup VIC bank – define character set and screen memory (VIC_MEMORY_CONTROL) – Load the Processor Port ($0001) and turn off LRAM (Basic), HIRAM (Kernal), and CHAREN (Character ROM).

At the beginning of the game, we turn off all the sprites (VIC_SPRITE_ENABLE), turn the screen off (VIC_SCREEN_CONTROL), switch the bank memory to reserve larger memory for graphics/sprites (VIC_BANK). We also point to screen memory (VIC_MEMORY_CONTROL), and finally get our character set into memory (PROC_PORT).

lda #0 ; Turn off sprites
sta VIC_SPRITE_ENABLE
lda VIC_SCREEN_CONTROL ; turn screen off with bit 4
and #%11100000 ; mask out bit 4 - Screen on/off
sta VIC_SCREEN_CONTROL ; save back - setting bit 4 to off

lda VIC_BANK ; Fetch the status of CIA 2 ($DD00)
and #%11111100 ; the first 2 bits are your desired VIC bank valueĀ 
ora #%00000010 ; In this case bank 1 ($4000 - $7FFF)
sta VIC_BANK
lda #%00000010 ; bits 4-7 (000) = screen memory 0 : $0000 - $03FF 
sta VIC_MEMORY_CONTROL ; bits 1-3 (001) = character memory 2 : $0800 - $0FFF
lda PROC_PORT ; store ram setup 
sta PARAM1 
lda #%00110000 ; Switch out BASIC, KERNAL, CHAREN, IO 
sta PROC_PORT
loadPointer ZEROPAGE_POINTER_1, MAP_MEM ; source
loadPointer ZEROPAGE_POINTER_2, LEVEL_1_MAP ; destination
loadPointer ZEROPAGE_POINTER_1, CHAR_MEM 
loadPointer ZEROPAGE_POINTER_2, LEVEL_1_CHARS
Ā 

The Commodore 64 is comprised of a built in character set that can be seen when you hold the Commodore key and press numbers, letters, etc. Rather to make a more interesting visual background we can change these. Since they are safely tucked away in ROM memory we must move them into memory using the subroutine below.

CopyChars = This is where we copy the character set from ROM into RAM

Then the next thing you will want to do is load them from CharPad into your game. This will require first creating them in CharPad and using the export ability to save them as binary files that can then be displayed on your map after pointing to the appropriate place in memory and drawing them as “tiles” on the screen, which we will learn shortly in our lesson.

Download CharPad here at their website.

After you have created the character set and are happy with your map design, click on the File menu, selected Import/Export, choose Binary and export all 3 binary saves: Export Map, Export Tile Set, Export Character Set Attributes, and Export Character Set. These three files are then loaded into memory using the incbin commands in CBM Prg Studio’s Editor.

If you do not have the knowledge to create a new map, you can also just (for example) use one of the sample maps built into the CharPad tool. Just go to File. – Object Project. Then open the Example – Rips folder. Any of the files appended with .ctm can be loaded as a project file into CharPad. Then you can export these into binary files to use with your map.

jsr CopyChars 
lda PARAM1 ; restore ram setup 
sta PROC_PORT

In this area below, we change the background color of the screen, and set our character set multicolors.

Screen_Setup 
lda #COLOR_ORANGE 
sta VIC_BACKGROUND_COLOR 
lda #COLOR_ORANGE 
sta VIC_CHARSET_MULTICOLOR_1 
lda #COLOR_BROWN 
sta VIC_CHARSET_MULTICOLOR_2
 
loadPointer CURRENT_SCREEN,SCREEN1_MEM 
loadPointer CURRENT_BUFFER,SCREEN2_MEM

Next we utilize a subroutine (ClearScreen1) to clear a variable we defined as (SCREEN1_MEM) which clears all the characters on our first screen. The next subroutine (ClearScreen2) utilizes a variable defined as (SCREEN2_MEM) to clear a second screen, which is used as a backbuffer. The backbuffer is necessary to get the speed we need when scrolling a screen. You will learn later that it is copied from Screen1 to Screen2 (vice versa) to keep the tiles moving smoothly.

The subroutine (ClearScoreScreen) handles clearing the screen area below the map playfield. In the example you will see some test debug values there (in hexadecimal), but later this will be used as a score panel, lives counter, etc.

The final subroutine (ClearColorRam) is used to clear our color memory. Each Commodore 64/VIC/PET background contains color memory that can handle 8×8 pixel data. This area must be cleared when scrolling to prevent colors from bleeding into other characters. It does not require a backbuffer (like Screen1/Screen2) since it is very fast to move memory here.

lda #$40 ; Use #$40 as the fill character on GameScreen 
jsr ClearScreen1 ; Clear both screens (double buffer) 
jsr ClearScreen2 
lda #32 ; Use #32 (space) as the fill character on 
jsr ClearScoreScreen ; Score Screen  
lda #COLOR_BLUE ; Fill entire visible color ram with COLOR_BLUE 
jsr ClearColorRam 

This area manages some scrolling controls. The variable (SCROLL_COUNT_Y) is used when the screen is scrolled vertically each frame. This variable is used when we scroll the screen up or down.

The next variable (SCROLL_COUNT_X) is used when the scroll is scrolled horizontally each frame. This is used when the screen is being scrolled in the right or left direction.

This variable (SCROLL_STOP) is used when you want the program to actually stop moving the screen. There are several subroutines in the file (Scrolling.asm) that control this process.

The variable (SCROLL_DIRECTION) is to tell the program which way the screen is scrolling. It then will pass several other “states” using variables that manage a specific direction, such as:

SCROLL_RIGHT = used when you want the game to scroll the screen in the right direction. The variable SCROLL_STOP will freeze it.

SCROLL_LEFT = used when you want the game to scroll the screen in the left direction. The variable SCROLL_STOP will freeze it.

SCROLL_UP = used when you want the game to scroll the screen in a upward direction. The variable SCROLL_STOP will freeze it.

SCROLL_DOWN = used when you want the game to scroll the screen in a downward direction. The variable SCROLL_STOP will freeze it.

The variable (SCROLL_SPEED) was implemented when the program was originally created (back in 2016/2017). This could still be used to set a speed on the scrolling screen for later.

;Ā  (Scroll Counters)
; Here we setup scroll counters for pixels scrolls and several other constants
lda #3   
sta SCROLL_COUNT_Y ; Vertical scroll 
lda #4 ; Start centered on character 
sta SCROLL_COUNT_X ; Horizontal scroll
lda #SCROLL_STOP ; direction = up 
sta SCROLL_DIRECTION ; Which way is the sprite moving in? 
lda #0 ; speed = 0 ; How fast is the screen moving? 
sta SCROLL_SPEED ; (not implemented yet)

The first variable (CURRENT_LEVEL) is used to point at a specific map. Then the subroutine (LoadLevel) will begin to build the tiles on the screen and place them one by one. Each tile is 4×4 (4 tiles in width and 4 tiles in height) and occupy a range of 10 tiles per screen.

After this we utilize an X parameter and set its value (ldx #27). Then we utilize a Y parameter and set its value (ldy #0). These parameters are actually being used to position the map at a specific tile area on the screen. If you change these values, the map viewpoint area will move around. So for example, you could change these values to point to a new area that the Player has advanced beyond after fighting an enemy, etc. Likewise they could also be reset in the event of a Player’s death. The subroutine (DrawMap) will put the map on the screen pulling it from a binary file (created using the CharPad tool).

After this we call the subroutine (CopyToBuffer) to transfer our map data to a backbuffer. This is how we achieve the high speed when scrolling the map on the screen in memory.

;**************************** Map Position ************************************ 
;-------------------------------------------------- CHARPAD LEVEL SETUP 
lda #1 ; Start Level = 1
sta CURRENT_LEVEL
jsr LoadLevel ; load level 1 data
ldx #27 ; Y start pos (in tile coords) (129,26=default)
ldy #0 ; X start pos (in tile coords)
jsr DrawMap ; Draw the level map (Screen1) ; And initialize it
jsr CopyToBuffer ; Copy to the backbuffer(Screen2)

First we utilize a macro pointer (loadPointer) to be used by our subroutine call. In order to set this up we need to pass value into parameters. So (PARAM1) contains the X screen’s coordinate value. Then we use (PARAM2) to hold data for our Y screen’s coordinate value. A white color is stored in (PARAM3). Finally we call the subroutine (DisplayText) to write data to the Score screen (below our map). These two parameters help position the text on the screen and set its’ text color.

Then we enable the variable (VIC_SCREEN_CONTROL) to set the Y scroll value for the vertical direction. Finally we set the border color with the variable (VIC_BORDER_COLOR) to black.

; Display the Debug Console Text
loadpointer ZEROPAGE_POINTER_1, CONSOLE_TEXT
lda #0 ; PARAM1 contains X screen coord (column) 
sta PARAM1 
lda #19 ; PARAM2 contains Y screen coord (row) 
sta PARAM2 
lda #COLOR_WHITE ; PARAM3 contains the color to use 
sta PARAM3 
jsr DisplayText ; Then we display the text 
jsr DisplaySpriteInfoNow ; Now update it with the debug info

The subroutine (WaitFrame) is used to manage invisible horizontal lines that scan from left to right across displays. On the American screens (NTSC) there are 262 lines. The European (PAL) contains 312 lines. Each line is scanned and updated 60 times per second. The display can only utilize 200 of these scan lines at a given time. So deeper in this this subroutine, you will later learn that it can be used to tell the display which line to interrupt by the processor. This is used most effectively to manage the game’s speed, especially when it comes to screen scrolling and moving things around (like Sprites) or even setting timers and animation. It all depends on what you what that scan line to perform as.

The subroutine (InitRasterIRQ) is used to get the interrupts going. It specifically manages the screen scroll values, screen pointers (that tell which screen is being shown on the map), the scrolling counters (VIC_SCREEN_CONTROL_X) and (VIC_SCREEN_CONTROL_Y), a.k.a $D011 (53265 – vertical scrolling) and $D016 (53270 – horizontal scrolling). The subroutine call also sets up the scan lines for the Score display area, character sets (one and two), extended background mode color, and timers for our Player and the Enemies encountered (in later revisions).

jsr WaitFrame 
jsr InitRasterIRQ ; Setup raster interrupts 
jsr WaitFrame 
lda #%00011011 ; Default (Y scroll = 3 by default) 
sta VIC_SCREEN_CONTROL
lda #COLOR_BLACK
sta VIC_BORDER_COLOR

In this area (titled “Sprite Setup”) we call the variable (VIC_SPRITE_ENABLE) to turn all of the sprites (0-7) off. This is quite normal when first setting up a game map area. Then we clear the X extended bits using the variable (VIC_SPRITE_X_EXTEND). In the book Mapping the Commodore 64, this is known as the “Most Significant Bits of Sprites 0-7 Horizontal Position”. When the sprite needs to move across the screen when moving from left to right, this variable must be set in memory location $D010 (53264). When the Sprite begins to eventually reach halfway across the screen it crossed the “MSB” area (referred to as the “256th position”.) In order to continue moving the Sprite across the screen all the way to the right, a high bit must be set here. You will later see more of how this is done when you explore the button (SPRITE SETUP – subroutine SpriteToCharPos).

However, to save confusion, I would avoid clicking on it for now and wait until we get the that area, which will be covered in the subroutine “PlayerSetup” below.

;------------------------------------------------------------------------------------------- 
; SPRITE SETUP 
;------------------------------------------------------------------------------------------- 

lda #0 
sta VIC_SPRITE_ENABLE ; Turn all sprites off 
sta VIC_SPRITE_X_EXTEND ; clear all extended X bits 
sta SPRITE_POS_X_EXTEND ; in registers and data

Finally we arrive at subroutine (PlayerSetup). This area is used to set screen colors, sprite colors (also multicolors). It also sets up the X MSB for our sprite(s) so that can freely walk anywhere on the screen without restrictions (except bumping into a wall, falling, etc.).

When we get to that part of the project, you will learn how to call an indirect subroutine that manages specific sprite activity. So at first we set the sprite to idle using a variable you will later learn about called (PLAYER_STATE_IDLE). For now just understand that will utilize a call that watches the Player sprite’s events when the joystick is setting idle (not moved) and also when the Sprite first moves, which will then call many other subroutines that manage the (walking, gravity, fighting, swimming, etc.). This subroutine also sets our sprite to active using a variable called (SPRITE_IS_ACTIVE) and resets a variable (PLAYER_FALLCOUNT) that counts how long a sprite has fallen when they watched off a ledge or cliff.

Originally, the program utilized the variable (VIC_SPRITE_X_EXTEND) below to enable Sprite 7, which was a number counter in the upper right hand corner of the screen that showed either a “zero” or a “one” sprite that advised the developer which screen (Screen1 or Screen2) was being showed at a given time. I later removed this, so this area will need to be brushed as well eventually.

Finally, using the variable (VIC_SPRITE_ENABLE) again, we now turn on all the sprites (#%1111111). Each 1 sets a “bit” that turns on that specific sprite’s position. If you look at the 1’s from left to right each one represents a specific sprite). You can also visit the page “Assembly Language Bits” to learn more and see an example.

;------------------------------------------------------------------------------------------- 
; SETUP AND DISPLAY PLAYER & ENEMY(S) 
;------------------------------------------------------------------------------------------- 

jsr PlayerSetup 
lda VIC_SPRITE_X_EXTEND ; Set X extended bit for sprite 7 
ora #%10000000 ; (mask it into existing values) 
sta VIC_SPRITE_X_EXTEND 
lda SPRITE_POS_X_EXTEND ; Set X extended bit for sprite 7 in variables 
ora #%10000000 
sta SPRITE_POS_X_EXTEND 
lda #%11111111 ; Turn on sprites 0 1 and 7 
sta VIC_SPRITE_ENABLE

So we finally arrive at the Main Loop of the entire game. First we call the subroutine (WaitFrame) once again to setup our specific timing. After this we call the subroutine (UpdateTimers) to manage our fast and slow timers.

Then we call the subroutine (UpdateScroll) which holds all of the scrolling screen subroutines. As mentioned earlier, the screen is copied to a backbuffer (Screen2) and then projected onto the front display so you can see it (Screen1). This continues to happen as the screen scrolls. It happens so fast though, you cannot see it. So to be clear, the screen is switching very fast between Screen1 and Screen2 when the game is running. There are many other subroutines here that manage the screen scroll directions, vertical/horizontal buffers, color swapping, and so much more. You can click on the link to learn more.

Then we finally arrive at the most used subroutine (UpdatePlayer) in the whole game. This single call will update the Player States that manage the sprite’s actions. Here is a list below of those states (note: several are not in used and have been omitted).

PlayerStateIdle: Here is where the Player’s position is managed. If the Player is not moving then an idle animation switches back and forth. Also the gravity is checked in case the Player has fallen off a ledge. There is also a condition here that checks the fire button to let the Player jump up. It will also check the joystick for movement and execute the walk/right subroutine. The up/down movement is in use when the Player is climbing on a pole or moving through the water.

PlayerStateWalkR: This section manages the Player’s right movement. A walking animation is present while the Sprite is moving. As usual it also checks for the gravity routine and jumping to the right.

PlayerStateWalkL: This section manages the Player’s left movement. A walking animation is present while the Sprite is moving. As usual it also checks for the gravity routine and jumping to the left.

PlayerStateRope: When the Player is climbing on a pole this routine is called. It shows an up/down animation movement. It will also check if the Player has left the pole, which will then call the gravity routine.Ā 

PlayerStateJump: Pressing the fire button on your (joystick/xbox controller) will allow the sprite to jump up. Likewise it also manages the right/left movement to allow a jump in that direction.Ā 

Ā PlayerStatePunchR: Whenever the joystick is currently pressed up to the right, the Player will switch to a punching Sprite animation state, which repeats until you stop holding in that direction.

PlayerStatePunchL: Whenever the joystick is currently pressed up to the left, the Player will switch to a punching Sprite animation state, which repeats until you stop holding in that direction.

PlayerStateKickR: This animation state allows the Player to kick up to the right, but it has been added to the game yet.

PlayerStateKickL: This animation state allows the Player to kick up to the right, but it has been added to the game yet.

PlayerStateSwimR: If the Player has found water, then the Sprite will switch to a swimming animation state as the Player moves to the right.

PlayerStateSwimL: If the Player has found water, then the Sprite will switch to a swimming animation state as the Player moves to the left.

PlayerStateFloating: If the Player is in water, then a floating sequence will take place. This allows you to move the Sprite up/down as long as they are in the water. The only animation currently present here is in the idle state.

The last part manages the console display using the subroutine (CONSOLE_DISPLAY) and keeps the border black on the screen. Originally the border was changed here to manage the raster timing of the game. The colors were supposed to represent how fast the game was running, but it has been removed and left here until I decide to remove it.

MainLoop 
jsr WaitFrame ; wait for the vertical blank period
jsr UpdateTimers 
jsr UpdateScroll 
jsr UpdatePlayer ; Player animation, etc.  
jsr CONSOLE_DISPLAY ; Display simple debug info 
lda #COLOR_BLACK ; Restore the border to black - this gives a visual 
sta VIC_BORDER_COLOR ; on how much 'raster time' you have to work with  
jmp MainLoop

incAsm “raster.asm” ; raster interrupts incAsm “core_routines.asm” ; core framework routines incAsm “sprite_routines.asm” ; sprite handling incAsm “collision_routines.asm” ; sprite collision routines incAsm “screen_routines.asm” ; screen drawing and handling incAsm “Scrolling.asm” ; Screen scrolling routines incAsm “CharPadTools.asm” ; Tools for CharPad levels incAsm “Player.asm” ; Mobile Object/Player handing incAsm “Enemy.asm” incAsm “enemy_collisions.asm” ; sprite collision routines

SPRITE_CONSOLE_TEXT byte ‘ xpos:$ char:$ chrx:$ chry:$ /’ ; byte ‘ dltx:$ dlty:$ /’ byte ‘ attr: ‘ ; byte ‘ mapx:$ mapy:$ /’ ; byte ‘ sclx:$ scly:$ mdlx:$ mdly:$ /’ byte ‘ attr:’,0 SCROLL_CONSOLE_TEXT byte ‘ xpos:$ ypos:$ dltx:$ dlty:$ / scrlx: scrly: chrx: chry:’,0 JOY_X ; current positionĀ of Joystick(2) byte $00 ; –1 0 or +1 JOY_Y byte $00 ; –1 0 or +1 NE_DIR byte $00 JOY_NW byte $00 BUTTON_PRESSED ; holds 1 when the button is held down byte $00 ; holds 1 when a single press is made (button released) BUTTON_ACTION byte $00

BIT_TABLE byte 1,2,4,8,16,32,64,128

*=$4800

MAP_CHAR_MEM ; Character set for map screen

incbin“Parkour_Maps/Parkour Redo Chset6.bin” *=$5000 SCORE_CHAR_MEM

incbin “ScoreChars.cst”,0,255 ; Character set for scoreboard


*=$5C00

incbin “parkour.spt”,1,4,true ; idle (28,33)

incbin “parkour.spt”,5,6,true

incbin “parkour.spt”,7,12,true ; rope climb (3639)

incbin “parkour.spt”,13,18,true ; Walking left (1427)

incbin “parkour.spt”,19,24,true ; Walking right (013)

incbin “parkour.spt”,25,28,true ; Punching to the right

incbin “parkour.spt”,29,32,true ; Punching to the left

incbin “parkour.spt”,33,42,true

incbin “parkour.spt”,43,44,true ; Swimming to the right

incbin “parkour.spt”,45,46,true ; Swimming to the left

incbin “parkour.spt”,47,89,true

incbin “parkour.spt”,90,92,true

incbin “parkour.spt”,93,110,true


*=$8000

MAP_MEM incbin“Parkour_Maps/Parkour Redo Map6.bin”

ATTRIBUTE_MEM

incbin“Parkour_Maps/Parkour Redo ChsetAttrib6.bin”

TILE_MEM

incbin“Parkour_Maps/Parkour Redo Tileset6.bin”