Jump to content

Let's Make a Game - Procedual Content Creation (part 03)



blog-0020693001336928269.jpgAbout: This is a short series for the Leadwerks community on the process of creating a simple game using procedural content.


This week we implement the map creation process discussed in part 02 then add the mesh generation and a controller to fly around our level. Then we'll have the first iteration of our procedurally generated map.


Part 01

Part 02




Starting with the Map class.


Map:Create Function...first iteration, no corridors.


Simple nested for-loop to generate a room for each cell. Each room is stored in the table Map.Room[] with dimensions, a counter for reference and a cell offset in world units.


For our example a cell is a virtual 40x40 space, our example map is made of 4 x 4 cells.


We call the function like this...

Map:Create (407, 4 , 4 , 40 )


And our function definition is...


function Map:Create( seed , xsize , ysize , cellscale , roomscale )
self.xsize = xsize
self.ysize = ysize
self.seed  = seed
self.roomscale = roomscale
math.randomseed( seed )
if roomscale == nil then
 roomscale = 1.0
Map.roomscale = roomscale
if cellscale == nil then
 cellscale = 40.0
Map.cellscale = cellscale
Map.roomheight = 2.0
Print("Creating map, dimensions " .. xsize .. " x " .. ysize)

-- table to store our rooms
Map.Room = {}
-- room counter
rcount = 1
for x=1,xsize do
 for y=1,ysize do
  if math.random() > 0.2 then
self.Room[rcount] = {}
local r = self.Room[rcount]

r.roomID = rcount
r.cellx = x
r.celly = y
r.x = math.random(cellscale)
r.y = math.random(cellscale)
r.width  = math.ceil( math.random(3 , cellscale ) )
r.length = math.ceil( math.random(3 , cellscale ) )
r.info = string.format("id %.2d cellx %.2d  celly %.2d   x:%.3d  y:%.3d   w:%d   h:%d", r.roomID , r.cellx , r.celly , r.x , r.y , r.width , r.length )
rcount = rcount+1



In the above code to reduce rooms arranged in a solid 'grid' there's a random chance that a cell skips room creation. The cell offset ( r.x and r.y ) serves to add more irregularity to the layout.


Map.roomscale and Map.cellscale: bigger number = bigger space. One scales distance between rooms, the other scales the room mesh.



After generating the geometry from such an arrangement we get this...



I've added a head-up display to show an overhead map and annotations to the screenshot to show how the distribution of rooms work. So far so good.


Perhaps worth mentioning the CELLS are ordered top to bottom then left to right. That gives you some idea that the random offset works to radically shift rooms around to avoid being too attached to the grid arrangement.


Info: CELL is a term I use to describe a container in virtual space.


Before we generate any geometry for our rooms we have all the data we need to display a map (like the one above). Debug overlays or HUDs are handy during early development and can be migrated to a finished game HUD later. All we need right now is some way to check room volumes and positioning, some info and the players position. Later we can use a second camera to draw a top-down view if required.


Another feature we want in our HUD is some flag to draw it, a position to move it about the screen and a scale so we can fit the map to the whole screen or just squeeze it into a corner. The following code does all this. X and Y is typically used as a screen-coordinate position to draw an element and s is the size in pixels to fit the map into.


If you need to re-size text as well then you need to start rendering these to a buffer but we'll avoid the extra complexity. This is often made easier with OpenGL commands but they are not exposed to Leadwerks LUA scripts (as of 2.5).


Drawing the HUD and overhead map


function DrawHUD()
if App.showhud ~= true then return end
DrawText("ROOM DATA",22,22)
local c = string.format("RoomCount %d", #Map.Room)
local x = 40
local y = 80
for n,r in pairs(Map.Room) do
 DrawText( r.info , x , y )
 y = y + 16
x = 400
y = 80
s = 512
sx = s / (Map.cellscale * Map.xsize)
sy = s / (Map.cellscale * Map.ysize)

DrawText("OVERHEAD MAP",x,y-20)
--DrawLine(x,y,x+s,y) DrawLine(x+s,y,x+s,y+s) DrawLine(x+s,y+s,x,y+s) DrawLine(x,y+s,x,y)
for n=0,Map.xsize do
 DrawLine(x + (n*Map.cellscale*sx) , y , x + (n*Map.cellscale*sx) , y + s)
for n=0,Map.ysize do
 DrawLine(x , y + (n*Map.cellscale*sy) , x + s , y + (n*Map.cellscale*sy))
-- sorry for the fiddly math here
-- its needed to match the map scale with the HUD display scale
for n,r in pairs(Map.Room) do
 local roomx =  x + (( r.x + (r.cellx-1)*Map.cellscale) *sx )
 local roomy =  y + (( r.y + (r.celly-1)*Map.cellscale) *sy )

 DrawRect( roomx  , roomy , r.width * sx, r.length * sy )
 DrawText(string.format("%.2d",r.roomID), roomx + ((r.width*sx)*0.5)-8 , roomy + ((r.length*sx)*0.5)-9 )
DrawText(string.format("view co-ords x:%d y:%d", camera.position.x , camera.position.z) , 40, GraphicsHeight() - 20 )
-- player marker cross in yellow
DrawText("X", x + (camera.position.x * sx)-4 , y - (camera.position.z * sy) -8  )


The map size is adjusted by changing "s = 512" to however many pixels across. Objects are scaled and drawn accordingly. Once we start merging rooms this will need some alteration, the overhead camera might be a viable alternative and one we can have some fun with.



randomly flagged tile data, looks like a "BallBlazer" level.



Tiles and Geometry


Rather than write up how it works you can look through the code and tinker with it. The next and penultimate part will cover generating corridors and using "tile" data to merge overlapping rooms and add props like doors. You'll see already there are random light sources assigned to each room, random assortments of props such as columns, crates, particles etc. can be done in a similar fashion.




The Code for this article


I've attached the full code which creates the room data and geometry (sans no corridors and intelligent tile merging - we'll deal with that in part 04).


Don't forget to execute this script using ScriptEditor.exe (or run it with ENGINE.EXE) you will need to make sure the path at the top of START.LUA (MediaDir) points to your Leadwerks SDK location and the default scripts are present (as "required" at the top of the script).


You'll find a few bits of code commented out and older functions I used to create meshes. I've left them in for curiosity (learning and laughter).


Until part 04, have a good weekend.


Recommended Comments

This series of tutorial is more or less designed for my as I'm working on my game CELL's ;) Good work.

Share this comment

Link to comment

Nice of you to say so Roland. Of course I only use the word cell as reference to an area, a virtual space of n size.


If you make the patches small and room height to something like 8 patches then and add vertex noise you get some very lumpy cave like walls, there's plenty of room to experiment.

Share this comment

Link to comment

Thanks for this great set of tutorials I am finding them very enjoyable. I recently did a series of game assets for a project using only procedural textures it’s a really exciting way to work.

Share this comment

Link to comment

I should finish this tutorial sometime :) I remember I had to break off to finish my terrain editing book. It might be more interesting to continue this with Leadwerks 3 though. The Delaney function might come into it's own for this.

Share this comment

Link to comment

Nice article. Any chance for a 4th part? Or at least some short comment with a hint about how to merge rooms and connect them with corridors.

Share this comment

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Create New...