How to make SCP-409?

I need to make crystals appear on the polygons of the surfaces of some parts that are the limbs of the character. If you have thought about a simple calculation of the sides, then this is necessary in order to avoid empty spaces and incorrect appearances. I’ve already tried raycasts, but it didn’t work because, as I understand it, raycasts fire too late.

7 Likes

No need to do it procedurally with a calculation.

You can just treat it as an accessory and set up attachment points manually where it will look correct. Then do some math.random to make it different for each player.

1 Like

I need the crystal to be strict with the surface. I’ll show you in this picture.


Don’t pay attention to that caveman dude. Pay attention to the crystals arranged the way I want them to be. Here’s how to choose a random polygon to insert a crystal? The crystal takes into account the normal and the position of the polygon.

As for your option, I can say that it’s not bad. However, there are other parts of the body called bundles. They have a completely different shape, which is why I want to choose a random polygon and put a crystal on it.

I’m thinking of taking a grid and trying to get a random polygon with the right properties out of it. However, as far as I know, there is no such method in Roblox. Therefore, I expect other, more correct solutions.

1 Like

can you elaborate on why raycasts “fire too late”?

raycasts could definitely be the way to go because of their “normal” property (the rotation of the face they hit)

No, I already tried, I sent it. Like workspace:Raycast(Position + Vector3.new(1, 0, 0), Vector3.new(-1000, 0, 0)). It doesn’t work. I’ve tried this a few times, I’ll note: it doesn’t work as a character. It’s hard to tell, I can only say that I had the same problem with the NPC spying on the player when I used the most banal method like (Head.Position - NPC Head.Position) * 300.

unfortunately, i do not think this is possible

your best bet is the new EditableMesh API to get the triangles/vertices of a mesh, but i am unaware if it is possible to convert mesh Object Space (the position of the vertices) to World Space (the position you would need to position the crystals)

It is interesting. However, how to use it? Can you give me a code snippet?

the documentation is here.

you also need to use AssetService to convert a normal meshpart to a editable mesh.

(keyword: meshpart; you need to convert a part with a mesh instance to a meshpart by copying the texture and mesh id to it)

Clear. But this is only for MeshParts, right? And for me, a character is a character who has limbs (parts) that have Character Mesh attached to them. Therefore, how do I get all the polygons from CharacterMesh?

yes, it is only for meshparts, but you can also use MeshIds.

i was experimenting with the EditableMesh API and i think i figured out how you could add the crystals to a random vertex on any body part.

i used attachments (although thats probably not the best way, im pretty sure there are some functions like CFrame:ConvertToWorldSpace()) to find out the CFrame for where the crystals should go, and then makes the crystals point away from the body part.

i ended up with this script, that as a base line adds crystals randomly across the body.
i have also added a table that can filter out body parts if you dont want crystals on them and i have added a crystal count variable that controls how many crystals are on each body part.
i programmed it to work with R6 characters (because they use character meshes)
i hope this helps as a base for you to customize!

keep in mind that it takes around a second to generate and the script could do with some optimization.
also keep in mind that the crystals will sometimes be orientated strangely.

local AssetService = game:GetService("AssetService") --Allows us to convert MesheId's to EditableMeshes

local Character = script.Parent --Script is parented under StarterCharacterScripts in this example
local CrystalModel = game.ReplicatedStorage.Crystal --Change this to wherever your crystal is parented

local CRYSTAL_COUNT = 2 --This is the amount of crystals per Body Part
local BodyPartIgnoreList = { --You can add any BodyPart Name to ignore it
	"HumanoidRootPart",
}

local function SpawnCrystals()
	--I am presuming the CharacterMeshes are parented to the character.. If not, you can change :GetChildren() to :GetDescendants() but this could use up more memory.
	local function GetBodyPartMeshId(bodyPartName)
		for _, CharMesh in pairs(Character:GetChildren()) do
			if CharMesh:IsA("CharacterMesh") then
				if CharMesh.BodyPart.Name == bodyPartName then
					return "rbxassetid://" .. CharMesh.MeshId --We need to add rbxassetid:// at the start for it to work :/
				end
			end
		end
		--The head usually uses a seperate Mesh instance instead, so we need to do that seperately
		for _, mesh in pairs(Character.Head:GetChildren()) do
			if mesh:IsA("SpecialMesh") then
				if mesh.MeshType == Enum.MeshType.Head then
					return "rbxassetid://5591363797" --This is the asset ID of the default head since it isn't provided by default.
				end
			end
		end

		--If the script has gotten here, it means that there is no associated CharacterMesh with the body part.
		return nil
	end

	local function AddCrystal(MeshId, bodyPart) 
		local Crystal = CrystalModel:Clone()

		local EditableMesh = AssetService:CreateEditableMeshAsync(MeshId) --Creates an EditableMesh from the CharacterMesh.
		local Vertices = EditableMesh:GetVertices() --Gets a list of all of the vertices in the EditableMesh

		local RandomVertex = Vertices[math.random(1, #Vertices)] --Picks a random Vertex from the table of vertices
		local RandomVertexPosition = EditableMesh:GetPosition(RandomVertex) --Gets the LOCAL OBJECT SPACE position, which we need to convert to World Space

		local PositionAttach = Instance.new("Attachment") --This attachment will be used for finding out the position of where the crystals will go.

		PositionAttach.Parent = bodyPart --It is important that we set the parent to the body part, because the attachment position is relative to it.

		PositionAttach.Position = RandomVertexPosition	
		local CrystalCFrame = CFrame.lookAt(PositionAttach.WorldPosition, bodyPart.Position) * CFrame.Angles(math.rad(90), 0, 0) --The CFrame.Angles is the rotation offset for the CFrame. Right now, it is set to turn 90 degrees. If you are changing it, make sure to put it in math.rad()

		PositionAttach:Destroy()
		return Crystal, CrystalCFrame
	end

	for _, bodyPart in pairs(Character:GetChildren()) do --Loop through all of the Characters Body Parts.
		if bodyPart:IsA("BasePart") then --Make sure the Body Part is actually a part.
			if not table.find(BodyPartIgnoreList, bodyPart.Name) then --BodyPart is not on the Ignore List, so don't ignore it :)
				local MeshId = GetBodyPartMeshId(bodyPart.Name)

				if MeshId ~= nil then --Body Part has associated CharacterMesh
					for i = 1, CRYSTAL_COUNT, 1 do --Repeats creating crystals until crystal count is reached
						local Crystal, CrystalCFrame  = AddCrystal(MeshId, bodyPart)

						--Setup Crystal--
						Crystal.Parent = Character

						Crystal.CanCollide = false
						Crystal.Anchored = false
						Crystal.Massless = true --Makes the crystal not heavy, disabling this could make it harder for the player to walk.

						Crystal.CFrame = CrystalCFrame

						--Weld the crystal to the Body Part
						local CrystalWeld = Instance.new("WeldConstraint")

						CrystalWeld.Parent = Crystal
						CrystalWeld.Name = "Crystal Weld"

						CrystalWeld.Part0 = bodyPart
						CrystalWeld.Part1 = Crystal
					end

					print("Attached Crystals to", bodyPart.Name)
				else --Body Part DOESNT have associated CharacterMesh
					warn(bodyPart.Name, "has no associated Character Mesh :(")
				end
			end
		end
	end
end

SpawnCrystals()

1 Like

Well, what can I say? Good job! Thanks! It’s working!

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.