Skip to main content

Controllers

Controllers Defined

Controllers are singleton provider objects that serve a specific purpose on the client. For instance, a game might have a CameraController, which manages a custom in-game camera for the player.

A controller is essentially the client-side equivalent of a service on the server.

For the sake of example, we will develop a CameraController.

Creating Controllers

In its simplest form, a controller can be created like so:

local CameraController = Knit.CreateController { Name = "CameraController" }

return CameraController

The Name field is required. The name is how code outside of your controller will find it. This name must be unique from all other controllers. It is best practice to name your variable the same as the controller (e.g. local CameraController matches Name = "CameraController").

The last line (return CameraController) assumes this code is written in a ModuleScript, which is best practice for containing controllers.

Adding Methods

Controllers are just simple tables at the end of the day. As such, it is very easy to add methods to controllers.

function CameraController:LockTo(part)
-- TODO: Lock camera
end

function CameraController:Unlock()
-- TODO: Unlock
end

Adding Properties

Again, controllers are just tables. We can simply add in properties as we want. Let's add a property to describe how far away our camera should be from the part we lock onto, and another to describe if the camera is currently locked:

CameraController.Distance = 20
CameraController.Locked = false

Adding Basic Behavior

Let's add some basic behavior to our controller. When the camera is locked, we should set the CurrentCamera's CameraType to Scriptable, and set the CFrame to the part. When unlocked, we should set the CameraType back to Custom. We will also utilize the Locked property so other code can check if we are currently locked to a part.

function CameraController:LockTo(part)
local cam = workspace.CurrentCamera
self.Locked = true
cam.CameraType = Enum.CameraType.Scriptable
cam.CFrame = part.CFrame * CFrame.new(0, 0, self.Distance)
end

function CameraController:Unlock()
local cam = workspace.CurrentCamera
self.Locked = false
cam.CameraType = Enum.CameraType.Custom
end

More Behavior

Right now, when we lock onto a part, we simply set the camera's CFrame once. But what if the part moves? We need to constantly set the camera's CFrame to properly lock onto the part. We can bind to RenderStep to do this.

local RunService = game:GetService("RunService")

CameraController.RenderName = "CustomCamRender"
CameraController.Priority = Enum.RenderPriority.Camera.Value

function CameraController:LockTo(part)
if (self.Locked) then return end -- Stop if already locked
local cam = workspace.CurrentCamera
self.Locked = true
cam.CameraType = Enum.CameraType.Scriptable
-- Bind to RenderStep:
RunService:BindToRenderStep(self.RenderName, self.Priority, function()
cam.CFrame = part.CFrame * CFrame.new(0, 0, self.Distance)
end)
end

function CameraController:Unlock()
if (not self.Locked) then return end -- Stop if already unlocked
local cam = workspace.CurrentCamera
self.Locked = false
cam.CameraType = Enum.CameraType.Custom
-- Unbind:
RunService:UnbindFromRenderStep(self.RenderName)
end

Events

What if we want to create an event that gets fired when the camera is locked and unlocked? We can easily do this! Just create a new signal object as a property of the controller, and fire it in our LockTo and Unlock methods. Let's created a LockedChanged signal. It will pass true when locked and false when unlocked.

local Signal = require(Knit.Util.Signal)

CameraController.LockedChanged = Signal.new()

function CameraController:LockTo(part)
-- Other code...
self.LockedChanged:Fire(true)
end

function CameraController:Unlock()
-- Other code...
self.LockedChanged:Fire(false)
end

Other code could then listen in for that event:

-- Somewhere else on the client
local CameraController = Knit.GetController("CameraController")

CameraController.LockedChanged:Connect(function(isLocked)
print(if isLocked then "Camera is now locked" else "Camera was unlocked")
end)

Server Communication

Knit allows client code to access certain server-side service methods and events that have been explicitly exposed.

See the Services: Client Communication section for more info.

An example of accessing a service on the server might look like such:

function CameraController:KnitStart()
local SomeService = Knit.GetService("SomeService")
SomeService:DoSomething()
SomeService.SomeEvent:Connect(function(...) end)
SomeService.AnotherEvent:Fire("Some data")
end
No client table forces server-only mode

If the Client table is omitted from the service, the service will be interpreted as server-side only. This means that the client will not be able to access the service using Knit.GetService on the client if there is no Client table present.

KnitInit and KnitStart

The KnitInit and KnitStart methods are optional lifecycle methods that can be added to any controller. For more info, check out the service version of this section (which has the same behavior) and the execution model.

These methods can be added just like any other method:

function CameraController:KnitStart()
print("CameraController KnitStart called")
end

function CameraController:KnitInit()
print("CameraController KnitInit called")
end