How would you infinitely generate models on a grid like pattern as the player walks?

Hello. I’m looking for a way to clone models in a grid like pattern around the player as you walk.
I’ve tried doing so many things and the closest I can do is getting a bunch of the models to generate in a large square only once (not in relation to the player).

Thanks in advance.

5 Likes

There are a lot of different ways to do it, what does your current attempt look like? Generally, you’ll have two parts: a grid system, and a logic chain which will choose models to assign to that grid at a certain time (when a player moves)

1 Like

I don’t really have a current system, but if you’re asking about the square thing here’s the code:

for x = 1,10 do
	for z = 1,10 do
		local rc = math.random(1,9)
		local clone = workspace.Templates:FindFirstChild(rc):Clone()
		clone.Parent = workspace.World
		clone:SetPrimaryPartCFrame(CFrame.new(25*x, 0, 25*z))
	end
end

This just basically picks one of the preset squares I have set (named numbers 1 through 9) and places it at the correct x and z coordinates.
Result:
image
The thing about this is, it cannot continually generate as the player walks.

2 Likes

As long as you pick a grid size you can always round a position to the nearest grid position. Generate that position and the n number of positions around it.

How would I round a position to a grid position? How would I even define the grid positions?

When I created a grid system, I stored it as a 2D table. In order to keep store objects inside of the grid, I would round their position to the nearest grid index, then store the model (in code) at grid[x][z].

Basically what you need for the grid is this:

  1. A list of all elements in the grid (A 2d array is probably optimal for this)
  2. A way to organize the elements within that list (a coordinate system)
  3. A method of finding specific indexes of the elements list based on a given world position.

Once you have created a grid system, dynamically adding models to the grid should be relatively easy as long as you make sure it’s easy to check if a grid space has been filled, and it’s easy to insert a model to that grid space.

If you’re having trouble visualizing grids, RedBlobGames has some good articles about grids.

I think people are struggling for good ways to help because the question is so vague, particularly with respect to what the grid is for. Spawning items near a player doesn’t inherently require a grid, but it’s not clear to me if you mean random spawns (like loot), or if you’re thinking as grand as procedurally generating a whole world in chunks.

I want to procedurally generate in a chunk fashion. I want it to spawn around the player in a grid as you walk.

So, as an example of ExtremeBuilder15’s method, I would personally use code like the block at the bottom.

By the way, all functions used in this code use Vector2 for formatting positions for the sake of concept. Because of this, I provided a simple function that converts Vector3 to Vector2 near the top.

math.randomseed(tick())

local ModelGen = {
    Enabled = false,
	Grid_Size = 25,
	Seed = Vector2.new(math.random(),math.random()),
    Render_Radius = 5,
    Templates = script.Templates:GetChildren(),
    Space = {}
}
ModelGen.Time_Before_Next_Update = ModelGen.Grid_Size/16

function ModelGen:toVector2(v3)
    return Vector2.new(math.floor(v3.X + 0.5),math.floor(v3.Z + 0.5))
end

-- set and get functions for Space, since it will error if the first index is nil.
function ModelGen.Space:set(index1,index2,value)
	if self[index1] then
	    self[index1][index2] = value
	else
	    self[index1] = {}
	    self[index1][index2] = value
	end
end

function ModelGen.Space:get(index1,index2)
	if self[index1] then
	    return self[index1][index2]
	else
	    return nil
	end
end

function ModelGen:SetEnabled(bool) -- simplest way to disable ModelGen:AutoUpdate
	self.Enabled = bool
end


-- Base function for creating a model at a certain position in Space
function ModelGen:Create(AtPos)
	if not self.Space:get(AtPos.X, AtPos.Y) then
		local mid = math.clamp(math.noise(AtPos.X*math.pi + self.Seed.X,AtPos.Y*math.pi + self.Seed.Y),-0.499,0.5) + 0.5
		local rc = math.ceil(#self.Templates*mid)
		local clone = self.Templates[rc]:Clone()
		clone:SetPrimaryPartCFrame(CFrame.new(AtPos.X*self.Grid_Size,0,AtPos.Y*self.Grid_Size))
		clone.Parent = workspace
		self.Space:set(AtPos.X, AtPos.Y, clone)
    end
end

-- Base function for destroying a model at a certain position in Space
function ModelGen:Destroy(AtPos)
    local model = self.Space:get(AtPos.X, AtPos.Y)
    if model then
        model:Destroy()
        self.Space:set(AtPos.X, AtPos.Y, nil)
    end
end

function ModelGen:Update(AtPos)
	
	-- Destroy any chunks that are outside of the Render_Radius and still loaded
    for x, tab in pairs(self.Space) do
        if x ~= "set" and x ~= "get" then
			for y, value in pairs(tab) do
	            if math.abs(AtPos.X - x) > self.Render_Radius or math.abs(AtPos.Y - y) > self.Render_Radius then
	                self:Destroy(Vector2.new(x, y))
	            end
	        end
		end
    end

    -- Create any chunks that are in the Render_Radius and not loaded
    for x = -self.Render_Radius, self.Render_Radius do
        for y = -self.Render_Radius, self.Render_Radius do
           self:Create(Vector2.new(AtPos.X + x,AtPos.Y + y))
        end
    end
end

function ModelGen:AutoUpdate(rootPart) -- takes the HumanoidRootPart as its argument
    self.Enabled = true
    self:Update(self:toVector2(rootPart.Position/self.Grid_Size))
    while wait(self.Time_Before_Next_Update) and self.Enabled do
        self:Update(self:toVector2(rootPart.Position/self.Grid_Size))
    end
end

return ModelGen

This is written in a ModuleScript format for ease of use, I guess. It is best used if required from a Server Script however.

I should also note that this script saves metadata in the form of ModelGen.MetaSpace, which means that if you come back to this chunk after it has been destroyed, it will generate a new chunk that is identical to the old one, which is pretty nice in my opinion.

Edit 1: I haven’t tested this at all by the way, so it might have some errors.

Edit 2: It has now been tested and it works for me.

Edit 3: I used the suggestion from Khodin to use math.noise, no more MetaSpace, still works perfectly.

7 Likes

Adding onto this, instead of ModelGen.MetaSpace you could simply seed the RNG with the coordinates of the chunk (or use math.noise) to save memory.

3 Likes

That is perfect. It works great and I love that it generates old chunks back too.

2 Likes

You would only need to define a grid size then you can round all positions to a factor of the grid size. Example: grid size = 5 studs, 123 rounds to closest grid position at 125.

I don’t think randomseed is required anymore. I do believe that math.random was updated to use the same algorithm as the Random API. If not, you could always just use that as well.

local RNG = Random.new(tick())

local V2Val = Vector2.new(RNG:NextNumber(), RNG:NextNumber())

Just an alternate way of doing it. :tongue:

2 Likes