Skip to main content

Jecs

note

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

note

The versions for ukendio/jecs and alicesaidhi/jabby may not be the latest versions, only the version used and confirmed to work with this setup guide.

Check the respective repositories for Jecs and Jabby for updates.

wally.toml
[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.

ReplicatedStorage/shared/world.luau
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.

ReplicatedStorage/shared/scheduler.luau
local Planck = require("@packages/Planck")
local Scheduler = Planck.Scheduler

local scheduler = scheduler.new()

return scheduler

Then lets pass the world to our scheduler

ReplicatedStorage/shared/scheduler.luau
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

ReplicatedStorage/shared/scheduler.luau
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.

ReplicatedStorage/shared/scheduler.luau
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.

ReplicatedStorage/shared/components.luau
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.

ReplicatedStorage/shared/systems/systemA.luau
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.

ReplicatedStorage/shared/systems/systemB.luau
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.

note

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.

ReplicatedStorage/shared/startup.luau
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.

ReplicatedStorage/client/client.client.luau
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.

ServerScriptService/server/server.server.luau
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.