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

StarterPlayerScripts/Controllers/HUDController.lua
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 HUDController

Owl.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 Observe over Connect any time you're syncing a UI label to server state Connect alone 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 Client table you're consuming here.
  • Signals, Properties and Client Methods, a deeper look at each of the three abstractions above.
  • Dependencies - how Controller Dependencies affects OwlInit ordering, same mechanism as Services.