Controllers
Available in: Client Only
This module runs entirely on the client side. It handles the UI, inputs and visual effects.
A Controller is the client-side mirror of a Service: a ModuleScript created with Owl.CreateController() and loaded from StarterPlayerScripts via Owl.AddControllers() (see Installation). It owns everything the player actually sees and interacts with UI, camera, input, tweens and talks to Services exclusively through their Client table.
Anatomy of a Controller
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Owl = require(ReplicatedStorage.OwlKnit.Owl)
local HUDController = Owl.CreateController({
Name = "HUDController",
Dependencies = {}, -- > // Names of the Controllers this one depends on
})
function HUDController:OwlInit()
-- > // Fetch the Service's client-side proxy (auto-generated by OwlClient)
self.DataService = Owl.GetService("DataService")
end
function HUDController:OwlStart()
-- > 1. Listen to a Signal (one-off event)
self.DataService.PointsChanged:Connect(function(newTotal)
print("My points:", newTotal)
end)
-- > 2. Observe a Property (persistent state)
-- > // Observe() fires immediately with the current value, then again on every change
self.DataService.State:Observe(function(state)
print("Current state:", state)
end)
-- > 3. Call a Client method (RemoteFunction > Promise)
self.DataService:GetMyPoints()
:andThen(function(points)
print("Points at startup:", points)
end)
:catch(warn)
end
return HUDControllerOwl.CreateController options
| Key | Type | Required | Description |
|---|---|---|---|
Name |
string |
✅ | Unique identifier, used by Owl.GetController(Name) and in log output |
Dependencies |
{string} |
- | Other Controller names this one needs initialized first, see Dependencies |
Unlike Services, Controllers don't take a Middleware or Client option, middleware is a server-side security concern and there's nothing to expose to a Service from the client.
Consuming a Service, three ways
Owl.GetService("Name") returns a client-side proxy not the real Service instance, which never leaves the server. That proxy only exposes what the Service put in its Client table and it's the same object every time you call Owl.GetService, so fetching it once in OwlInit and caching it on self (as above) is the standard pattern.
Once you have it, there are exactly three things you can do with it:
| Call | Use it for | Fires |
|---|---|---|
:Connect(fn) on a Signal |
One-off reactions a gain animation, a sound effect | Every time, going forward |
:Observe(fn) on a Property |
Anything driving a persistent UI element | Immediately with the current value, then on every change |
:MethodName(...) on a Client method |
Fetching a value on demand or triggering a server action | Once, returns a Promise |
Reach for
ObserveoverConnectany time you're syncing a UI label to server stateConnectalone would leave that label blank until the very next change, since it never fires with the value that was already set before you connected.
Where to go next
- Services - the server-side counterpart and what actually populates the
Clienttable you're consuming here. - Signals, Properties and Client Methods, a deeper look at each of the three abstractions above.
- Dependencies - how Controller
DependenciesaffectsOwlInitordering, same mechanism as Services.