I'm looking at semi-random maps, not quite roguelike but using prebuilt "chunks" of a map, however it seems like a massive undertaking.
It looks like, roughly, I'd need to:
*Generate a starting point and have the map branch off at certain points, randomly selecting each prebuilt "chunk" of the map and moving the player there using doors.
*Generate and clear NPCs from maps when the player goes between floors
I'm really struggling with starting on this, is it realistic given the constraints of the OHRRPGCE? Or should I give up on the idea?
Any help or suggestions are appreciated greatly.
-Cheers
Semi-random maps
Moderators: marionline, SDHawk
Semi-random maps
vvight.wordpress.com
Re: Semi-random maps
This is totally doable. My advice: Start as small as possible, and start with your hero in the center of the map. Then I'd use the each step script to track where your hero is. As your generated room expands, make sure to keep the coordinates of its corners stored in global variables. Then as your hero steps to the edge of your generated room, call up a script (from the each step script) that will choose of (to start with) 4 options of SMALL rooms to be written.
Re: Semi-random maps
This game I was trying to make has randomly generated dungeons: https://www.slimesalad.com/forum/viewto ... 32#p133732
Each area is a map definition from Custom, and it generates NPCs randomly upon loading. Whenever you leave an area in the dungeon, it loads a new random map with randomly generated treasure and enemies. It doesn't, however, remember the layout of the dungeon. To do that, you'd likely have to store that within a custom array and use 'teleport to map' with the stored data instead of using actual doors.
This method is really limited in obvious ways but can work depending on the game. It also circumvents the need to do any tile writing, which can get pretty complicated.
If you do want to write tiles, you could make the predefined chunks as strings in hspeak. Something like this:
Where # = nothing, W = wall, D = door, and 0 = floor, or something like that. Each time it loads a chunk, it reads each line of the string to write the maptiles accordingly.
Each area is a map definition from Custom, and it generates NPCs randomly upon loading. Whenever you leave an area in the dungeon, it loads a new random map with randomly generated treasure and enemies. It doesn't, however, remember the layout of the dungeon. To do that, you'd likely have to store that within a custom array and use 'teleport to map' with the stored data instead of using actual doors.
This method is really limited in obvious ways but can work depending on the game. It also circumvents the need to do any tile writing, which can get pretty complicated.
If you do want to write tiles, you could make the predefined chunks as strings in hspeak. Something like this:
Code: Select all
$0="##WDW##
#WW0WW#
W00000W
D0000WW
W0000WW
WWWWW##"
My pronouns are they/them
Ps. I love my wife
Ps. I love my wife
Re: Semi-random maps
Thanks for the replies! Kyle, I'd love to see your scripting for random maps with generated npcs etc.. that is what I'm going for more than per-tile generation. Cheers.
vvight.wordpress.com
- Pepsi Ranger
- Liquid Metal Slime
- Posts: 1460
- Joined: Thu Nov 22, 2007 6:25 am
- Location: South Florida
Re: Semi-random maps
I find it's better to use "seed random" for this type of function because then you can create a random map from a simple number and always reuse that map if you want (or create a different one by calling a new number). That way you can procedurally generate positions of tiles, wallmaps, npcs, whatever based on that number rather than risk tossing a duck on the road or a car in the pond.
The way to do this is to create functions that either draw complex objects first and then position them according to the layout rules you establish (with zones because you absolutely need to place zones that define what's allowed where, as well as consider which zones are allowed to overlap), and then use the zones that can produce randomly generated layouts between them.
For example:
Zone 1 = yard
Zone 2 = house
Zone 3 = vegetation
Zone 4 = mailbox
Zone 5 = driveway
Zone 6 = street
Rules:
Zone 2 is allowed to sit inside Zone 1, but should not step out of it.
Zone 3 can populate any part of Zone 1 that Zones 2 and 5 don't occupy.
Zone 4 should always be placed on a corner where Zones 5 and 6 meet, though which corner can be random.
If Zones 3 and 4 overlap, then a different tile graphic is drawn.
Zone 6 can run alongside Zone 1, but can never cross into it.
Zone 6 can draw as straight or curvy, as long as it doesn't overlap with Zone 1.
Optional Rules:
Zone 7 = sidewalk
Zone 8 = car
You get the idea. You draw the tiles, place the wallmaps, and place the npcs based on zone layout. Which tiles you place will depend on which zones overlap. For best results, tile types should be random. For example, the house could be brick or tin. The car could be a sedan or a truck. But the car shouldn't be made of brick. You can predesign the objects and then move them onto the zones, provided the zones are drawn to respect the size and shapes of the objects that will contain them.
Using "seed random" means you can regenerate the same layout consistently, provided you use the same number. Call a different number and get a different layout.
Where the procedural elements come into play are in how you lay out flexible zones.
For example, Zone 3 (vegetation) may be scattered randomly, but only allow hedges to grow where two or more Zone 3 tiles connect, with flowers sprouting whenever two Zone 3 tiles surround it. Likewise, isolated Zone 3 tiles may generate trees only, but depending on how close they sit toward Zone 2 (house) or Zone 5 (driveway) will depend on how large or complex they get.
Does that all make sense?
This thread is all about a game I started (and still need to finish) using this model of procedural generation.
The way to do this is to create functions that either draw complex objects first and then position them according to the layout rules you establish (with zones because you absolutely need to place zones that define what's allowed where, as well as consider which zones are allowed to overlap), and then use the zones that can produce randomly generated layouts between them.
For example:
Zone 1 = yard
Zone 2 = house
Zone 3 = vegetation
Zone 4 = mailbox
Zone 5 = driveway
Zone 6 = street
Rules:
Zone 2 is allowed to sit inside Zone 1, but should not step out of it.
Zone 3 can populate any part of Zone 1 that Zones 2 and 5 don't occupy.
Zone 4 should always be placed on a corner where Zones 5 and 6 meet, though which corner can be random.
If Zones 3 and 4 overlap, then a different tile graphic is drawn.
Zone 6 can run alongside Zone 1, but can never cross into it.
Zone 6 can draw as straight or curvy, as long as it doesn't overlap with Zone 1.
Optional Rules:
Zone 7 = sidewalk
Zone 8 = car
You get the idea. You draw the tiles, place the wallmaps, and place the npcs based on zone layout. Which tiles you place will depend on which zones overlap. For best results, tile types should be random. For example, the house could be brick or tin. The car could be a sedan or a truck. But the car shouldn't be made of brick. You can predesign the objects and then move them onto the zones, provided the zones are drawn to respect the size and shapes of the objects that will contain them.
Using "seed random" means you can regenerate the same layout consistently, provided you use the same number. Call a different number and get a different layout.
Where the procedural elements come into play are in how you lay out flexible zones.
For example, Zone 3 (vegetation) may be scattered randomly, but only allow hedges to grow where two or more Zone 3 tiles connect, with flowers sprouting whenever two Zone 3 tiles surround it. Likewise, isolated Zone 3 tiles may generate trees only, but depending on how close they sit toward Zone 2 (house) or Zone 5 (driveway) will depend on how large or complex they get.
Does that all make sense?
This thread is all about a game I started (and still need to finish) using this model of procedural generation.
Place Obligatory Signature Here
Re: Semi-random maps
Thanks Pepsi! That's really helped me grasp the ideas behind it. I think, given my limited scripting/coding abilities, that this might be a bit of a learning curve. I may pester with questions along the way if that's ok?
Appreciate it.
Appreciate it.
vvight.wordpress.com
- Pepsi Ranger
- Liquid Metal Slime
- Posts: 1460
- Joined: Thu Nov 22, 2007 6:25 am
- Location: South Florida
Re: Semi-random maps
Honestly, if I can figure it out, anyone can. It's really easy. The hardest part is grasping the concept of creating a function (which is basically a script that works like a command). The game I made using this system doesn't have any maps defined in Custom. They are entirely scripted together using language like this (cleaned up from my clunky original scripts):
[Note: This is just a sample.]
#Creates park based on number (up to 16 digits)
script, Use Number Park, begin
show text box (4)
suspend box advance
input string (1, 16, false, false, 160, 115)
resume box advance
wait for text box
if (number from string (1)==false) then(
show text box (5)
wait for text box
Use Number Park
)
else(
seedparkname := number from string (1)
seed random (number from string (1))
Choose Park Size
)
end
#Creates park based on name (but can include numbers), up to 16 characters
script, Use ASCII Park, begin
variable (f, a)
show text box (6)
suspend box advance
input string (1, 16, false, false, 160, 125)
resume box advance
wait for text box
for (f, 1, string length (1)) do(
a := a * 31 + (ascii from string (1, f))
)
seedparkname := a
seed random (a)
Choose Park Size
end
#Creates park at random
script, Use Random Park, begin
variable (r1, r2, r3, r4, r5)
r1 := random (0, 32000)
r2 := random (0, 32000)
r3 := random (0, 32000)
r4 := random (1, 32000)
r5 := (r1 + r2 + r3) * r4
seedparkname := r5
seed random (r5)
Choose Park Size
end
script, Choose Park Size, begin
load palette (0)
update palette
open menu (1)
end
#Note: Menu 1 opens a park size selection. Choosing "Small Park (63x63 tiles)" calls this script.
#Note 2: The "map" as defined in Custom is blank. The only prerequisite is to set the size, in this case 63x63 tiles. Everything else is scripted.
#Note 3: You still have to draw and assign the tileset.
script, Generate Small Park, begin
suspend player
variable (f, g, r, spread, updown x, updown y, lay, zspot, zrange1, zrange2, zrange3, upstart x, upstart y) #I think these variables were supposed to represent values I never used or chose to hard-code instead. For this script, they don't do anything.
wait (5)
fade screen out (0, 0, 0)
wait (5)
teleport to map (0)
wait (5)
seed earth (63, 63) #Notice the values equal the map size.
entrance orientation
form rock borders (63, 63)
form border fence (63, 63)
place lease agent cabin (63, 63)
clear entrance (63, 63)
form lake zones (63, 63, 32, 70, 70, 10, 40) #The extra numbers determine procedural behavior.
form sandpit zones (63, 63, 45, 94, 80, 5, 20)
darken grass zones (63, 63, 35, 84, 90, 5, 20)
form path zones (63, 63, 20, 40, 100, 5, 20)
form rock zones (63, 63, 205, 300, 100, 1, 10)
form tree zones (63, 63, 205, 300, 15, 5, 40)
form environment (63, 63)
find tops (63, 63)
pepper landscape (63, 63)
find shroud edge (63, 63)
generate shroud (63, 63)
hide selection npcs
wait (5)
position hero (63, 63)
save map state (mapstate:all, 8) #Important for storing information.
save in slot (9)
wait (5)
fade screen in
wait (5)
initiate limited movement
end
#Tells the script where zones are allowed. Used as a function, hence the lowercase titles. Returns true or false, depending on present conditions for that spot. Values for "blocks" will be defined in the script that uses this function.
script, zone fine here, x, y, block1=-1, block2=-1, block3=-1, block4=-1, block5=-1, begin
variable (zone)
zone := zone at spot (x, y)
if ((zone==0) || (zone==block1) || (zone==block2) || (zone==block3) || (zone==block4) || (zone==block5)) then(
exit returning (true)
)
else(
exit returning (false)
)
end
#Tells the script where zones should not be drawn.
script, no zone here, x, y, block1=-1, block2=-1, block3=-1, block4=-1, block5=-1, begin
variable (zone)
zone := zone at spot (x, y)
if ((zone==block1) || (zone==block2) || (zone==block3) || (zone==block4) || (zone==block5)) then(
exit returning (false)
)
else(
exit returning (true)
)
end
#This script defines the map edges and checks whether the current tile coordinates (in the procedure) sits inside the border area. This helps regulate tile behavior. "Parkedge" is checking for NESW edges.
script, set border, x, y, begin
if ((x<=3) || (x>=map width--4) || (y<=3) || (y>=map height--4)) then(
exit returning (false)
)
if (parkedge==1) then(
if (y>=(map height--6)) then(
exit returning (false)
)
)
if (parkedge==2) then(
if (x<=5) then(
exit returning (false)
)
)
if (parkedge==3) then(
if (y<=5) then(
exit returning (false)
)
)
if (parkedge==4) then(
if (x>=(map width--6)) then(
exit returning (false)
)
)
return (true)
end
#The dir X/dir Y functions move to the next tile.
script, dir X, dir, begin
switch (dir) do(
case (up, down) do(
return (0)
)
case (right) do(
return (1)
)
case (left) do(
return (-1)
)
)
end
script, dir Y, dir, begin
switch (dir) do(
case (left, right) do(
return (0)
)
case (down) do(
return (1)
)
case (up) do(
return (-1)
)
)
end
#This script sweeps the map with base ground tiles (occupying tiles 3-8)
#Note: "Sweeping the map" in this way is essential for procedural drawing of all areas and zones.
script, seed earth, x, y, begin
variable (f, g, r)
for (g, 0, y) do(
for (f, 0, x) do(
r := random (3, 8)
write map block (f, g, r, 0)
)
)
end
#This is an example of a function that forms zone rules for where lakes can go.
#Notice how the arguments are used in the "Generate Small Map" script for this function.
#Volume, mass, and frequency determine how zones take shape.
script, form lake zones, x, y, low volume, high volume, frequency, low mass, high mass, begin
variable (f, g, r, spread, updown x, updown y, lay, zspot, zrange1, zrange2, zrange3)
zrange1 := random (low volume, high volume)
for (g, 0, y) do(
for (f, 0, x) do(
r := random (1, frequency) #70
if (r==7) then(
if (set border (f, g)) then(
if (zrange1>>0) then(
if (zone fine here (f, g, 1)) then(
zrange1 -= 1
write zone (1, f, g)
updown x := f
updown y := g
spread := random (low mass, high mass)
for (lay, spread, 0, -1) do(
r := random (0, 3)
updown x += dir X (r)
updown y += dir Y (r)
if (set border (updown x, updown y)) then(
if (zone fine here (updown x, updown y, 1)) then(
write zone (1, updown x, updown y)
)
else(
updown x -= dir X (r)
updown y -= dir Y (r)
)
)
)
)
)
)
)
)
)
end
#I'm skipping a bunch of zone layout scripts, but figure each landscape type has its own zone rules.
#Once zones are shaped and built, then you can fill them.
#The following script is long, but here's a sample based on the zones we built for lakes.
script, form environment, x, y, begin
variable (f, g, r, spread, updown x, updown y, lay, zspot, zrange1, zrange2, zrange3, upstart x, upstart y, fullwall)
fullwall := westwall + eastwall + northwall + southwall
for (g, 0, y) do(
for (f, 0, x) do(
if ((read zone (8, f, g)==false) && (read zone (10, f, g)==false)) then(
if (read zone (1, f, g)) then(
write map block (f, g, 160, 0) #water
)
#...
)
)
#round lake edges
for (g, 0, y) do(
for (f, 0, x) do(
if (read zone (1, f, g)) then(
if ((read zone (1, f--1, g)==false) && (read zone (1, f, g--1)==false) && (read zone (1, f, g+1)==false) && (read zone (1, f+1, g))) then(
write map block (f, g, 190, 0)
write pass block (f, g, westwall + northwall + southwall)
)
#...
)
)
end
Hopefully this all makes sense. Most of what you need is to define zones and fill them. If you plan out your rules ahead of time, then scripting them in should be quick and painless. And I'm sure you can make it simpler than I had. I wrote this when I was still repeating code. If you practice "good coding habits" (don't repeat code), you can do this even quicker and easier.
Let me know if you have any questions.
[Note: This is just a sample.]
#Creates park based on number (up to 16 digits)
script, Use Number Park, begin
show text box (4)
suspend box advance
input string (1, 16, false, false, 160, 115)
resume box advance
wait for text box
if (number from string (1)==false) then(
show text box (5)
wait for text box
Use Number Park
)
else(
seedparkname := number from string (1)
seed random (number from string (1))
Choose Park Size
)
end
#Creates park based on name (but can include numbers), up to 16 characters
script, Use ASCII Park, begin
variable (f, a)
show text box (6)
suspend box advance
input string (1, 16, false, false, 160, 125)
resume box advance
wait for text box
for (f, 1, string length (1)) do(
a := a * 31 + (ascii from string (1, f))
)
seedparkname := a
seed random (a)
Choose Park Size
end
#Creates park at random
script, Use Random Park, begin
variable (r1, r2, r3, r4, r5)
r1 := random (0, 32000)
r2 := random (0, 32000)
r3 := random (0, 32000)
r4 := random (1, 32000)
r5 := (r1 + r2 + r3) * r4
seedparkname := r5
seed random (r5)
Choose Park Size
end
script, Choose Park Size, begin
load palette (0)
update palette
open menu (1)
end
#Note: Menu 1 opens a park size selection. Choosing "Small Park (63x63 tiles)" calls this script.
#Note 2: The "map" as defined in Custom is blank. The only prerequisite is to set the size, in this case 63x63 tiles. Everything else is scripted.
#Note 3: You still have to draw and assign the tileset.
script, Generate Small Park, begin
suspend player
variable (f, g, r, spread, updown x, updown y, lay, zspot, zrange1, zrange2, zrange3, upstart x, upstart y) #I think these variables were supposed to represent values I never used or chose to hard-code instead. For this script, they don't do anything.
wait (5)
fade screen out (0, 0, 0)
wait (5)
teleport to map (0)
wait (5)
seed earth (63, 63) #Notice the values equal the map size.
entrance orientation
form rock borders (63, 63)
form border fence (63, 63)
place lease agent cabin (63, 63)
clear entrance (63, 63)
form lake zones (63, 63, 32, 70, 70, 10, 40) #The extra numbers determine procedural behavior.
form sandpit zones (63, 63, 45, 94, 80, 5, 20)
darken grass zones (63, 63, 35, 84, 90, 5, 20)
form path zones (63, 63, 20, 40, 100, 5, 20)
form rock zones (63, 63, 205, 300, 100, 1, 10)
form tree zones (63, 63, 205, 300, 15, 5, 40)
form environment (63, 63)
find tops (63, 63)
pepper landscape (63, 63)
find shroud edge (63, 63)
generate shroud (63, 63)
hide selection npcs
wait (5)
position hero (63, 63)
save map state (mapstate:all, 8) #Important for storing information.
save in slot (9)
wait (5)
fade screen in
wait (5)
initiate limited movement
end
#Tells the script where zones are allowed. Used as a function, hence the lowercase titles. Returns true or false, depending on present conditions for that spot. Values for "blocks" will be defined in the script that uses this function.
script, zone fine here, x, y, block1=-1, block2=-1, block3=-1, block4=-1, block5=-1, begin
variable (zone)
zone := zone at spot (x, y)
if ((zone==0) || (zone==block1) || (zone==block2) || (zone==block3) || (zone==block4) || (zone==block5)) then(
exit returning (true)
)
else(
exit returning (false)
)
end
#Tells the script where zones should not be drawn.
script, no zone here, x, y, block1=-1, block2=-1, block3=-1, block4=-1, block5=-1, begin
variable (zone)
zone := zone at spot (x, y)
if ((zone==block1) || (zone==block2) || (zone==block3) || (zone==block4) || (zone==block5)) then(
exit returning (false)
)
else(
exit returning (true)
)
end
#This script defines the map edges and checks whether the current tile coordinates (in the procedure) sits inside the border area. This helps regulate tile behavior. "Parkedge" is checking for NESW edges.
script, set border, x, y, begin
if ((x<=3) || (x>=map width--4) || (y<=3) || (y>=map height--4)) then(
exit returning (false)
)
if (parkedge==1) then(
if (y>=(map height--6)) then(
exit returning (false)
)
)
if (parkedge==2) then(
if (x<=5) then(
exit returning (false)
)
)
if (parkedge==3) then(
if (y<=5) then(
exit returning (false)
)
)
if (parkedge==4) then(
if (x>=(map width--6)) then(
exit returning (false)
)
)
return (true)
end
#The dir X/dir Y functions move to the next tile.
script, dir X, dir, begin
switch (dir) do(
case (up, down) do(
return (0)
)
case (right) do(
return (1)
)
case (left) do(
return (-1)
)
)
end
script, dir Y, dir, begin
switch (dir) do(
case (left, right) do(
return (0)
)
case (down) do(
return (1)
)
case (up) do(
return (-1)
)
)
end
#This script sweeps the map with base ground tiles (occupying tiles 3-8)
#Note: "Sweeping the map" in this way is essential for procedural drawing of all areas and zones.
script, seed earth, x, y, begin
variable (f, g, r)
for (g, 0, y) do(
for (f, 0, x) do(
r := random (3, 8)
write map block (f, g, r, 0)
)
)
end
#This is an example of a function that forms zone rules for where lakes can go.
#Notice how the arguments are used in the "Generate Small Map" script for this function.
#Volume, mass, and frequency determine how zones take shape.
script, form lake zones, x, y, low volume, high volume, frequency, low mass, high mass, begin
variable (f, g, r, spread, updown x, updown y, lay, zspot, zrange1, zrange2, zrange3)
zrange1 := random (low volume, high volume)
for (g, 0, y) do(
for (f, 0, x) do(
r := random (1, frequency) #70
if (r==7) then(
if (set border (f, g)) then(
if (zrange1>>0) then(
if (zone fine here (f, g, 1)) then(
zrange1 -= 1
write zone (1, f, g)
updown x := f
updown y := g
spread := random (low mass, high mass)
for (lay, spread, 0, -1) do(
r := random (0, 3)
updown x += dir X (r)
updown y += dir Y (r)
if (set border (updown x, updown y)) then(
if (zone fine here (updown x, updown y, 1)) then(
write zone (1, updown x, updown y)
)
else(
updown x -= dir X (r)
updown y -= dir Y (r)
)
)
)
)
)
)
)
)
)
end
#I'm skipping a bunch of zone layout scripts, but figure each landscape type has its own zone rules.
#Once zones are shaped and built, then you can fill them.
#The following script is long, but here's a sample based on the zones we built for lakes.
script, form environment, x, y, begin
variable (f, g, r, spread, updown x, updown y, lay, zspot, zrange1, zrange2, zrange3, upstart x, upstart y, fullwall)
fullwall := westwall + eastwall + northwall + southwall
for (g, 0, y) do(
for (f, 0, x) do(
if ((read zone (8, f, g)==false) && (read zone (10, f, g)==false)) then(
if (read zone (1, f, g)) then(
write map block (f, g, 160, 0) #water
)
#...
)
)
#round lake edges
for (g, 0, y) do(
for (f, 0, x) do(
if (read zone (1, f, g)) then(
if ((read zone (1, f--1, g)==false) && (read zone (1, f, g--1)==false) && (read zone (1, f, g+1)==false) && (read zone (1, f+1, g))) then(
write map block (f, g, 190, 0)
write pass block (f, g, westwall + northwall + southwall)
)
#...
)
)
end
Hopefully this all makes sense. Most of what you need is to define zones and fill them. If you plan out your rules ahead of time, then scripting them in should be quick and painless. And I'm sure you can make it simpler than I had. I wrote this when I was still repeating code. If you practice "good coding habits" (don't repeat code), you can do this even quicker and easier.
Let me know if you have any questions.
Place Obligatory Signature Here
- Pepsi Ranger
- Liquid Metal Slime
- Posts: 1460
- Joined: Thu Nov 22, 2007 6:25 am
- Location: South Florida
Re: Semi-random maps
P.S. I think I wrote some related shortcuts in the Convenience Functions that you may find useful. Might be worth a look.
Place Obligatory Signature Here