Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations IamaSherpa on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Saving Space when Spriting

Status
Not open for further replies.

Stainnd

Programmer
Jul 9, 2001
65
US
I've been working on different RPG techniques, startegies, etc. before I truly commit a significant amount of time towards a project. The method i've been using for maps and graphics is actually pretty cool, i think. Or at least I thought so when I first learned it. You write a couple lines of code that look like this:


DATA 00,01,01,01,00
DATA 00,01,01,01,00
DATA 00,00,00,00,00
DATA 00,01,01,01,00
DATA 00,01,01,01,00
[i/]

This would create a crude black H, 5x5 pixels in size, with a blue background. (Each of the numbers stand for a color. The sprite is then call using the following code:


FOR y = 1 TO 13
FOR x = 1 TO 15

READ clr
PSET (x, y), clr

NEXT: NEXT
GET (1, 1)-(15, 13), sprite1


Then, let's pretend I had done three different types of sprites. We'll call them sprite1, sprite 2, and sprite 3. Another DATA thing is made after the three sprites ones, which would look like this:

DATA 1,1,1,1
DATA 1,2,2,2
DATA 1,3,3,3


Then, finally, somewhere in the code, the program takes the map, and converts the one's to sprite one, the two's to sprite two, etc. So in my example here, if all the sprites had been the same size as the first one. It would be a 4x3 map of 5x5 sprites. There is a little more to it, but it isn't really necessary. Anyway, I want to know if there is a way that I can store all those lines of DATA 00, etc. into a file. One file for each sprite/map, or else one file for all the sprites and maps. I waste 266 lines of code (and i would ideally like to create maybe ten times the number of sprites i have now.) So anyway, is there anyway to do this? -Mike
 
This is known as a map-based tile engine :) It's a fairly standard technique. Many engines in fact use multiple layers of tiles with transparency, which allows for some neat compositing effects and such. The big problem is that even though there is great compression in reusing the tiles, the map can still become excruciatingly large for any kind of realistic game universe. For this, you need somewhat more advanced compression techniques, but nothing that can't be done, of course. The keyword is think -- spend a lot of time before you start coding planning out how you want the system to work, otherwise you'll spend a lot of time changing it later on as your needs develop.
 
I know that it can take a lot of room. Rather than figure out complex compression techniques, though, (which I know is smart, but I don't want to do right now) I just want a way to store all those DATA lines into files. If I have a game with a sub-folder that contains a hundred different files (or one big one), that is fine with me. I just want to save space in my actual code. Couldn't I write some kind of data file, using WRITE or PUT or something that saves all the DATA lines, and then recall them when I need them? -Mike
 
Well, I'll take care about this on! Sprites and RPG have always pleased me. First of all, I think that there's a more effective way to save sprites on file: BSAVE. But there's something about what you asked for(I doesn't like much when answers are far behind what we're searching for and behind our comprehensive[blink at logiclrd ;-)])

So, when your little gentle sprite is display on screen, instead of GETing it, take it point by point into an array(that would be ready to transfer into file)

'Supposing that your sprite is displayed on top-left and
'is 13x15
FOR x% = 1 to 13: FOR y% = 1 to 15
image%(x%,y%) = POINT(x%,y%)
NEXT y%: NEXT x%

OPEN path and file name FOR OUTPUT AS #1
FOR x% = 1 to 13: FOR y% = 1 to 15
print #1,image%(x%,y%)
NEXT y%: NEXT x%
CLOSE #1

That should resolve your problem at least.

But let me introduce you to another way to save sprites; you'll use them with PUT/GET as far as I understood.

Lets take your exemple:

FOR y = 1 TO 13
FOR x = 1 TO 15

READ clr
PSET (x, y), clr

NEXT: NEXT
GET (1, 1)-(15, 13), sprite1


GETing your sprite has setup a kind of DATA into your variable already. Save them with BSAVE and relaod your sprite from a file whenever it comes to with BLOAD.

I made a very basic program to save sprites into files with BLOAD/BSAVE. It allows only 15x15 pixels on SCREEN 7 but I made it for my needs. It always could be modified for others screens and sprites ratio. There is it anyway if it could help. (I used screen 7 because it allows 4 page to PCOPY; good for multi-sprites coordination(for Qbasic of corse!))

'SPRITES EDITION SCREEN 7 15x15
'IT SAVE SPRITES INTO THE C:\SPRITES DIRECTORY
'IT LOAD SPRITES INTO THE C:\SPRITES DIRECTORY
'IT ALWAYS AUTOMATICALY PUT THE EXTENTION .SPR

DECLARE SUB PageInitiale ()
DECLARE SUB PagePalette ()
DECLARE SUB PageCouleur ()
DECLARE SUB Dessine ()
DECLARE SUB Nord ()
DECLARE SUB Ouest ()
DECLARE SUB Est ()
DECLARE SUB Sud ()
DECLARE SUB Sauver ()
DECLARE SUB Charger ()
DECLARE SUB MenuDisque ()

DEFINT A-Z
DIM SHARED couleur, curX, curY, oldX, oldY, disque
DIM SHARED image(2 TO 16, 2 TO 16), imageBite(200)
curX = 2: curY = 2: oldX = 2: oldY = 2: couleur = 7

SCREEN 7
WIDTH 40
CLS

GET (204, 4)-(218, 18), imageBite

PageInitiale
PagePalette

DO
clef$ = INKEY$
SELECT CASE clef$
CASE CHR$(0) + CHR$(72): Nord
CASE CHR$(0) + CHR$(75): Ouest
CASE CHR$(0) + CHR$(77): Est
CASE CHR$(0) + CHR$(80): Sud
CASE CHR$(9): PageCouleur
CASE CHR$(13): MenuDisque
CASE CHR$(32): Dessine
CASE CHR$(27): EXIT DO
CASE ELSE
END SELECT
LOOP

END

SUB Charger
CLS : PRINT "Charger": PRINT
FILES "C:\Sprites"
INPUT "C:\Sprites", nFichier$
nFichier$ = "C:\Sprites\" + nFichier$ + ".SPR"
DEF SEG = VARSEG(imageBite(0))
BLOAD nFichier$, VARPTR(imageBite(0))
DEF SEG
END SUB

SUB Dessine
image(curX, curY) = couleur

FOR x = 2 TO 16
FOR y = 2 TO 16
PSET (x + 202, y + 2), image(x, y)
NEXT y
NEXT x

END SUB

SUB Est
IF curX < 16 THEN
curX = curX + 1
COLOR image(oldX, oldY)
LOCATE oldY, oldX: PRINT &quot;Û&quot;
oldX = curX
oldY = curY
COLOR couleur
LOCATE curY, curX: PRINT &quot;¾&quot;
END IF
END SUB

SUB MenuDisque
GET (204, 4)-(218, 18), imageBite
LOCATE 10, 15: COLOR 14: PRINT &quot;S&quot;;
COLOR 7: PRINT &quot;auver&quot;
LOCATE 11, 15: COLOR 14: PRINT &quot;C&quot;;
COLOR 7: PRINT &quot;harger&quot;
LOCATE 12, 15: COLOR 14: PRINT &quot;A&quot;;
COLOR 7: PRINT &quot;nnuler&quot;
DO
clef$ = UCASE$(INKEY$)
LOOP UNTIL clef$ <> &quot;&quot;

SELECT CASE clef$
CASE &quot;S&quot;: Sauver
CASE &quot;C&quot;: Charger
CASE ELSE
END SELECT

PageInitiale
END SUB

SUB Nord
IF curY > 2 THEN
curY = curY - 1
COLOR image(oldX, oldY)
LOCATE oldY, oldX: PRINT &quot;Û&quot;
oldX = curX
oldY = curY
COLOR couleur
LOCATE curY, curX: PRINT &quot;¾&quot;
END IF
END SUB

SUB Ouest
IF curX > 2 THEN
curX = curX - 1
COLOR image(oldX, oldY)
LOCATE oldY, oldX: PRINT &quot;Û&quot;
oldX = curX
oldY = curY
COLOR couleur
LOCATE curY, curX: PRINT &quot;¾&quot;
END IF
END SUB

SUB PageCouleur
couleur = couleur + 1
IF couleur > 15 THEN couleur = 0
COLOR couleur: LOCATE 19: PRINT &quot;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ&quot;
LOCATE curY, curX: PRINT &quot;¾&quot;
END SUB

SUB PageInitiale
CLS

LINE (2, 2)-(133, 133), 8, BF
LINE (4, 4)-(131, 131), 7, BF
LINE (7, 7)-(128, 128), 0, B

LINE (201, 1)-(221, 21), 8, BF
LINE (202, 2)-(220, 20), 7, BF
LINE (203, 3)-(219, 19), 0, BF

COLOR couleur: LOCATE 19: PRINT &quot;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ&quot;

PUT (204, 4), imageBite, PSET

FOR x = 2 TO 16
FOR y = 2 TO 16
image(x, y) = POINT(x + 202, y + 2)
NEXT y
NEXT x

FOR x = 2 TO 16: FOR y = 2 TO 16
LOCATE y, x: COLOR image(x, y): PRINT &quot;Û&quot;
NEXT y: NEXT x

LOCATE curY, curX: COLOR couleur: PRINT &quot;¾&quot;

END SUB

SUB PagePalette

END SUB

SUB Sauver
CLS : PRINT &quot;Sauver&quot;: PRINT
FILES &quot;C:\Sprites&quot;
INPUT &quot;C:\Sprites&quot;, nFichier$

nFichier$ = &quot;C:\Sprites\&quot; + nFichier$ + &quot;.SPR&quot;

DEF SEG = VARSEG(imageBite(0))
BSAVE nFichier$, VARPTR(imageBite(0)), 169
DEF SEG
END SUB

SUB Sud
IF curY < 16 THEN
curY = curY + 1
COLOR image(oldX, oldY)
LOCATE oldY, oldX: PRINT &quot;Û&quot;
oldX = curX
oldY = curY
COLOR couleur
LOCATE curY, curX: PRINT &quot;¾&quot;
END IF
END SUB

Well ¾ stand for ascii 219 and Û stand for any other cursor of your kind if it doesn't reinitial themselves.
Enjoy.
 
Re-reading my last answer, I found out that you should get ride of my first idea and use your variable sprite1 directely.

DIM sprite1%(200)
FOR y% = 1 TO 13: FOR x% = 1 TO 15
READ clr
PSET (x%, y%), clr
NEXT x%: NEXT y%
GET (1, 1)-(15, 13), sprite1%

FILES &quot;C:\Sprites&quot;
INPUT &quot;File name: &quot;,fileName$
fileName$ = &quot;c:\Sprites\&quot; + fileName$
OPEN fileName$ FOR OUTPUT AS #1
FOR i% = 0 TO 200 'sprite1%(200)
PRINT #1, sprite1%(i%)
NEXT i%
CLOSE #1
END

DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

Or use the BLOAD/BSAVE methode for a binary file format, that is more smaller, instead of a sequencial format like above.

DIM sprite1%(200)
FOR y% = 1 TO 13: FOR x% = 1 TO 15
READ clr
PSET (x%, y%), clr
NEXT x%: NEXT y%
GET (1, 1)-(15, 13), sprite1%

FILES &quot;C:\Sprites&quot;
INPUT &quot;File name: &quot;,fileName$
fileName$ = &quot;c:\Sprites\&quot; + fileName$
DEF SEG = VARSEG(sprite1%(0))
BSAVE fileName$, VARPTR(sprite1%(0)),200
DEF SEG
END

DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
DATA 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

In both way, you should pay attention about your screen mode and your variables size to avoid complicated debugging(it becomes pretty quickly a mess). So a better way to avoid this kind of tricky thing should be to save sprites with OPEN fileName$ AS BINARY AS #1 and provide a screen mode information and a variable size at the beginnig of it. But I'm not use about them. Sorry.
 
i'm more a fan of the first method, and thanks a lot. How would I get the values back out of the file in the actual program? -Mike
 
FILES &quot;C:\Sprites&quot;
INPUT &quot;File name: &quot;,fileName$
fileName$ = &quot;c:\Sprites\&quot; + fileName$
OPEN fileName$ FOR INPUT AS #1
FOR i% = 0 TO 200 'sprite1%(200)
INPUT #1, sprite1%(i%)
NEXT i%
CLOSE #1

I suggest you to write a sprite editor for your needs; it allows you to work more effiently your sprites and eventually save a lot of time.
 
The most important thing to do is to abstract :) I misread the original post, by the way, completely missed the question in there somehow :)

Write a separate routine to load a map into an array. Then, you can put any particularly complicated code into the [tt]SUB[/tt] and call it from multiple places in the code without hesitation, since you know that &quot;it works&quot; (or at least, will, once you have debugged it). The same applies to sprites.

Also note that using a text file like Oak suggested will be relatively slow, and if you do end up with a lot of sprites to load & save, and large maps, it will take a noticeable amount of time to complete loading. Sure, you say, this is what progress bars are for, but it's preferable to load sufficiently quickly that a progress bar is not needed in the first place :) Using [tt]BSAVE[/tt] and [tt]BLOAD[/tt] will be much faster than [tt]PRINT[/tt] and [tt]INPUT[/tt], but it requires you to have hundreds of tiny little files. I prefer to keep all the files inside a bigger file, but this does require some thought as to how exactly you're going to store them. Your best bet is probably to break the file into two parts, internally -- an index part, and a data part. Write a utility that builds a list of files to add, gets the length of each file in the list, and then figures out where inside the file it will be stored, given its placement and the files before it (and the index). Then, output the index to the start of the file, followed by the files themselves in the correct order. Loading things from this file becomes a little bit more tricky, but as long as you have familiarized yourself with binary file I/O, there shouldn't be any problem. Speaking of which...

To access a file in a binary fashion (e.g., without attaching any special meaning to end-of-line or end-of-file characters and allowing any data that can fit into a byte to be stored in each byte), [tt]OPEN[/tt] it [tt]FOR BINARY[/tt]. To move to a particular byte position in the file (the first byte being numbered 1), use [tt]SEEK #f, offset[/tt]. To read bytes from the file into a string, use the function [tt]INPUT$(n, #f[/tt]. Alternately (and preferably, but less trivially), use the file I/O [tt]GET/PUT[/tt] statements. They each take three arguments: a file number ([tt]#f[/tt]), an optional offset (without which they access to the current offset and advance the pointer by the number of bytes involved), and a variable for the data. The variable can be of any type, and the [tt]GET[/tt] or [tt]PUT[/tt] operation will automatically convert it to or from a sequence of bytes that is exactly the right size.

Strings are a bit more difficult to use, since their length is not stored in the file, but rather only their contents. Since the length is not stored by [tt]PUT[/tt], the only cue that [tt]GET[/tt] is the length of the string before the read operation. So, to read 5 bytes from the current offset of file #2,
[tt]
a$ = SPACE$(5) ' a$ is now 5 bytes long
GET #2, , a$
[/tt]
Typically, what you'll want to do if you're not using directly-indexable records is to store the length of the string prior to the string in the file. Then, when reading the file, you read a length first, initialize a string to that length and [tt]GET[/tt] into the string. (If you do want the records to be indexable, then you need to set a maximum length for the string and pad out/truncate input to that length. Alternatively, you can use a separate file to store just the strings, but then you have to set up a whole allocation scheme and things get rather complicated. ^_^)

Hmm, I should make a FAQ out of this :)

Anyway, good luck with your map & sprite storage :)
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top