No-Library guide to BASIC Sprites and Maps



*Note the examples in this guide are written in TI-Basic for the 83+ series, however the concepts may be applied with a little bit of work to other calculators. Also the example code will appear better in TI-GraphLink or TI-Connect Mac. You can also install the two font files included in the zip file. Finally I don't recommend this tutorial unless you are already familiar with using TI-Basic and the menu's and your calculator.

Introduction:

I'm sure that most of you BASIC programmers out there have wondered why BASIC games almost never have good graphics? Why do ASMer's get to make all the cool programs? And I am sure that most of you have arrived at the conclusion that it is because ASM is a more powerful language, and that to make BASIC games with good graphics you need to use an ASM library such as xLib, Omnicalc, or CODEX. While the first part of this is true, the second most definitely is not. Programming good graphics in BASIC without ASM libs is slower than pure ASM, but with good coding habits, it can be as fast as BASIC programs with ASM libs. Take for instance Kevin Oullette's great series of RPG's written in BASIC. Sure the have great graphics, and are incredible for being written in BASIC, but they are not particularly fast. However QuestGRPG (by me) is as far as I know, the first BASIC game of its kind. It is fully graphical, it uses no ASM libs, it has a scrollable map, and animated sequences. It is not complete, and probably won't be for a while, since I am now competing in the UTI Basic Contest, however I will be using the demo version as an example along with several other programs by other people, and here is the source for the three most important files:

*NOTE:
-> is the store key
n= is not equal to
>=/<= is greater than/less than or equal to
Things in * are not supposed to be put on the calc, this is notes for you.
prgmLNDMARKS:		;(NEEDS OPTIMIZING. SEE HOW MUCH YOU CAN CUT THE SIZE BY)
randInt(1,40)->θ
If θ>=1 and θ<4
Then
augment(L5,{0,1,2,2,2,3,3,4,5,6,7,8}+E)->L5
augment(L6,{0,1,3,1,4,0,4,3,1,2,2,1}+F)->L6
End
If θ>=8 and θ<12
Then
augment(L5,{0,1,2,3,4,4,5,6,6,7,8,9}+E)->L5
augment(L6,{0,1,2,1,F,2,3,2,F,1,2,1}+F)->L6
End
If θ>=16 and θ<18
Then
augment(L5,{0,0,1,1}+E)->L5
augment(L6,{0,1,0,1}+F)->L6
End
=========================================================================================================================================
prgmMOTIONGN:
:"Quest <θ>
FnOff 
{Xmin,Xmax,Ymin,Ymax}->L3
{5,20,20,30,30,0}->LROBRT
{51}->L1
{30}->L2
Plot1(xyLine,L1,L2,+)		*NOTE: Found by pressing 2nd, Y=, and <*
{48,49,49,49,49,50,50,50,51,51,51,52,52,52,53,53,53,53,54,-20,-20,-19,-19,-19,-19,-18,-17,-16,-16,-16,-16,-15,-15}->L5
{34,32,33,34,35,32,35,36,32,33,36,32,35,36,32,33,34,35,34,-20,-15,-19,-18,-17,-16,-15,-15,-19,-18,-17,-16,-20,-15}->L6
Plot2(Scatter,L5,L6,.) 		*See previous note*
PlotsOff 
prgmTITLE
Text(40,2,"Generating initial terrain...
For(X,-20,105,1)
X->E
Ymin+randInt(-20,100)->F
prgmLNDMARKS
Text(50,15,int((X+20)/126*100)," percent complete"
End
PlotsOn 1,2
0->K
While Kn=45
getKey->K
Line(-21,106,-21,-21
Line(-21,-21,116,-21
Line(116,-21,116,106
Line(116,106,-21,106
(K=26 and L1(1)<114)+L1(1)->L1(1)
-(K=24 and L1(1)>-19)+L1(1)->L1(1)
(K=25 and L2(1)<104)+L2(1)->L2(1)
-(K=34 and L2(1)>-19)+L2(1)->L2(1)
L1(1)-45->Xmin
Xmin+94->Xmax
L2(1)-32->Ymin
Ymin+62->Ymax
randInt(1,40
If Ans=5
prgmBATTLNGN
End
PlotsOff 
DelVar L1DelVar L2DelVar L3DelVar L5DelVar L6DelVar LENEMYDelVar LROBRT
ClrHome
Output(1,1," 
=========================================================================================================================================
prgmBATTLNGN:
{randInt(LROBRT(1)-1,LROBRT(1)+1),randInt(LROBRT(2)-10,LROBRT(2)+5),randInt(LROBRT(3)-5,LROBRT(3)+5),0,randInt(LROBRT(4)-10,LROBRT(4)+10)}->LENEMY
ClrDraw
PlotsOff 
Vertical Xmin
Vertical Xmax
Horizontal Ymin
Horizontal Ymax
Text(ú1,25,25,"Battle!
Repeat getKey
DispGraph
End
Text(-1,25,25,"       " *7 spaces*
Line(Xmin+5,Ymin+4,Xmin+8,Ymin+10
Line(Xmin+12,Ymin+10,Xmin+15,Ymin+4
Line(Xmin+8,Ymin+10,Xmin+12,Ymin+10
Line(Xmin+10,Ymin+10,Xmin+10,Ymin+18
Line(Xmin+8,Ymin+16,Xmin+4,Ymin+12
Line(Xmin+12,Ymin+16,Xmin+16,Ymin+12
Line(Xmin+8,Ymin+16,Xmin+12,Ymin+16
Circle(Xmin+10,Ymin+23,4
Line(Xmin+14,Ymin+13,Xmin+18,Ymin+13
Line(Xmin+16,Ymin+11,Xmin+16,Ymin+21
"ENEMY DRAW
Line(Xmax-5,Ymin+4,Xmax-8,Ymin+10
Line(Xmax-12,Ymin+10,Xmax-15,Ymin+4
Line(Xmax-8,Ymin+10,Xmax-12,Ymin+10
Line(Xmax-10,Ymin+10,Xmax-10,Ymin+18
Line(Xmax-8,Ymin+16,Xmax-4,Ymin+12
Line(Xmax-12,Ymin+16,Xmax-16,Ymin+12
Line(Xmax-8,Ymin+16,Xmax-12,Ymin+16
Circle(Xmax-10,Ymin+23,4
Line(Xmax-14,Ymin+13,Xmax-18,Ymin+13
Line(Xmax-16,Ymin+11,Xmax-16,Ymin+21
Text(35,25,"Die human scum
Line(Xmax-10,Ymin+21,Xmax-18,Ymin+24
Pause 
Line(Xmax-10,Ymin+21,Xmax-18,Ymin+24,0
Text(-1,35,25,"         " *NOTE: 9 spaces*
Circle(Xmax-10,Ymin+23,4
"LOWER SWORD
Line(Xmin+14,Ymin+13,Xmin+18,Ymin+13,0
Line(Xmin+16,Ymin+11,Xmin+16,Ymin+21,0
Line(Xmin+16,Ymin+15,Xmin+16,Ymin+11
Line(Xmin+14,Ymin+13,Xmin+24,Ymin+13
"LOWER ENEMY SWORD
Line(Xmax-14,Ymin+13,Xmax-18,Ymin+13,0
Line(Xmax-16,Ymin+11,Xmax-16,Ymin+21,0
Line(Xmax-16,Ymin+11,Xmax-16,Ymin+15
Line(Xmax-14,Ymin+13,Xmax-24,Ymin+13
"FIGHT
While LROBRT(5)>0 and LENEMY(5)>0
Text(3,3,"HEALTH: ",LROBRT(5),"     ENEMY: ",LENEMY(5),"           " *Spaces: 6 and 11*
Line(Xmin+24,Ymin+13,Xmax-25,Ymin+12
Line(Xmin+24,Ymin+13,Xmax-14,Ymin+20
Line(Xmin+24,Ymin+13,Xmax-15,Ymin+6
Line(Xmin+24,Ymin+13,Xmax-25,Ymin+12,0
LENEMY(5)-int(LROBRT(2)/LENEMY(3)+randInt(1,4))->LENEMY(5)
For(W,1,10
Text(2,2,"
End
Line(Xmin+24,Ymin+13,Xmax-14,Ymin+20,0
Line(Xmin+24,Ymin+13,Xmax-15,Ymin+6,0
For(W,1,25
"FIGHT2
Text(2,2,"
End
Line(Xmax-24,Ymin+13,Xmin+25,Ymin+12
Line(Xmax-24,Ymin+13,Xmin+14,Ymin+20
Line(Xmax-24,Ymin+13,Xmin+15,Ymin+6
LROBRT(5)-int(LENEMY(2)/LROBRT(3)+randInt(1,4))->LROBRT(5)
For(W,1,10
Text(2,2,"
End
Line(Xmax-24,Ymin+13,Xmin+25,Ymin+12,0
Line(Xmax-24,Ymin+13,Xmin+14,Ymin+20,0
Line(Xmax-24,Ymin+13,Xmin+15,Ymin+6,0
End
"SCORE
If LROBRT(5)>0
Then
ClrHome
Disp "VICTORY!
LROBRT(6)+LENEMY(1)2->LROBRT(6) *2 is the x squared key*
While LROBRT(6)>=2^LROBRT(1)
LROBRT(1)+1->LROBRT(1)
LROBRT(2)+LROBRT(1)5->LROBRT(2)
LROBRT(3)+LROBRT(1)5->LROBRT(3)
LROBRT(4)+LROBRT(1)5->LROBRT(4)
LROBRT(4)->LROBRT(5)
End
Disp "Level:",LROBRT(1),"Points:",LROBRT(6),"Health:",LROBRT(5)
Pause 
ClrHome
Else
ClrHome
Disp "Defeat :("
Pause 
ClrHome
{50}->L1
{31}->L2
LROBRT(4)->LROBRT(5)
End
ClrDraw
PlotsOn 1,2
=========================================================================================================================================

Text-Sprites and Matrix Maps:

One method for displaying sprites is to use text sprites. This uses overlapping text characters to generate a wide variety of sprites. Two examples of text sprites are Donut Quest by Mikhail Lavrov, and BomberMan by Travis Fischer. This screenshot is from Donut Quest:

Doesn't look like a BASIC game does it? Amazingly it is, and this is the most perfect example of text sprites I have ever seen. To generate text sprites, first store the characters needed for your sprite to a string, and then use a loop to loop through the string, and use sub( to display each character. The program FindSprite, included in this zip, will read a 6*6 sprite from the top left corner of the graph screen, and generate code that you can use to display the code.
To store maps like this, you can hard code in the map, or use a matrix to store it. If you hard code it, it will draw faster, but it will take up more memory, and it will still be easier to use a matrix for wall detection. Donut Quest uses matrices. After loading matrix [B], it then stores that graphic to a picture, and uses matrices for wall detection. This method also works fastest for non-scrolling maps. Here is a quick sample program for using a text sprite. Fiddle with Str1 and see what you can come up with:

prgmTXTSPRIT:
"[{4}->Str1
1->Y
DelVar K
1->X
DispGraph
While Kn=45
getKey->K
If (K<=26 and K>=24) or K=34
Then
Text(Y,X,"      " *6 spaces*
X+(K=26 and Xn=90)-(K=24) and Xn=1)->X
X-(K=25 and Yn=1)+(K=34 and Yn=55)->Y
For(I,1,length(Str1
Text(Y,X+I,sub(Str1,I,1
End
End
End

Hard coded Maps and Sprites:

Another method for displaying sprites is to use the built in drawing commands. This unfortunately, takes up a lot of memory, and hit detection is slow. However for simple, non-scrolling uses it can be fine, if somewhat memory intensive. NEVER hard code maps with the drawing commands, and if you do sprites do not use the pxl- commands. Here is an example of the hard code graphics in the battle engine for QuestGRPG.

If you look at the source for prgmBATTLNGN, you will see a lot of Line( and Circle( commands. This is the hard coded part. If you have a hard time understanding that, don’t worry. The single most important piece of advice when hard-coding graphics is “Draw it first, code it after.” This means design your graphics before hand, and then use those handy coordinates at the bottom of the graph screen to figure out how to code the drawing into your program. Remember that you can use a “0” as an optional 5th parameter to Line(, which will draw your Line( command in white instead of black. Use this for erasing stuff that needs to be redrawn somewhere else. Here is a screenshot of a battle in progress from QuestGRPG.


Statistical Maps and Sprites:



Statistical maps and sprites require less storage space than hard coding, and more than text sprites, however during run time it requires about the same amount of memory as hard coding. This is the BASIC mapping method best suited to scrolling maps, and can be combined with statistical or simple hard-coded sprites. For statistical maps or sprites, first design the sprite or terrain feature on the graph screen. Create a list with the X-coordinates starting at zero, reading from the left, and a list of the Y-coordinates reading from the bottom up, starting at zero. Then recall these lists into your program and store them to lists 1 and 2 like this:

{0,1,2,2,2,3,3,4,5,6,7,8->LHILLX  //Store X coordinates to User-List HILLX
{0,1,3,1,4,0,4,3,1,2,2,1->LHILLY  //Store Y coordinates to User-List HILLY

Now if you want to use the hill stored in these lists augment them the list onto the end of the lists in which you are storing the map data, but add the x and y values where you want to place them:

augment(LTRNX,LHILLX+X->LTRNX
augment(LTRNY,LHILLY+Y->LTRNY

Now, as you move about the map, adjust the x and y window variables to show the part of the map would like to see. The last bit of information you need is how to set up a stat plot from a program. First, inside the program editor, go to the line where you want to insert your code. Next hit [2nd] and [Y=] and select Plot1(. This will be your map plot. Next enter hit [2nd] and [Y=] and then the right arrow, and select Scatter. Then enter the name of the list containing x-coordinates and then the name of the list containing y-coordinates. Then hit [2nd] and [Y=] and hit the left arrow. Then select the mark you want to use--probably the dot. To set up the stat plot for a character, enter in Plot2(Scatter and then the names of your x and y lists. Then select the mark you want to use, again, probably the dot. Now use your key detection routine to add/subtract from the X,Y coordinates.

Advanced Features:

So you have your cool terrain features or sprites figured out, but you don’t want to have to redraw them upside down and sideways, to use them differently. Here's a cool table to save you time and effort:

DirectionX UsageY Usage Regular:augment(Xlist1, Xlist2)augment(Ylist1, Ylist2) 90 ccw:augment(Xlist1, Ylist2)augment(Ylist1, Xlist2) 180 (c)cw:augment(Xlist1, -Xlist2)augment(Ylist1, -Ylist2) 90 cw:augment(Xlist1, -Ylist2)augment(Ylist1, -Xlist2)

Here is another trick:

Like that cool trail in Oregon Trail? When setting up your character plot, use XYline instead of Scatter as the first argument. It can be found under the same menu as Scatter. Then, instead of simply moving the X or Y coordinates augment the new coordinates onto the end of the old list, making sure you augment onto both lists.

Credits: Mikhail Lavrov for his expertise on test sprites, and the two programs of his used here.
Travis Fischer for BomberMan
TI-Freakware for the html version of this document
Me (aka Thomas Dickerson aka ElfPrince13) for this tutorial and QuestGRPG

This document can be found in its original form at: http://www.ticalc.org/archives/files/fileinfo/376/37617.html