---
url: /roblox-fundamentals/services-and-events.md
description: Reach Engine services with GetService and react to the world with events.
---

# Services and Events

*Written: {{ $frontmatter.date }}*

## Learning objectives

* You can get each service once with `game:GetService`.
* You can name the key services and their jobs.
* You can connect, fire once, and wait on [events](https://create.roblox.com/docs/en-us/scripting/events).
* You can explain deferred [events](https://create.roblox.com/docs/en-us/scripting/events/deferred).
* You can wire a small service-driven system.

## Prerequisites

* [Scripts and Execution](./scripts-and-execution.md)
* [DataModel and Instances](./datamodel-and-instances.md)
* [Functions Advanced](../luau-basics/functions-advanced.md)

## Get each service once

Services give you engine features. You reach them through `game`. The [services](https://create.roblox.com/docs/en-us/scripting/services) guide sets the convention: retrieve a service once per script and name the variable after the service.

**Script in ServerScriptService**

```luau
--!strict
local Players = game:GetService("Players")

local function greet(player: Player): ()
	print(player.Name)
end

for _, player in Players:GetPlayers() do
	greet(player)
end
Players.PlayerAdded:Connect(greet)
```

Get Players once. Add a local function. Handle current players. Connect future players. Expected behavior: Output prints every current player name, then prints each newcomer name. This also covers players who joined before the script ran; you revisit that loop in [Players and Characters](./players-and-characters.md).

## Key services tour

Learn these nine first. Each one answers a common need.

[Workspace](https://create.roblox.com/docs/en-us/projects/data-model) holds the 3D world. You parent visible parts and models there. [Players](https://create.roblox.com/docs/en-us/reference/engine/classes/Players) tracks everyone in the game. It fires [PlayerAdded](https://create.roblox.com/docs/en-us/reference/engine/classes/Players#PlayerAdded) and PlayerRemoving.

[ReplicatedStorage](https://create.roblox.com/docs/en-us/projects/data-model) is visible to server and clients. You store shared modules and remotes there. ServerScriptService holds server-only scripts and modules. Clients never see inside it.

[TweenService](https://create.roblox.com/docs/en-us/reference/engine/classes/TweenService) animates numeric properties with easing and timing controls. [RunService](https://create.roblox.com/docs/en-us/reference/engine/classes/RunService) drives per-frame work and time checks. [UserInputService](https://create.roblox.com/docs/en-us/reference/engine/classes/UserInputService) reports device input on the client.

[Lighting](https://create.roblox.com/docs/en-us/projects/data-model) holds global light settings such as atmosphere and sky objects. [SoundService](https://create.roblox.com/docs/en-us/scripting/services) controls global audio behavior and sound groups.

**Script in ServerScriptService**

```luau
--!strict
local TweenService = game:GetService("TweenService")
local Workspace = game:GetService("Workspace")

local target = Instance.new("Part")
target.Name = "Mover"
target.Size = Vector3.new(2, 2, 2)
target.Anchored = true
target.Position = Vector3.new(0, 5, 0)

local info = TweenInfo.new(2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
local tween = TweenService:Create(target, info, { Position = Vector3.new(0, 10, 0) })
target.Parent = Workspace
tween:Play()
```

Build the part fully before parenting it. Create tween timing with TweenInfo. Ask [TweenService](https://create.roblox.com/docs/en-us/reference/engine/classes/TweenService) to interpolate Position. Parent last. Play the tween. Expected behavior: a part appears and glides upward over two seconds.

**LocalScript in StarterPlayerScripts**

```luau
--!strict
local UserInputService = game:GetService("UserInputService")

local function onJumpRequest(input: InputObject, processed: boolean): ()
	if processed then
		return
	end
	if input.KeyCode == Enum.KeyCode.Space then
		print("Jump pressed")
	end
end

UserInputService.InputBegan:Connect(onJumpRequest)
```

This runs on the client. Connect one input event. Ignore input the game already handled. React to the space key. Expected behavior: Output prints `Jump pressed` when you press space outside chat and menus.

## Connect, Once, and Wait

An [event](https://create.roblox.com/docs/en-us/scripting/events) fires when something happens. You respond with [Connect](https://create.roblox.com/docs/en-us/reference/engine/datatypes/RBXScriptSignal#Connect), Once, or Wait.

[Connect](https://create.roblox.com/docs/en-us/reference/engine/datatypes/RBXScriptSignal#Connect) runs your function on every fire. It returns a connection you can disconnect later. Once runs your function on the first fire only, which the [events](https://create.roblox.com/docs/en-us/scripting/events) guide recommends over manual connect-then-disconnect. Wait yields your thread until the next fire and returns the event arguments.

**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local plate = Instance.new("Part")
plate.Name = "WelcomeMat"
plate.Size = Vector3.new(8, 1, 8)
plate.Anchored = true
plate.Position = Vector3.new(0, 1, 0)
plate.Parent = Workspace

plate.Touched:Connect(function(hit: BasePart): ()
	print(hit.Name)
end)
```

Create a floor pad. Set size and position before parenting. Connect Touched. The handler receives the part that touched. Expected behavior: Output prints toucher names whenever something touches the pad.

**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local door = Instance.new("Part")
door.Name = "FirstTouchDoor"
door.Size = Vector3.new(4, 6, 1)
door.Anchored = true
door.Position = Vector3.new(10, 4, 0)
door.Parent = Workspace

local first = door.Touched:Wait()
print(first.Name)

door.Touched:Once(function(hit: BasePart): ()
	print(hit.Name)
end)
```

Both one-shot styles run in order. Wait yields for the first touch and prints it. Once then arms for the next touch and prints that one. Expected behavior: Output prints the first touch once from Wait, then prints the next touch once from Once. Connect both before any fire and they would both catch the same first touch, so sequence them when you want separate touches.

## Deferred events change timing

Deferred [events](https://create.roblox.com/docs/en-us/scripting/events/deferred) do not run handlers immediately. The engine queues them until the next resumption point. Order stays predictable, but nested fires run later than beginners expect.

Do not read a flag right after you trigger its event in the same thread. The handler may not have run yet. Yield with `task.wait` or restructure around the handler instead. The [events](https://create.roblox.com/docs/en-us/scripting/events) guide points you to this deferred behavior for performance and consistency.

**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local flag = Instance.new("BoolValue")
flag.Name = "Ready"
flag.Value = false
flag.Parent = Workspace

flag.Changed:Connect(function(): ()
	print(flag.Value)
end)

flag.Value = true
task.wait()
print(flag.Value)
```

Connect Changed first. Flip the value. Yield one scheduler step. Read the value after the queue has run. Expected behavior: Output prints `true` from the handler and again from the final print.

## Common mistakes

**Mistake 1: calling GetService for the same service twice.**

Wrong example below. It repeats the lookup.

**Script in ServerScriptService**

```luau
--!strict
local Players = game:GetService("Players")
local Again = game:GetService("Players")

print(#Players:GetPlayers() + #Again:GetPlayers())
```

It works but it breaks the [services](https://create.roblox.com/docs/en-us/scripting/services) convention. Duplicate lookups clutter larger scripts. Fix it by binding once and reusing the variable.

**Script in ServerScriptService**

```luau
--!strict
local Players = game:GetService("Players")

print(#Players:GetPlayers())
```

**Mistake 2: assuming the handler ran immediately.**

Wrong example below. It reads the flag too soon.

**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local done = false
local plate = Workspace:FindFirstChild("WelcomeMat")
if plate and plate:IsA("BasePart") then
	plate.Touched:Connect(function(): ()
		done = true
	end)
end
print(done)
```

It prints `false` because deferred handlers run later. The [deferred](https://create.roblox.com/docs/en-us/scripting/events/deferred) page describes this queue. Fix it by doing follow-up work inside the handler or after an explicit yield.

**Mistake 3: never disconnecting a temporary connection.**

Wrong shape below. It stays connected after its job ends.

**LocalScript in StarterPlayerScripts**

```luau
--!strict
local UserInputService = game:GetService("UserInputService")

UserInputService.InputBegan:Connect(function(input: InputObject): ()
	if input.KeyCode == Enum.KeyCode.E then
		print("Used")
	end
end)
```

It keeps printing for the whole session. Fix it by saving the connection and disconnecting, or by using Once for one-shot behavior described in [events](https://create.roblox.com/docs/en-us/scripting/events).

## Exercises

1. Connect a Touched handler that prints toucher names.
2. Use Once to reward only the first touch.
3. Combine a service lookup with a deferred-safe read.

::: details Solution 1
**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local pad = Instance.new("Part")
pad.Name = "ExercisePad"
pad.Size = Vector3.new(6, 1, 6)
pad.Anchored = true
pad.Position = Vector3.new(0, 1, 12)
pad.Parent = Workspace

pad.Touched:Connect(function(hit: BasePart): ()
	print(hit.Name)
end)
```

Expected behavior: touching the pad prints the touching part name.
:::

::: details Solution 2
**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local chest = Instance.new("Part")
chest.Name = "FirstReward"
chest.Size = Vector3.new(3, 3, 3)
chest.Anchored = true
chest.Position = Vector3.new(6, 3, 0)
chest.Parent = Workspace

chest.Touched:Once(function(hit: BasePart): ()
	print(hit.Name)
end)
```

Expected behavior: only the first touch prints.
:::

::: details Solution 3
**Script in ServerScriptService**

```luau
--!strict
local Workspace = game:GetService("Workspace")

local marker = Instance.new("BoolValue")
marker.Name = "ExerciseReady"
marker.Value = false
marker.Parent = Workspace

marker.Changed:Connect(function(): ()
	print(marker.Value)
end)

marker.Value = true
task.wait()
print(marker.Value)
```

Expected behavior: Output prints `true` twice, once from the handler and once after the yield.
:::

## Summary

Get services once. Pick the right tool for the job. Connect with care. Respect deferred timing. Next, schedule work cleanly in [Task and Async](./task-and-async.md).
