Jump to content

Procedural (Generated) Level's using prefabs


AnthonyPython
 Share

Recommended Posts

as the Titles says it all, is this now easier to do? I know Flexman has a unfinished guide in 2012. but it's still some great info, of course though, that was LE 2.x, and Leadwerks has gotten better. can anyone create such a thing, like specify, the prefabs, what height/width/length.

 

(VIA LUA)

 

and is it possible to have them, saved as a .map without having them being saved in Ricks save/load since that wouldn't be really a good thing.

 

if it is too much to do I'll understand it. but having something like this will increase my work flow of getting puzzles done quicker.

OS: Windows 10 Pro

CPU:  i3-10100 CPU @ 3.60GHz
GPU: NVIDIA 2060 Super - 8 GB

RAM: 32 GB

Link to comment
Share on other sites

Nothing stops anyone from doing this. It could get trickier to save it as an LE .map file though, Josh did release the Map class that shows the .map format, but it changes fairly regularly so you'd have to account for that. I personally would make my own map file, but then you wouldn't be able to edit it in the LE editor which I'm not sure if that's a requirement you would have. However, I wouldn't use the save/load I made since that was more specific to just game data instead of the entire level itself. I would personally use sqlite3 as the map file. It's very fast, has a lua implementation for it, and you can easily open it up with a db editor for visually see it.

Link to comment
Share on other sites

Nothing stops anyone from doing this. It could get trickier to save it as an LE .map file though, Josh did release the Map class that shows the .map format, but it changes fairly regularly so you'd have to account for that. I personally would make my own map file, but then you wouldn't be able to edit it in the LE editor which I'm not sure if that's a requirement you would have. However, I wouldn't use the save/load I made since that was more specific to just game data instead of the entire level itself. I would personally use sqlite3 as the map file. It's very fast, has a lua implementation for it, and you can easily open it up with a db editor for visually see it.

 

Thanks for the advise rick. I'll definitely look into sqlite3.

OS: Windows 10 Pro

CPU:  i3-10100 CPU @ 3.60GHz
GPU: NVIDIA 2060 Super - 8 GB

RAM: 32 GB

Link to comment
Share on other sites

Not sure if this can help you, but I have made basic dungeon generation shown on this video:

I have five models (painted in different colors in video) and specified each enter and exit spot with pivot point.

Here is code:

 

 


Script.startPivot="" --entity

Script.dungeon={}

Script.names={"d1", "d2l", "d2r", "d3l", "d3r"} --Names of all possible dungeon segments to check AABB intersection

Script.d1="Prefabs/Dungeon/d1.pfb"

Script.d2l="Prefabs/Dungeon/d2l.pfb"

Script.d2r="Prefabs/Dungeon/d2r.pfb"

Script.d3l="Prefabs/Dungeon/d3l.pfb"

Script.d3r="Prefabs/Dungeon/d3r.pfb"

Script.currentR=0

Script.nextR=0

Script.j=0

Script.i=0

Script.s=0

Script.allDungeonSegments={}

 

--Debug

function Script:Start()

--math.randomseed(10)

end

 

function Script:UpdateWorld()

if App.window:KeyHit(Key.G) then self:Use() end

end

 

function Script:Use()

self:GenerateDungeon()

end

 

 

 

function Script:GenerateDungeon() --main generation function

self.currentR=0

self.nextR=0

self.i=0

self.j=0

player:ConsoleLog("Generation started. Countentities = "..App.world:CountEntities())

self:DeleteAllDungeonSegments()

player:ConsoleLog("All segments deleted. Countentities = "..App.world:CountEntities())

self.dungeon={}

 

self.dungeon[0]=self:CreateSegment(self:GetRandomPart()) --Create first dungeon element

self.dungeon[0]:SetRotation(0,self.currentR,0,true)

local pos = self.dungeon[0]:FindChild("Enter"):GetPosition(true)

local previousPos = self.startPivot:GetPosition(true)

local dPos = pos-previousPos

self.dungeon[0]:SetPosition(pos-dPos, true)

 

local result = true --Create main path

self.dungeon, result=self:GeneratePath(100, self.dungeon) --If this function failed to generate dungeon we still need to save it, but we sholdn't proceed further

if not result then

self:GenerateDungeon() --Failed to generate dungeon, starting again

return

end

 

--Generate forks

self:GenerateForks()

end

 

function Script:GeneratePath(amount, startingTable, emergencyLimit)

if not emergencyLimit then emergencyLimit=200 end

local dungeon={}

dungeon=startingTable

local emergencyCounter=0

local i=#dungeon+1

self.intersectionFound=false

while i<=amount do

emergencyCounter=emergencyCounter+1

--System:Print(emergencyCounter.." i="..i)

if emergencyCounter>emergencyLimit then --reached limit of retries, generate again

return dungeon, false --still need to return current dungeon to be able to clear it

end

dungeon=self:CreateSegment(self:GetRandomPart())

local yRotation=dungeon[i-1]:GetRotation(true).y+self:GetExtraRotation(dungeon[i-1])

dungeon:SetRotation(0,yRotation,0,true)

pos = dungeon:FindChild("Enter"):GetPosition(true)

previousPos = dungeon[i-1]:FindChild("Exit"):GetPosition(true)

dPos = pos-previousPos

dungeon:SetPosition(dungeon:GetPosition(true)-dPos, true)

 

--Check AABB

local aabb=dungeon:GetAABB(Entity.GlobalAABB)

local j=1

local maximum=#self.allDungeonSegments

while j<=maximum do

--System:Print(j)

if self.allDungeonSegments[j]~=dungeon and self.allDungeonSegments[j]~=dungeon[i-1] and self.allDungeonSegments[j]~=dungeon[i-2] then

if aabb:IntersectsAABB(self.allDungeonSegments[j]:GetAABB(Entity.GlobalAABB)) then

System:Print(dungeon:GetKeyValue("name").." intersects with "..self.allDungeonSegments[j]:GetKeyValue("name"))

if i<4 then

return dungeon, false

else

System:Print("Three steps back")

self:DeleteSegment(dungeon)

System:Print("Deleted last segment")

dungeon=nil

self:DeleteSegment(dungeon[i-1])

System:Print("Deleted i-1")

dungeon=nil

self:DeleteSegment(dungeon[i-2])

System:Print("Deleted i-2")

dungeon=nil

i=i-3

j=1000--maximum+1

end

end

end

j=j+1

end

i=i+1

--System:Print("End of I loop. i="..i)

end

return dungeon, true --returning true if managed to generate dungeon properly

end

 

function Script:GenerateForks()

local i=1

while i <= #self.dungeon do

if self.dungeon:GetKeyValue("name")=="d1" and math.random(0, 20)==0 then

local matrix=self.dungeon:GetMatrix(true)

self:DeleteSegment(self.dungeon)

self.dungeon={}

self.dungeon[0]=self:CreateSegment(self:GetRandomPart(4,5))

self.dungeon[0]:SetMatrix(matrix, true)

self.dungeon[1]=self:CreateSegment(self:GetRandomPart())

 

local yRotation=self.dungeon[0]:GetRotation(true).y+self:GetExtraRotation(self.dungeon[0]) --second element

self.dungeon[1]:SetRotation(0,yRotation,0,true)

pos = self.dungeon[1]:FindChild("Enter"):GetPosition(true)

previousPos = self.dungeon[0]:FindChild("Exit2"):GetPosition(true)

dPos = pos-previousPos

self.dungeon[1]:SetPosition(self.dungeon[1]:GetPosition(true)-dPos, true)

 

--local dungeon

--local result

local dungeon, result = self:GeneratePath(15, self.dungeon, 30)

if not result then

self:DeleteSegment(dungeon[#dungeon])

self.dungeon=dungeon

else

self.dungeon=dungeon

end

end

i=i+1

end

end

 

--other function

function Script:GetRandomPart(minimum, maximum) --Choose random element

if not minimum then minimum=0; maximum=3 end

local r=math.random(minimum, maximum)

if r==1 or r==0 then

return self.d1

elseif r==2 then

return self.d2r

elseif r==3 then

return self.d2l

elseif r==4 then

return self.d3r

elseif r==5 then

return self.d3l

end

end

 

function Script:GetExtraRotation(name)

name=name:GetKeyValue("name")

if name=="d2r" or name=="d3r" then

return (90)

elseif name=="d2l" or name=="d3l" then

return (-90)

end

return 0 --all other segments return 0

end

 

function Script:CreateSegment(name)

local entity=Prefab:Load(name)

table.insert (self.allDungeonSegments, entity)

return entity

end

 

function Script:DeleteSegment(entity)

local i=1

local maximum=#self.allDungeonSegments

while i<=maximum do

if self.allDungeonSegments==entity then

local temp = self.allDungeonSegments

table.remove (self.allDungeonSegments, i)

--self.allDungeonSegments=0

temp:Release()

i=maximum

end

i=i+1

end

end

 

function Script:DeleteAllDungeonSegments()

for i=1, #self.allDungeonSegments do

self.allDungeonSegments:Release()

end

self.allDungeonSegments={}

end

 

function Script:IsDungeonSegment(name) --not needed

name=name:GetKeyValue("name")

for i=1, #self.names do

if self.names==name then return true end

end

return false

end

 

 

  • Upvote 6
Link to comment
Share on other sites

Not sure if this can help you, but I have made basic dungeon generation shown on this video:

I have five models (painted in different colors in video) and specified each enter and exit spot with pivot point.

Here is code:

 

 


Script.startPivot="" --entity

Script.dungeon={}

Script.names={"d1", "d2l", "d2r", "d3l", "d3r"} --Names of all possible dungeon segments to check AABB intersection

Script.d1="Prefabs/Dungeon/d1.pfb"

Script.d2l="Prefabs/Dungeon/d2l.pfb"

Script.d2r="Prefabs/Dungeon/d2r.pfb"

Script.d3l="Prefabs/Dungeon/d3l.pfb"

Script.d3r="Prefabs/Dungeon/d3r.pfb"

Script.currentR=0

Script.nextR=0

Script.j=0

Script.i=0

Script.s=0

Script.allDungeonSegments={}

 

--Debug

function Script:Start()

--math.randomseed(10)

end

 

function Script:UpdateWorld()

if App.window:KeyHit(Key.G) then self:Use() end

end

 

function Script:Use()

self:GenerateDungeon()

end

 

 

 

function Script:GenerateDungeon() --main generation function

self.currentR=0

self.nextR=0

self.i=0

self.j=0

player:ConsoleLog("Generation started. Countentities = "..App.world:CountEntities())

self:DeleteAllDungeonSegments()

player:ConsoleLog("All segments deleted. Countentities = "..App.world:CountEntities())

self.dungeon={}

 

self.dungeon[0]=self:CreateSegment(self:GetRandomPart()) --Create first dungeon element

self.dungeon[0]:SetRotation(0,self.currentR,0,true)

local pos = self.dungeon[0]:FindChild("Enter"):GetPosition(true)

local previousPos = self.startPivot:GetPosition(true)

local dPos = pos-previousPos

self.dungeon[0]:SetPosition(pos-dPos, true)

 

local result = true --Create main path

self.dungeon, result=self:GeneratePath(100, self.dungeon) --If this function failed to generate dungeon we still need to save it, but we sholdn't proceed further

if not result then

self:GenerateDungeon() --Failed to generate dungeon, starting again

return

end

 

--Generate forks

self:GenerateForks()

end

 

function Script:GeneratePath(amount, startingTable, emergencyLimit)

if not emergencyLimit then emergencyLimit=200 end

local dungeon={}

dungeon=startingTable

local emergencyCounter=0

local i=#dungeon+1

self.intersectionFound=false

while i<=amount do

emergencyCounter=emergencyCounter+1

--System:Print(emergencyCounter.." i="..i)

if emergencyCounter>emergencyLimit then --reached limit of retries, generate again

return dungeon, false --still need to return current dungeon to be able to clear it

end

dungeon=self:CreateSegment(self:GetRandomPart())

local yRotation=dungeon[i-1]:GetRotation(true).y+self:GetExtraRotation(dungeon[i-1])

dungeon:SetRotation(0,yRotation,0,true)

pos = dungeon:FindChild("Enter"):GetPosition(true)

previousPos = dungeon[i-1]:FindChild("Exit"):GetPosition(true)

dPos = pos-previousPos

dungeon:SetPosition(dungeon:GetPosition(true)-dPos, true)

 

--Check AABB

local aabb=dungeon:GetAABB(Entity.GlobalAABB)

local j=1

local maximum=#self.allDungeonSegments

while j<=maximum do

--System:Print(j)

if self.allDungeonSegments[j]~=dungeon and self.allDungeonSegments[j]~=dungeon[i-1] and self.allDungeonSegments[j]~=dungeon[i-2] then

if aabb:IntersectsAABB(self.allDungeonSegments[j]:GetAABB(Entity.GlobalAABB)) then

System:Print(dungeon:GetKeyValue("name").." intersects with "..self.allDungeonSegments[j]:GetKeyValue("name"))

if i<4 then

return dungeon, false

else

System:Print("Three steps back")

self:DeleteSegment(dungeon)

System:Print("Deleted last segment")

dungeon=nil

self:DeleteSegment(dungeon[i-1])

System:Print("Deleted i-1")

dungeon=nil

self:DeleteSegment(dungeon[i-2])

System:Print("Deleted i-2")

dungeon=nil

i=i-3

j=1000--maximum+1

end

end

end

j=j+1

end

i=i+1

--System:Print("End of I loop. i="..i)

end

return dungeon, true --returning true if managed to generate dungeon properly

end

 

function Script:GenerateForks()

local i=1

while i <= #self.dungeon do

if self.dungeon:GetKeyValue("name")=="d1" and math.random(0, 20)==0 then

local matrix=self.dungeon:GetMatrix(true)

self:DeleteSegment(self.dungeon)

self.dungeon={}

self.dungeon[0]=self:CreateSegment(self:GetRandomPart(4,5))

self.dungeon[0]:SetMatrix(matrix, true)

self.dungeon[1]=self:CreateSegment(self:GetRandomPart())

 

local yRotation=self.dungeon[0]:GetRotation(true).y+self:GetExtraRotation(self.dungeon[0]) --second element

self.dungeon[1]:SetRotation(0,yRotation,0,true)

pos = self.dungeon[1]:FindChild("Enter"):GetPosition(true)

previousPos = self.dungeon[0]:FindChild("Exit2"):GetPosition(true)

dPos = pos-previousPos

self.dungeon[1]:SetPosition(self.dungeon[1]:GetPosition(true)-dPos, true)

 

--local dungeon

--local result

local dungeon, result = self:GeneratePath(15, self.dungeon, 30)

if not result then

self:DeleteSegment(dungeon[#dungeon])

self.dungeon=dungeon

else

self.dungeon=dungeon

end

end

i=i+1

end

end

 

--other function

function Script:GetRandomPart(minimum, maximum) --Choose random element

if not minimum then minimum=0; maximum=3 end

local r=math.random(minimum, maximum)

if r==1 or r==0 then

return self.d1

elseif r==2 then

return self.d2r

elseif r==3 then

return self.d2l

elseif r==4 then

return self.d3r

elseif r==5 then

return self.d3l

end

end

 

function Script:GetExtraRotation(name)

name=name:GetKeyValue("name")

if name=="d2r" or name=="d3r" then

return (90)

elseif name=="d2l" or name=="d3l" then

return (-90)

end

return 0 --all other segments return 0

end

 

function Script:CreateSegment(name)

local entity=Prefab:Load(name)

table.insert (self.allDungeonSegments, entity)

return entity

end

 

function Script:DeleteSegment(entity)

local i=1

local maximum=#self.allDungeonSegments

while i<=maximum do

if self.allDungeonSegments==entity then

local temp = self.allDungeonSegments

table.remove (self.allDungeonSegments, i)

--self.allDungeonSegments=0

temp:Release()

i=maximum

end

i=i+1

end

end

 

function Script:DeleteAllDungeonSegments()

for i=1, #self.allDungeonSegments do

self.allDungeonSegments:Release()

end

self.allDungeonSegments={}

end

 

function Script:IsDungeonSegment(name) --not needed

name=name:GetKeyValue("name")

for i=1, #self.names do

if self.names==name then return true end

end

return false

end

 

 

 

this will come in handy

OS: Windows 10 Pro

CPU:  i3-10100 CPU @ 3.60GHz
GPU: NVIDIA 2060 Super - 8 GB

RAM: 32 GB

Link to comment
Share on other sites

Nice! Thanks !

I'm trying to make one of these 3D dungeon generators as well.

I have one in C# but idunno how to convert it to lua. Any ideas on how to convert it ?

Maybe someone can convert it and post it in this thread so that everyone can use it ? Here it is:

using System;
using System.Collections;
using System.Collections.Generic;
public class MazeGenerator : MonoBehaviour {
public int width, height;
public Material brick;
private int[,] Maze;
private List<Vector3> pathMazes = new List<Vector3>();
private Stack<Vector2> _tiletoTry = new Stack<Vector2>();
private List<Vector2> offsets = new List<Vector2> { new Vector2(0, 1), new Vector2(0, -1), new Vector2(1, 0), new Vector2(-1, 0) };
private System.Random rnd = new System.Random();
private int _width, _height;
private Vector2 _currentTile;
public String MazeString;
public Vector2 CurrentTile {
get { return _currentTile; }
private set {
if (value.x < 1 || value.x >= this.width - 1 || value.y < 1 || value.y >= this.height - 1){
throw new ArgumentException("Width and Height must be greater than 2 to make a maze");
}
_currentTile = value;
}
}
private static MazeGenerator instance;
public static MazeGenerator Instance {
get {return instance;}
}
void Awake() { instance = this;}
void Start() { MakeBlocks(); }
// end of main program
// ============= subroutines ============
void MakeBlocks() {

Maze = new int[width, height];
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
Maze[x, y] = 1;
}
}
CurrentTile = Vector2.one;
_tiletoTry.Push(CurrentTile);
Maze = CreateMaze(); // generate the maze in Maze Array.
GameObject ptype = null;
for (int i = 0; i <= Maze.GetUpperBound(0); i++) {
for (int j = 0; j <= Maze.GetUpperBound(1); j++) {
if (Maze[i, j] == 1) {
MazeString=MazeString+"X"; // added to create String
ptype = GameObject.CreatePrimitive(PrimitiveType.Cube);
ptype.transform.position = new Vector3(i * ptype.transform.localScale.x, 0, j * ptype.transform.localScale.z);

if (brick != null) { ptype.renderer.material = brick; }
ptype.transform.parent = transform;
}
else if (Maze[i, j] == 0) {
MazeString=MazeString+"0"; // added to create String
pathMazes.Add(new Vector3(i, 0, j));
}
}
MazeString=MazeString+"\n"; // added to create String
}
print (MazeString); // added to create String
}
// =======================================
public int[,] CreateMaze() {

//local variable to store neighbors to the current square as we work our way through the maze
List<Vector2> neighbors;
//as long as there are still tiles to try
while (_tiletoTry.Count > 0)
{
//excavate the square we are on
Maze[(int)CurrentTile.x, (int)CurrentTile.y] = 0;
//get all valid neighbors for the new tile
neighbors = GetValidNeighbors(CurrentTile);
//if there are any interesting looking neighbors
if (neighbors.Count > 0)
{
//remember this tile, by putting it on the stack
_tiletoTry.Push(CurrentTile);
//move on to a random of the neighboring tiles
CurrentTile = neighbors[rnd.Next(neighbors.Count)];
}
else
{
//if there were no neighbors to try, we are at a dead-end toss this tile out
//(thereby returning to a previous tile in the list to check).
CurrentTile = _tiletoTry.Pop();
}
}
print("Maze Generated ...");
return Maze;
}

// ================================================
// Get all the prospective neighboring tiles "centerTile" The tile to test
// All and any valid neighbors</returns>
private List<Vector2> GetValidNeighbors(Vector2 centerTile) {
List<Vector2> validNeighbors = new List<Vector2>();
//Check all four directions around the tile
foreach (var offset in offsets) {
//find the neighbor's position
Vector2 toCheck = new Vector2(centerTile.x + offset.x, centerTile.y + offset.y);
//make sure the tile is not on both an even X-axis and an even Y-axis
//to ensure we can get walls around all tunnels
if (toCheck.x % 2 == 1 || toCheck.y % 2 == 1) {

//if the potential neighbor is unexcavated (==1)
//and still has three walls intact (new territory)
if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 && HasThreeWallsIntact(toCheck)) {

//add the neighbor
validNeighbors.Add(toCheck);
}
}
}
return validNeighbors;
}
// ================================================
// Counts the number of intact walls around a tile
//"Vector2ToCheck">The coordinates of the tile to check
//Whether there are three intact walls (the tile has not been dug into earlier.
private bool HasThreeWallsIntact(Vector2 Vector2ToCheck) {

int intactWallCounter = 0;
//Check all four directions around the tile
foreach (var offset in offsets) {

//find the neighbor's position
Vector2 neighborToCheck = new Vector2(Vector2ToCheck.x + offset.x, Vector2ToCheck.y + offset.y);
//make sure it is inside the maze, and it hasn't been dug out yet
if (IsInside(neighborToCheck) && Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1) {
intactWallCounter++;
}
}
//tell whether three walls are intact
return intactWallCounter == 3;
}

// ================================================
private bool IsInside(Vector2 p) {
//return p.x >= 0 p.y >= 0 p.x < width p.y < height;
return p.x >= 0 && p.y >= 0 && p.x < width && p.y < height;
}
}

Link to comment
Share on other sites

ok, anyways I found this open source Unity C# Procedural generation both for dungeon if you want that or single rooms for like i need.

 

I used a C# to C++ convert and it worked very well, of course though since it was unity it has unity specific coding in it

 

I cleaned some of it up, and now is to actually complete it, any help to get this to work with prefabs would be great, looking through the leadwerks command references right now trying to fix those error's. and adapt, any help would be great wink.png

Generator.zip

  • Upvote 3

OS: Windows 10 Pro

CPU:  i3-10100 CPU @ 3.60GHz
GPU: NVIDIA 2060 Super - 8 GB

RAM: 32 GB

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   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.

 Share

×
×
  • Create New...