C-64 Game Development Tutorial #2

Richard Marks - www.ccpssolutions.com, May 18, 2009

Welcome to my Commodore 64 Game Development Tutorials!

I am going to be using the VICE x64 emulator, though you can use any Commodore 64 emulator or even the real hardware if you have it!

While it is not completely necessary to read the first article in the series before you read this one, I recommend that you do so in order to have a better understanding of the code.

In this article, we are going to add a paddle to our bouncing ball demo, that the user can move left and right with the keyboard.

The ball will not, however, bounce off the paddle in this demo. That will be covered in my next article.

Game Programming gets significantly more complex when you are writing for old hardware because there aren't any "libraries" of pre-written code that can ease any tasks. You need to plan everything in advance, otherwise you cannot get it written. The code is dependent on the line numbers that you use, resulting in a sort of code-lock that makes the task of making any changes once you start coding typically mean a complete rewrite. A clear and concise plan is required in order to write games on the C64 and I'm hoping that my article conveys this fact.

The Program Layout

In addition to the code from the first article, we will be using two sub routines in this demo.

Each sub routine will start at a line number starting with 1000 in increments of 200. That gives us 20 lines for each subroutine. That should be plenty of space to write a subroutine. If 20 lines is not enough, then you need to break up your subroutines more.

The Program Design

Now, lets talk a little about what our program will do.

Our demo is going to be very simple so that you can follow it easily.

Here is the program logic:

Flowchart for Program Logic

There are two sections to the flowchart above.

  1. The main program logic
  2. The subroutines: MOVE PADDLE LEFT (#1), and MOVE PADDLE RIGHT (#2)

Lets walk through the program's logic so that you have a better understanding of what you see above.

Start at the top at the START bubble and follow the arrows.

  1. START This is where the program starts executing when the user RUNs the program.
  2. INITIALIZE PROGRAM VARIABLES This is where we give the initial values to the variables that we will use in our program.
  3. SET SCREEN COLORS We change the color of the screen and border here.
  4. CLEAR SCREEN We clear the entire screen of all characters at this point.
  5. RUN/STOP PRESSED? Here we have a dummy condition because our program will run indefinitely until the user hits the RUN/STOP key. This is a conditional block. If the result of the condition test is YES then we will follow the arrow down to the next step.
  6. END Here is where our program is no longer running. We are back in the C64 BASIC INTERPRETER at this point.
  7. GET KEYBOARD INPUT We are going to scan the keyboard for a single keypress and store it in a variable.
  8. IS A KEY PRESSED? We need to see if the key that was pressed is our key for moving the paddle left, which is the A key. If the result of this condition test is YES, then we JUMP to the #1 subroutine, otherwise we continue to the next step.
  9. IS D KEY PRESSED? We need to see if the key that was pressed is our key for moving the paddle right, which is the D key. If the result of this condition test is YES, then we JUMP to the #2 subroutine, otherwise we continue to the next step.
  10. A This is a JUMP TARGET which is just a marker to let us know that we will be returning to this point from somewhere else in the code. Just move to the next step, since no code is executed here.
  11. UPDATE BALL POSITION We add the ball delta values to the ball's position to obtain the new ball position.
  12. BALL X OUT OF BOUNDS? We want to know if the X position of the ball has reached either the left or right edges of the screen. If the result of this condition test is YES, then we will continue to the next step, otherwise we skip down to the following condition test.
  13. INVERT X DELTA To achieve the effect of bouncing the ball, we invert the value of the ball's horizontal motion delta X.
  14. BALL Y OUT OF BOUNDS? We want to know if the Y position of the ball has reached either the top or bottom edges of the screen. If the result of this condition test is YES, then we will continue to the next step, otherwise then we skip down to the DRAW BALL step.
  15. INVERT Y DELTA To achieve the effect of bouncing the ball, we invert the value of the ball's vertical motion delta Y.
  16. DRAW BALL We draw the character that will represent the ball on the screen when the code reaches this point of the execution.
  17. WAIT A SHORT TIME We are using a simple FOR-NEXT LOOP DELAY here.
  18. ERASE BALL We erase the ball from the screen to achieve the effect of motion.
  19. DRAW PADDLE We draw the characters that make up our paddle on the screen when the code reaches this point of execution. We JUMP back to the RUN/STOP PRESSED? condition test to complete our loop and the code will execute again from that point.

The MOVE PADDLE LEFT (#1) subroutine logic is as follows:

  1. PADDLE X-1+W/2 OUT OF BOUNDS? We test to see if the position left of the paddle reaches the left edge of the screen. If the result of this condition test is YES, then we RETURN to our Jump Target A. If the result of this condition test is NO, then we continue down to the next step.
  2. DECREMENT PADDLE X We decrease the value of the variable that holds the paddle's X position.
  3. ERASE RIGHT OF PADDLE When we move the paddle, there will be a ghost character on the right side of the paddle, we erase this character before we RETURN to our Jump Target A.

The MOVE PADDLE RIGHT (#2) subroutine logic is the same as the #1 subroutine except its reversed as you will see below:

  1. PADDLE X+1+W/2 OUT OF BOUNDS? We test to see if the position right of the paddle reaches the right edge of the screen. If the result of this condition test is YES, then we RETURN to our Jump Target A. If the result of this condition test is NO, then we continue down to the next step.
  2. INCREMENT PADDLE X We increase the value of the variable that holds the paddle's X position.
  3. ERASE LEFT OF PADDLE When we move the paddle, there will be a ghost character on the left side of the paddle, we erase this character before we RETURN to our Jump Target A.

As you can see, there isn't anything really complex going on here. Lets get on with the code next!

The Program Code

I said that I was going to reuse the code from the first article, however I'm not simply loading the old program and adding new lines. I am writing everything over from scratch because we need to add more variables, do more initialization, and the line numbers are going to change quite a bit.

The first 9 lines will remain the same except for line #7 which will hold a new comment for our program name. The header is below:

1 REM C-64 GAME DEVELOPMENT TUTORIALS
2 REM BY RICHARD MARKS
3 REM CCPSCEO@GMAIL.COM
4 REM WWW.CCPSSOLUTIONS.COM
5 REM *******************************
6 REM *
7 REM * ENTER THE PADDLE
8 REM *
9 REM *******************************
		

Next we type in the program init section

Our program will require several new variables.

I should have mentioned in the first article that variable names can be only one, or two characters in length, made up of only alpha-numeric characters A-Z and 0-9. Additionally, STRING variables are suffixed with a dollar sign. Such as A$

There are a few reserved variables that you may not define, since they are used for special purposes.

  • ST I/O status.
  • TI Every 1/60th of a second this variable will be updated. When you turn on the C64, this value starts at 0.
  • TI$ Automatically updated by the C64; This string holds a clock in the form of three pairs of numbers to represent the hours, minutes, and seconds.

We are not going to use either of these in our program, so I will not explain their usage in depth.

Moving on, lets see what variables will our program require.

MEMORY ADDRESS POINTER VARIABLES

We will be POKE-ing and PEEK-ing different memory locations, and to keep from retyping the memory addresses over and over, we define variables that hold the starting address of the hardware we need to access.

  • M1 A variable to hold the screen memory address.
  • M2 A variable to hold the color memory address.
  • M3 A variable to hold the screen background color register memory address.
  • M4 A variable to hold the screen border color register memory address.

Note: By default, the screen memory address space starts at 1024, and the color memory address space starts at 55296.

The screen is 40x25 (1000) characters in size. That is, forty characters across and 25 down. Every character screen cell has two attributes that are located in two different places in memory. The character value is in screen memory, and the color is in color memory. By POKE-ing values into the screen memory (1024 - 2023) we will see the specified character displayed on the screen, and when we POKE values into the color memory (55296 - 56295) we will change the color of the screen cell we specified.

The values that can be POKEd into screen memory are 0 - 255. The values that you can POKE into the color memory are 0 - 15, each value corresponding to the following colors:

  1. 0 BLACK
  2. 1 WHITE
  3. 2 RED
  4. 3 CYAN
  5. 4 PURPLE
  6. 5 GREEN
  7. 6 BLUE
  8. 7 YELLOW
  9. 8 ORANGE
  10. 9 BROWN
  11. 10 LIGHT RED
  12. 11 DARK GRAY
  13. 12 GRAY
  14. 13 LIGHT GREEN
  15. 14 LIGHT BLUE
  16. 15 LIGHT GRAY

If you POKE values larger than 15 into color memory, you will cycle through the color table again. Eg 16 is BLACK, 17 is WHITE, etc..

COLOR VARIABLES

These variables will let us change the colors we use easily.

  • C1 A variable to hold the color of the screen background.
  • C2 A variable to hold the color of the screen border.
  • C3 A variable to hold the color of the ball.
  • C4 A variable to hold the color of the paddle.

GENERAL PURPOSE / SUBROUTINE VARIABLES

These variables will serve different purposes depending on when they are used. They are used by subroutines.

  • K$ A variable to hold the key that was last pressed.

BALL OBJECT VARIABLES

These variables define the properties of our ball object.

  • BX A variable to hold the ball's horizontal screen position.
  • BY A variable to hold the ball's vertical screen position.
  • B1 A variable to hold the ball's horizontal delta. (The velocity of the ball's motion along the X axis.)
  • B2 A variable to hold the ball's vertical delta. (The velocity of the ball's motion along the Y axis.)

PADDLE OBJECT VARIABLES

These variables define the properties of our paddle object.

  • PX A variable to hold the paddle's horizontal screen position.
  • PY A variable to hold the paddle's vertical screen position.
  • PW A variable to hold the paddle's width in characters.

We need to initialize our program's variables now. Lets make our screen green, the border light green, our ball white, and our paddle black. We are going to create our paddle using 3 special C64 characters. The paddle width will be 5. We will start our ball near the center of the screen, and the paddle will start in the bottom center of the screen. The paddle position corresponds to the left edge of the paddle. The ball should start moving down and to the right. Using this knowledge, we can initialize all our program's variables.

99 REM *** PROGRAM INIT ***
100 M1=1024:M2=55296:M3=53281:M4=53280
110 C1=5:C2=13:C3=1:C4=0
120 BX=20:BY=11:B1=1:B2=1
130 PX=20:PY=24:PW=5
		

Clearing the screen and setting our screen colors is nearly the same as the bouncing ball demo code. We just need to use our new memory address pointer variables and color variables.

140 POKE M3,C1:POKE M4,C2:PRINT "{SHIFT+CLR/HOME}"
		

Okay, now we begin the main loop of our program.

Referring to the flowchart that I showed you earlier, we see that the first thing that we must do is get the keyboard input. We do this using the GET BASIC statement. It allows one to get a single character of data from the keyboard.

199 REM *** MAIN LOOP ***
200 GET K$
		

That was easy... Right? Okay, lets get the conditions out the way.

210 IF K$ = "A" THEN GOTO 1000
220 IF K$ = "D" THEN GOTO 1200
		

Next we update the ball position like our flowchart tells us. And then we handle the ball bouncing condition tests.

230 BX=BX+B1:BY=BY+B2
240 IF BX <= 0 OR BX >= 39 THEN B1 = -B1
250 IF BY <= 0 OR BY >= 24 THEN B2 = -B2
		

Lets draw the ball, wait five clock ticks, and erase the ball.

This line looks a little complex. Let me break it down for you.

  1. A1=M1+BX+BY*40 We calculate the memory address in screen memory space that the ball will be drawn in, and save the address in the A1 variable.
  2. POKE A1+M2-M1,C3 We POKE the ball color into color memory space at the calculated address that we get by adding the color memory address to our A1 variable and subtracting the screen memory address.
  3. POKE A1,81 We POKE the ball character into screen memory space at the address we calculated before.
  4. FOR W = 1 TO 5:NEXT We wait for five clock ticks using a simple FOR-NEXT LOOP construct.
  5. POKE A1,32 We POKE an empty space character into screen memory space to erase the ball.
260 A1=M1+BX+BY*40:POKE A1+M2-M1,C3:POKE A1,81:FOR W = 1 TO 5:NEXT:POKE A1,32
		

We now need to draw the paddle like our flowchart tells us to. This is a little more complex than the ball drawing code since our paddle is made of five characters and not one. I guess I should explain the fun stuff below so you are not scratching your head wondering.

  1. A1=M1+PX+PY*40 We add the screen memory address, the paddle X position, and the paddle Y position and multiply the whole shbang by 40 to get the proper memory address for the left side of the paddle, and save the result in the A1 variable.
  2. POKE A1,85 We POKE the character for the paddle's left side to draw it on the screen.
  3. POKE A1+PW,73 We POKE the character for the paddle's right side to draw it on the screen.
  4. FOR A2 = A1+1 TO A1+(PW-1):POKE A2,67:NEXT We POKE the character that makes up the paddle center using a FOR-NEXT LOOP.
  5. A2=A1+M2-M1 We calculate the color memory address for the paddle's left side.
  6. FOR A3 = A2-1 TO A2+PW:POKE A3,C4:NEXT We loop across the entire paddle and color it with our paddle color.
270 A1=M1+PX+PY*40
280 POKE A1,85:POKE A1+PW,73:FOR A2 = A1+1 TO A1+(PW-1):POKE A2,67:NEXT
290 A2=A1+M2-M1:FOR A3 = A2-1 TO A2+PW:POKE A3,C4:NEXT
		

Finally, we end our main loop by returning to the first line of our main loop code.

300 GOTO 200
		

Now we need to write the subroutines. Remember that we are starting subroutines at line 1000, and that we skip 200 line numbers between each subroutine.

Moving to the left

999 REM ** MOVE PADDLE LEFT SUB
1000 X1=PX-1
1010 IF X1 < 0 THEN GOTO 230
1020 PX=PX-1
1030 POKE M1+PX+PW+1+PY*40,32: GOTO 230
		

Moving to the right

1199 REM ** MOVE PADDLE RIGHT SUB
1200 X1=PX+PW+1
1210 IF X1 > 39 THEN GOTO 230
1220 PX=PX+1
1230 X2=X1-(PW+1)
1240 POKE M1+X2+PY*40,32:GOTO 230
		

And that is the end of this tutorial! Thank you for reading. If you have any questions or comments, please contact me.

Full Source

I've provided the full source below to make it easier for you to see the program as a whole.

1 REM C-64 GAME DEVELOPMENT TUTORIALS
2 REM BY RICHARD MARKS
3 REM CCPSCEO@GMAIL.COM
4 REM WWW.CCPSSOLUTIONS.COM
5 REM *******************************
6 REM *
7 REM * ENTER THE PADDLE
8 REM *
9 REM *******************************
99 REM *** PROGRAM INIT ***
100 M1=1024:M2=55296:M3=53281:M4=53280
110 C1=5:C2=13:C3=1:C4=0
120 BX=20:BY=11:B1=1:B2=1
130 PX=20:PY=24:PW=5
140 POKE M3,C1:POKE M4,C2:PRINT "{SHIFT+CLR/HOME}"
199 REM *** MAIN LOOP ***
200 GET K$
210 IF K$ = "A" THEN GOTO 1000
220 IF K$ = "D" THEN GOTO 1200
230 BX=BX+B1:BY=BY+B2
240 IF BX <= 0 OR BX >= 39 THEN B1 = -B1
250 IF BY <= 0 OR BY >= 24 THEN B2 = -B2
260 A1=M1+BX+BY*40:POKE A1+M2-M1,C3:POKE A1,81:FOR W = 1 TO 5:NEXT:POKE A1,32
270 A1=M1+PX+PY*40
280 POKE A1,85:POKE A1+PW,73:FOR A2 = A1+1 TO A1+(PW-1):POKE A2,67:NEXT
290 A2=A1+M2-M1:FOR A3 = A2-1 TO A2+PW:POKE A3,C4:NEXT
300 GOTO 200
999 REM ** MOVE PADDLE LEFT SUB
1000 X1=PX-1
1010 IF X1 < 0 THEN GOTO 230
1020 PX=PX-1
1030 POKE M1+PX+PW+1+PY*40,32: GOTO 230
1199 REM ** MOVE PADDLE RIGHT SUB
1200 X1=PX+PW+1
1210 IF X1 > 39 THEN GOTO 230
1220 PX=PX+1
1230 X2=X1-(PW+1)
1240 POKE M1+X2+PY*40,32:GOTO 230
		

Screenshots

Click on a thumbnail to view the full-sized image.

Loading in the code after I've saved.

The code running.

The first section of code..

The second section of code..

The third section of code..

All contents of this tutorial are © Copyright 2009, Richard Marks. All rights reserved. Permission to publish this article granted byRichard Marks to Mattias Gustavsson. This article may not be redistributed in any form without permission.Contact Richard Marks if you are interested in publishing this article on your site.