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.
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.
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.
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()
Well, what can I say? Good job! Thanks! It’s working!
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.