Simple break glass system

Hello roblox developers !!! :wave: :wave:

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 together

local 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: Captura de pantalla 2021-08-14 095223
Captura de pantalla 2021-08-14 095303 .
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

School of a killer - Roblox Studio 2021-08-14 10-18-43 (1)

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):

General test - Roblox Studio 2021-08-14 20-57-11

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

General test - Roblox Studio 2021-08-14 21-08-36 (1)

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

General test - Roblox Studio 2021-08-14 21-19-58

Now we run into a problem, if the Z-axis is larger than the X-axis, this happens:

General test - Roblox Studio 2021-08-14 21-26-58

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

General test - Roblox Studio 2021-08-15 08-49-34 (1)

Good! It’s already working! Now let’s see what happens when you change the rotation of the crystal, will it still work? :thinking:

General test - Roblox Studio 2021-08-15 08-55-43

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

General test - Roblox Studio 2021-08-15 09-10-12 (1)

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!

77 Likes

Really good tutorial! I definitely see myself using this break glass system in the future not only for glass but also other things like walls.

5 Likes

That is a good tutorial! Also, how can I change it so that it breaks into more pieces? :slightly_smiling_face:

1 Like

Just a very minor gripe with this code, since the draw3dTriangle function sets the parent first, this code is slower. You could just not parent the triangles in the draw3dTriangle function, and instead parent it manually after all the properties are set.

3 Likes

In the table where you specify the corners. you add more points.

It doesnt seem to spawn in any triangle. Should I put it in any server storage or something?

It shouldn’t, could you send if you get an error in the Output?

Got nothing. I put it in a script, in workspace

hmmm… Can you show me your code?

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

Put a task.wait () at the beginning or check if what happens is that the scristales are in Transparency: 1


image

Nothing pops up at all

Where is your part? Are u running the function?

how do i change it from the back to the front? (line 20)

Awesome! How I could make it not change the thickness of the generated pieces? So the wedges have the same thickness as the original part?

Edit: I just noticed that I need to change the first value of the Vector3:

	local w1 = wedge:Clone()
	w1.Size = Vector3.new(0.1, 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.1, height, math.abs(ac:Dot(back)))
	w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
	w2.Parent = parent

0 makes it too thin, so I wanted it was thicker

With 0.1 I got a better result:

This works fine for glass panels, because they’re 0.1 thin. However, what to do if I want to break a cubic part, like a jewelry counter (so when you brake it, you can stole the jewelry)?

Got this result, this doesn’t look good.

When you’re defining the size of the wedges, by default, the x-axis size is set as “0.1”, you could just add a new parameter “thickness” and change that value

1 Like