Can these scripts be simplified/combined?

Laundry Machines Testing.rbxl (343.4 KB)

I have made washers and dryers that you can put dirty clothes in, it runs the cycle, and you receive clean clothes when it finishes. The machines all lock so that only the person who paid for it can use it and take the clothes from it. From my testing, it all works, but I am not very experienced in coding and I am sure there is a way better way of doing all this.

The main issue is that I have about 20 machines all with their own script, and I think it would be much more efficient if I could combine them all into one script for washers and one script for dryers, or something like that. I have tried to think of ways to do this but I couldn’t figure it out. I have clothes that spin inside of each machine while it runs and they all have different CFrames, and I thought maybe I could put players into a list if they paid for a machine so that it locks to only them but I just don’t know the best way to do it. Individual scripts was the only way I could get everything to work the way I wanted.

So mostly I am hoping someone can help me simplify my code, combine it, or just help me figure out the best methods of doing what I’m trying to do! Thanks!

Here is the script I have on all the washers. They just all have a different remote event that calls another script that basically just fires this script. The Dryers are pretty much the same minus the water filling up and the clothes you receive from it.

local p = script.Parent
local buttons = script.Parent.Parent.Buttons
local countDown = script.Parent.Parent.Buttons.Timer.SurfaceGui.TextLabel
local water = p.Parent.Water.Mesh
local OwnerValue = p.Parent.Owner.Value

inProgress = false
Stopped = false

--make buttons only clickable by person who paid

-- Pay for Machine
buttons.CoinSlot.Purchase.ClickDetector.MouseClick:Connect(function()
	if p.isPaid.Value == false then
		game.ReplicatedStorage.Washers.Washer1.OnServerEvent:Connect(function(player)
			if player.leaderstats.Coins.Value >= 10 and OwnerValue ~= player.Name then
				player.leaderstats.Coins.Value = player.leaderstats.Coins.Value - 10
				OwnerValue = player.Name
				p.HingeUpper.Coin:Play()
				p.isPaid.Value = true
				p.Parent.Buttons.CoinLight.Color = Color3.fromRGB(77, 255, 23)
				p.Parent.Buttons.LockLight.Color = Color3.fromRGB(77, 255, 23)
				inProgress = false
				hasbeenOn = false
				countDown.Text = string.format("00:00")
				buttons.CoinSlot.Purchase.ClickDetector.MaxActivationDistance = 0
				p.Parent.Clothes.ProximityPrompt.ActionText = "Load Clothes"

				--Time Out
				coroutine.wrap(function()
					repeat
						task.wait(120)
						if p.isPaid.Value == true and inProgress == false then
							if p.Parent.WadD.a.Transparency == 0 or p.Parent.Wad.a.Transparency == 0 then
								player.Character:WaitForChild("Humanoid"):UnequipTools()
								player.Backpack:FindFirstChild("Empty Basket"):Destroy()
								player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
							end
							for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
							for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
							for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 1 end
							for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
							OwnerValue = "..."
							p.isPaid.Value = false
							countDown.Text = string.format("VACANT")
							buttons.CoinLight.Color = Color3.fromRGB(193, 0, 6)
							buttons.ReadyLight.Color = Color3.fromRGB(163, 162, 165)
							buttons.LockLight.Color = Color3.fromRGB(161, 165, 75)
							hasbeenOn = false
							buttons.CoinSlot.Purchase.ClickDetector.MaxActivationDistance = 32
							p.Parent.Clothes.ProximityPrompt.ActionText = "Please pay first!"
						end
					until
					p.isPaid.Value == false
				end)() -- make sure you put that there otherwise it wont run
				--Time Out End


			elseif player.leaderstats.Coins.Value < 10 then
				p.isPaid.Value = false
				buttons.CoinSlot.Purchase.ClickDetector.MaxActivationDistance = 0
				p.Parent.Buttons.CoinLight.Color = Color3.fromRGB(0, 0, 0)
				wait(1)
				p.Parent.Buttons.CoinLight.Color = Color3.fromRGB(193, 0, 6)
				wait(1)
				p.Parent.Buttons.CoinLight.Color = Color3.fromRGB(0, 0, 0)
				wait(1)
				p.Parent.Buttons.CoinLight.Color = Color3.fromRGB(193, 0, 6)
				buttons.CoinSlot.Purchase.ClickDetector.MaxActivationDistance = 32
			end

		end)
	end
end)





-- Start Machine
buttons.StartButton.ClickDetector.MouseClick:Connect(function(player)
	if (p.isPaid.Value == true and OwnerValue == player.Name and p.dOpen.Value == false and Stopped == false) and (p.Parent.Wad.a.Transparency == 0 or p.Parent.WadD.a.Transparency == 0 or p.Parent.WadWet.a.Transparency == 0 or p.Parent.WadDWet.a.Transparency == 0) then
		p.isOn.Value = true
		p.HingeUpper.Click:Play()
		p.Parent.Buttons.ReadyLight.Color = Color3.fromRGB(161, 165, 75)
		p.isLocked.Value = true
		p.Parent.Buttons.LockLight.Color = Color3.fromRGB(193, 0, 6)
		p.OpenDoor.Enabled = false
		hasbeenOn = true



		if inProgress == false then
			inProgress = true


			if water.Scale.Y < 2 then
				p.HingeUpper.Fill:Play()
				p.Dry.Value = false
				for i = 0, 13, 0.1 do
					water.Scale = water.Scale + Vector3.new(0,0.01,0)
					wait(.01)

					if p.isOn.Value == false then
						repeat wait() until p.isOn.Value == true
					end
				end
				p.HingeUpper.Fill:Stop()
			end
			
			if p.Clean.Value == true then
				for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
				for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
				for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 0 end
				for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
			else
				for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
				for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
				for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 1 end
				for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 0 end
			end

			for i = 600, 0, -1 do
				local mins = math.floor(i / 60)
				local seconds = math.floor(i % 60)
				countDown.Text = string.format("%02d:%02d", mins, seconds)
				wait(1)


				if p.isOn.Value == false then
					return
				end
			end
			
			for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 0 end
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end

			if water.Scale.Y > 2 then
				p.HingeUpper.Drain:Play()
				for i = 0, 13, 0.1 do
					water.Scale = water.Scale + Vector3.new(0,-0.01,0)
					wait(.01)

					if p.isOn.Value == false and Stopped == true then
						return
					end
				end
				p.HingeUpper.Drain:Stop()
			end

			p.Clean.Value = true
			p.isOn.Value = false
			p.Parent.Buttons.ReadyLight.Color = Color3.fromRGB(77, 255, 23)
			countDown.Text = string.format("READY")

			inProgress = false
			p.isLocked.Value = false
			p.Parent.Buttons.LockLight.Color = Color3.fromRGB(77, 255, 23)
			p.OpenDoor.Enabled = true


		end
	elseif (p.isPaid.Value == true and OwnerValue == player.Name) and (p.dOpen.Value == true or p.Parent.Wad.a.Transparency == 1 or p.Parent.WadD.a.Transparency == 1 ) then
		p.HingeUpper.Error:Play()

	end
end)

-- Cancel Machine
buttons.PauseButton.ClickDetector.MouseClick:Connect(function(player)
	if p.isOn.Value == true and OwnerValue == player.Name and water.Scale.Y > 2 and Stopped == false then
		Stopped = true
		wait(1)
		p.HingeUpper.Click:Play()
		hasbeenOn = false
		inProgress = false
		p.isOn.Value = false
		p.Parent.Buttons.ReadyLight.Color = Color3.fromRGB(193, 0, 6)
		p.HingeUpper.Fill:Stop()
		p.HingeUpper.Spin:Stop()
		p.HingeUpper.Drain:Play()
		countDown.Text = string.format("00:00")
		
		for i = 0, 13, 0.1 do
			water.Scale = water.Scale + Vector3.new(0,-0.01,0)
			wait(.01)
		end
		p.HingeUpper.Drain:Stop()
		p.OpenDoor.Enabled = true
		p.isLocked.Value = false
		p.Parent.Buttons.LockLight.Color = Color3.fromRGB(77, 255, 23)
		Stopped = false
	else
		p.HingeUpper.Error:Play()
	end
end)

-- Lock Machine
buttons.LockButton.ClickDetector.MouseClick:Connect(function(player)
	if p.isPaid.Value == true and p.isLocked.Value == false and OwnerValue == player.Name then
		p.HingeUpper.Click:Play()
		p.isLocked.Value = true
		p.Parent.Buttons.LockLight.Color = Color3.fromRGB(193, 0, 6)
		p.OpenDoor.Enabled = false
	elseif p.isPaid.Value == true and p.isLocked.Value == true and OwnerValue == player.Name and inProgress == false then
		p.HingeUpper.Click:Play()
		p.isLocked.Value = false
		p.Parent.Buttons.LockLight.Color = Color3.fromRGB(77, 255, 23)
		p.OpenDoor.Enabled = true
	elseif p.isPaid.Value == true and p.isLocked.Value == true and OwnerValue == player.Name and inProgress == true then
		p.HingeUpper.Error:Play()
		p.isLocked.Value = true
		p.Parent.Buttons.LockLight.Color = Color3.fromRGB(193, 0, 6)
		p.OpenDoor.Enabled = false
	end
end)

-- Drum
p.isOn:GetPropertyChangedSignal('Value'):Connect(function()
	if p.isOn.Value == true then
		wait(4)
		p.HingeUpper.Spin:Play()
		local drum = p.Parent.Drum
		local a = 1
		local speed = 3 -- the higher the number the faster it spins
		while true do
			a = a + speed
			drum.Orientation = Vector3.new(a,180,0)
			task.wait()
			if p.isOn.Value == false then
				return
			end
		end
	elseif p.isOn.Value == false then
		p.HingeUpper.Spin:Pause()
		p.Parent.Drum.Orientation = p.Parent.Drum.Orientation
	end
end)




-- Clothes Wad
p.isOn:GetPropertyChangedSignal('Value'):Connect(function()
	if p.isOn.Value == true then
		wait(4)
		local wad = p.Parent.Wad
		while true do
			
			wait()
				wad:SetPrimaryPartCFrame(wad:GetPrimaryPartCFrame() * CFrame.Angles(math.rad(-10), 0, 0))
			
			if p.isOn.Value == false then
				p.Parent.Wad:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-12.746, 4, 12.361)) * CFrame.Angles(math.rad(0),0,0))
				return
			end
		end
	end
end)

p.isOn:GetPropertyChangedSignal('Value'):Connect(function()
	if p.isOn.Value == true then
		wait(4)
		local wad = p.Parent.WadD
		while true do
				wait()
				wad:SetPrimaryPartCFrame(wad:GetPrimaryPartCFrame() * CFrame.Angles(math.rad(-10), 0, 0))
			
			if p.isOn.Value == false then
				p.Parent.WadD:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-12.746, 4, 12.361)) * CFrame.Angles(math.rad(0),0,0))
				return
			end
		end
	end
end)

p.isOn:GetPropertyChangedSignal('Value'):Connect(function()
	if p.isOn.Value == true then
		wait(4)
		local wad = p.Parent.WadWet
		while true do

			wait()
			wad:SetPrimaryPartCFrame(wad:GetPrimaryPartCFrame() * CFrame.Angles(math.rad(-10), 0, 0))

			if p.isOn.Value == false then
				p.Parent.WadWet:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-12.746, 4, 12.361)) * CFrame.Angles(math.rad(0),0,0))
				return
			end
		end
	end
end)

p.isOn:GetPropertyChangedSignal('Value'):Connect(function()
	if p.isOn.Value == true then
		wait(4)
		local wad = p.Parent.WadDWet
		while true do
			wait()
			wad:SetPrimaryPartCFrame(wad:GetPrimaryPartCFrame() * CFrame.Angles(math.rad(-10), 0, 0))

			if p.isOn.Value == false then
				p.Parent.WadDWet:SetPrimaryPartCFrame(CFrame.new(Vector3.new(-12.746, 4, 12.361)) * CFrame.Angles(math.rad(0),0,0))
				return
			end
		end
	end
end)
-- Water Spill


--Clothes in/out


p.Parent.Clothes.ProximityPrompt.Triggered:Connect(function(player)
	if p.isPaid.Value == true and OwnerValue == player.Name and hasbeenOn == false and p.Parent.Wad.a.Transparency == 1 and p.Parent.WadD.a.Transparency == 1 and p.Parent.WadWet.a.Transparency == 1 and p.Parent.WadDWet.a.Transparency == 1 then -- putting in clothes, receive empty
		if player.Character:FindFirstChild("Dirty Dry Clothes") then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Dirty Dry Clothes"):Destroy()
			player.StarterGear:FindFirstChild("Dirty Dry Clothes"):Destroy()		
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 0 end
			for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
			p.Clean.Value = false
			p.Dry.Value = true
			p.Parent.Clothes.ProximityPrompt.ActionText = "Unload Clothes"
		elseif player.Character:FindFirstChild("Dirty Wet Clothes") then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Dirty Wet Clothes"):Destroy()
			player.StarterGear:FindFirstChild("Dirty Wet Clothes"):Destroy()
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 0 end
			p.Clean.Value = false
			p.Dry.Value = false
			p.Parent.Clothes.ProximityPrompt.ActionText = "Unload Clothes"
		elseif player.Character:FindFirstChild("Clean Wet Clothes") then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Clean Wet Clothes"):Destroy()
			player.StarterGear:FindFirstChild("Clean Wet Clothes"):Destroy()
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 0 end
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
			p.Clean.Value = true
			p.Dry.Value = false
			p.Parent.Clothes.ProximityPrompt.ActionText = "Unload Clothes"
		elseif player.Character:FindFirstChild("Clean Dry Clothes") then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Clean Dry Clothes"):Destroy()
			player.StarterGear:FindFirstChild("Clean Dry Clothes"):Destroy()
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Empty Basket"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 0 end
			for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 1 end
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
			p.Clean.Value = true
			p.Dry.Value = true
			p.Parent.Clothes.ProximityPrompt.ActionText = "Unload Clothes"
		elseif p.Parent.Wad.a.Transparency == 0 or p.Parent.WadD.a.Transparency == 0 then
			p.Parent.Clothes.ProximityPrompt.ActionText = "Equip basket to unload!"
		elseif p.Parent.Wad.a.Transparency == 1 or p.Parent.WadD.a.Transparency == 1 then
			p.Parent.Clothes.ProximityPrompt.ActionText = "Equip clothes to load!"
		end
	elseif p.isPaid.Value == true and OwnerValue == player.Name and hasbeenOn == false and p.Parent.WadD.a.Transparency == 0 or p.Parent.WadDWet.a.Transparency == 0 then
		if player.Character:FindFirstChild("Empty Basket") and p.Clean.Value == false and p.Dry.Value == true then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Empty Basket"):Destroy()
			player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
			game.ServerStorage.Baskets["Dirty Dry Clothes"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Dirty Dry Clothes"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.WadD:GetDescendants()) do v.Transparency = 1 end
			p.Parent.Clothes.ProximityPrompt.ActionText = "Load Clothes"
		elseif player.Character:FindFirstChild("Empty Basket") and p.Clean.Value == false and p.Dry.Value == false then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Empty Basket"):Destroy()
			player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
			game.ServerStorage.Baskets["Dirty Wet Clothes"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Dirty Wet Clothes"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
			p.Parent.Clothes.ProximityPrompt.ActionText = "Load Clothes"
		else
			p.Parent.Clothes.ProximityPrompt.ActionText = "Equip basket to unload!"
		end
	elseif p.isPaid.Value == true and OwnerValue == player.Name and hasbeenOn == false and p.Parent.WadWet.a.Transparency == 0 or p.Parent.Wad.a.Transparency == 0 then
		if player.Character:FindFirstChild("Empty Basket") and p.Clean.Value == true and p.Dry.Value == false then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Empty Basket"):Destroy()
			player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
			game.ServerStorage.Baskets["Clean Wet Clothes"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Clean Wet Clothes"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.WadWet:GetChildren()) do v.Transparency = 1 end
			p.Parent.Clothes.ProximityPrompt.ActionText = "Load Clothes"
		elseif player.Character:FindFirstChild("Empty Basket") and p.Clean.Value == true and p.Dry.Value == true then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Empty Basket"):Destroy()
			player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
			game.ServerStorage.Baskets["Clean Dry Clothes"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Clean Dry Clothes"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.Wad:GetDescendants()) do v.Transparency = 1 end
			p.Parent.Clothes.ProximityPrompt.ActionText = "Load Clothes"
		else
			p.Parent.Clothes.ProximityPrompt.ActionText = "Equip basket to unload!"
		end
	elseif p.isPaid.Value == true and OwnerValue == player.Name and hasbeenOn == true then
		if player.Character:FindFirstChild("Empty Basket") and p.Clean.Value == true then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Empty Basket"):Destroy()
			player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
			game.ServerStorage.Baskets["Clean Wet Clothes"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Clean Wet Clothes"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.WadWet:GetDescendants()) do v.Transparency = 1 end
			OwnerValue = "..."
			p.isPaid.Value = false
			buttons.CoinLight.Color = Color3.fromRGB(193, 0, 6)
			buttons.ReadyLight.Color = Color3.fromRGB(163, 162, 165)
			buttons.LockLight.Color = Color3.fromRGB(161, 165, 75)
			hasbeenOn = false
			buttons.CoinSlot.Purchase.ClickDetector.MaxActivationDistance = 32
			countDown.Text = string.format("VACANT")
			p.Parent.Clothes.ProximityPrompt.ActionText = "Please pay first!"
		elseif player.Character:FindFirstChild("Empty Basket") and p.Clean.Value == false then
			player.Character:WaitForChild("Humanoid"):UnequipTools()
			player.Backpack:FindFirstChild("Empty Basket"):Destroy()
			player.StarterGear:FindFirstChild("Empty Basket"):Destroy()
			game.ServerStorage.Baskets["Dirty Wet Clothes"]:Clone().Parent = player.Backpack
			game.ServerStorage.Baskets["Dirty Wet Clothes"]:Clone().Parent = player.StarterGear
			for i,v in pairs(p.Parent.WadDWet:GetDescendants()) do v.Transparency = 1 end
			OwnerValue = "..."
			p.isPaid.Value = false
			buttons.CoinLight.Color = Color3.fromRGB(193, 0, 6)
			buttons.ReadyLight.Color = Color3.fromRGB(163, 162, 165)
			buttons.LockLight.Color = Color3.fromRGB(161, 165, 75)
			hasbeenOn = false
			buttons.CoinSlot.Purchase.ClickDetector.MaxActivationDistance = 32
			countDown.Text = string.format("VACANT")
			p.Parent.Clothes.ProximityPrompt.ActionText = "Please pay first!"
		else
			p.Parent.Clothes.ProximityPrompt.ActionText = "Equip basket to unload!"
		end
	end
end)

CollectionService could probably help. Here’s a utube vid for reference Organizing your Game with Tags and CollectionService - Roblox Studio - YouTube

1 Like