Knit
Module Github Documentation
[A base game made using Knit](https://www.roblox.com/games/8272396896/Game-Knit-Game-Framework(Editing Is Enabled))
- A lightweight framework that simplifies communication between core parts of your Roblox experience and seamlessly bridges the gap between the server and the client. Getting Started
Why should you use Knit?
-
It simplifies Client-Server communication. In knit you will never be interacting directly with remote events. Client functions can be created in services and remote events will be created in the background.
-
Unlike Aero Game Framework Knit allows developers to write their bootstrapping code to start and configure Knit, which gives developers the freedom to extend how Knit functions.
-
A lot of developers donāt know how to structure their game and a lot of the time Iāve seen developers make an entire game using one script for performance benefits(Viable but not worth it). Knit structures your code using the Service(Script) Controller(LocalScript) Paradigm which scaleās quite well.
-
It modularizes your code. Knit Services and Controllers are tables by default meaning you can inject properties that will be assessable from almost anywhere.
Tutorial
- Next, we need to make the folders to contain our Services, Controllers, and Components on the server and the client
Server
Client
- Next weāre going to need the main game script to start knit on the server and the client after you place the knit module in replicated storage make a script in ServerScriptService called Runtime and a script on the client called ClientRuntime.
Server
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local Component = require(Knit.Util.Component)
Knit.AddServices(script.Parent.Services)
Component.Auto(script.Parent.Components)
Knit.Start():andThen(function()
warn("Server Started")
end):catch(warn)
Client
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local Component = require(Knit.Util.Component)
Knit.AddControllers(script.Parent:WaitForChild("Controllers"))
Component.Auto(script.Parent.Components)
Knit.Start():andThen(function()
warn("Client Started")
end):catch(warn)
Thatās it for setup next is learning to make Services, Controllers, and Components.
Services
Services are singleton provider objects that serve a specific purpose on the server. For instance, a game might have a PointsService, which manages in-game points for the players.
A game might have many services. They will serve as the backbone of a game.
Example
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local TestService = Knit.CreateService{
Name = "TestService",
Client = {}
}
function TestService:KnitInit()
print("Test Service Initialized")
end
function TestService:KnitStart()
print("Test Service Started")
end
return TestService
AdvancedExample(This example uses a module that I have added to knit util if you would like to text this here is a link
local Players = game:GetService("Players")
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local Promise = require(Knit.Util.Promise)
local SaveStructure = require(script.SaveStructure)
local ProfileService = require(Knit.Util.ProfileService)
local ReplicaService = require(Knit.Util.ReplicaService)
local Profiles = {}
local Replicas = {}
local PlayerProfileClassToken = ReplicaService.NewClassToken("PlayerData")
local ProfileStore = ProfileService.GetProfileStore("PlayerData", SaveStructure)
local DataService = Knit.CreateService {Name = "DataService"}
function DataService:GetReplica(Player: Player)
return Promise.new(function(Resolve, Reject)
assert(typeof(Player) == "Instance" and Player:IsDescendantOf(Players), "Value passed is not a valid player")
if not Profiles[Player] and not Replicas[Player] then
repeat
if Player then
wait()
else
Reject("Player left the game")
end
until Profiles[Player] and Replicas[Player]
end
local Profile = Profiles[Player]
local Replica = Replicas[Player]
if Profile and Profile:IsActive() then
if Replica and Replica:IsActive() then
Resolve(Replica)
else
Reject("Replica did not exist or wasn't active")
end
else
Reject("Profile did not exist or wasn't active")
end
end)
end
local function PlayerAdded(Player: Player)
local StartTime = tick()
local Profile = ProfileStore:LoadProfileAsync("Player_" .. Player.UserId)
if Profile then
Profile:AddUserId(Player.UserId)
Profile:Reconcile()
Profile:ListenToRelease(function()
Profiles[Player] = nil
Replicas[Player]:Destroy()
Replicas[Player]= nil
Player:Kick("Profile was released")
end)
if Player:IsDescendantOf(Players) == true then
Profiles[Player] = Profile
local Replica = ReplicaService.NewReplica({
ClassToken = PlayerProfileClassToken,
Tags = {["Player"] = Player},
Data = Profile.Data,
Replication = "All"
})
Replicas[Player] = Replica
warn(Player.Name.. "'s profile has been loaded. ".."("..string.sub(tostring(tick()-StartTime),1,5)..")")
else
Profile:Release()
end
else
Player:Kick("Profile == nil")
end
end
function DataService:KnitInit()
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(PlayerAdded, player)
end
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(function(Player)
if Profiles[Player] then
Profiles[Player]:Release()
end
end)
end
function DataService:KnitStart()
end
return DataService
Controllers
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 service on the server.
Example
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local TestController = Knit.CreateController{
Name = "TestController",
}
function TestController:KnitInit()
print("Test Controller Initialized")
end
function TestController:KnitStart()
print("Test Controller Started")
end
return TestController
Server <> Client Communication
Knit allows you to make remote events without really making remote events and the many annoyances of using WaitForChild and invoking events.
Letās make a remote event that we can listen to. Anything in the client table will be replicated to the client this is where we place events and functions. We can also have a function that can be called by the client.
Server
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local TestService = Knit.CreateService{
Name = "TestService",
Client = {
SomeThingHappened = Knit.CreateSignal(); -- Create the signal
}
}
function TestService.Client:GetPoints(player)
return 0
end
function TestService:KnitInit()
print("Test Service Initialized")
end
function TestService:KnitStart()
print("Test Service Started")
end
return TestService
Client
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local TestController = Knit.CreateController{
Name = "TestController",
}
function TestController:KnitInit()
print("Test Controller Initialized")
end
function TestController:KnitStart()
local TestService = Knit.GetService("TestService")
local function SomeThingHappened(points)
print("My points:", points)
end
TestService:GetPoints():andThen(PointsChanged)
TestService.SomeThingHappened:Connect(SomeThingHappened)
PointsService.GiveMePoints:Fire() end
print("Test Controller Started")
return TestController
Components
Bind components to Roblox instances using the Component class and CollectionService tags.
Components can be placed in the server or the client depending on what it isā¦
Server/Client
--You need to have the tag editor plugin here is the link https://www.roblox.com/library/948084095/Tag-Editor
local Knit = require(game:GetService("ReplicatedStorage").Packages.Knit)
local Component = require(Knit.Util.Component)
local MyComponent = {}
MyComponent.__index = MyComponent
MyComponent.Tag = "MyComponent"
-- CONSTRUCTOR
function MyComponent.new(instance)
local self = setmetatable({}, MyComponent)
-- Add more properties to self before returning like self.TimeCreated = tick()
return self
end
-- OPTIONAL LIFECYCLE HOOKS
function MyComponent:Init()--> Called right after constructor
local instance:Part = self.Instance
instance.Color = Color3.fromRGB(51, 255, 0)
print(MyComponent.Tag.. " has been applied to ".. self.Instance.Name.. " on the client")
-- Self.Instance --> instance with tag
end
function MyComponent:Deinit()--> Called right before deconstructor
-- Self.Instance --> instance with tag
end
function MyComponent:HeartbeatUpdate(DeltaTime) end --> Updates every heartbeat
function MyComponent:SteppedUpdate(DeltaTime) end --> Updates every physics step
function MyComponent:RenderUpdate(DeltaTime) end --> Updates every render step
-- DESTRUCTOR
function MyComponent:Destroy()
--> Clean up connections or better yet use trove in the knit util folder
end
return MyComponent
Miscellaneous Game Development Tools
https://www.roblox.com/library/5012883501/SwapUI
https://www.roblox.com/library/1614059092/Instance-Serializer
https://www.roblox.com/library/4987562465/InCommand-versatile-adaptable-command-execution
https://www.roblox.com/library/3619526449/Get-Object-Path
https://www.roblox.com/library/4369062118/Easy-Crop