Posted: Wed Jun 13, 2007 4:13 pm Post subject: [Tutorial] 2D Tile based games
2D Tile Based Games
Before going into the tutorial, you will need to be fairly experienced with arrays (2d, flexible etc), function and procedures.
How tile based games work
Basically you have a text map saved in grid format. E.g. like:
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
Imagine that the screen is broken into blocks, 7 across and 3 down. Now imagine that each number in the text map represents a 10x10 tile/block on the screen. 1 means red tiles and 0 means black tiles. The position of the numbers is important, as that is going to be where the tiles are drawn. The top row in the text grid represents the top row which will be drawn on our screen (in this case the entire top row will be red, since the entire top row is 1). In the text map if I asked you what is at grid (1,3 ) you would say 1. So a red tile needs to be draw at grid 1,3 on our runtime screen. In short, the text map above should translate on screen into: *removed*
Creating and drawing a map.
First off let’s assume that our map is going to be 15 by 15 tiles. So our text file will have 15 numbers going across and 15 numbers going down. Now, let’s assume that each tile takes up 10 by 10 pixels. The number 1 still represents red tiles and the number 0 still represents black tiles. Here is a text file I made, save it as map.txt on your desktop:
Now we need a 2d array which will store the numbers from the text file. The array size should be as follows: 0..14, 0..14. You will see why I started the array at 0 rather than 1 when we draw the map. We want the way we call our array to be the same as how we call a coordinate in the text grid (for collision purposes). Array (0,0) will store the number at the bottom left corner, array (0,1) will store the number right above it, array(1,1) will store the number which is to the right of the previous number, array (14,14) is the top right tile (which will be a red tile)…etc. So if I look at array (10,1) I am looking at a black tile. Now we need a way of storing the number from our text file into our array. Well, first we will need to open the text file. The way we get the information is simple: for every y and for every x in the array get the number. Drawing the map will have a similar for structure; for every y and for every x in the array, if the number is 1 draw a red box at x*10,y*10 (since each tile is 10 by 10 as stated above) or else draw a black box x*10,y*10 (We started the array at 0 because 1*10 is 10, so the whole map would be off 10 pixels). Here is some code which will draw our map (save this to where you save the text file above):
Turing:
View.Set("graphics:150;150") var tiles :array0.. 14, 0.. 14ofint
var FileName :string:="map.txt" var FileNo :int
open: FileNo, FileName, get for y :0.. 14% for every y for x :0.. 14%for every x get: FileNo, tiles (x, y)%get the tile type at (x,y) endfor endfor close(FileNo)
for x :0.. 14 for y :0.. 14 if tiles (x, y)=1then Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, red)% the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, black) endif locate(5, 1) put x, ' ', y .. % the current tile we are drawing delay(150) %that just shows you what's going on if you still aren't sure endfor endfor
If you are observant you will notice that the displayed map is upside down. Why? Well when turing reads from a file, it starts at the top line and goes to the bottom. When we read our file we assume that the first line is the bottom and goes up. We should be starting our y at the top and work our way down. So we should be doing this:
Turing:
View.Set("graphics:150;150") var tiles :array0.. 14, 0.. 14ofint
var FileName :string:="map.txt" var FileNo :int
open: FileNo, FileName, get for decreasing y :14.. 0% we start off from the top and go to the bottom for x :0.. 14%for every x get: FileNo, tiles (x, y)%get the tile type at (x,y) endfor endfor close(FileNo)
for x :0.. 14 for y :0.. 14 if tiles (x, y)=1then Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, red)% the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, black) endif locate(5, 1) put x, ' ', y .. % the current tile we are drawing delay(150) %that just shows you what's going on if you still aren't sure endfor endfor
Collision detection.
Finally we are into collision! First off, let’s make a moveable piece (which will be our player) and make a redraw procedure:
Turing:
View.Set("graphics:150;150,offscreenonly") var tiles :array0.. 14, 0.. 14ofint
var FileName :string:="map.txt" var FileNo :int
open: FileNo, FileName, get for decreasing y :14.. 0% for every y for x :0.. 14%for every x get: FileNo, tiles (x, y)%get the tile type at (x,y) endfor endfor close(FileNo) procedure reDraw
for x :0.. 14 for y :0.. 14 if tiles (x, y)=1then Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, red)% the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, black) endif endfor endfor end reDraw
var key :arraycharofboolean var px, py :int:=1% we start off at grid 1,1. %Note we don't begin at 2,2 because we set the first grid %to 0,0 so everything is off by 1
loop
reDraw
Input.KeyDown(key) if key ('a')then
px -=1 endif if key ('d')then
px +=1 endif if key ('s')then
py -=1 endif if key ('w')then
py +=1 endif
Collision is very easy. All we need to do is check weather or not the tile in front of us is open or not. Let all the black tiles be walk able, and red tiles are walls. So if the tile in front of the player is 1 (a red tile) we shouldn’t be able to move there. How are we going to do that? Well if ‘w’ is pressed check the tile above the player. If Array(px,py+1) = 0 then we can move. Now just do it for all keys. We get the code:
Turing:
View.Set("graphics:150;150,offscreenonly") var tiles :array0.. 14, 0.. 14ofint
var FileName :string:="map.txt" var FileNo :int
open: FileNo, FileName, get for decreasing y :14.. 0% for every y for x :0.. 14%for every x get: FileNo, tiles (x, y)%get the tile type at (x,y) endfor endfor close(FileNo) procedure reDraw
for x :0.. 14 for y :0.. 14 if tiles (x, y)=1then Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, red)% the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, black) endif endfor endfor end reDraw
var key :arraycharofboolean var px, py :int:=1% we start off at grid 1,1
Whoa that was easy!!! And the beauty is that we can make the map larger, and all we need to change in the main program is the size of the array, and how many x’s and y’s there are (i.e. in the for loops in which we read the text file and in the for loops in which we re draw the map). But we have one small problem, this method assumes we go 10 pixels each step (ie. 1 tile a step). What if I want to go less pixels each steps so I get a smoother game play? Well I’ll cover that in the lesson after the next
Next: Making a map editor and introducing a new method of detecting which tile you are in.
Sponsor Sponsor
LaZ3R
Posted: Wed Jun 13, 2007 4:53 pm Post subject: Re: [Tutorial] 2D Tile based games
Excellent Tutorial. Simple to understand (If you understand arrays already that is ).
I haven't bothered trying to make a 2D Tile based game but I guess that's one of the small amount of games I can still try to make. I'm working on a slime soccer game in turing (Circular hit detection is what I'm into now ).
Good job on this turotial.
SNIPERDUDE
Posted: Thu Jun 14, 2007 9:11 am Post subject: Re: [Tutorial] 2D Tile based games
Yes, it was a great tutorial - simple and easy to understand. I have done 2D mapping games before (although with more advanced collision detection), but now I am into 2D isometric mapping. I have most of it working, I just want to remove some of the lag and fix some other problems (knowing which tile the mouse is on and placing objects accordingly). I hope to get enough of it working soon so I can post some of it .
DifinityRJ
Posted: Thu Jun 14, 2007 9:41 am Post subject: Re: [Tutorial] 2D Tile based games
Awesome Tutorial, thanks codemonkey!
CodeMonkey2000
Posted: Thu Jun 14, 2007 7:37 pm Post subject: RE:[Tutorial] 2D Tile based games
Thanks for the comments. I'm glad everyone found it easy to understand. I hope I didn't sound too redundant though... Anyway I plan to add more after exams.
Dan
Posted: Thu Jun 14, 2007 7:58 pm Post subject: RE:[Tutorial] 2D Tile based games
Not a bad tutorial at all. This is a simple version of how alot of old 2d rpgs where made.
There is alot that could be added on to this idea like how to add more propterys to a title, and making it so the world (titles move) rather then the char, so you get scorling type effect and do not have to reload the map so much. You could also add more info on how to load the titles from a image rather then just boxs.
Overall tho it is a very good intro to the tile system, and is alot clearer then the current posts we have on it.
+ 100 bits
Computer Science CanadaHelp with programming in C, C++, Java, PHP, Ruby, Turing, VB and more!
CodeMonkey2000
Posted: Mon Jul 02, 2007 2:54 pm Post subject: Re: [Tutorial] 2D Tile based games
Changing the map size without hassle.
The current way of drawing our map isn’t flexible. We can’t make the map bigger or smaller without changing the code. One possible solution to this is to use a 2 dimensional flexible array. The problem is that turing doesn’t support 2d flexible arrays. Now what? Well we could store all the information from a 2d array into a single dimensional flexible array, after all a 2d array is just another way of organizing information. (Note that the size of the array must be totalX’s times totallY’s. So if our grid was 15 by 15, our array should have 225 elements. We also we need to know the totalX’s and totallY’s beforehand). For simplicities sake, the way we will read our text file exactly how we did before, only instead of doing
code:
get : FileNo, tiles(x,y)
we will do
code:
get : FileNo, tiles( Array(x,y) )
. Where array is a function that takes in x and y (which would normally be used in a 2d array) and converts it into the single dimensional equivalent. How do we do that? There are two formulas we can use. They are: TotalX *currentY+currentX or TotalY *currentX+currentY. Both are practically identical.
Next we need to enclose the subscripts for the 2d array with Array(), and change the values in our for loop structure. Here’s what our new code looks like:
Turing:
View.Set("graphics:150;150,offscreenonly") var tiles :flexiblearray0.. 0ofint
var FileName :string:="map.txt" var FileNo :int
var totalX, totalY :int:=0
fcn Array (x, y :int):int result totalX * y + x %totalY * x + y also works end Array
%open the file open: FileNo, FileName, get
get : FileNo, totalX %how many X's get: FileNo, totalY %how many Y's new tiles, totalX * totalY - 1%total number of elements %the -1 is there because we started the aray at 0 for decreasing y : totalY - 1.. 0% for every y remember we started the array at 0! for x :0.. totalX - 1%for every x get: FileNo, tiles (Array (x, y))%get the tile type at (x,y) and store it in it's 1d counter-part endfor endfor
close(FileNo)
procedure reDraw
for x :0.. totalX - 1 for y :0.. totalY - 1 %array is a function and px-1,py are the parameters if tiles (Array (x, y))=1then Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, red)% the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, black) endif endfor endfor end reDraw
var key :arraycharofboolean var px, py :int:=1% we start off at grid 1,1
loop
reDraw
Input.KeyDown(key) %array is a function and px-1,py are the parameters if key ('a')and tiles (Array (px - 1, py))=0then
px -=1 endif if key ('d')and tiles (Array (px + 1, py))=0then
px +=1 endif if key ('s')and tiles (Array (px, py - 1))=0then
py -=1 endif if key ('w')and tiles (Array (px, py + 1))=0then
py +=1 endif
Let’s set the boundaries of where we should scroll. If the player’s x is more than (or less than) maxx div 2 move the map left/right depending on the direction. Similarly if the player’s y is more than (or less than) maxy div 2 move the map up/down depending on the direction the player is moving. Let’s make 2 counters that keep track of how much the map has moved (x and y displacements). This number will increase/decrease (depending on the direction that the has player moved in) and will be added to the x and y’s of both the player and tiles when we draw them. Code:
Turing:
View.Set("graphics:150;150,offscreenonly") var tiles :flexiblearray0.. 0ofint
var FileName :string:="map.txt" var FileNo :int
var displacementX, displacementY :int:=0 var totalX, totalY :int:=0
fcn Array (x, y :int):int result totalX * y + x %totalY * x + y also works end Array
%open the file open: FileNo, FileName, get
get : FileNo, totalX %how many X's get: FileNo, totalY %how many Y's new tiles, totalX * totalY - 1%total number of elements %the -1 is there because we started the aray at 0 for decreasing y : totalY - 1.. 0% for every y remember we started the array at 0! for x :0.. totalX - 1%for every x get: FileNo, tiles (Array (x, y))%get the tile type at (x,y) and store it in it's 1d counter-part endfor endfor
close(FileNo)
procedure reDraw
for x :0.. totalX - 1 for y :0.. totalY - 1 %array is a function and px-1,py are the parameters if tiles (Array (x, y))=1then Draw.FillBox(x *10 + displacementX, y *10 + displacementY,
x *10 + 10 + displacementX, y *10 + 10 + displacementY, red) % the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10 + displacementX, y *10 + displacementY, x *10 + 10 + displacementX, y *10 + 10 + displacementY, black) endif endfor endfor end reDraw
var key :arraycharofboolean var px, py :int:=1% we start off at grid 1,1
loop
reDraw
Input.KeyDown(key) %array is a function and px-1,py are the parameters if key ('a')and tiles (Array (px - 1, py))=0then
px -=1 %notice how this if is within the if that checks weather or not the player has collided? %this is convenient since the map should move only if the player moved. if px *10 + displacementX <= maxxdiv2then
displacementX +=10 endif endif if key ('d')and tiles (Array (px + 1, py))=0then
px +=1 if px *10 + displacementX >= maxxdiv2then
displacementX -=10 endif endif if key ('s')and tiles (Array (px, py - 1))=0then
py -=1 if py *10 + displacementY <= maxydiv2then
displacementY +=10 endif endif if key ('w')and tiles (Array (px, py + 1))=0then
py +=1 if py *10 + displacementY >= maxydiv2then
displacementY -=10 endif endif
This works great, but the map should snap back to the screen when it goes off screen. What we want to happen is:
If the displacementX plus the location (relative to our screen) of the very last X tile is less than maxx then the displacementX should equal maxx - totalX * 10.
Or else if the displacementX plus the location (relative to our screen) of the very first X tile is more than 0 then the displacementX should equal 0 - 0 *10.
And we repeat the same thing for displacementY. Code:
Turing:
View.Set("graphics:150;150,offscreenonly") var tiles :flexiblearray0.. 0ofint
var FileName :string:="map.txt" var FileNo :int
var displacementX, displacementY :int:=0 var totalX, totalY :int:=0
fcn Array (x, y :int):int %make sure that going offscreen won't crash the game if x >= 0and x < totalX and y >= 0and y < totalY then result totalX * y + x %totalY * x + y also works else for k :0.. upper(tiles) if tiles (k)=1then result k
endif endfor endif end Array
%open the file open: FileNo, FileName, get
get : FileNo, totalX %how many X's get: FileNo, totalY %how many Y's new tiles, totalX * totalY - 1%total number of elements %the -1 is there because we started the aray at 0 for decreasing y : totalY - 1.. 0% for every y remember we started the array at 0! for x :0.. totalX - 1%for every x get: FileNo, tiles (Array (x, y))%get the tile type at (x,y) and store it in it's 1d counter-part endfor endfor
close(FileNo)
procedure reDraw
for x :0.. totalX - 1 for y :0.. totalY - 1 %array is a function and px-1,py are the parameters if tiles (Array (x, y))=1then Draw.FillBox(x *10 + displacementX, y *10 + displacementY,
x *10 + 10 + displacementX, y *10 + 10 + displacementY, red) % the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10 + displacementX, y *10 + displacementY, x *10 + 10 + displacementX, y *10 + 10 + displacementY, black) endif endfor endfor end reDraw
var key :arraycharofboolean var px, py :int:=1% we start off at grid 1,1
proc snapGrid
%if the grid is too far left if(displacementX + totalX *10) < maxxthen
displacementX :=maxx - totalX *10%we use 10 because that is the size of each tile %if the grid is too far right elsif(displacementX + 0*10) > 0then
displacementX :=0 - 0*10%or just zero endif %if the map is too far down, move it up if(displacementY + totalY *10) < maxythen
displacementY :=maxy - totalY *10 % if the map is too far up move it down elsif(displacementY + 0*10) > 0then
displacementY :=0 - 0*10%or just zero endif end snapGrid
loop
reDraw
Input.KeyDown(key) %array is a function and px-1,py are the parameters if key ('a')and tiles (Array (px - 1, py))=0then
px -=1 %notice how this if is within the if that checks weather or not the player has collided? %this is convenient since the map should move only if the player moved. if px *10 + displacementX <= maxxdiv2then
displacementX +=10 endif endif if key ('d')and tiles (Array (px + 1, py))=0then
px +=1 if px *10 + displacementX >= maxxdiv2then
displacementX -=10 endif endif if key ('s')and tiles (Array (px, py - 1))=0then
py -=1 if py *10 + displacementY <= maxydiv2then
displacementY +=10 endif endif if key ('w')and tiles (Array (px, py + 1))=0then
py +=1 if py *10 + displacementY >= maxydiv2then
displacementY -=10 endif endif
snapGrid
Draw.FillBox(px *10 + displacementX, py *10 + displacementY, px *10 + 10 + displacementX, py *10 + 10 + displacementY, yellow) View.Update delay(50) endloop
Editing a map.
To edit a map we could change the text in the text map. But that is too tedious. Why not make program that can enable us to edit the map with ease? First we should give the user choice to create a new map, or to load an old one. If the user chooses to start a new map we should ask for the dimensions of the map and then set all the tiles to 0 (so we don’t get any “variable has to value error”). If they choose to load, we should load the map how we normally do. We can copy and paste a bunch of things from the main game onto our map editor (eg. The redraw procedure).
Now if the user left clicks we should place a red tile in the tile the mouse is on (tiles in Array(mouseX, mouseY) now equals 1). If the user right clicks place a black tile in the tile the mouse is on on (tiles in Array(mouseX, mouseY) now equals 0). We can figure out what tile the mouse is on with the formula: mx div sizeOfTile (which is 10), my div sizeOfTile (which is 10). This method of detecting what tile we are on can be used for collision. It is very accurate but it can be slow. There is a trick to using it though. (Note: if we know what tile we are on, but want to find out its position on screen we do: x*10 and y*10). Moving along, if the user presses ‘q’ we should quit and save. The format to save is exactly the same as how we load, but we replace get with put. Code [Note: I added the ability to tell weather or not the mouse is within the grid. Don’t let it confuse you.]:
Turing:
var tiles :flexiblearray0.. 0ofint
var answer :string var totalX, totalY :int var FileName :string:="map.txt" var FileNo :int
fcn Array (x, y :int):int result totalX * y + x %totalY * x + y also works end Array
put"Would you like to load or create a new map? (load/new)" get answer
if answer ="new"then put"How many tiles across and down?" get totalX, totalY
new tiles, totalX * totalY - 1%total number of elements for x :0.. totalX * totalY - 1
tiles (x):=0 endfor elsif answer ="load"then %open the file open: FileNo, FileName, get get: FileNo, totalX %how many X's get: FileNo, totalY %how many Y's new tiles, totalX * totalY - 1%total number of elements %the -1 is there because we started the aray at 0 for decreasing y : totalY - 1.. 0% for every y remember we started the array at 0! for x :0.. totalX - 1%for every x get: FileNo, tiles (Array (x, y))%get the tile type at (x,y) and store it in it's 1d counter-part endfor endfor close(FileNo) else put"Invalid input, now crashing" delay(1000) quit endif
procedure reDraw
for x :0.. totalX - 1 for y :0.. totalY - 1 %array is a function and px-1,py are the parameters if tiles (Array (x, y))=1then Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, red) % the +10 is there because each tile is 10 by 10 else Draw.FillBox(x *10, y *10, x *10 + 10, y *10 + 10, black) endif endfor endfor end reDraw
Mouse.ButtonChoose("multibutton")%check the turing help section if you don't know what this does cls var mx, my, mb, left, middle, right :int var key :arraycharofboolean
View.Set("offscreenonly") loop Mouse.Where(mx, my, mb) %how this work can be found in the turing help section under 'Mouse.ButtonChoose'
left := mb mod10
middle :=(mb - left)mod100
right := mb - middle - left
%move the map around Input.KeyDown(key)
reDraw
%make sure that the mouse is within the grid %remember mx div 10,my div 10 tells us what grid we are on % the lower bound of the grid is 0 and the upper bound is totalX/Y if mx div10 >= 0and mx div10 < totalX and my div10 >= 0and my div10 < totalY then %places red tiles if left =1then
tiles (Array (mx div10, my div10)):=1 endif %places black tiles if right =100then
tiles (Array (mx div10, my div10)):=0 endif %just makes it clearer which tile you are on Draw.Box(mx div10*10, my div10*10, mx div10*10 + 10, my div10*10 + 10, white) endif View.Update exitwhen key ('q') endloop
open: FileNo, FileName, put
put : FileNo, totalX %how many X's put: FileNo, totalY %how many Y's %new tiles, totalX * totalY - 1 %total number of elements %the -1 is there because we started the aray at 0 for decreasing y : totalY - 1.. 0% for every y remember we started the array at 0! for x :0.. totalX - 1%for every x put: FileNo, tiles (Array (x, y))%get the tile type at (x,y) and store it in it's 1d counter-part endfor endfor close(FileNo)
Now we can just start playing in the new map right away, and not worry about changing the main code.
If you open the text file you will see that you now have a number per line. We don’t need to worry about organizing the text file any more.
Next: I will show you how to add property to tiles, how to only draw the tiles that are on screen, change tiles during game play, how to use pictures rather than the Draw.Box procedure, and finally add enemies. I will probably use pacman as an example as it’s the simplest to understand.
skaarj
Posted: Sun Jul 08, 2007 10:00 am Post subject: Re: [Tutorial] 2D Tile based games
Hi,
I'm writing a RPG game (C++/OpenGL/OpenAL) and this map editor would be very useful, can i find binary for this tutorial? somewhere.
Sponsor Sponsor
CodeMonkey2000
Posted: Sun Jul 08, 2007 10:36 am Post subject: RE:[Tutorial] 2D Tile based games
You can't find binaries for turing. If you understand the logic you probably could program your own map editor.
Oh and how experienced are you with openGL? If you are new, then SDL is probably a better alternative. It's simple (hence the name Simple Direct-Media Layer). I found openGl to be very confusing and frustrating.
skaarj
Posted: Sun Jul 08, 2007 11:19 am Post subject: Re: [Tutorial] 2D Tile based games