Hello roblox developers !!!
I have seen that a lot of people have some problems trying to understand this system and how to make use of it, so I’m going to try to explain it as simplest as possible.
In Roblox, you can’t break a block, but what you can do is give the player the impression that it was really broken, but… how? The truth is simpler than many believe, you just have to make triangles from three strategic points.
As you can imagine, first we need a formula that generates a triangle from three points, for this, we can take advantage of the tutorial made by EgoMoose as it is the best way to do this and the only one I know of. But I am going to go explaining what the used things do that may not all understand:
Drawing the triangle
local wedge = Instance.new("WedgePart")
wedge.Anchored = true
wedge.TopSurface = Enum.SurfaceType.Smooth
wedge.BottomSurface = Enum.SurfaceType.Smooth
The first thing we find is these lines, which generate a “WedgePart”, which is a three-dimensional right triangle:
, which will be used to generate the triangle, since in Roblox, the only way you can generate a triangle that is not a right triangle is by putting two wedges togetherlocal function draw3dTriangle(a, b, c, parent, w1, w2)
local ab, ac, bc = b - a, c - a, c - b;
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc);
end
Already inside the function we find this, what it does is simply subtract the points (which have to be Vector3) and do the product Dot between themselves, this returns a single number, which is equivalent to: X0*X1 + Y0*Y1 + Z0*Z1
.
if (abd > acd and abd > bcd) then
c, a = a, c;
elseif (acd > bcd and acd > abd) then
a, b = b, a;
end
ab, ac, bc = b - a, c - a, c - b;
What this does is recalculate the subtraction just in case the order is wrong so we don’t have to worry about it
local right = ac:Cross(ab).Unit;
local up = bc:Cross(right).Unit;
local back = bc.Unit;
local height = math.abs(ab:Dot(up));
Here we already encounter the Cross product, which returns a vector perpendicular to the other two, for example:
.
We also find the .Unit, which returns the normalized vector, which means that it becomes a vector that goes in all its axes between -1 and 1. And finally the math.abs(), which returns any positive number, no matter if it is already positive or was negative (absolute value).
w1 = w1 or wedge:Clone();
w1.Size = Vector3.new(0, height, math.abs(ab:Dot(back)));
w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back);
w1.Parent = parent;
w2 = w2 or wedge:Clone();
w2.Size = Vector3.new(0, height, math.abs(ac:Dot(back)));
w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back);
w2.Parent = parent;
return w1, w2;
Here we are done, here, I already explained everything except CFrame.fromMatrix, which has four parameters: CFrame.fromMatrix(Vector3 pos , Vector3 vX , Vector3 vY , Vector3 vZ)
:
Vector3 pos = The position
Vector3 vX = The direction of the right vector of a CFrame.
Vector3 vY = The direction of the up vector.
Vector3 vZ = The direction of the forward / view vector, (this is optional).
This returns a result similar to CFrame.new(Position, lookAt)
.
If you want to know more about CFrame.fromMatrix, you can visit this link.
And we would already have the function, but we can remove some things, like the ;
or the parameters w1 and w2, and it would be like this:
local wedge = Instance.new("WedgePart")
wedge.Anchored = true
wedge.TopSurface = Enum.SurfaceType.Smooth
wedge.BottomSurface = Enum.SurfaceType.Smooth
local function draw3dTriangle(a, b, c, parent)
local ab, ac, bc = b - a, c - a, c - b
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
if (abd > acd and abd > bcd) then
c, a = a, c
elseif (acd > bcd and acd > abd) then
a, b = b, a
end
ab, ac, bc = b - a, c - a, c - b
local right = ac:Cross(ab).Unit
local up = bc:Cross(right).Unit
local back = bc.Unit
local height = math.abs(ab:Dot(up))
local w1 = wedge:Clone()
w1.Size = Vector3.new(0, height, math.abs(ab:Dot(back)))
w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back)
w1.Parent = parent
local w2 = wedge:Clone()
w2.Size = Vector3.new(0, height, math.abs(ac:Dot(back)))
w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
w2.Parent = parent
return w1, w2
end
Perfect! Now that we have a function to generate the triangles, let’s create a function with two parameters, the first one will be the Instance (the part of the crystal we want to break) and a Vector3, which will tell us at what point it broke:
local function Break(part, center)
end
Now, inside the function we will generate a table, which will contain all the points that we want to take into account when breaking the crystal, but we must first take one thing into account, the rotation, this can be taken into account by multiplying the CFrame of the part with a new CFrame in this way:
As you can see in the image, to go to the corners you need to join the X-axis and Y-axis calculations, and as you can see, the formula in all of them is basically the same, just changing the subtraction by the addition depending on where we want to go, it would look like this:
local cs, cf = part.Size, part.CFrame
local Points = {
cf * CFrame.new(cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, 0, 0);
cf * CFrame.new(-cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, 0, 0);
}
We’re almost done! The only thing left to do is to generate the triangles from the points we already calculated, so let’s get all the points in order, as shown in the image:
For this, we are going to use in pairs of the table in question and generate the triangle between the point where we are and the next one, but… How do we get the next one? It’s really simple! The in pairs loops return two values, the selected object and its index, if we take into account that you can get a value of a table from its order number, we would already have it!
part:Destroy()
for i, v in pairs(Points) do
local nxt = Points[i + 1]
local w0, w1 = draw3dTriangle(v.p, nxt.p, center, workspace)
end
As you can see, when executing the function that generates the triangles, in the points I put at the end a “p”, this is because the value is a CFrame and the function needs a Vector, this “p” returns the position of the CFrame, (it’s like CFrame.Position):
But… Oh no! It gives an error and also the triangles are not completed, a part is missing! This is because we are looking for a value that does not exist when we get to the last member of the table, which should be connected to the first as seen in the image above, but… how is that done? Just detect if the variable “nxt” is nil, if it is not, nothing happens, follow the code, otherwise, the variable becomes the first member of the table:
for i, v in pairs(Points) do
local nxt = Points[i + 1]
if nxt == nil then
nxt = Points[1]
end
local w0, w1 = draw3dTriangle(v.p, nxt.p, center, workspace)
end
Good! The error is gone and all the pieces are generated, but it does not look like it broke, it does not fall, it is not transparent, it does not have the same color, to solve this, the properties of the Transparency and Color parts are applied to the two wedges and we remove the anchored:
for i, v in pairs(Points) do
local nxt = Points[i + 1]
if nxt == nil then
nxt = Points[1]
end
local w0, w1 = draw3dTriangle(v.p, nxt.p, center, workspace)
w0.Anchored = false
w1.Anchored = false
w0.Transparency = part.Transparency
w1.Transparency = part.Transparency
w0.Color = part.Color
w1.Color = part.Color
end
Now we run into a problem, if the Z-axis is larger than the X-axis, this happens:
To solve this, we detect if Z is greater than X and change the table to the same one, only with the X-axis calculations changed to the Z-axis:
local Points = {}
if cs.Z > cs.X then
Points = {
cf * CFrame.new(0, cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, 0, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, 0, cs.Z * .5);
}
else
Points = {
cf * CFrame.new(cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, 0, 0);
cf * CFrame.new(-cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, 0, 0);
}
end
Good! It’s already working! Now let’s see what happens when you change the rotation of the crystal, will it still work?
Great! Looks pretty good! But. How do we get it to regenerate? That’s the easiest part of the code! In the line where we destroy the “crystal”, what we will do is to remove the collisions and make it invisible and the crystals put it inside a folder that will be generated inside another to distinguish the triangles that are of a crystal or another, and finally, wait a while and destroy the folder and put the transparency and collisions back to the crystal:
local function Break(part, center)
local cs, cf = part.Size, part.CFrame
local triangles = workspace:WaitForChild("Triangles")
local transparency = part.Transparency
local Folder = Instance.new("Folder")
Folder.Name = tostring(#triangles:GetChildren())
Folder.Parent = triangles
local Points = {}
if cs.Z > cs.X then
Points = {
cf * CFrame.new(0, cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, 0, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, 0, cs.Z * .5);
}
else
Points = {
cf * CFrame.new(cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, 0, 0);
cf * CFrame.new(-cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, 0, 0);
}
end
part.Transparency = 1
part.CanCollide = false
for i, v in pairs(Points) do
local nxt = Points[i + 1]
if nxt == nil then
nxt = Points[1]
end
local w0, w1 = draw3dTriangle(v.p, nxt.p, center, Folder)
w0.Anchored = false
w1.Anchored = false
w0.Transparency = transparency
w1.Transparency = transparency
w0.Color = part.Color
w1.Color = part.Color
end
task.wait(5)
Folder:Destroy()
part.CanCollide = true
part.Transparency = transparency
end
Good! It’s working perfectly! Here are both complete codes so that you can see it better, and not in pieces:
Without regeneration
local wedge = Instance.new("WedgePart")
wedge.Anchored = true
wedge.TopSurface = Enum.SurfaceType.Smooth
wedge.BottomSurface = Enum.SurfaceType.Smooth
local function draw3dTriangle(a, b, c, parent)
local ab, ac, bc = b - a, c - a, c - b
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
if (abd > acd and abd > bcd) then
c, a = a, c
elseif (acd > bcd and acd > abd) then
a, b = b, a
end
ab, ac, bc = b - a, c - a, c - b
local right = ac:Cross(ab).Unit
local up = bc:Cross(right).Unit
local back = bc.Unit
local height = math.abs(ab:Dot(up))
local w1 = wedge:Clone()
w1.Size = Vector3.new(0, height, math.abs(ab:Dot(back)))
w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back)
w1.Parent = parent
local w2 = wedge:Clone()
w2.Size = Vector3.new(0, height, math.abs(ac:Dot(back)))
w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
w2.Parent = parent
return w1, w2
end
local function Break(part, center)
local cs, cf = part.Size, part.CFrame
local transparency = part.Transparency
local Points = {}
if cs.Z > cs.X then
Points = {
cf * CFrame.new(0, cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, 0, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, 0, cs.Z * .5);
}
else
Points = {
cf * CFrame.new(cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, 0, 0);
cf * CFrame.new(-cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, 0, 0);
}
end
part:Destroy()
for i, v in pairs(Points) do
local nxt = Points[i + 1]
if nxt == nil then
nxt = Points[1]
end
local w0, w1 = draw3dTriangle(v.p, nxt.p, center, workspace)
w0.Anchored = false
w1.Anchored = false
w0.Transparency = transparency
w1.Transparency = transparency
w0.Color = part.Color
w1.Color = part.Color
end
end
With regeneration
local wedge = Instance.new("WedgePart")
wedge.Anchored = true
wedge.TopSurface = Enum.SurfaceType.Smooth
wedge.BottomSurface = Enum.SurfaceType.Smooth
local function draw3dTriangle(a, b, c, parent)
local ab, ac, bc = b - a, c - a, c - b
local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
if (abd > acd and abd > bcd) then
c, a = a, c
elseif (acd > bcd and acd > abd) then
a, b = b, a
end
ab, ac, bc = b - a, c - a, c - b
local right = ac:Cross(ab).Unit
local up = bc:Cross(right).Unit
local back = bc.Unit
local height = math.abs(ab:Dot(up))
local w1 = wedge:Clone()
w1.Size = Vector3.new(0, height, math.abs(ab:Dot(back)))
w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back)
w1.Parent = parent
local w2 = wedge:Clone()
w2.Size = Vector3.new(0, height, math.abs(ac:Dot(back)))
w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
w2.Parent = parent
return w1, w2
end
local function Break(part, center)
local cs, cf = part.Size, part.CFrame
local triangles = workspace:WaitForChild("Triangles")
local transparency = part.Transparency
local Folder = Instance.new("Folder")
Folder.Name = tostring(#triangles:GetChildren())
Folder.Parent = triangles
local Points = {}
if cs.Z > cs.X then
Points = {
cf * CFrame.new(0, cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, 0, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, -cs.Z * .5);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, cs.Z * .5);
cf * CFrame.new(0, 0, cs.Z * .5);
}
else
Points = {
cf * CFrame.new(cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(0, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, cs.Y * .5, 0);
cf * CFrame.new(-cs.X * .5, 0, 0);
cf * CFrame.new(-cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(0, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, -cs.Y * .5, 0);
cf * CFrame.new(cs.X * .5, 0, 0);
}
end
part.Transparency = 1
part.CanCollide = false
for i, v in pairs(Points) do
local nxt = Points[i + 1]
if nxt == nil then
nxt = Points[1]
end
local w0, w1 = draw3dTriangle(v.p, nxt.p, center, Folder)
w0.Anchored = false
w1.Anchored = false
w0.Transparency = transparency
w1.Transparency = transparency
w0.Color = part.Color
w1.Color = part.Color
end
task.wait(5)
Folder:Destroy()
part.CanCollide = true
part.Transparency = transparency
end
I hope you found it useful! I’ll say goodbye, I hope your projects go well, and I’ll see you in other posts, bye!