33. Events and connections

In a terminal program, things happen because you call a function. In a game, they happen because the world changes: a part is touched, a player joins, a button is clicked. Roblox hands you these moments as events; you respond by connecting a function to them. Chapter 31 showed this once; this chapter makes it solid, since it is the heart of nearly all Roblox scripting.

Connecting a function to an event

An event lives on an Instance. Attach a function to it with :Connect, and that function runs every time the event fires:

local part = workspace.Coin

part.Touched:Connect(function(otherPart)
    print("Something touched the coin: " .. otherPart.Name)
end)

What each piece does:

  • part.Touched is an event on the Part — it fires when something bumps into it.
  • :Connect(...) registers the function. It does not run it now; it arranges for it to run later, each time the event fires.
  • The function receives information about what happened — for Touched, the other Part that did the touching.

No loop needed: after this runs, the Roblox engine calls your function whenever the event happens.

part.Touched(...) without :Connect is wrong — Touched is an event, not a function you call. Always connect to it with :Connect.

Finding the player behind a touch

A Touched event hands you a Part, often a piece of a character (an arm or leg). To get the player, find the character model it belongs to, then ask the Players service:

local Players = game:GetService("Players")

part.Touched:Connect(function(otherPart)
    local character = otherPart.Parent
    local player = Players:GetPlayerFromCharacter(character)
    if player then
        print(player.Name .. " touched the coin.")
    end
end)

GetPlayerFromCharacter returns nil when the toucher was not a player (a falling brick, say), so the if player guard keeps the rest safe.

Debounce: stop an event firing too fast

Touched can fire many times a second while a character rests on a Part. If your handler gives a coin or a point, you do not want it running dozens of times per touch. The fix is a debounce: a flag saying "already handling this".

local collected = false

part.Touched:Connect(function(otherPart)
    if collected then return end   -- already done; ignore extra touches
    collected = true
    print("Collected!")
    part:Destroy()
end)

The first touch sets collected = true; every touch after hits the early return and does nothing. After :Connect itself, this is the most common Roblox pattern.

PlayerAdded: react when someone joins

The Players service has a PlayerAdded event that fires once per player who joins. It is where you set up anything per-player:

local Players = game:GetService("Players")

Players.PlayerAdded:Connect(function(player)
    print(player.Name .. " joined the game.")
end)

Connect versus Wait

There are two ways to respond to an event:

  • :Connect(fn) — runs fn every time the event fires, forever. Almost always what you want.
  • :Wait() — pauses the script until the event fires once, then continues, handing back the event's information. Use it only when you need a single occurrence.

For a coin that should work on every touch, :Connect is right. :Wait would catch one touch and then stop listening.

Homework

Roblox snippets again — write them, check the solutions, and try them in Studio if you can.

Problem 1 — Touched printer

Open exercises/33/homework/01-touched.lua. Given a variable part, connect a function to its Touched event that prints <name> touched it, where <name> is the touching Part's Name.

Problem 2 — Greet on join

Open exercises/33/homework/02-join.lua. Using the Players service and PlayerAdded, print Welcome, <name>! whenever a player joins.

Problem 3 — Add a debounce

Open exercises/33/homework/03-debounce.lua. The starter prints bang! on every touch, which fires too often. Add a collected debounce so it prints bang! only once.

Challenge — Player-only touch

Open exercises/33/homework/04-player-touch.lua. Connect to a Part's Touched. Inside, find the player behind the touch with GetPlayerFromCharacter, and print <player> scored only when the toucher really was a player (ignore non-player parts).

Stuck or finished? Open the homework solutions page.