Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

 
Advanced search

877223 Posts in 32852 Topics- by 24294 Members - Latest Member: RopeDrink

May 18, 2013, 09:56:56 PM
TIGSource ForumsDeveloperTutorialsBraving Procedural Generation
Pages: 1 [2] 3 4 ... 30
Print
Author Topic: Braving Procedural Generation  (Read 129399 times)
Kneecaps
Level 3
***



View Profile Email
« Reply #15 on: March 12, 2009, 04:53:18 AM »

It takes me less than a second, it seems to generate as fast as I can click. Maybe my CPU is just better at it or something. Really nice though. Any chance you could let us see the source code?
Sure!  I'll need to make it user-friendly though, which is something I could work on during school today.
Logged
Kneecaps
Level 3
***



View Profile Email
« Reply #16 on: March 12, 2009, 12:18:19 PM »

Yay double post!

Here's my code.  I'm sure there's better ways to organize it and such, but it worked for me, and that's all that matters.

Code:
package 
{
import flash.display.*;
import flash.events.MouseEvent;

/**
* ...
* @author Kneecaps
*/
public class Map1 extends Sprite
{
private var map:Array;
private var i:int;
private var j:int;
private var image:Bitmap;
private var imageData:BitmapData;
public function Map1()
{
//Start out with a map displayed
createMap();
stage.addEventListener(MouseEvent.CLICK, onMouseClick);
}
//Generate a new map when the user clicks the mouse
private function onMouseClick(e:MouseEvent):void {
stage.removeChild(image);
createMap();
}
private function createMap():void {
//Create an 8x8 map
map = new Array(8);
for (i = 0; i < 8; i++) {
map[i] = new Array(8);
}
//Randomly fill the map with land (1) and water (0)
//I use j as the y-value and i as the x-value in my code normally
for (j = 0; j < map.length; j++) {
for (i = 0; i < map[0].length; i++) {
if (Math.random() < 0.4) map[j][i] = 1;
else map[j][i] = 0;
}
}
//Make the map bigger until it is bigger than 500 pixels.  Since
//the dimensions double each time the map is refined, the final
//size is 512 pixels.
while(map.length<500){
map = refineMap(map);
}
//Make a bitmap the size of the map
imageData = new BitmapData(map[0].length, map.length);
//Draw the map
for (j = 0; j < map.length; j++) {
for (i = 0; i < map[0].length; i++) {
//If the current cell is land
if (map[j][i]) {
//x and y values for the cells bordering the current cell
var left:int = i - 1;
var right:int = i + 1;
var up:int = j - 1;
var down:int = j + 1;
//Ignore cells out of the map's boundaries
if (left < 0) left = i;
else if (right > map[0].length-1) right = i;
if (up < 0) up = j;
else if (down > map.length - 1) down = j;
//If one of the bordering cells is water, make this cell dark green
if (!map[up][i] || !map[down][i] || !map[j][left] || !map[j][right]) imageData.setPixel(i, j, 0x003300);
//Else, make the cell light green
else imageData.setPixel(i, j, 0x009933);
}
//Make the water blue
else imageData.setPixel(i, j, 0xAEEBFF);
}
}
image = new Bitmap(imageData);
stage.addChild(image);
}
//Doubles the map's size while making it more "complex"
private function refineMap(a:Array):Array {
//Make a new array (na) twice the size of the initial array
var na:Array = new Array(a.length * 2);
for (i = 0; i < na.length; i++) {
na[i] = new Array(a[0].length * 2);
}
/*Copy the values to the new array, but twice as big.
* 01            0011
* 00   becomes  0011
*               0000
*               0000
*/
for (j = 0; j < a.length; j++) {
for (i = 0; i < a[0].length; i++) {
na[j * 2][i * 2] = a[j][i];
na[j * 2 + 1][i * 2] = a[j][i];
na[j * 2 + 1][i * 2 + 1] = a[j][i];
na[j * 2][i * 2 + 1] = a[j][i];
}
}
/* We want two copies of the bigger version.  Cells will be read off
* the first array (a), and the new cell will be written to the new
* array.  Using only one array for reading/writing can lead to problems.
*/
a = na;
for (j = 1; j < na.length-1; j++) {
for (i = 1; i < na[0].length - 1; i++) {
//The number of bordering cells (including the first cell) that are land.
var landSpaces:Number = 0;
//x and y values for the cells bordering the current cell
var left:int = i - 1;
var right:int = i + 1;
var up:int = j - 1;
var down:int = j + 1;
//If values were out of bounds, wrap them around the map to the other side
if (left < 0) left = na[0].length-1;
else if (right > na[0].length) right = 0;
if (up < 0) up = na.length-1;
else if (down > na.length) down = 0;
//Find the total amount of bordering land cells (out of 9)
if (a[up][left]) landSpaces++;
if (a[j][left]) landSpaces++;
if (a[down][left]) landSpaces++;
if (a[up][i]) landSpaces++;
if (a[j][i]) landSpaces++;
if (a[down][i]) landSpaces++;
if (a[up][right]) landSpaces++;
if (a[j][right]) landSpaces++;
if (a[down][right]) landSpaces++;
//Randomly generate a cell based on how many land cells are bordering it.
//A cell with 3 bordering land cells has a 3/9 chance of becoming a land cell.
if (Math.random() < landSpaces / 9) na[j][i] = 1;
else na[j][i] = 0;
}
}
return na;
}
}

}

If my commenting makes no sense, let me know.

FUN EDIT TIME:
So, I tried adding more stuff to my maps, and it hasn't been too friendly to me.  Specifically, rivers.  I can get a river line to generate fine, like so:



However, whenever I try to expand that river (like one would expand a selection in Photoshop), I get this:



Here's my expand function.  It takes in the map that is going to be changed, the value of the cells to search for (water is 2), the number of times to expand, and the chance that any particular cell will turn its neighbors into its value.  With that number at 1, it should theoretically just do a normal "expand" thingy, but that fills the whole screen at one.  The screenshot is when that value is at 0.7.  If you could help me at all with this, I would very much appreciate it.

Code:
private function expandTerrain(a:Array, v:int, n:int, chance:Number=1):Array {
var na:Array = a;
for (k = 0; k < n;k++){
for (i = 0; i < a[0].length; i++) {
for (j = 0; j < a.length; j++) {
if (a[j][i] == v&&Math.random()<chance) {
if (j - 1 > 0) na[j - 1][i] = v;
if (j + 1 < 127) na[j + 1][i] = v;
if (i - 1 > 0) na[j][i - 1] = v;
if (i + 1 < 127) na[j][i + 1] = v;
}
}
}
}
return na;
}
« Last Edit: March 13, 2009, 12:52:53 PM by Kneecaps » Logged
ChevyRay
Guest
« Reply #17 on: March 13, 2009, 02:02:39 PM »

I usually do mountain generation before rivers, and then create glacier-points on top of those mountains. After that, I just run rivers down from the mountains, moving in the general direction of the ocean, and ending them when they reach it.
Logged
Kneecaps
Level 3
***



View Profile Email
« Reply #18 on: March 13, 2009, 02:17:33 PM »

I usually do mountain generation before rivers, and then create glacier-points on top of those mountains. After that, I just run rivers down from the mountains, moving in the general direction of the ocean, and ending them when they reach it.
I'm not too concerned about making the rivers flow realistically just yet; I'm more concerned about  making the river 3 pixels wide.  Also, for what I have in mind with the generation, I won't be needing mountains (yet).

So one again, if someone has a function that can do this:
Code:
00000        00000
00000        00110
00110  --->  01111
00100        01110
00000        00100
I would be so happy.  And I would give you sexual favors.
Logged
Cthulhu32
Level 6
*


Brawp


View Profile WWW Email
« Reply #19 on: March 13, 2009, 03:01:57 PM »

Seemed like a pretty swanky tutorial so I decided to give it a go in Pygame :D Took me about an hour with getting the algorithm small. I also started with using 8 corner neighbors + self when determining whether to become a land or island, but quickly learned that 4 corners (up, down, left, right) + self is a much better alternative.

If you want to play around with the source, try modifying LAND_COUNT (1-15), LAND_RATIO, and SEA_RATIO. LAND_COUNT is the initial how many land blocks will we generate off of, and it randomly chooses to be a sea or land based on neighbors.



And of course full source Smiley
Code:
import pygame, random
from pygame.locals import *

SCREEN_WIDTH = 512 # For now this must be 512, 1024, 1536, or 2048
SCREEN_HEIGHT = 512 # For now this must be 512, 1024, 1536, or 2048
LAND_COUNT = 5 # how many initial islands can we spawn off of?
LAND_RATIO = 9  # 8/5 chance of having land vs. sea
SEA_RATIO = 5   #   we can change this to any ratio
LAND1_RGBA = (132,176,138,255)
LAND2_RGBA = (119,168,126,255)
SEA1_RGBA = (75,162,255,255)
SEA2_RGBA = (62,142,255,255)
BORDER_RGBA = (80,124,87,255)

class PLand:
def __init__(self, landLayer):
self.landLayer = landLayer
self.largeGrid = []
self.mediumGrid = []
self.smallGrid = []
self.tinyGrid = []
for i in xrange(16): # 4*4
self.largeGrid.append(0)
for i in xrange(256): # 16*16
self.mediumGrid.append(0)
for i in xrange(4096): # 64*64
self.smallGrid.append(0)
for i in xrange(65536): # 256*256
self.tinyGrid.append(0)
self.state = 'largeGrid' # always start at large grid for this implementation
self.tileIdx = 0 # which tile idx are we at?
self.neighbors = [(0,-1),(-1,0),(0,0),(1,0),(0,1)] # check 4 neighbors (up,down,left,right) + self

def update(self):
while self.state != 'done':
if self.state == 'largeGrid':
print 'Creating Large Grid'
randGrid = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
width = SCREEN_WIDTH/4
height = SCREEN_HEIGHT/4
for i in xrange(LAND_COUNT): # fill in with defined land count
r = random.randint(0, len(randGrid)-1)
idx = randGrid.pop(r)
self.largeGrid[idx] = 1
for i in xrange(len(randGrid)): # fill the other half with water
idx = randGrid.pop(0)
self.largeGrid[idx] = 2
print 'Moving to Medium Grid'
self.state = 'mediumGrid'
elif self.state == 'mediumGrid':
if self.updateGrid(256, 16, 4, self.mediumGrid, self.largeGrid) == True:
print 'Moving to Small Grid'
self.state = 'smallGrid'
elif self.state == 'smallGrid':
if self.updateGrid(4096, 64, 16, self.smallGrid, self.mediumGrid) == True:
print 'Moving to Tiny Grid'
self.state = 'tinyGrid'
elif self.state == 'tinyGrid':
if self.updateGrid(65536, 256, 64, self.tinyGrid, self.smallGrid) == True:
print 'Drawing Grid'
self.state = 'drawGrid'
elif self.state == 'drawGrid':
width = SCREEN_WIDTH/256
height = SCREEN_HEIGHT/256
for idx in xrange(65536):
x = idx % 256
y = idx / 256
if self.tinyGrid[idx] == 1: # Is it land?
isBorder = False
for nx,ny in self.neighbors:
nx += int(x) # our current difference in grids (64/16 = 4, 16/4 = 4)
ny += int(y)
if nx >= 0 and ny >= 0 and nx < 256 and ny < 256:
if self.tinyGrid[ny*256+nx] == 2: # draw this as darker and break
pygame.draw.rect(self.landLayer, BORDER_RGBA, Rect(x*width,y*height,width,height))
isBorder = True
break
if isBorder == False:
if ( random.randint(0,1) == 1 ): #50% chance of coloring different
pygame.draw.rect(self.landLayer, LAND1_RGBA, Rect(x*width,y*height,width,height))
else:
pygame.draw.rect(self.landLayer, LAND2_RGBA, Rect(x*width,y*height,width,height))
else: # is it sea?
if ( random.randint(0,1) == 1 ): #50% chance of coloring different
pygame.draw.rect(self.landLayer, SEA1_RGBA, Rect(x*width,y*height,width,height))
else:
pygame.draw.rect(self.landLayer, SEA2_RGBA, Rect(x*width,y*height,width,height))
print 'Done!'
self.state = 'done'

def updateGrid(self, maxSize, tileSize, oldSize, newGrid, oldGrid):
if self.tileIdx < maxSize:
x = self.tileIdx % tileSize
y = self.tileIdx / tileSize
neighborList = [] # Check our neighbors
for nx,ny in self.neighbors:
nx += int(x/4) # our current difference in grids (256/4 = 64, 64/4 = 16, 16/4 = 4)
ny += int(y/4)
if nx >= 0 and ny >= 0 and nx < oldSize and ny < oldSize:
neighborList.append(oldGrid[ny*oldSize+nx])
waterChance = 0
landChance = 0
for neighbor in neighborList:
if neighbor == 1: # land!
landChance += LAND_RATIO
elif neighbor == 2: # water!
waterChance += SEA_RATIO
r = random.randint(0, landChance+waterChance-1)
width = SCREEN_WIDTH/tileSize
height = SCREEN_HEIGHT/tileSize
if r < landChance:
newGrid[self.tileIdx] = 1 # land
else:
newGrid[self.tileIdx] = 2 # water
self.tileIdx += 1
return False
else:
self.tileIdx = 0
return True

def draw(self,screen):
screen.blit(self.landLayer, (0,0))

def main():
"""Procedural Generation - Luke Arntson '09
"""
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption('Land Generation')
pygame.mouse.set_visible(0)
background = pygame.Surface(screen.get_size())
background = background.convert()
background.fill((0, 0, 0))
landLayer = pygame.Surface(screen.get_size())
landLayer = landLayer.convert_alpha()
landLayer.fill((0, 0, 0, 0))
mLand = PLand(landLayer)
screen.blit(background, (0, 0))
pygame.display.flip()
print '-- Creating Map(%i,%i) with %i Land Initializer(s), and a Water-to-Sea average of %i/%i --\n'%(SCREEN_WIDTH,SCREEN_HEIGHT,LAND_COUNT,LAND_RATIO,SEA_RATIO)
clock = pygame.time.Clock()
while 1:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
return
elif event.type == KEYDOWN and event.key == K_ESCAPE:
return
mLand.update()
screen.blit(background, (0, 0))
mLand.draw(screen)
pygame.display.flip()

if __name__ == '__main__': main()
Logged

ChevyRay
Guest
« Reply #20 on: March 14, 2009, 09:09:31 AM »

Looks like it works great, Cthulhu.



Got a little view-system working so I can see a blown-up view of a section of the map. The numbers currently are land-height, though all the water is just 0s right now. Still got a lot of work to do on those mountains.

Gonna make it so I can switch what digital info is displayed on the right-side there.
Logged
muku
Level 10
*****



View Profile WWW
« Reply #21 on: March 14, 2009, 10:01:35 AM »

Code:
private function expandTerrain(a:Array, v:int, n:int, chance:Number=1):Array {
var na:Array = a;
for (k = 0; k < n;k++){
for (i = 0; i < a[0].length; i++) {
for (j = 0; j < a.length; j++) {
if (a[j][i] == v&&Math.random()<chance) {
if (j - 1 > 0) na[j - 1][i] = v;
if (j + 1 < 127) na[j + 1][i] = v;
if (i - 1 > 0) na[j][i - 1] = v;
if (i + 1 < 127) na[j][i + 1] = v;
}
}
}
}
return na;
}

Your problem is that you set your new array "na" to the old one, "a". So when you change stuff in "na", you simultaneously change it in "a" since they really refer to the same object. You need to make "na" an actual copy of "a" for this code to work.

I would be so happy.  And I would give you sexual favors.
Well, hello there!
Logged

The Cosyne Synthesis Engine - realtime music synthesis for games
Kneecaps
Level 3
***



View Profile Email
« Reply #22 on: March 14, 2009, 05:53:15 PM »

Your problem is that you set your new array "na" to the old one, "a". So when you change stuff in "na", you simultaneously change it in "a" since they really refer to the same object. You need to make "na" an actual copy of "a" for this code to work.
Awesome!  It works fine now.  Now I can move on to better things.
« Last Edit: August 12, 2009, 07:20:15 PM by Kneecaps » Logged
Impossible
Level 3
***



View Profile Email
« Reply #23 on: March 15, 2009, 01:28:26 PM »

wow...
Logged
Zaratustra
Level 7
**



View Profile WWW Email
« Reply #24 on: March 15, 2009, 01:50:11 PM »

http://zarawesome.googlepages.com/island.png

An island generator I made for a game I never finished.
Logged

eddietree
Level 5
*****


I consume memory, therefore I am.

eddieisthx
View Profile WWW
« Reply #25 on: March 18, 2009, 03:56:22 PM »

Just read your article - now I am in the know.
Logged

agj
Level 10
*****



View Profile WWW
« Reply #26 on: March 20, 2009, 07:45:40 PM »

I liked that post, Chevy; very nice! So far I haven't delved too deeply into procedural generation yet, so it was an interesting read.
Logged

ChevyRay
Guest
« Reply #27 on: March 20, 2009, 07:55:46 PM »

Thanks Smiley

I'm thinking of doing another soon, but have it concentrate on using procedural and random methods for other, more specific aspects of games.

For example, having manually created levels, but having just the treasures randomly placed. Or having enemies behavior based on some randomly chosen factors.

I'd also eventually like to also write one on string manipulation (eg. random name generation, or NPC dialogue).
« Last Edit: March 20, 2009, 11:26:55 PM by ChevyRay » Logged
ChevyRay
Guest
« Reply #28 on: March 25, 2009, 07:26:35 AM »

Hehe, I noticed that pgc.wikidot.com (link) posted about my article in their news (I linked to their site from my article). That makes me happy, because it's probably the best PG wiki I could find on the internet, and the 2nd link that pops up when I google "Procedural Generation".

I need to write more articles. This one doubled the traffick to our site in March.
Logged
nihilocrat
Level 10
*****


Full of stars.


View Profile WWW Email
« Reply #29 on: March 25, 2009, 03:20:09 PM »

This thread is pure gold, procedural content generation is one of my favorite aspects of programming.

When I find the time to find the code, I will post my dead-simple asteroid generation algorithm I used once for an Asteroids-like game. Small scope and not really that fascinating, but it's a good example of PCG that's dead simple, and the method would give a few ideas for algorithms to people who don't know where to start.

Also, "Slartibartfast" is an awesome name for a terrain/planet generation system. Wizard

http://zarawesome.googlepages.com/island.png

An island generator I made for a game I never finished.

That's a really awesome island... it looks vaguely like Australia.
Logged

Pages: 1 [2] 3 4 ... 30
Print
Jump to:  

Theme orange-lt created by panic