Saving TileMap Data?
Monkey Forums/Monkey Programming/Saving TileMap Data?| 
 | ||
| What would be the best method of saving my TileMap Data to file? I noticed Load/SaveString but alas I'm at a loss on how to proceed. I'm not using any frameworks just simple coding it it to suit my current game. So far here is what I have, just the mere simple basics: for anyone interested. 
Strict
Import mojo
Function Main:Int()
	New SimpleMap()
	Return 1
End Function
Class SimpleMap Extends App
	Global tiles:Image
	Global tileArray:Int[][]
	Global OffX:Float
	Global offY:Float
	Global Tilesize:Int
	Global currentTile:Int
	
	Method OnCreate:Int()
		OffX = 10
		offY = 10
		currentTile = 0
		Tilesize = 64
		tiles = LoadImage("tiles.png", 32, 32, 10, Image.DefaultFlags)
		tileArray = AllocateArray(12, 10)
		For Local x:Int = 0 until 12
			For Local y:Int = 0 until 10
				tileArray[x][y] = -1
			Next
		Next
		
		SetUpdateRate(60)
		Return 1
	End Method
	
	Method OnUpdate:Int()
		If KeyHit(KEY_SPACE) Then SimpleMap.currentTile+=1
		If SimpleMap.currentTile > 9 Then SimpleMap.currentTile = 0	
		Return 1
	End Method
	
	Method OnRender:Int()
		Cls
		DrawText("CurrentTile="+SimpleMap.currentTile, 0, 0, 0, 0)
		For Local x:Int = 0 until 12
			For Local y:Int = 0 until 10
				If tileArray[x][y] =  0 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 0)
				If tileArray[x][y] =  1 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 1)
				If tileArray[x][y] =  2 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 2)
				If tileArray[x][y] =  3 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 3)
				If tileArray[x][y] =  4 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 4)
				If tileArray[x][y] =  5 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 5)
				If tileArray[x][y] =  6 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 6)
				If tileArray[x][y] =  7 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 7)
				If tileArray[x][y] =  8 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 8)
				If tileArray[x][y] =  9 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 9)
			Next
		Next
		PlaceTile()
		Return 1
	End Method
	
End Class
Function PlaceTile:Int()
	If MouseDown(MOUSE_LEFT)
		If MouseX() - SimpleMap.OffX > 0 And MouseX() - SimpleMap.OffX < SimpleMap.Tilesize * 12 - 32
			If MouseY() - SimpleMap.offY > 0 And MouseY() - SimpleMap.offY < SimpleMap.Tilesize * 10 - 32
				SimpleMap.tileArray[MouseX() / SimpleMap.Tilesize][MouseY() / SimpleMap.Tilesize] = SimpleMap.currentTile
			EndIf
		EndIf
	EndIf
	If MouseDown(MOUSE_RIGHT) Then SimpleMap.tileArray[MouseX() / SimpleMap.Tilesize][MouseY() / SimpleMap.Tilesize] = -1
	Return 0
End Function
Function AllocateArray:Int[][]( i:Int, j:Int)
    Local arr:Int[][] = New Int[i][]
    For Local ind:Int = 0 Until i
        arr[ind] = New Int[j]
    Next
    Return arr		
End
 | 
| 
 | ||
| I just added saving to my game jam game last weekend and added some things to help with that to my minigame framework.  Here's some code from my project that may help you if you adapt it. 
	Method SaveLevel()
		#If TARGET="glfw" Then
			Local levelString:TFileString = New TFileString
			For Local block:TBlock = Eachin level.blockList
				
				levelString.AddParam("tag",block.tag)
				levelString.AddParam("x",Int(block.x))
				levelString.AddParam("y",Int(block.y))
				levelString.AddLineBreak()
			Next
			Print levelString.output
			os.SaveString(levelString.output,ccGetSourceDataPath()+levelNumber+".txt")
		#End		
	End Method
	Method Load:Void(levelNumber:Int)
		Local levelString:TFileString = New TFileString
		#If TARGET="glfw" Then
			'No need to add loading protection as it fails safely.
			If DEBUG Then
				'Use source data folder as that's what the Editor writes to and reads from.
				levelString.output=os.LoadString(ccGetSourceDataPath()+levelNumber+".txt")
			Else
				levelString.output=os.LoadString(ccGetDataPath()+levelNumber+".txt")
			Endif
		#Else
			'This will load from the data folder instead (I believe it gets compiled into the exe)
			levelString.output=app.LoadString(levelNumber+".txt")			
		#End
		
		levelString.RemoveLineBreaks()
	End Method
Function ccGetSourceDataPath:String()
	'Returns the source data path e.g. mygame.data.  This is not the data path in the final build.
	#If HOST = "winnt" Then
		Return RealPath(ExtractDir(AppPath())+"/../../../../"+APP_NAME+".data/")+"/"
	#Elseif HOST = "macos" Then
'		Return RealPath(ExtractDir(AppPath())+"/../../../../../../../../"+os.StripAll(AppPath())+".data/") 'This doesn't work because the exe is called monkeygame
		Return RealPath(ExtractDir(AppPath())+"/../../../../../../../../"+APP_NAME+".data/")+"/" 'APP_NAME is a constant I have defined
	#End
	Return ""
End Function
Function ccGetDataPath:String()
	'Returns the data path of the final build
	#If HOST = "winnt" Then
		Return ExtractDir(AppPath())+"/data/"
	#Elseif HOST = "macos" Then
		Return ExtractDir(AppPath())+"/../Resources/data/"
	#End
	Return ""
End Function
'/////////////////////////////////////////////////
'TFileString
'/////////////////////////////////////////////////
	
Class TFileString
	Public
	Field output:String
	Field delimiter:String = ","
	
	Public
	
	Method AddParam(paramName:String, paramValue:String)
		output+=paramName+"="+paramValue+delimiter
	End Method
	
	Method AddLineBreak()
		output+=String.FromChars([13,10]) 
	End Method
	
	Method GetParam:String()
		'Throw away the param name and = sign.
		output = output[output.Find("=")+1..]
		Local pos:Int = output.Find(delimiter)
		Local result:String = Left(output, pos)
		output = output[pos+1..]
		Return result
	End Method
	
	Method RemoveLineBreaks:Void()
		output = output.Replace(String.FromChars([13,10]) , "")
	End Method
End Class
 | 
| 
 | ||
| I should mention several things: - You can't save/load data to folders on your computer with HTML5/Flash so when I use my editor I compile a GLFW build. - The Editor in my game is only available in Debug mode and then it loads and saves directly from the source data folder. The one named mygame.data. This way whenever I make a new Flash/HTML 5 build the data folder is already up-to-date with the latest levels. - However when the game is running in release mode it just loads data in from the build's data folder which is the correct place to load data from. | 
| 
 | ||
| Don't get it. :) I mean I get some of it. Seeing my code in the OP, how would I implement it in that? After testing for a while I got as far as nowhere which is near can't get it to work... :) [edit]Specifically I got SaveString working but it only saves the first tile. It most definitely does not work the same way as BMAX. | 
| 
 | ||
| The code you've provided doesn't seem to try saving anything. If you post what you've got then maybe someone can spot where you're going wrong. | 
| 
 | ||
| Here is the updated code with my SaveString hack implemented: 
Strict
Import mojo
Import os
Function Main:Int()
	New SimpleMap()
	Return 1
End Function
Class SimpleMap Extends App
	Global tiles:Image
	Global tileArray:Int[][]
	Global OffX:Float
	Global offY:Float
	Global Tilesize:Int
	Global currentTile:Int
	
	Method OnCreate:Int()
		OffX = 10
		offY = 10
		currentTile = 0
		Tilesize = 64
		tiles = LoadImage("tiles.png", 32, 32, 10, Image.DefaultFlags)
		tileArray = AllocateArray(12, 10)
		For Local x:Int = 0 until 12
			For Local y:Int = 0 until 10
				tileArray[x][y] = -1
			Next
		Next
		
		SetUpdateRate(60)
		Return 1
	End Method
	
	Method OnUpdate:Int()
		If KeyHit(KEY_SPACE) Then SimpleMap.currentTile+=1
		If SimpleMap.currentTile > 9 Then SimpleMap.currentTile = 0	
		SaveMap()
		Return 1
	End Method
	
	Method OnRender:Int()
		Cls
		DrawText("CurrentTile="+SimpleMap.currentTile, 0, 0, 0, 0)
		For Local x:Int = 0 until 12
			For Local y:Int = 0 until 10
				If tileArray[x][y] =  0 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 0)
				If tileArray[x][y] =  1 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 1)
				If tileArray[x][y] =  2 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 2)
				If tileArray[x][y] =  3 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 3)
				If tileArray[x][y] =  4 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 4)
				If tileArray[x][y] =  5 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 5)
				If tileArray[x][y] =  6 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 6)
				If tileArray[x][y] =  7 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 7)
				If tileArray[x][y] =  8 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 8)
				If tileArray[x][y] =  9 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 9)
			Next
		Next
		PlaceTile()
		Return 1
	End Method
	
End Class
Function SaveMap:Int()
	If KeyHit(KEY_F1)
		Local tempArr:Int[][]
		Local Fileout:String
		Local tempData:String
		Local level:Int
		level = -1
		Fileout = "map"
		tempArr = AllocateArray(12, 10)
		For Local x:Int = 0 until 12
			For Local y:Int = 0 until 10
				tempArr[x][y] = SimpleMap.tileArray[x][y]
			Next
		Next
		For Local x2:Int = 0 until 12
			For Local y2:Int = 0 until 10
				tempData = String(tempArr[x2][y2])
			Next
		Next
		SaveString( tempData, ExtractDir(AppPath()+"/data/"+Fileout+""+level+".txt"))
	EndIf
	Return 0
End Function
Function PlaceTile:Int()
	If MouseDown(MOUSE_LEFT)
		If MouseX() - SimpleMap.OffX > 0 And MouseX() - SimpleMap.OffX < SimpleMap.Tilesize * 12 - 32
			If MouseY() - SimpleMap.offY > 0 And MouseY() - SimpleMap.offY < SimpleMap.Tilesize * 10 - 32
				SimpleMap.tileArray[MouseX() / SimpleMap.Tilesize][MouseY() / SimpleMap.Tilesize] = SimpleMap.currentTile
			EndIf
		EndIf
	EndIf
	If MouseDown(MOUSE_RIGHT) Then SimpleMap.tileArray[MouseX() / SimpleMap.Tilesize][MouseY() / SimpleMap.Tilesize] = -1
	Return 0
End Function
Function AllocateArray:Int[][]( i:Int, j:Int)
    Local arr:Int[][] = New Int[i][]
    For Local ind:Int = 0 Until i
        arr[ind] = New Int[j]
    Next
    Return arr		
End
 | 
| 
 | ||
| From a brief glance, this: 
For Local x2:Int = 0 until 12
    For Local y2:Int = 0 until 10
        tempData = String(tempArr[x2][y2])
    Next
Next
...is going to always only include one cell because you're just replacing the previous tempData value every time. If you use "tempData += " you'll append the values. | 
| 
 | ||
| Yep that seems to have solved it. Thanks! I put another line after the first tempData: tempData+="," so that each value of the Array is separated by a comma. I've got it working nicely now and can proceed. Thanks Again! | 
| 
 | ||
| Is there a non confusing way of loading .txt files from the data directory? Looking at the example Grey posted it's too confusing for me to figure out. | 
| 
 | ||
| 
Local aString:String = LoadString("file.txt")
Then do what ever is needed with the string. :) | 
| 
 | ||
| Also, for what it's worth... the following part of code could be quicker. If tileArray[x][y] = 0 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 0) If tileArray[x][y] = 1 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 1) If tileArray[x][y] = 2 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 2) If tileArray[x][y] = 3 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 3) If tileArray[x][y] = 4 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 4) If tileArray[x][y] = 5 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 5) If tileArray[x][y] = 6 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 6) If tileArray[x][y] = 7 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 7) If tileArray[x][y] = 8 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 8) If tileArray[x][y] = 9 Then DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 9) you could either do a select statement so it's... 
Select tileArray[x][y]
Case 0
    DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 0)
Case 1
    DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, 1)
...
End
or unless things change drastically, just do... DrawImage(tiles, OffX + x * Tilesize, offY + y * Tilesize, 0, 2, 2, tileArray[x][y]) | 
| 
 | ||
| Sorry Amon.  It was confusing for me too when I was figuring it out :-) | 
| 
 | ||
| possibly this will help. The code bellow loads my Terminal 2 projects level's into the level array, the save code is kinda the same just saving out the data as a comma delimited file. I would show you that as well, but it's not written in monkey, I did the editor in Max. The file itself is just 213,123,1,23,12,3,1,31,23, with each number representing a tile for the level. It's kinda crude, does not store any additional information about each tile, or collisions, but then I didnt need it to so. Anyway. hope it helps. [monkeycode] #Rem sumary:Load Stage Loads the given stage, parsing in wave data, and loading in the correct tile sets and tile data. #End Method LoadStage(stage:Int=1) CacheAliens(stage) Self.StageSpeed=.7 Local tmpImage:Image Self.offsetx=-16 Local LevelIN:String LevelIN = LoadString("Stage_"+String(stage)+".txt") Local SpawnIN:String SpawnIN = LoadString("Stage_"+String(stage)+"_wave.txt") Self.BackGround = game.images.LoadAnim("backgrounds/stage_"+stage+".png", 480,200,6,tmpImage,True) Self.BackGroundOffset = 0 Self.BackGroundStep = 0 game.images.LoadAnim("StageTiles_"+stage+".png", 128, 128, 80, tmpImage,True) Self.StageTiles = game.images.Find("StageTiles_"+String(stage)) Local loop:Int = 0 For Local NewGrid:String = Eachin LevelIN.Split(",") If loop<500 Self.grid[loop]=Int(NewGrid) loop+=1 End If Next Local bit:Int=1 Local id:Int=0 Local trig:Int=0 Local cnt:Int=0 Local unit:Int =0 For Local value:String = Eachin SpawnIN.Split(",") Print "Bit ="+bit Select bit Case 1 unit=Int(value) Case 2 id=Int(value) Case 3 trig=Int(value) Case 4 cnt=Int(value) End Select If bit=4 Then AddWave(unit,id,trig,cnt) Print "Adding Wave ["+id+","+trig+","+cnt+"]" bit=0 End If bit=bit+1 bit = bit Mod 5 Next Local loop2:Int = 0 For Local NewGrid:String = Eachin LevelIN.Split(";") 'gets a line ended by a ; 'split by , to get each spawn data. Next End Method [/monkeycode] EDIT, just fixing the bbocde and switching it to monkeycode. |