Crystals appearing in random spots but not in buildings - how would i do this?

So I’m creating a system where you collect crystals and then it’ll disappear for a few seconds and reappear somewhere else. Now I’m wondering, “how would I do that?”

Now I could just have it disappear then reappear in the same spot but I want it to reappear in a different spot.

BUT, how would I make it reappear in only certain spots?

My map as seen in the thumbnail has buildings and I wouldn’t want the crystals to reappear in/on the building.

So how would I do this?

Here is a better view:

1 Like

You could spawn them using math.random and having a part that covers the section of where you want them to spawn. For example,

crystal.Position = Vector3.new(math.random(part.Position.X/2,part.Position.X),part.Position.Y,math.random(part.Position.Z/2,part.Position.Z))

Note this is untested

But this could spawn it in the building MAYBE cuz Idk

You could set some pre determined points within the game and then choose one of those points at random, that’s how a lot of games do it.

You need to make the part in the section where the buildings aren’t.

1 Like

You could also use unions, not sure if meshes would work the same way.

I came up with a solution.

local RNG = Random.new()
local item = -- this is the object you want to place
local bases = {
    {Object = --[[path to base one]], Weight = --[[rarity]]};
    {Object = --[[path to base two]], Weight = --[[rarity]]};
    {Object = --[[path to base three]], Weight = --[[rarity]]};
}

local function random(choices)
	local weightSum = 0
	for i = 1, #choices do
		weightSum = weightSum + choices[i].Weight
	end

	local rand = RNG:NextInteger(0, weightSum)
	for i = 1, #choices do
		if rand <= choices[i].Weight then
			return choices[i].Object
		end
		rand = rand - choices[i].Weight
	end
end

local function setRandomPosition()
	local Base = random(bases)

	item.CFrame = CFrame.new(
		RNG:NextInteger((-Base.Size.X / 2), (Base.Size.X / 2)),
		Base.Position.Y + (Base.Size.Y / 2) + (item.Size.Y / 2),
		RNG:NextInteger((-Base.Size.Z / 2), (Base.Size.Z / 2))
	) * CFrame.Angles(0, math.rad(RNG:NextInteger(0, 360)), 0)
end

setRandomPosition()

Call the setRandomPosition() function when you want to update the position of the parts.

To avoid these Sapphires spawning in buildings, I suggest you go into Studio and make a table of possible positions that the Sapphires can spawn in. Then let the script pick any one of those positions. However, if there are any other solutions above this reply, I suggest you use those if you don’t have time for picking out positions.

I think mine is better because it’s more random and accomplishes the same task

So I tried it out and added the touching part, but I’m getting this error in the output:

attempt to call a nil value

I couldn’t find the source so I don’t know which line.
Here is my script:

local rs = game.ReplicatedStorage
local crystalFolder = rs.Crystals

local deb = false
local RNG = Random.new()
local bases = {
    {Object = workspace.Ground.Grass, Weight = 5};
    {Object = workspace.Path, Weight = 2};
}

local function random(choices)
	local weightSum = 0
	for i = 1, #choices do
		weightSum = weightSum + choices[i].Weight
	end

	local rand = RNG:NextInteger(0, weightSum)
	for i = 1, #choices do
		if rand <= choices[i].Weight then
			return choices[i].Object
		end
		rand = rand - choices[i].Weight
	end
end

local function setRandomPosition(item)
	local Base = random(bases)

	item.CFrame = CFrame.new(
		RNG:NextInteger((-Base.Size.X / 2), (Base.Size.X / 2)),
		Base.Position.Y + (Base.Size.Y / 2) + (item.Size.Y / 2),
		RNG:NextInteger((-Base.Size.Z / 2), (Base.Size.Z / 2))
	) * CFrame.Angles(0, math.rad(RNG:NextInteger(0, 360)), 0)
end

local function crystalTouched(hit)
	if not deb then
		if hit.Parent:FindFirstChild("Humanoid") then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local crystals = plr:WaitForChild('leaderstats').Crystals
			
			crystals.Value = crystals.Value + (1 * plr:WaitForChild("Gamepasses").CrystalsMultiplier.Value)
			
			deb = true
			for i, gem in pairs(hit:GetTouchingParts()) do
				if gem.Name == "Crystal" then
					gem.Parent = crystalFolder
					--wait(math.random(10,20))
					wait(1) -- for testing purposes
					gem.Parent = workspace
					setRandomPosition(gem)
					deb = false
					break
				end
			end
		end
	end
end

for i, crystal in pairs(workspace:GetChildren()) do
	if crystal.Name == "Crystal" then
		crystal.Touched:Connect(crystalTouched)
	end
end

This happens when I step on the crystal.

Have you tried debugging using print statements? If you put a couple of print statements interspersed between the lines of code you can narrow down the area where the bug is.

1 Like

It’s hard to know what’s wrong without an exact line, could you add prints or breakpoints and see which parts are working?

Edit: oops i didn’t see above reply lmao

1 Like

It stops when it gets to the for loop.

Do you know which for loop :eyes:

1 Like
for i, crystal in pairs(workspace:GetChildren()) do
	if crystal.Name == "Crystal" then
		crystal.Touched:Connect(crystalTouched)
	end
end

This means the error is in this section. By the looks of it you havent put any checks to check whether the object named Crystal is actually a BasePart. Objects such as Folders and Models don’t have a .Touched event.

Edit: Sorry, I assumed you had meant this for loop, not the other one.

This for loop, not the one @Quackers337 just said.

which part in the for loop does it get up to

2 Likes

It doesn’t get past this print statement:

for i, gem in pairs(hit:GetTouchingParts()) do
	print(gem.Name)

well actually it doesn’t print anything.

So I guess hit:GetTouchingParts() is nil

1 Like

before print(gem.Name), add print(not not hit.GetTouchingParts).
see if it prints true or false.

If it’s true, that means there’s no touching parts. If it’s false, it means the function doesn’t exist.

1 Like

Part:GetTouchingParts() does not return parts that are only touching, it only returns a list of the parts intersecting with Part.

Parts that are adjacent but not intersecting are not considered touching.

https://developer.roblox.com/en-us/api-reference/function/BasePart/GetTouchingParts

You can also just do print(#hit:GetTouchingParts()) before the for loop. If the resultant number is 0, then no parts are touching and the for loop will not run.