Before I ask this question, I want you to know that I tried to learn this on my own, I've tried to research it here on SBS, but to no avail, so now I'm posting a thread asking for help.
So what I'm trying to do, is to create a map in the game's programming using DATA, so that I can place a sprite/BG tile on the screen by "Assigning" a character/number/letter in a DATA string, to a sprite/BG tile, and that said sprite/BG tile, can then be further specified on collision, animation, etc. This is so that I can define what a sprite/BG tile does, it's collision, and so forth so on, only once, without having to place and define multiple sprites, (By using SPSET, SPOFS, etc.) For every sprite on screen. (I'm terrible at explaining things like this, so here's an idea of what I'm struggling to do): http://kland.smilebasicsource.com/i/tbsgn.jpg
I know that games like Bub, Ice floor, and a lot more have used the kind of mapping system I'm trying to use, but I can't seem to figure out exactly how to go about it, all I would hope for is an explanation of how to do it, AND an explanation of how it works. (Example: Why GoodGameGod, J$ is used here and here so that this adds up to that.) Otherwise I won't be able to learn from this experience. Please help, and thank you.
How do I create maps using DATA?
Root / Programming Questions / [.]
MorganCreated:
OPTION STRICT 'Require I declare variables VAR MAP_W%, MAP_H% 'Size of the map VAR I%, J%, ROW$, CH$ 'For decoding the data statements VAR TILE%, PX% = 0, PY% = 0 'Tile to write to the background, and the player's start x,y coordinates VAR T_LADDER% = 806 'Tile codes for the various characters. Makes it easier to read and change things in one place VAR T_ROCK% = 159 'I could have put these in data statements too, I just thought this might be easier to read. VAR T_WALL% = 570 'You could also try 543 VAR T_WALL_UL% = 442 + (1 << 14) 'adding 1 << 14 turns on the horizontal flip flag VAR T_WALL_UR% = 442 'UL = Upper Left, UR = Upper Right, LL = Lower Left, LR = Lower Right VAR T_WALL_LL% = 506 + (1 << 14) VAR T_WALL_LR% = 506 'You can choose other numbers I just went into SMILE tools and chose BG numbers that looked nice. VAR T_PLATFORM_L% = 577 'Left side of the platform VAR T_PLATFORM_M% = 578 'Middle of the platform (repeatable) VAR T_PLATFORM_R% = 579 'Right side of the platform VAR T_SPADE% = 5 'Didn't know what these were on the map so I just chose CHESS pieces. VAR T_DIAMOND% = 6 VAR T_HEART% = 7 VAR T_CLUB% = 9 'I may have type-o's here and there from re-typing in things manually. You will have to debug. VAR T_BG% = 639 'I am going to fill a background layer with this since some of the other tiles have transparent portions VAR HERO% = 496 'Sprite number to use for the player character. VAR HERO_SP% 'Will receive the sprite number of the player character ACLS 'Let's rid the screen of any junk RESTORE @LEVEL_01 'We want to make sure we read from where we are expecting READ MAP_W%, MAP_H% 'The first two variables are the width and height of the map. 'Please note I didn't add anything for scrolling. That would require some extra world versus screen coordinates code. 'Resize then fill in layer 0 with T_BG% BGSCREEN 0, MAP_W%, MAP_H% BGFILL 0, 0, 0, MAP_W% - 1, MAP_H% - 1, T_BG% 'Zero based so it goes from 0 to 1 minus the width/height BGSCREEN 1, MAP_W%, MAP_H% 'We will be putting non-empty tiles here. BGCLR 1 'Make sure the layer is empty FOR J% = 0 to MAP_H% - 1 READ ROW$ 'Each row is a string we will chop up into characters. We could have done it character by character but that would make the data statements ugly. FOR i% = 0 TO MIN(LEN(ROW$), MAP_W%) - 1 'Extra bit of code to make sure we don't over-run the string length if it is too small or go too far if it is too big. CH$ = MID$(ROW$, I%, 1) 'Note had to change some characters around to be more keyboard friendly on the PC 'Big giant if/then/else to change a character to a tile number. I probably should have put this in data statements and arrays or at least a function. IF CH$ == "[" THEN TILE% = T_WALL_UL% ELSEIF CH$ == "]" THEN TILE% = T_WALL_UR% ELSEIF CH$ == "(" THEN TILE% = T_WALL_LL% ELSEIF CH$ == ")" THEN TILE% = T_WALL_LR% ELSEIF CH$ == "X" THEN TILE% = T_WALL% ELSEIF CH$ == "H" THEN TILE% = T_LADDER% ELSEIF CH$ == "O" THEN TILE% = T_ROCK% ELSEIF CH$ == "<" THEN TILE% = T_PLATFORM_L% ELSEIF CH$ == "=" THEN TILE% = T_PLATFORM_M% ELSEIF CH$ == ">" THEN TILE% = T_PLATFORM_R% ELSEIF CH$ == "S" THEN TILE% = T_SPADE% ELSEIF CH$ == "H" THEN TILE% = T_HEART% ELSEIF CH$ == "D" THEN TILE% = T_DIAMOND% ELSEIF CH$ == "C" THEN TILE% = T_CLUB% ELSEIF CH$ == "@" THEN 'Special case, this is a blank but we record this is where the position the player should start at in the level. TILE% = -1 'Don't draw a tile PX% = I% * 16 'Multiply by 16 to change from tile coordinates to pixel/screen coordinates. PY% = I% * 16 ELSE 'CH$ = " " or some other unrecognized character TILE% = -1 'Skip the tile ENDIF IF TILE% >= 0 THEN BGPUT 1, I%, J%, TILE% 'Write the tile out. ENDIF NEXT I% NEXT J% 'Now let's see about setting up the hero character a little. SPSET HERO% OUT SP_HERO% 'Let SmileBasic choose the ID for us, we don't care SPOFS SP_HERO%, PX%, PY% 'Warp us to the location the level asked for. 'Write a message telling the user how to exit this demo. LOCATE 0, 25 '12 rows of 16 pixels / 8 pixels per character = 24. Add one for a bit of spacing = 25. In case you wondered where that came from. PRINT "PRESS [B] TO EXIT" 'Just wait for the user to click the B button. Then exit the program REPEAT VSYNC UNTIL (BUTTON() AND #B) != 0 'Lets clean up before we go. SPCLR SP_HERO% BGCLR 0 BGCLR 1 'Probably should have used a loop, oh well. END 'It is nice to end purposefully. @LEVEL_01 DATA 24, 12 DATA "[XXXXXXXXXXXXXXXXXXXXXX]" DATA "X X" DATA "X X" DATA "X X" DATA "X X" DATA "X H<==> X" DATA "X H X" DATA "X H X" DATA "X H X" DATA "X H OO S X" DATA "X H @ OO HDC X" DATA "(XXXXXXXXXXXXXXXXXXXXXX)"Is that sort of what you are looking for? Note that I am filling up BG tiles, not making a bunch of sprites. You only have a few hundred sprites and they take more processing than a BG tile, so do try to conserve them. For the record if I was selecting the tile with mapping arrays, I would have had some code like the following:
VAR TILE_CHAR$[0], TILE_NUM%[0], T_CHAR$, T_NUM%, DONE% = FALSE RESTORE @TILE_LOOKUP WHILE DONE% == FALSE READ T_CHAR$, T_NUM% IF (LEN(T_CHAR$) > 0) THEN 'END WHEN T_CHAR$ IS EMPTY DONE% = TRUE ELSE PUSH TILE_CHAR$, T_CHAR$ PUSH TILE_NUM%, T_NUM% ENDIF WEND 'THEN INSTEAD OF THE BIG IF/THEN/ELSE LOOP YOU COULD HAVE SOMETHING LIKE VAR K% TILE% = -1 'DEFAULT VALUE IF CH$ == "@" THEN PX% = I% * 16 PY% = J% * 16 TILE% = -1 ELSE FOR K% = 0 TO LEN(TILE_CHAR$) - 1 IF CH$ == TILE_CHAR$[K%] THEN TILE% = TILE_NUM%[K%] BREAK END IF NEXT K% ENDIF IF TILE% >= 0 THEN BGPUT 1, I%, J%, TILE% ENDIF '.... @TILE_LOOKUP DATA "H", 806 DATA "O", 159 DATA "X", 570 DATA "]", 442 + (1 << 14) 'Not sure if I can leave in the math or not. DATA "[", 442 DATA "(", 506 + (1 << 14) DATA ")", 506 DATA "<", 577 DATA "=", 578 DATA ">", 579 DATA "S", 5 DATA "D", 6 DATA "H", 7 DATA "C", 9 DATA "", 0 'Last record flagAnyway if you typed it all in right (Ignore the second block that says how I would have done it with a lookup table), it should look something like this: There is a lot left to code if you want to move around and have collision detection and such.
In your second example, that math would work, except you forgot to add DATA before all the lines.
Also, if you want it to be slightly faster (even though it's probably already really fast) you could use INSTR to seach for the character. For example, when reading the lookup table:
VAR TILES$ DIM TILES%[0] RESTORE @TILE_LOOKUP REPEAT READ TEMP$ 'read tile character IF TEMP$=="" THEN BREAK 'exit loop if "" is found INC TILES$,TEMP$ 'add tile character to string READ TEMP% 'read tile number PUSH TILES%,TEMP% 'add number to list UNTIL 0 'endless loop RESTORE @LEVEL_01 READ MAPWIDTH%,MAPHEIGHT% FOR Y%=0 TO MAPHEIGHT%-1 READ ROW$ 'read row of tiles FOR X%=0 TO MAPWIDTH%-1 VAR TILENUM%=INSTR(TILES$,ROW$[X%]) 'convert to tile number IF TILENUM%!=-1 THEN 'make sure tile exists BGPUT X%,Y%,TILES%[TILE] 'put tile ENDIF NEXT NEXT
You are right, I did forget to put in DATA on the second example, I will go fix that. INSTR should be faster too.
I think the annotations in the examples do a pretty good job at explaining how it works...
The background (BG) is a large rectangular grid. Each cell of the grid we fill with a tile. If you go to smile tool and look at the BG tab there are a bunch of pictures. I call each picture a tile. If you select a picture/tile the top screen will show you a number associated with it. You will want to keep track of which number goes with which tile. At least for the ones you want to use. If you load an image into the graphics page with the BG tiles you can have your own custom tiles.
In a way the BG layer is set up very similarly to the text on each screen. You can only put each tile at certain spots on the grid. However unlike the text page, you can move the BG page around with pixel accuracy using BGOFS (but that is the whole page not an individual tile). While in text mode you might call LOCATE x, y : PRINT "X"; for a background tile you would use BGPUT layer_number, x, y, tile_number. You can have up to four layers (shared across both screens). In the example layer 0 is filled with a blue tile, and layer 1 has everything else (see BGFILL).
The first thing you need to do is size your BG layer. You use BGSCREEN(layer_number, number_of_tiles_wide, number_of_tile_high). If you are going to have different sizes of backgrounds then it would be nice to be able to specify the width and height in DATA instead of coding it in with constants. So if you look at the first DATA line in my example you find: DATA 24, 12. The example BG is 24x12 tiles. Each tile is 16x16 pixels so the final image is 384x192 pixels. There are limits of how big a BG layer can be. Please consult SmileBasic for the exact limits.
We use BGSCREEN to size the BG layer then we use BGPUT to populate each spot in the layer with a tile. Which tile do we want to put at each location? That sounds like a good job for another DATA statement. We could just have a bunch of data statements with tile numbers separated by commas. Then you could have a for loop for the y coordinate with a for loop for the x coordinate inside of it. Then you would just read a tile number and BGPUT it at the loop counter for x, and y. The problem with that is it is hard to read and edit in the code.
So, another way to do it is to have a string per row. Then you look at each character in a string and figure out which tile number goes with each character in the string. This makes it easier to edit the DATA statements as you can better see what the level is going to look like. You do however need to stop early if a row is too long or fill with empty tiles if the string is too short (or just BGFILL with 0 before you start).
There are up to four layers. If you have tiles with transparent pixels you can have tiles from a lower level visible behind tiles on the top layer. Looking at the screenshot you can see the blue of the background behind the rocks and chess pieces. If you scroll the layers around at different rates you can get a nice parallax scrolling effect so that things on the back layer appear to be further away than closer layer images.
I hope that helps. If you have specific questions, let me know.
I forgot to note, you don't have to put just tiles in the data. For instance in the example, I use @ to say where to put the player character. You could do similar things to place coins, or enemy characters around the map.