Let's Make Robots!

Learning Spin: Make a videogame, part 2

Add collisions, movement and color to your lightcycles game

This is the second of a 3 part series.  Here's part 1, and stay tuned for part 3.  For more Spin tutorials, go here.

In part 2 of this tutorial series, we'll expand our graphics to make custom shapes, add movement to the lightcycle, and include collision detection.  You can grab a copy of the code below here zip.  Be sure to go through part 1 before working on this part.  When you've completed part 2, you'll have a program like the video above.

Where we left off

Last time we spent some time learning Spin basics as well as some of the basics of game design and control. The outcome of part 1 was a simple drawing program which used the method .pokechar to place characters on the screen depending on the X,Y variables changed by pressing buttons on the controller.  This time we'll take things to the next level by creating the first part of our LIGHTCYCLES game. Create a new Spin program in your source folder from lesson 1 called LIGHTCYCLE.spin and we'll get started.

Our program starts out pretty much the same as last time, defining the speed of the Propeller, our controller settings (refer to lesson 1) as well as the two objects we'll call on. Type the following code;

game_tutorial2_1

Notice I've added some text after a couple ' characters? This is a way of making a remark or comment in your code which won't affect the outcome of the program. Want to vent a little? Leave notes for others? Using ' characters you can. This is called "code commenting" and is a really good idea for making it easy for others to understand your code when you don't have time to write a document like the one you are reading now. If you have ever programmed in BASIC they work pretty much the same as a REM statement.

Next we need to create a new VAR (variables) section. Remember these are the empty byte-boxes we'll store information in later. Note my use of the '' remarks again? You don't have to type them in. but it's a good idea to do so. The explanations of what we are doing with our byte-boxes will come in handy later when re-reading the code.

game_tutorial2_2

Next let's create our PUB section. Part of this will look very familiar to you;

game_tutorial2_3

The migs.init(controller) & text.start(video) lines kick start our objects just as had before, but let's look at the new .redefine method.

Custom characters

Below the text.start(vide) line there's;

     text.redefine("#",255,255,255,255,255,255,255,255)

In our drawing program we used the # character to draw all over the screen. We'll use the same character for our LIGHTCYCLES game, but this time we'll change what the character looks like by changing its pixels. Ah. Game Stuff!

Remember last lesson when I mentioned that the entire screen could be filled with 1000 characters? Each character is made up of 64 pixels. Eight pixels across, and eight pixels down. We can change how the character looks by turning these pixels off or on.

So let's get on with creating a redefined character. A character is 8 dots wide by 8 dots tall. Get a piece of notebook paper or graph paper and setup a sample grid that is 8 spaces down and 8 spaces across. Across the top write 128,64,32,16,4,2,1. Draw a line at the end of each row. Now fill in the grid with any design you want. It's easiest to outline the shape first and then go back in fill in the grid.

Now if you think of all the squares you filled in as "on" then substitute a 1 for each filled square. For the one's that aren't filled in, they are "off" so put in a zero.

Starting on the first line, you need to convert the dots into data that the .redefine method can read. Each set of 8 squares is equal to one of the numbers in .redefine. Working from the left, add up each of the squares which contain a 1 and write that value on line to the right.

game_tutorial2_4

You should wind up with 8 values which go into the .redefine method.

Here's a sample of a possible redefined character;

Compare this to the line we actually typed into our program and see if you can tell what the newly redefined character will look like. Here's a blank graph for designing your own custom, redefined characters;


Redefining characters is an interesting method contained in the AIGeneric object. Older readers might have immediate recognized the concept of calculating the pixels from the Commodore 64 computer. It used the same method to calculate sprites. AIGeneric doesn't have sprites, but image what would happen if you redefined several characters to sit next to each other on the screen? It's food for thought.

Let's get back into coding our game. Add the following lines, inserting some extra carriage returns like the graphic shown below;

game_tutorial2_7

There is another of the comments (remarks) and the recognizable commands, .pokechar and .Updatescreen, but what on earth is happening with SetupPlayers?  SetupPlayers is the name of a new PUB command we're about to add to our program.

Add the following lines below the bunch of extra carriage returns;

game_tutorial2_8

We could have simply inserted these lines into the spot the SetupPlayers was typed, but by putting these in their own PUB block, we can call these commands any time without typing them all in again and again. We've called them the first time to set up our initial playing variables, but what happens when we crash? We'll call them again to start the game over. So SetupPlayers puts all the starting variables into our byte-boxes and returns to our code when finished. Make as many PUB sections as you need when programming in Spin.

It's time to re-create our game loop. Roll up your sleeves and cursor back up to our original PUB main block and continue adding code;

game_tutorial2_9

Before we start breaking this down, we need to add some additional CON entries. Remember those are English words that simply represent numbers to our Propeller.

Add the following lines of code right between the waitcnt line & SetupPlayers PUB;

game_tutorial2_10

Just like PUB, you can create multiple instances of CON in your programs. There is no reason why I couldn't have included these in our initial CON section, but for design style reason I've put them here.

Time to break down the game loop;

game_tutorial2_11

In our paint program, we used the same MIGS.Player1 lines to add or subtract from our X,Y variables. This meant that the only time X, and Y changed was when we pressed the controls. This time we are changing the variable one_dir to equal right, left, down, or up. Have a look at the new CON section we added above and see if you can determine what is happening. Yes, if the Right button is pressed on the controller, then one_dir is equal to 2. The variable one_dir is the direction our player is traveling.

So the direction was changed "if" the button was pressed. These next four lines check the one_dir variable to determine which direction it is facing. If the direction is "left" then one_x has 1 subtracted from it's total. If the direction is "right" then one_x has 1 added to its total. Every time the loop repeats, a number is added or subtracted depending on the direction set. Hmm.. Suddenly our paint program has a mind of its own.

And finally, some code you should be completely familiar with;

game_tutorial2_13

Remember .pokechar and .UpdateScreen? These update our display.  Now that the movement doesn't stop, the waitcnt command becomes valuable.

Here's the whole game loop once again;

game_tutorial2_14

Hit F10 and send the program to your Propeller. The line should take right off!

There are no rules or boundaries yet, so you can steer right off the edge of the screen and come back on the other side. It's the start of LIGHTCYCLES.

Collision detection

It's time to create some simple rules for our game. Insert the following code above the line that reads: ''Update player positions and update the screen;

game_tutorial2_15

     one_cd := text.GetChar(one_x, one_y)

This retrieves the character at the location we just moved to. Remember every time the loop repeats, the one_x, and one_y variables are changed depending on the direction.

Another IF statement is created to check the contents of our byte-box, one_cd.

All of the black areas of the screen are created with spaces, (character 32). When our player moves around the screen, the # is drawn. (character 35) The condition of our IF statement is checked to see if we have just run into any character higher than 32. If we have, then the indented lines below the IF statement are enforced.

     text.str(string(" PLAYER 1 DEREZZED! PRESS [START]:"))

Another new method! .str(string(" {text} ")) sends a line of text to screen memory.

     text.Updatescreen

sends the screen memory to the screen displaying the change.

      repeat until MIGS.Start == 1

This is an interesting command. Repeat simply keeps check for the Start button to be pressed before allowing the program to continue, waiting for the user.

     text.cls

This clears the screen to start the game over.

     SetupPlayers

Since our game is starting over, our SetupPlayers PUB is called to reset everything back to starting positions. After this command is completed, our program exits the IF statement block and returns to our game loop.

Send the new code to the Propeller and see what happens when you crash into your own line.

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

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.
text.cls Clears the screen
text.str(string(" ")) Sends a line of text to screen memory
text.GetChar(X,Y) Retrieves the value of character at X,Y


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
if MIGS.Start == 1 Checks to see if game control START is pressed


This lesson took you from drawing on the screen to trying not to crash your on-screen player. The next lesson will expand on this to include a second player and some other refinements. Experiment with the .redefine and waitcnt commands and see what you come up with! See you in Part 3!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Jeff (oldbitcollector) did 'MIGS" as an interchangeable set of objects, so you can use an NES gamepad, keyboard, N64 keyboard, or virtual keyboard by just swapping the object.  Each version is here.

The default graphics object offers a 64 color palette, and each tile can have 4 colors.  Rayman did a useful writeup here, but the ultimate source is the hydra book.  I probably use the default the most because I'm lazy, but there are a lot of independent efforts, folks have figured out how to get true color, hi-res, etc.  They pop up on the Parallax forums every now and again, but they should also be in the obex. 

 

Nice walkthrough - liked the character sprite idea - and yes i remember C64 sprites.

       I do not have a MIGS controller .... so i installed a keyboard obj and placed a call to it in a separate cog (as the keyboard obj appears to hang everything until a key is pressed) ......  it worked well by using the cursor keys. (not sure how it would cope with 2 players fighting over the keyboard though .....maybe other player would use the mouse).

I have a question about colours in general - how many colours can be displayed on the screen at any one time ?

...and What would you consider to be the best all-round graphics obj ?