LoginLogin
Nintendo shutting down 3DS + Wii U online services, see our post

How do I create maps using DATA?

Root / Programming Questions / [.]

MorganCreated:
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.

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 flag
Anyway 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.

Thank you so much! All I needed was the help with the map, I know how to add collision, movement, etc. But yeah, thank you!

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.

sorry, but instead of giving an example, i wanna know how it works. that would help alot!

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.