Learning Spin: Make a videogame, part 1

LIGHTCYCLES-300x220.jpg

This is the first of a 3 part series.  Stay tuned for parts 2 and 3!  For my other Spin tutorials, go here.

Introduction

One of the important differences that sets the Propeller apart from most other microcontrollers is its ability to do video. The hardware connections are extremely simple. Three resistors connected between the Propeller and the AV-IN of your television and you are in business. If you haven't taken the time to configure your Propeller to do video and view the "Graphics Demo" included in the Propeller Tool, this would be a good moment to stop reading and do so.

The game platforms from the 80's were extremely limited in their capabilities, but they provided something that modern gaming as left behind; an ability to understand of how things work. While modern gamers enjoy rich, powerful 3d graphics environments and games, actually being able to comprehend how the technology works would be a little like trying to learn Chinese in an afternoon.

The Propeller will allow us to "spin" the clock back twenty years to examine how basic graphics manipulation and gaming works. At the heart of every pulse pounding, 3D, multiplayer shooter, are the same concepts used by every game since the creation of PONG.

This tutorial will start with the fundamentals of game design. Through the course of this tutorial you will design a simple LIGHTCYCLES type game based on a popular movie. I will expect you to understand just enough Spin programming to be dangerous, so if you haven't completed some of the basic lessons, take the time to work through them before continuing.

Required Hardware

  1. A Propeller board capable of TV video out and keyboard or NES controller input
    I'll be using the Propeller Platform USB with the El Jugador Module, but a Parallax Propeller Demoboard with PS2 Keyboard (or equivalent) will also be fine.
  2. A couple NES controllers or PS2 keyboard
    We'll be creating a two player game, so if you are using NES controllers, you'll need a second one by part 3. If you are using a keyboard configuration, we'll squeeze both players in on either side of the keyboard.

A quick "Sanity Test"
Before we jump in, it is always wise to do a quick sanity test on the hardware. To make sure that our video connection is working correctly. Open the Propeller Tool and load the "Graphics Demo" to your Propeller using F10. Do you see moving graphics? If not take a few moments and check your connections. This simple test will insure that we have success in this lesson.

A preview of the objects
As you've seen in the Graphics Demo, the Propeller is capable of amazing video, but even the commands used in Graphics Demo can be a little daunting to a new user, so we're going to take one step backward and work with a text driver.

The microcomputers and game systems of the 80's had their graphics and sound capabilities hard-wired into the chips used in those systems. This meant that upgrading generally meant buying a new system every few years. Unlike those old systems, the Propeller is capable of a variety graphics and sound systems by simply changing the object code doing the work.

We'll be working with two objects; the first is a universal game control driver called, MIGS which allows us to create games which are compatible with a variety of game controllers like NES, N64, or keyboard without having to rewrite our code. The second is a video/text driver called AIGeneric which will power our display. These primitive 'drivers' will allow us to have a great time writing a game while understanding the concepts of game design.

Don't underestimate the power of the drivers we are going to be using. The systems of the 80's operated at single digit megahertz speeds. The Propeller will be operating at 80Mhz! This makes it possible to do amazing things with very little.

Setting up the environment

Download and extract the source code  to an empty folder. Create a new spin program called "Demo1" and save it to this folder. This is the program we'll create.

We're almost ready to jump right in, but first let's look at the two possible configurations you might be using depending on your hardware.

Option 1: Propeller Platform w/ El Jugador Module
This platform takes advantage of video out as well as NES controllers for real gaming feel. Our program will start with the following configuration:

Video out starts with Propeller pin 12
NES controllers start with Propeller pin 23

Type in the code above. The _clkmode & _xinfreq lines are pretty much standard in most Spin programs as they set the Propeller's speed to the 80MHz used by most boards.

(Compatibility Note: If you have a Parallax NES controller card plugged into a different board, the "controller = 23" line is simply the CLK pin. Adjust it to the pin number used accordingly, and the other lines should fall out fine.)

Option 2: Parallax Propeller Demoboard with PS2 Keyboard attached
This platform takes advantage of video out, as the keyboard to serve as input for both players. One player will use the cursor arrow keys. The other player will use F,S,E, and D, and [ENTER] will be used as Start.

Video out starts with Propeller pin 12
PS2 Keyboard starts with Propeller pin 26

Type in the code above. The _clkmode & _xinfreq lines are pretty much standard in most Spin programs as they set the Propeller's speed to the 80MHz used by most boards.

The MIGS object is designed to take advantage of a variety of game control systems. An addendum will be provided to show you how to easily add a Nintendo 64 game controller to your Propeller.

What the heck did we just do?  All microcontrollers work internally with numbers, in fact they prefer them! Humans on the other hand prefer words and pictures. The CON section has the two lines to set the speed, but also the following two lines:

    video = 12
    controller = 23

We have told the program to consider the word, "video" equal to the number "12". This way any time we need to say "12", we can simply say, "video". Because these are located in the CON section, their values are set in stone the entire time the program is running. We could have used almost any word or number.

The OBJ (Object) section includes the AIGeneric & MIGS controller objects into our code. These two objects will do the "heavy lifting" of making the magic happen by calling on their available methods. Later as you get further into programming your Propeller, you can even make your own Objects defining the rules that your program uses.

When we call on the "methods" available in these objects we'll use text.{method} & migs.{method} as defined in the OBJ section. If your eyes are starting to glaze over right about now, don't worry, you'll see how it works in the code.

VARiably speaking
It's time to add a couple variables to our program. Type the following code below what we have already entered. As you start to type VAR, you'll notice the color change again.

The VAR (Variable) section defines variables which can be used anywhere in our code. Unlike the words defined in the CON section variables in the VAR section can be assigned any value we want, anytime in our program. Think of variables as empty boxes which can store information. See the word, "byte"? In this case, the word "byte" defines the size of our box. Bytes are small boxes which are only big enough to store a single number or letter. We'll store some information in character_x, and character_y soon enough, but for now we'll set those empty boxes aside. Just like CON we could have called our byte-boxes almost anything we wanted.

It's time to go PUBlic
Type the following code below the VAR section. Again the color will adjust as you start to type in PUB.

The PUB (Public) section of our code will contain the commands to actually do something interesting. The two lines we just entered kick start the objects we called in the OBJ section. Remember what we said about the words, "controller", and "video"? The Propeller sees this as migs.init(23) & text.start(12). Why do we do this? This makes it easy to adjust these settings in the top of our code without fishing through the program to adjust pin settings.

Graphics

The AIGeneric Graphics & Text Driver is a collaborative project between several members of the Propeller forums. It is a great example of what happens when the members of the community work together.

This driver includes:

  • 16 colors
  • 40 x 24 text (1000 character positions)
  • redefinable characters
  • exact character placement

The screen layout is similar to those used by early microcomputers. Older readers will find it feels like the 8-bit Commodore or Atari computers, the perfect place to learn about the basics of graphics and gaming.  Here's a representation of the screen layout;

The 40 columns across the screen are numbered 0-39. The rows down are numbered 0-24. This gives us a total of 1000 possible characters. As a small child you were likely taught to start counting using your fingers, 1, 2, 3, but microcontrollers (and computers) start counting at 0, unless told otherwise.  By plotting the X (column) and Y (row) position, we can place any character on the screen in any location.  With that understanding, it's back to our code.  It's time to add two more lines to the PUB section of our program;

Remember the two empty byte-boxes we defined earlier?  We've placed the numbers, 19 and 12 into those boxes. This will give our program the location to use when we turn on a character on our screen. We'll use location column(x) "19" and row(y) "12", about the middle of our screen.  Let's add some additional lines of code and make things interesting;

The repeat command sets up a loop which will all commands "indented" below it will repeat over and over again. The repeat loop will later be expanded to contain the actual game itself. Notice how all the commands indented below have connecting lines showing that they are part of the loop?

Let's break down the next three lines;

text.pokechar(character_x, character_y, 7, "#")

This is the most interesting of the three. The .pokechar method does the work of actually placing the character into screen memory. The character_x, and character_y variables contain the numbers "19", and "12" defining which location to poke a character into. The "7" defines one of the 16 colors. The "#" character is the actual character to be placed on the screen. We could easily display a letter or number by changing the # sign. (Try it!)

text.UpdateScreen

The actual change made to the display doesn't happen until this command is issued. This allows us to make several .pokechar commands in the background, then display them all at once when the .UpdateScreen is issued. This makes screen movement look smooth.

waitcnt(clkfreq / 20 + cnt)

The waitcnt command is a simple delay which slows the loop a little. It isn't really important now, but when will become vital later to keep the game play at a reasonable speed to play. The Propeller is capable of speeds higher than needed here, so we need to slow things down to give the humans a chance.

Hit the F10 key and send this program to your Propeller if you haven't already and you should see a # sign that kinda looks like a railroad track displayed in the center of your screen. Experiment with changing the character_x and character_y variables to move it around. Change the "7" or even the "#" and see what happens. It's time to play.

When you are done, you should know how to use .pokechar to place any character in any of the 1000 locations of your screen. We've only scratched the surface.

Adding some controls

It's time to add some controls to our little program and make it interactive.  Insert some blank lines between the repeat command and the text.pokechar commands and type in the code below, making sure it's indented as shown;

Now we are reading our game controller (or keyboard) for right & left input. Each time we press the button we either add 1 or subtract 1 from our character_x variable.  Send the program to your Propeller again and see if you can draw characters left and right. Each time you press one of the buttons you are changing the variable sent to text.pokechar.  Let's add two more lines for up and down;

This time we are either adding 1 or subtracting 1 from character_y depending on which button is pressed on our game controller or keyboard. Play with it a little. We've created a simple drawing program!  It's pretty easy to create a big mess on the screen, so let's add one more button to clear the screen and start over.  Add the following;

Every time you press the "B" button (or key) the method text.cls is called clearing the screen.

We've added a lot of lines to our PUB section, so if you are having trouble, here's the entire PUB section;

You should be starting to see the beginnings of a game loop. The repeat command starts the loop, then each of the buttons on the controller is checked to see if anything needs to change, then the change is sent to .pokechar and .UpdateScreen to display it.  The waitcnt sets up a reasonable delay.

If you are having trouble a copy of the entire program has been included as "DEMO_Part1.spin" in the source code 

Review

Here are the methods we've used in this lesson:

AIGeneric Text Display Object

text.start(IO) Kick starts the text object AIGeneric
text.pokechar Sends a character to screen memory (X,Y,COLOR,"{character})
text.UpdateScreen Sends the current screen memory to your physical display.

 

MIGS Multi Interface Game System Object

if MIGS.Player1_Right == 1 Checks to see if game control right is pressed
if MIGS.Player1_Left == 1 Checks to see if game control left is pressed
if MIGS.Player1_Down == 1 Checks to see if game control down is pressed.
if MIGS.Player1_Up == 1 Checks to see if game control up is pressed
if MIGS.B == 1 Checks to see if game control B is pressed

We've covered a lot of ground in this lesson, moving from almost zero to the creation of a drawing program. In the next lesson we'll take things to the next level by adding some game logic converting this into a simple game.

In the meantime I want to invite you to make changes, and try things with this code. Experimentation is a powerful way to learn!

https://vimeo.com/18768157