How To Make Parts Randomly 'Poke' Out Of Part

Heya there!

I’m trying to make SCP-009 (red ice). I’m practically done with the main stuff, but I have just one more thing left to do. I want to generate a random amount of crystals poking out of the character. I’m wondering as to how I would do this in the first place.

If I learn how to randomly generate crystals on a single part, then I can move on and do it for the entire character. By the way, this is how I would like it to look like:

Any kind of help as to how I would do this is appreciated!

1 Like

It depends on the shape of the part that the crystals will be poking out from. If they are a block shape like your image is suggesting, then you could randomly pick a face for each crystal to spawn on. The crystal has to be offset and rotated properly based on what face it is going to be on. If it is colliding with an already placed crystal, then randomize it position again. Hope this helps!

1 Like

You could save some spike models in the ReplicatedStorage and then generate a random number like 1-3 and then loop the Clone() on the part. Or just have three models saved and pick a random number like 1-3 and choose from whatever number it is.

1 Like

Yeah that’s what I’m planning to do. Problem is, I’m not sure how I would convert that into CFrame math as I’m not that good at CFrames.

To start off, we need to create a CFrame variable that is relative to the part, like so:
local spikeCFrame = CFrame.new():ToObjectSpace(part.CFrame)

After that, you would need to offset and rotate the CFrame based on what face it is. For example, if it is the bottom face, you would need to do this:
spikeCFrame = spikeCFrame * CFrame.new(Vector3.new(0, -(part.Size.Y/2)-(spike.Size.Y/2), 0)) * CFrame.Angles(math.rad(180), 0, 0)
Notice how we multiply the translation offset, not adding it? This is important to make it relative to the part. After translating, you need to rotate it by multiplying it again with another CFrame like CFrame.Angles. It is important to use math.rad() as you need to convert the degrees to radians.

Finally, just set this value to the spikes’ CFrame!
If you want more info on CFrames, you could check out their API page: CFrame

1 Like

Thank you so much! Took me some time trying to figure out the offsets of each faces.

Here’s the final code:

local SS = game:GetService("ServerStorage")

local crystal = SS.Assets.RedCrystal
local part = script.Parent

local function OrientationFromFace(face: string)
	local cframe = {
		["up"] = CFrame.new(),
		["down"] = CFrame.Angles(math.rad(180),0,0),
		["right"] = CFrame.Angles(0,0,math.rad(-90)),
		["left"] = CFrame.Angles(0,0,math.rad(90)),
		["front"] = CFrame.Angles(math.rad(-90),0,0),
		["back"] = CFrame.Angles(math.rad(90),0,0)
	}
	
	return cframe[string.lower(face)]
end

local function OffsetFromFace(part1: BasePart, part2: BasePart, face: string)
	local cframe = {
		["up"] = CFrame.new(0, (part2.Size.Y/2) + (part1.Size.Y/2), 0),
		["down"] = CFrame.new(0, -(part2.Size.Y/2) - (part1.Size.Y/2), 0),
		["right"] = CFrame.new((part2.Size.X/2) + (part1.Size.X), 0, 0),
		["left"] = CFrame.new(-(part2.Size.X/2) - (part1.Size.X), 0, 0),
		["front"] = CFrame.new(0, 0, -(part2.Size.Z/2) - (part1.Size.Z)),
		["back"] = CFrame.new(0, 0, (part2.Size.Z/2) + (part1.Size.Z))
	}

	return cframe[string.lower(face)]
end

local function RandomOffset(part: BasePart, face: string)
	local x,y,z = part.Size.X / 2, part.Size.Y / 2, part.Size.Z / 2
	local size = {
		["up"] = CFrame.new(Random.new():NextNumber(-x,x), 0, Random.new():NextNumber(-z,z)),
		["down"] = CFrame.new(Random.new():NextNumber(-x,x), 0, Random.new():NextNumber(-z,z)),
		["right"] = CFrame.new(0, Random.new():NextNumber(-y,y), Random.new():NextNumber(-z,z)),
		["left"] = CFrame.new(0, Random.new():NextNumber(-y,y), Random.new():NextNumber(-z,z)),
		["front"] = CFrame.new(Random.new():NextNumber(-x,x), Random.new():NextNumber(-y,y), 0),
		["back"] = CFrame.new(Random.new():NextNumber(-x,x), Random.new():NextNumber(-y,y), 0)
	}
	
	return size[string.lower(face)]
end

local function PlaceCrystal(face: string)
	local clone = crystal:Clone()

	local spikeCFrame = part.CFrame * OffsetFromFace(clone, part, face) * RandomOffset(part, face) * OrientationFromFace(face) 
	
	clone.CFrame = spikeCFrame
	clone.Parent = workspace
	
	task.delay(.2, function()
		clone:Destroy()
	end)
end

local selections = {"front", "back", "up", "down", "left", "right"}

while task.wait(.2) do
	for i = 1,5 do
		PlaceCrystal(selections[Random.new():NextInteger(1, #selections)])
	end
end

I just have one question, why do you need this line?

CFrame.new():ToObjectSpace(part.CFrame)

I tried it out with just

part.CFrame

and so far it works just fine? Isn’t part.CFrame already relative to itself, or am I just not getting it?

Oh yeah, you’re right. part.CFrame is already relative to itself, so you wouldn’t need the ToObjectSpace function. Sorry bout that :sweat_smile:

1 Like

Looks like I’m learning :D!

Anyways here’s basically a video showcasing what the script can do. It’s really cool, couldn’t have done it without you man.

1 Like