7.2.1 Illuminated Dance Floor

Let’s create a project that uses a BindableEvent to notify multiple scripts about an event. This will be an illuminated dance floor that uses the event to turn on the music and play the activate the dance floor.

A ClickDetector will be used to listen for the player input. But in order to start the dance, the player must have a ticket.

Meaning there needs to be a check for the existence of the ticket. Normally, the dance and music script could just simply connect to the ClickDetector and toggle their functionality.

But because there now needs to be a check for the ticket, that also has to be handled in the connected scripts. If more scripts are added on, you’re gonna be repeating a lot of code in those scripts that connect to the click event.

And then what happens if we add a different means of starting the dance. Say, the option to pay at the door.

Pretty soon, the scripts designated for doing one thing become bloated with repetitive code that would be better handled elsewhere.

So rather than connecting the dance floor related scripts directly to the ClickDetector, we’ll connect them to a BindableEvent. And then have the ClickDetector script or the ticket booth script fire the BindableEvent after the player passes any required checks.


Start by creating the ticket. Just a Part placed down somewhere named ‘Tix.’ I made mine gold and added a decal with the ID http://www.roblox.com/asset/?id=385652894.

Then add to that a child Script.

Lua
local Players = game:GetService("Players")

local part = script.Parent
local debounceTick = 0

part.Touched:Connect(function(otherPart)
	if tick() - debounceTick < 0.5 then return end
	
	debounceTick = tick()
	
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)
	if player then
		part.Parent = player.Backpack
	end
end)

This is just a simple touch script. When the player touches the part, it gets moved into the player’s backpack. The ClickDetector will look for this object.

For the ClickDetector, create a stand for the button and the button itself. Then insert the ClickDetector along with a Script.

In ReplicatedStorage, add a BindableEvent object.

Lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local clickDectector = script.Parent.ClickDetector
local bindableEvent = ReplicatedStorage.Event

local errorSoundId = "http://www.roblox.com/asset/?id=3779045779"
local sound = Instance.new("Sound")
sound.SoundId = errorSoundId
sound.Parent = script.Parent

local on = false

clickDectector.MouseClick:Connect(function(player)
	if not player or not player.Backpack:FindFirstChild("Tix") then
		sound:Play()
		return
	end
	
	on = not on
	
	bindableEvent:Fire(on)
end)

The button will play an error sound if the player does not have a ticket. To do that, we create a sound object and assign it the ID above then parent it to the button. This will make the sound come from that position.

The script also keeps track of a flag to determine the status of the dance floor. If you had multiple possible sources to enable/disable the dance floor, you might make this a shared variable between those scripts or maybe use a BoolValue and forgo the BindableEvent completely.

In the main function, after the ticket check is successful, all that remains is to fire the event and pass in the status. Because this script is decoupled from the other scripts, we don’t have to track which scripts need to activate or deactivate.

For the music, place a Script into the workspace somewhere.

Lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local bindableEvent = ReplicatedStorage.Event

local songId = 'rbxassetid://1839686965'
local sound = Instance.new("Sound")

sound.SoundId = songId
sound.Looped = true
sound.Parent = game.Workspace

bindableEvent.Event:Connect(function(status)
	if status then
		sound:Resume()
	else
		sound:Pause()
	end
end)

Similar to the error sound, we create a sound and give it a sound ID. This time, the sound is parented to the workspace which will make it audible from anywhere in the game.

Then the BindableEvent will just plays or pauses the song based on the ‘status’ parameter.


To build the illuminated dance floor, start by placing the tiles and then pattern them however you want. I’m using 4×4 tiles in a checkerboard pattern.

Then parent a SurfaceLight to each tile and set the ‘Face’ property to ‘Top.’ This will make the light shine in the upward direction.

After placing all of the tiles, group them into a model and add a ModuleScript into the model. Rename it ‘DanceFloor.’

We’ll first define the variables.

Lua
local RunService = game:GetService("RunService")

local SPEED = 1
local COLOR_CHANCE_CHANCE = 0.5
local TILE_TRANSPARENCY = 0.5

local connection = nil
local timeLeft = 1 / SPEED

local tilesList = {}
local colorsList = {
	Color3.fromRGB(164, 20, 217),
	Color3.fromRGB(255, 128, 43),
	Color3.fromRGB(52, 199, 165),
	Color3.fromRGB(249, 225, 5),
	Color3.fromRGB(93, 80, 206),
	Color3.fromRGB(255, 0, 0),
	Color3.fromRGB(0, 255, 0),
	Color3.fromRGB(0, 0, 255),
	Color3.fromRGB(255, 255, 255)
}

for _, tile in script.Parent:GetChildren() do
	if tile:IsA("Part") then
		local data = {
			tile = tile,
			light = tile:FindFirstChild("SurfaceLight"),
			defaultColor = tile.Color,
		}
		
		table.insert(tilesList, data)
	end
end

The speed variable determines how often the tiles change color, in number of times per second. The higher the number, the shorter the cycle and therefore the time between changes.

However, I only wanted only about 50% of the tiles to change each cycle so I created a ‘COLOR_CHANGE_CHANCE’ variable which will be used to determine if a tile changes color.

As the name implies, the ‘timeLeft’ variable is the countdown until the next cycle.

The color list table defines the available colors. During runtime, a color will be randomly selected and assigned to the tile and light.

Finally, we use a loop to populate the tile list. The default color is stored so that we can revert to that color when the dance floor is turned off.


Next, we define the function to change the tile+light colors and the update function:

Lua
local function changeColor(tile, color)
	local light = tile:FindFirstChild("SurfaceLight")
	
	if color then
		tile.Material = Enum.Material.Plastic
		tile.Transparency = 0
		light.Enabled = false
		
	else
		color = colorsList[math.random(1, #colorsList)]
		tile.Material = Enum.Material.Neon
		tile.Transparency = TILE_TRANSPARENCY
		light.Enabled = true
		
	end
	
	tile.Color = color
	light.Color = color
end

local function update(dt)
	timeLeft = timeLeft - dt
	
	if timeLeft > 0 then return end
	timeLeft = 1 / SPEED
	
	for _, tileData in tilesList do
		if math.random() > COLOR_CHANCE_CHANCE then
			changeColor(tileData.tile)
		end
	end
end

In addition to changing a tile’s color, the function also needs to have the ability to turn illumination off. To reduce the number of required arguments, we will use the color parameter to determine if the tile should be turned off.

If a color is specified, then the tile is intended to be turned off. And if there is not a color specified, then select a random color from the list and assign it to the tile and light.

In the update function, we decrement the timer until it reaches 0 after which, the time is reset. Then iterate through the list and randomly change color on some of the tiles.


Since this module is self-contained, the only required functions for the interface are a stop and play function.

Lua
local DanceFloor = {}

function DanceFloor:play()
	if connection then return end
	
	for _, tileData in tilesList do
		changeColor(tileData.tile)
	end
	
	timeLeft = 1 / SPEED
	connection = RunService.Heartbeat:Connect(update)
end

function DanceFloor:stop()
	if connection then
		connection:Disconnect()
	end
	
	for _, tileData in tilesList do
		changeColor(tileData.tile, tileData.defaultColor)
	end
	
	timeLeft = 1 / SPEED
	connection = nil
end

return DanceFloor

The play function turns the tile on for the first time and then sets the timer and connects the update function.

The the stop function works in the opposite. First disconnecting the update function and then loops through each tile, passing the default color to the color change function to turn the tile off.

Finally, the dance floor also needs to listen for the BindableEvent and invoke the module. While this can be integrated into the module, we’ll keep is separate so that dance floor will require fewer external dependencies.

Lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local danceModule = require(game.Workspace.Model.DanceFloor)

local bindableEvent = ReplicatedStorage.Event

bindableEvent.Event:Connect(function(status)
	if status then
		danceModule:play()
	else
		danceModule:stop()
	end
end)

I added some additional decorations to complete the look and set it to run.

Lua
--Workspace/Tix/Script

local Players = game:GetService("Players")

local part = script.Parent
local debounceTick = 0

part.Touched:Connect(function(otherPart)
	if tick() - debounceTick < 0.5 then return end
	
	debounceTick = tick()
	
	local player = Players:GetPlayerFromCharacter(otherPart.Parent)
	if player then
		part.Parent = player.Backpack
	end
end)
Lua
--Workspace/Button/Script

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local clickDectector = script.Parent.ClickDetector
local bindableEvent = ReplicatedStorage.Event

local errorSoundId = "http://www.roblox.com/asset/?id=3779045779"
local sound = Instance.new("Sound")
sound.SoundId = errorSoundId
sound.Parent = script.Parent

local on = false

clickDectector.MouseClick:Connect(function(player)
	if not player or not player.Backpack:FindFirstChild("Tix") then
		sound:Play()
		return
	end
	
	on = not on
	
	bindableEvent:Fire(on)
end)
Lua
--Workspace/SoundScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local bindableEvent = ReplicatedStorage.Event

local songId = 'rbxassetid://1839686965'
local sound = Instance.new("Sound")

sound.SoundId = songId
sound.Looped = true
sound.Parent = game.Workspace

bindableEvent.Event:Connect(function(status)
	if status then
		sound:Resume()
	else
		sound:Pause()
	end
end)
Lua
--Workspace/Model/DanceFloor (ModuleScript)

local RunService = game:GetService("RunService")

local SPEED = 1
local COLOR_CHANCE_CHANCE = 0.5
local TILE_TRANSPARENCY = 0.5

local connection = nil
local timeLeft = 1 / SPEED

local tilesList = {}
local colorsList = {
	Color3.fromRGB(164, 20, 217),
	Color3.fromRGB(255, 128, 43),
	Color3.fromRGB(52, 199, 165),
	Color3.fromRGB(249, 225, 5),
	Color3.fromRGB(93, 80, 206),
	Color3.fromRGB(255, 0, 0),
	Color3.fromRGB(0, 255, 0),
	Color3.fromRGB(0, 0, 255),
	Color3.fromRGB(255, 255, 255)
}

for _, tile in script.Parent:GetChildren() do
	if tile:IsA("Part") then
		local data = {
			tile = tile,
			defaultColor = tile.Color,
		}
		
		table.insert(tilesList, data)
	end
end

local function changeColor(tile, color)
	local light = tile:FindFirstChild("SurfaceLight")
	
	if color then
		tile.Material = Enum.Material.Plastic
		tile.Transparency = 0
		light.Enabled = false
		
	else
		color = colorsList[math.random(1, #colorsList)]
		tile.Material = Enum.Material.Neon
		tile.Transparency = TILE_TRANSPARENCY
		light.Enabled = true
		
	end
	
	tile.Color = color
	light.Color = color
end

local function update(dt)
	timeLeft = timeLeft - dt
	
	if timeLeft > 0 then return end
	timeLeft = 1 / SPEED
	
	for _, tileData in tilesList do
		if math.random() > COLOR_CHANCE_CHANCE then
			changeColor(tileData.tile)
		end
	end
end

local DanceFloor = {}

function DanceFloor:play()
	if connection then return end
	
	for _, tileData in tilesList do
		changeColor(tileData.tile)
	end
	
	timeLeft = 1 / SPEED
	connection = RunService.Heartbeat:Connect(update)
end

function DanceFloor:stop()
	if connection then
		connection:Disconnect()
	end
	
	for _, tileData in tilesList do
		changeColor(tileData.tile, tileData.defaultColor)
	end
	
	timeLeft = 1 / SPEED
	connection = nil
end

return DanceFloor
Lua
--Workspace/DanceFloorScript

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local danceModule = require(game.Workspace.Model.DanceFloor)

local bindableEvent = ReplicatedStorage.Event

bindableEvent.Event:Connect(function(status)
	if status then
		danceModule:play()
	else
		danceModule:stop()
	end
end)

Next 🠞