Code archives/3D Graphics - Misc/miniB3D Simple Dungeon

This code has been declared by its author to be Public Domain code.

Download source code

miniB3D Simple Dungeon by jhans0n2008
An example of an old school randomly generated Wolfenstein-3D type dungeon.
Import sidesign.minib3d

Strict
SeedRnd MilliSecs()

'//////////////////////////////////////////////////////////////////////
'// Simple 3d Dungeon 1.1 by Jeff Hanson                             //
'// ------------------------------------                             //
'// This uses Ryan Burnside's Simple Dungeon Engine Release 1.0 from //
'// the code archives to generate a simple 3d dungeon that you can   //
'// walk around in using the arrow keys (and Escape to quit).  It    //
'// requires MiniB3D version 0.51.                                   //
'//                                                                  //
'// You can play with the dungeon generation variables to change the //
'// characteristics of the dungeon.                                  //
'//                                                                  //
'// And sorry for the sloppy code.  It's just meant to be a quick    //
'// and dirty example, not a masterpiece.                            //
'//////////////////////////////////////////////////////////////////////

'//////////////////////////////////////////////////////////////////////
'// Version History                                                  //
'// ---------------                                                  //
'// 1.0 - Initial Release                                            //
'// 1.1 - Fixed ground problem by dividing ground into several       //
'//       chunks when the map gets big.                              //
'//     - Added texture ability (shut off by default)                //
'//                                                                  //
'//////////////////////////////////////////////////////////////////////

Graphics3d 800, 600, 32, 2

Const USETEXTURES:Int = 0 '0=don't use textures, 1=use textures
Const GRAVITY:Float = -0.3 'apply gravity

'start dungeon generation variables
Global dungeonWidth:Int = 30
Global dungeonHeight:Int = 30
Global roomSizeMin:Int = 3
Global roomSizeMax:Int = 10
Global numberRooms:Int = Rand(5,12)
'end dungeon generation variables

Global ground:TMesh[Ceil((dungeonWidth+dungeonHeight)/20)+1]
Global ceiling:TMesh[Ceil((dungeonWidth+dungeonHeight)/20)+1]
Global wall:TMesh = CreateMesh()
Global camera:TCamera = CreateCamera()
Global light:TLight = CreateLight(2, camera)

For Local i:Int = 0 To Ceil((dungeonWidth+dungeonHeight)/20)
	ground[i] = CreateMesh()
	ceiling[i] = CreateMesh()
Next


'dungeon generation code
Local d:dungeon = create_dungeon(dungeonWidth, dungeonHeight, numberRooms, roomSizeMin, roomSizeMax, roomSizeMin, roomSizeMax) 

' seed the rooms with 1 player starting point, we will call this value "2"
add_value(d, 2, 1)

'remove all of the walls that will never be seen by the player
remove_blocked(d, dungeonWidth, dungeonHeight)


'Here's where you load your textures
'Make sure to put them where the program can find them, or it'll fail
Global texture:TTexture[3]
If USETEXTURES Then
	texture[0]=LoadTexture("ground.jpg")
	texture[1]=LoadTexture("wall.jpg")
	texture[2]=LoadTexture("ceiling.jpg")
EndIf

'create a 3d version of the dungeon map
makeDungeon(d)

EntityType camera, 1
EntityRadius camera, 0.9

CameraRange camera, 0.1, 51.0
CameraFogMode camera, 1
CameraFogRange camera, 0.1, 50
CameraFogColor camera, 0,0,0

'turning on the light range seems to cause some artifacts on the screen
'not sure why.  you can uncomment it and try it though.
'LightRange light, 10

Collisions 1, 2, 2, 2

While Not KeyDown (KEY_ESCAPE)

	If KeyDown (KEY_LEFT) Then
		TurnEntity camera, 0, 2.0, 0
	ElseIf KeyDown (KEY_RIGHT) Then
		TurnEntity camera, 0, -2.0, 0
	EndIf
	If KeyDown (KEY_UP) Then
		MoveEntity camera, 0, 0, 0.3
	ElseIf KeyDown (KEY_DOWN) Then
		MoveEntity camera, 0, 0, -0.3
	EndIf
	
	MoveEntity camera, 0, GRAVITY, 0
	
	UpdateWorld()
	RenderWorld()
	Flip 1
Wend



'FUNCTIONS
Function makeDungeon (d:dungeon)
	Local master:TMesh = CreateCube()
	
	For Local i = 0 To d.array.dimensions()[0] - 1
		For Local j = 0 To d.array.dimensions()[1] - 1
			If d.array[i, j] 
			 	Select d.array[i, j] 
					Case 1 'wall
						Local tempblock:TMesh = CopyMesh(master)
						PositionMesh tempblock, i*2, 2, j*2
						AddMesh tempblock, wall
						FreeEntity tempblock
	
					Case 2 'player starting point
						PositionEntity camera, i*2, 2, j*2
				EndSelect
			End If
			
			'ground
			Local temp_obj:TMesh = CopyMesh(master) 
			PositionMesh temp_obj, i*2, 0, j*2
			AddMesh temp_obj, ground[Floor((j+i)/20)]
			FreeEntity temp_obj
			
			'ceiling
			Local temp_obj2:TMesh = CopyMesh(master) 
			PositionMesh temp_obj2, i*2, 4, j*2
			AddMesh temp_obj2, ceiling[Floor((j+i)/20)]
			FreeEntity temp_obj2
		Next
	Next
	
	'block the outside edges of the map, since the dugeon generator
	'will sometimes put rooms against the edge, and you don't want
	'anyone falling off the map
	For Local xa:Int = -1 To dungeonWidth 
		Local temp_obj3:TMesh = CopyMesh(master)
		PositionMesh temp_obj3, xa*2, 2, -2
		Local temp_obj4:TMesh = CopyMesh(master)
		PositionMesh temp_obj4, xa*2, 2, (dungeonHeight)*2
		AddMesh temp_obj3, wall
		AddMesh temp_obj4, wall
		FreeEntity temp_obj3
		FreeEntity temp_obj4
	Next

	For Local ya:Int = -1 To dungeonHeight
		Local temp_obj3:TMesh = CopyMesh(master)
		PositionMesh temp_obj3, -2, 2, ya*2
		Local temp_obj4:TMesh = CopyMesh(master)
		PositionMesh temp_obj4, dungeonWidth*2, 2, ya*2
		AddMesh temp_obj3, wall
		AddMesh temp_obj4, wall
		FreeEntity temp_obj3
		FreeEntity temp_obj4
	Next
	
	For Local i:Int = 0 To Ceil((dungeonWidth+dungeonHeight)/20)
		EntityType ground[i], 2
		EntityType ceiling[i], 2
		
		If USETEXTURES Then
			EntityTexture ground[i], texture[0]
			EntityTexture ceiling[i], texture[2]
		Else
			EntityColor ground[i], 0,150,0
			EntityColor ceiling[i], 0, 0, 150
		EndIf
	Next
	
	EntityType wall, 2
	
	If USETEXTURES Then
		EntityTexture wall, texture[1]
	Else
		EntityColor wall, 150, 0, 0
	EndIf
EndFunction

Function remove_blocked(d:dungeon, width:Int, height:Int)
	Local tempArray:Int[width, height]

	For Local x:Int = 0 To width-1
		For Local y:Int = 0 To height-1
			If x = 0 Or x = width-1 Or y=0 Or y=height-1 Then
				'at edge, do nothing
			Else
				If d.array[x,y] = 1 Then
					If d.array[x-1,y] = 1 And d.array[x+1,y] = 1	 And d.array[x,y-1] = 1	 And d.array[x,y+1] = 1 Then
						temparray[x,y]=1
					EndIf
				EndIf
			EndIf
		Next
	Next
	
	For Local x2:Int = 0 To width-1
		For Local y2:Int = 0 To height-1
			If temparray[x2,y2]=1 Then
				d.array[x2,y2] = 0
			EndIf
		Next
	Next
	
EndFunction


'.........................................................................................................
' Ryan Burnside Dungeon Engine Release 1.0
' ***Please credit "Ryan Burnside" if possible.***
' This dungeon generator is meant to do just that, nothing more and nothing less
' a dungeon holds an array and rooms that overlay spaces in the array
' the dungeon array is simply called "array"
' this array holds values for walls floor and will eventually hold special data for tiles
' it is up to the programmer to defing meaningful conventions for special values
' rooms can be seeded with special values using the "add_value" command
' it is important to note that the add_value command will overwrite values if the random room spot is taken
' because of this you will want to make your stairs last and items and monster values first
' once an item is obtained you will want to set the square back to 0 in the dungeon array
'.........................................................................................................



' A wrapper object for an array and holder object for room instances
Type dungeon
	Field array:Byte[,] , rooms:TList = New TList, rooms_maxed:Byte = False, room_count:Int = 0
	
	Method add_room(min_width:Int, min_height:Int, max_width:Int, max_height:Int) 
		Local array_width:Int = array.dimensions()[0] 
		Local array_height:Int = array.dimensions()[1] 
		' first ensure that the room is not larger than the array
		If max_width > array_width
			max_width = array_width
		End If
		
		If max_height > array_height
			max_height = array_height
		End If
		
		' ensure that the min values are larger than 0
		If min_width < 2 min_width = 2
		If min_height < 2 min_height = 2
		' now set the size of this room
		Local width:Int = Rand(min_width, max_width) 
		Local height:Int = Rand(min_height, max_height) 
		Local search_width:Int = array_width - width
		Local search_height:Int = array_height - height
		Local search_x:Int = Rand(0, search_width) 
		Local search_y:Int = Rand(0, search_height) 
		Local max_checks:Int = search_width * search_height
		Local checked:Int = 0
		Local finished:Int = 0
		While Not finished
			If room_count > 0
				Local r:room = New room
				r.x = search_x
				r.y = search_y
				r.x2 = search_x + width - 1
				r.y2 = search_y + height - 1
				Local collisions:Int = 0
				For Local b:room = EachIn(rooms) 
					If rooms_collide(b, r) = True
						collisions:+1
					End If
				Next
				If Not collisions
					carve_room(r) 
					ListAddFirst(rooms, r) 
					room_count:+1
					finished = True
				Else
				End If
			EndIf
			
			If room_count = 0
				Local r:room = New room
				r.x = search_x
				r.y = search_y
				r.x2 = search_x + width - 1
				r.y2 = search_y + height - 1
				carve_room(r) 
				ListAddFirst(rooms, r) 
				room_count:+1
				finished = True
				Exit
			EndIf
			
			' add to the index
			search_x:+1
			If search_x > search_width
				search_x = 0
				search_y:+1
			End If
			If search_y > search_height
				search_y = 0
			End If
			checked:+1
			If checked = max_checks
				finished = True
			End If
		Wend
	End Method
	
	Method carve_room(r:room) 
		' take a room and carve out the space needed set squares to 0's
		For Local x = r.x To r.x2
			For Local y = r.y To r.y2
				array[x, y] = 0
			Next
		Next
	End Method
	
	Method ready_array(length:Int, height:Int) 
		' sets all indexes to 1 and readys the array for writing
		Local a:Byte[length, height] 
		array = a
		For Local x:Int = 0 To length - 1
			For Local y:Int = 0 To height - 1
				array[x, y] = 1
			Next
		Next
	End Method
	
	End Type

' A rectangular field that serves as a storage house for seeded values
Type room
	Field x:Int, y:Int, x2:Int, y2:Int
End Type

' return if rooms collide (used in create_dungeon)
Function rooms_collide:Int(room1:room, room2:room) 
	If room1.y2 + 3 < room2.y Return 0
	If room1.y - 3 > room2.y2 Return 0
	If room1.x2 + 3 < room2.x Return 0
	If room1.x - 3 > room2.x2 Return 0

	Return 1
End Function

' connect 2 rooms (used in create_dungeon)
Function connect_rooms(room1:room, room2:room, d:dungeon) 
	' first pick between 2 connection styles
	' we always draw from left to right so we must choose what order to process the rooms
	
	Local x1:Int = (room1.x + room1.x2) / 2.0
	Local y1:Int = (room1.y + room1.y2) / 2.0
	Local x2:Int = (room2.x + room2.x2) / 2.0
	Local y2:Int = (room2.y + room2.y2) / 2.0
	' make sure the values for each of the x's and y's are EVEN so no corridors touch
	If Float(x1 Mod 2.0) 
		x1:+1
	End If
	If Float(x2 Mod 2) 
		x2:+1
	End If
	If Float(y1 Mod 2) 
		y1:+1
	End If
	If Float(y2 Mod 2) 
		y2:+1
	End If
	draw_hori(y1, x1, x2, d) 
	draw_vert(x2, y1, y2, d) 
End Function

' draw a verticle line on the array (used in create_dungeon)
Function draw_vert(x1:Int, y1:Int, y2:Int, d:dungeon) 
	' see if step multiplier is negative
	Local dist:Int = Abs(y1 - y2) 
	Local mult:Int = 1
		If y1 > y2
			mult = -1
		End If
		' draw 
		For Local i = 0 To dist
			d.array[x1, y1 + (i * mult)] = 0
		Next
	EndFunction

' draw a horizontal line on the array (used in create_dungeon)
Function draw_hori(y1:Int, x1:Int, x2:Int, d:dungeon) 
	' see if step multiplier is negative
	Local dist:Int = Abs(x1 - x2) 
	Local mult:Int = 1
		If x1 > x2
			mult = -1
		End If
		' draw 
		For Local i = 0 To dist
			d.array[x1 + (i * mult), y1] = 0
		Next
EndFunction

' *IMPORTANT* lets a programmer seed the dungeon with item, monster and exit values as needed
Function add_value(d:dungeon, value:Int, attempts:Int) 
	For Local i:Int = 0 To attempts - 1
	Local r:room = room(d.rooms.ValueAtIndex(Rand(0, CountList(d.rooms) - 1))) 
	d.array[Rand(r.x, r.x2), Rand(r.y, r.y2)] = value
	Next
End Function

' *IMPORTANT* returns a freshly made dungeon, the workhorse of the program!
Function create_dungeon:dungeon(width:Int, height:Int, room_count:Int, room_min_height:Int, room_max_height:Int, room_min_width:Int, room_max_width:Int) 
	Local d:dungeon = New dungeon
	'ready the array
	d.ready_array(width, height) 
	'add rooms
	For Local i = 0 To room_count - 1
		d.add_room(room_min_width, room_min_height, room_max_width, room_max_height) 
	Next
	'connect rooms in a loop
	Local length:Int = CountList(d.rooms) 
 
	For Local j:Int = 0 To length
		If j + 1 < length
			connect_rooms(room(d.rooms.ValueAtIndex(j)) , room(d.rooms.ValueAtIndex(j + 1)) , d) 
		EndIf
	Next
	connect_rooms(room(d.rooms.ValueAtIndex(0)) , room(d.rooms.ValueAtIndex(length - 1)) , d) 
	
	Return d
End Function

Comments

None.

Code Archives Forum