Scrolling Tile Map with Simple Dungeon Generation

Monkey Forums/Monkey Code/Scrolling Tile Map with Simple Dungeon Generation

Why0Why(Posted 2013) [#1]
Here is some code that scrolls a 2D map of tiles that is centered on a player sprite. It is culled from a larger project that has multiple files so it isn't quite as neat as usual and there might be some extra variables laying around. I didn't see anything like this and I felt like there should be an example up here for people just getting started with Monkey.

It requires 2 32x32 png tiles. Here is a link to the source and the sprites or you can just pull the code and supply your own. The tiles I have supplied are free for use from the David Gervais Angband tileset(which is awesome for placeholders.)

http://sdrv.ms/ZvYJml




Sammy(Posted 2013) [#2]
http://sdrv.ms/ZvYJml produces an error for me.


Why0Why(Posted 2013) [#3]
I just tried it on both Chrome and Explorer(not signed in) and it works fine. Right now it is a link from Skydrive. Once I get my new website up I will move the link to my dedicated server. Would be interested to hear if anyone else is having issues.


slenkar(Posted 2013) [#4]
Works here,

seems like a very open-plan dungeon,

The evil wizard must have an open-plan kitchen at his house


Why0Why(Posted 2013) [#5]
It is just a cavern. I stripped out the other dungeon generation I had since it wasn't quite complete. I just put this up for the tile scrolling.


slenkar(Posted 2013) [#6]
ohh ok its nice for a cavern


DeltaWolf7(Posted 2013) [#7]
Hi,

Great example, I found it to be great for learning Monkey.
One question though (from a noob). How would you add an object like a chest or something, without it moving with the player?


Why0Why(Posted 2013) [#8]
Glad you found it helpful. There are different ways. One way would be make an item class with a list field. Then you can iterate through the list of items in the draw routine for the item class. In the primary OnRender loop call a draw routine for items between the currentlevel.draw and the player.draw. When you call items.draw and iterate through the list it will draw on top of the map.

You can also add a field to level for items. Have an list or array of items(so that you can stack more than one item per map square) and then during the level draw routine check to see if there are any items on that square when it is going through and drawing the map. If so, draw them there.


Gerry Quinn(Posted 2013) [#9]
It has always seemed pragmatic to me to make each tile have a reference to any item or monster on it, as well as having items or monsters available elsewhere in a list with their locations. It does mean information exists in two places, but not having it quickly accessible in both ways is a pain.

To avoid problems, all movement of items or creatures must pass through a small set of functions/methods that update both data structures at once.


DeltaWolf7(Posted 2013) [#10]
Hi,

Finally got some time to have a play. I have trimmed out so of your code to simplify it and make it easier to learn from.

Strict

Import mojo

Global Player1:Player
Global ScreenWidth:Int = 640
Global ScreenHeight:Int = 480
Global Tiles:Int[][]

Class TileEngine Extends App
	
	Field CurrentLevel:Level
	
	Method OnCreate:Int()
		SetUpdateRate(60)
		Player1 = New Player()
		CurrentLevel = New Level(20, 20)
		Return 0
	End

	Method OnUpdate:Int()
		If KeyHit(KEY_RIGHT)
			Player1.Move(East)
		End
		If KeyHit(KEY_LEFT)
			Player1.Move(West)
		End
		If KeyHit(KEY_UP)
			Player1.Move(North)
		End
		If KeyHit(KEY_DOWN)
			Player1.Move(South)
		End
		Return 0
	End

	Method OnRender:Int()
		Cls(0, 0, 0)
		' Sends the players coordinates through so the level can be drawn cenetered on the player
		CurrentLevel.Draw(Player1.x, Player1.y)
		Player1.Draw()
		Return 0
	End
End


Function Main:Int()

	New TileEngine()
	Return 0
	
End

' Cardinal Directions
	Const North:Int=1
	Const East:Int=2
	Const South:Int=3
	Const West:Int=4

' Tile Types	
	Const IMPASSABLE:Int = 0
	Const WALL:Int = 1
	Const GRASS:Int = 2
	Const DIRT:Int = 3

	
Class Level
	' Size of the level in tiles
	Field LevelWidth:Int
	Field LevelHeight:Int
	
	' Number of pixels
	Field TileWidth:Int = 32
	Field TileHeight:Int = 32
	
	Field FloorPic:Image
	
	Method New(Width:Int, Height:Int)
		LevelWidth = Width
		LevelHeight = Height
		Tiles = AllocateArray(Width, Height)
		
		' Seed the whole map with solid tiles
		For Local i:Int = 0 To LevelWidth - 1
			For Local j:Int = 0 To LevelHeight - 1
				Tiles[i][j] = WALL
			Next
		Next
		
		Generate
		
	End

	Method Draw:Void(xPos:Int, yPos:Int)
		Local NumXTiles:Int
		Local NumYTiles:Int
		Local StartX:Int
		Local StartY:Int
		Local EndX:Int
		Local EndY:Int
		Local ScreenX:Int = 0
		Local ScreenY:Int = 0
		
		NumXTiles = ScreenWidth / TileWidth
		NumYTiles = ScreenHeight / TileHeight
		
		StartX = xPos - (NumXTiles / 2)
		StartY = yPos - (NumYTiles / 2)
		
		If StartX < 0 Then StartX = 0
		If StartY < 0 Then StartY = 0
		
		EndX = xPos + (NumXTiles / 2)
		EndY = yPos + (NumYTiles / 2)
		
		If EndX > LevelWidth - 1 Then EndX = LevelWidth - 1
		If EndY > LevelHeight - 1 Then EndY = LevelHeight - 1
		
		For Local X:Int = StartX To EndX
			For Local Y:Int = StartY To EndY
				If X > LevelWidth - 1 Then X = LevelWidth - 1
				If Y > LevelHeight - 1 Then Y = LevelHeight - 1
				Select Tiles[X][Y]
					Case GRASS
						If xPos = X And yPos = Y
							Tiles[X][Y] = DIRT
						Endif
						DrawImage(FloorPic, ScreenX * TileWidth, ScreenY * TileHeight, 1)
					Case WALL
						DrawImage(FloorPic, ScreenX * TileWidth, ScreenY * TileHeight, 3)
					Case IMPASSABLE
						DrawImage(FloorPic, ScreenX * TileWidth, ScreenY * TileHeight, 4)
					Case DIRT
						DrawImage(FloorPic, ScreenX * TileWidth, ScreenY * TileHeight, 2)
				End
				ScreenY += 1
			Next
			ScreenY = 0
			ScreenX += 1
		Next
		ScreenX = 0
		ScreenY = 0
	
	End
	

	Method Generate:Void()
		Local CellCount:Int = 0
		FloorPic = LoadImage("tiles.png", 32, 32, 5)

		For Local x:Int = 3 To LevelWidth - 3
			For Local y:Int = 3 To LevelHeight - 3
				Tiles[x][y] = GRASS
			Next
		Next	
	End	
	
End


Class Character
	Field x:Int = 10
	Field y:Int = 7
	Field Picture:Image
	Field PlayerTileWidth:Int = 32
	Field PlayerTileHeight:Int = 32
	
	Method Move:Int(direction:Int)
		Select direction
			Case East
				If LegalMove(x + 1, y) Then Self.x += 1
			Case West
				If LegalMove(x - 1, y) Then Self.x -= 1
			Case North
				If LegalMove(x, y - 1) Then Self.y -= 1
			Case South
				If LegalMove(x, y + 1) Then Self.y += 1
		End Select
		
		If x < 0 Then x = 0
		If y < 0 Then y = 0
		If x > 99 Then x = 99
		If y > 99 Then y = 99
		
		Return 0
	End
	
	'This method draws the player and keeps him in the center of the screen unless you are at the edge of the map
	Method Draw:Void()
		Local DrawX:Int
		Local DrawY:Int
		
		DrawX = ScreenWidth / 2
		DrawY = ScreenHeight / 2
		
		If x * PlayerTileWidth < DrawX Then
			DrawX = x * PlayerTileWidth
		Endif
		If y * PlayerTileHeight < DrawY Then
			DrawY = y * PlayerTileHeight
		Endif
		DrawImage(Picture, DrawX, DrawY, 1)
	
	End
	
	Method LegalMove:Bool(x:Int, y:Int)
		If Tiles[x][y] = IMPASSABLE or Tiles[x][y] = WALL
			Return False
		Else
			Return True
		EndIf
	End
	
End

Class Player Extends Character
		
	Method New()
		Picture = LoadImage("hero.png", 32, 32, 3)
	End
End


' Muddy Shoes 2D Array Code
Function AllocateArray:Int[][] (i:Int, j:Int)
    Local arr:Int[][] = New Int[i][]
	Local ind:Int
    For ind = 0 Until i
        arr[ind] = New Int[j]
    Next
    Return arr		
End


The only problem I have so far it that the character overlaps the wall at the bottom, I think he's getting out of position or something. Any ideas what causing it?

Thanks