Jecs
This setup guide is only a suggestion, you can pick and choose which Plugins you use with Planck or how you use Planck and setup your project.
Recommended project structure
ReplicatedStorage/ ├─ Packages/ │ ├─ Jabby.luau │ ├─ Jecs.luau │ ├─ Planck.luau │ ├─ PlanckJabby.luau ├─ client/ │ ├─ systems/ ├─ shared/ │ ├─ systems/ │ ├─ components.luau │ ├─ scheduler.luau │ ├─ startup.luau │ ├─ world.luau
ServerScriptService/ ├─ server/ │ ├─ systems/ │ ├─ server.server.luau
StarterPlayerScripts/ ├─ client.client.luau
[dependencies]
Jabby = "alicesaidhi/jabby@0.2.0-rc.8"
Jecs = "ukendio/jecs@0.5.3"
Planck = "yetanotherclown/planck@0.2.0-rc.2"
PlanckJabby = "yetanotherclown/planck-jabby@0.2.0-rc.1"
PlanckRunService = "yetanotherclown/planck-runservice@0.2.0-rc.1"
MatterHooks = "yetanotherclown/planck-matter-hooks@0.2.0-rc.1"
Creating the World
First, we'll create a module called world.luau
where we create and export our Jecs World.
local Jecs = require("@packages/Jecs")
local World = Jecs.World
local world = World.new()
return world
Creating the Scheduler
Next, we'll create a module called scheduler.luau
where we create and export our scheduler.
local Planck = require("@packages/Planck")
local Scheduler = Planck.Scheduler
local scheduler = scheduler.new()
return scheduler
Then lets pass the world to our scheduler
local Planck = require("@packages/Planck")
local Scheduler = Planck.Scheduler
local world = require("@shared/world")
local scheduler = scheduler.new(world)
return scheduler
And then let's add the Jabby and RunService Plugins
local Planck = require("@packages/Planck")
local Scheduler = Planck.Scheduler
local world = require("@shared/world")
local PlanckJabby = require("@packages/PlanckJabby")
local jabbyPlugin = PlanckJabby.new()
local PlanckRunService = require("@packages/PlanckRunService")
local runServicePlugin = PlanckRunService.Plugin.new()
local scheduler = scheduler.new(world)
:addPlugin(jabbyPlugin)
:addPlugin(runServicePlugin)
return scheduler
This next part is up to preference, if you would like to use the Matter topoRuntime to run Matter Hooks with Jecs you can follow add the Matter Hooks Plugin too.
local Planck = require("@packages/Planck")
local Scheduler = Planck.Scheduler
local world = require("@shared/world")
local PlanckJabby = require("@packages/PlanckJabby")
local jabbyPlugin = PlanckJabby.new()
local MatterHooks = require("@packages/MatterHooks")
local hooksPlugin = MatterHooks.new()
local scheduler = scheduler.new(world)
:addPlugin(jabbyPlugin)
:addPlugin(hooksPlugin)
return scheduler
Making Components
We'll store our Jecs components in a components.luau
module.
local world = require("@shared/world")
return {
Name = world:component(),
Eats = world:component(),
Apples = world:component(),
Oranges = world:component(),
}
Creating Your First Systems
Let's create a basic system with Planck + Jecs
This system will setup our initial entities, hence it running on the Startup
phase.
local Jecs = require("@packages/Jecs")
local pair = Jecs.pair
local Planck = require("@packages/Planck")
local Phase = Planck.Phase
local components = require("@shared/components")
local Name = components.Name
local Eats = components.Eats
local Apples = components.Apples
local Oranges = components.Oranges
local function systemA(world)
world:set(Apples, Name, "apples")
world:set(Oranges, Name, "oranges")
local bob = world:entity()
world:set(bob, pair(Eats, Apples), 10)
world:set(bob, pair(Eats, Oranges), 5)
world:set(bob, Name, "bob")
local alice = world:entity()
world:set(alice, pair(Eats, Apples), 4)
world:set(alice, Name, "alice")
end
return {
system = systemA
phase = Phase.Startup
}
This next system will query each frame and print out which entity eats what.
local Jecs = require("@packages/Jecs")
local pair = Jecs.pair
local Wildcard = Jecs.Wildcard
local components = require("@shared/components")
local Name = components.Name
local Eats = components.Eats
local function systemB(world)
for id, amount in world:query(pair(Eats, Wildcard)) do
local food = world:target(id, Eats)
local foodName = world:get(food, Name)
local entityName = world:get(id, Name)
print(string.format("%s eats %d %s", entityName, amount, foodName))
end
end
return systemB
Notice how you can define systems as either a function or a table!
While you can set the phase directly in Scheduler:addSystem(fn, phase)
,
it may be convenient to use a System Table instead.
Notice how we pass world
into our system functions instead of requiring the
module we made.
We do this to keep systems pure, we avoid external dependencies by passing our dependencies as function parameters. This makes our systems more testable and reusable.
Your Startup Function
Your Startup function is where you first require the world and scheduler modules, where you would add your systems to the scheduler, and where we will setup Jabby.
local Jabby = require("@packages/Jabby")
local scheduler = require("@shared/scheduler")
local world = require("@shared/world")
return function(systems)
if #systems ~= 0 then
scheduler:addSystems(systems) -- Assuming you're using SystemTables!
end
if RunService:IsClient() then
local client = Jabby.obtain_client()
local function createWidget(_, state: Enum.UserInputState)
if state ~= Enum.UserInputState.Begin then
return
end
client.spawn_app(client.apps.home, nil)
end
ContextActionService:BindAction("Open Jabby", createWidget, false, Enum.KeyCode.F4)
end
Jabby.register({
applet = Jabby.applets.world,
name = "Jecs World",
configuration = {
world = world
}
})
end
Server / Client Scripts
On the client, we'll add the shared
and client
systems.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local startup = require("@shared/startup")
local systems = {}
local function addSystems(folder)
for _, system in folder:GetChildren() do
if not system:IsA("ModuleScript") then
continue
end
table.insert(systems, require(system))
end
end
addSystems(ReplicatedStorage.shared.systems)
addSystems(ReplicatedStorage.client.systems)
startup(systems)
On the server, we'll instead add the server
systems.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local startup = require("@shared/startup")
local systems = {}
local function addSystems(folder)
for _, system in folder:GetChildren() do
if not system:IsA("ModuleScript") then
continue
end
table.insert(systems, require(system))
end
end
addSystems(ReplicatedStorage.shared.systems)
addSystems(ReplicatedStorage.server.systems)
startup(systems)
Credits
This setup guide was heavily influenced by official Jecs examples and documentation.