mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 01:20:04 +00:00
Add systems to demo
This commit is contained in:
parent
ecd7b9f89e
commit
52060dbb06
21 changed files with 489 additions and 152 deletions
|
@ -3,24 +3,15 @@
|
|||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ReplicatedStorage": {
|
||||
"Shared": {
|
||||
"$path": "demo/src/shared"
|
||||
},
|
||||
"$className": "ReplicatedStorage",
|
||||
"$path": "demo/src/ReplicatedStorage",
|
||||
"ecs": {
|
||||
"$path": "src"
|
||||
}
|
||||
},
|
||||
"ServerScriptService": {
|
||||
"Server": {
|
||||
"$path": "demo/src/server"
|
||||
}
|
||||
},
|
||||
"StarterPlayer": {
|
||||
"StarterPlayerScripts": {
|
||||
"Client": {
|
||||
"$path": "demo/src/client"
|
||||
}
|
||||
}
|
||||
"$className": "ServerScriptService",
|
||||
"$path": "demo/src/ServerScriptService"
|
||||
},
|
||||
"Workspace": {
|
||||
"$properties": {
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
# example
|
||||
Generated by [Rojo](https://github.com/rojo-rbx/rojo) 7.4.1.
|
||||
# Demo
|
||||
|
||||
## Getting Started
|
||||
To build the place from scratch, use:
|
||||
## Build with Rojo
|
||||
To build the place, run the following commands from the root of the repository:
|
||||
|
||||
```bash
|
||||
rojo build -o "example.rbxlx"
|
||||
cd demo
|
||||
rojo build -o "demo.rbxl"
|
||||
```
|
||||
|
||||
Next, open `example.rbxlx` in Roblox Studio and start the Rojo server:
|
||||
Next, open `demo.rbxl` in Roblox Studio and start the Rojo server:
|
||||
|
||||
```bash
|
||||
rojo serve
|
||||
```
|
||||
|
||||
For more help, check out [the Rojo documentation](https://rojo.space/docs).
|
40
demo/src/ReplicatedStorage/std/bt.luau
Normal file
40
demo/src/ReplicatedStorage/std/bt.luau
Normal file
|
@ -0,0 +1,40 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
|
||||
-- original author @centau
|
||||
|
||||
local SUCCESS = 0
|
||||
local FAILURE = 1
|
||||
local RUNNING = 2
|
||||
|
||||
local function SEQUENCE(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == FAILURE or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
|
||||
local function FALLBACK(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == SUCCESS or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
|
||||
local bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING
|
||||
}
|
||||
|
||||
return bt
|
|
@ -1,73 +1,8 @@
|
|||
--!optimize 2
|
||||
--!native
|
||||
|
||||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
|
||||
type World = jecs.WorldShim
|
||||
type Entity<T = any> = jecs.Entity<T>
|
||||
|
||||
local function panic(str)
|
||||
-- We don't want to interrupt the loop when we error
|
||||
task.spawn(error, str)
|
||||
end
|
||||
|
||||
local function Scheduler(world, ...)
|
||||
local systems = { ... }
|
||||
local systemsNames = {}
|
||||
local N = #systems
|
||||
local system
|
||||
local dt
|
||||
|
||||
for i, module in systems do
|
||||
local sys = require(module)
|
||||
systems[i] = sys
|
||||
local file, line = debug.info(2, "sl")
|
||||
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
|
||||
end
|
||||
|
||||
local function run()
|
||||
local name = systemsNames[system]
|
||||
|
||||
debug.profilebegin(name)
|
||||
debug.setmemorycategory(name)
|
||||
system(world, dt)
|
||||
debug.profileend()
|
||||
end
|
||||
|
||||
local function loop(sinceLastFrame)
|
||||
debug.profilebegin("loop()")
|
||||
|
||||
for i = N, 1, -1 do
|
||||
system = systems[i]
|
||||
|
||||
dt = sinceLastFrame
|
||||
|
||||
local didNotYield, why = xpcall(function()
|
||||
for _ in run do end
|
||||
end, debug.traceback)
|
||||
|
||||
if didNotYield then
|
||||
continue
|
||||
end
|
||||
|
||||
if string.find(why, "thread is not yieldable") then
|
||||
N -= 1
|
||||
local name = table.remove(systems, i)
|
||||
panic("Not allowed to yield in the systems."
|
||||
.. "\n"
|
||||
.. `System: {name} has been ejected`
|
||||
)
|
||||
else
|
||||
panic(why)
|
||||
end
|
||||
end
|
||||
|
||||
debug.profileend()
|
||||
debug.resetmemorycategory()
|
||||
end
|
||||
|
||||
return loop
|
||||
end
|
||||
local world = require(script.Parent.world)
|
||||
local sparse = ((world :: any) :: jecs.World).entityIndex.sparse
|
||||
type World = world.World
|
||||
|
||||
type Tracker<T> = { track: (world: World, fn: (changes: {
|
||||
added: () -> () -> (number, T),
|
||||
|
@ -97,7 +32,7 @@ local function diff(a, b)
|
|||
return false
|
||||
end
|
||||
|
||||
local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
|
||||
local function ChangeTracker<T>(T: Entity<T>): Tracker<T>
|
||||
local PreviousT = jecs.pair(jecs.Rest, T)
|
||||
local add = {}
|
||||
local added
|
||||
|
@ -142,7 +77,7 @@ local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
|
|||
id, new, old = q.next()
|
||||
end
|
||||
|
||||
local record = world.entityIndex.sparse[id]
|
||||
local record = sparse[id]
|
||||
local archetype = record.archetype
|
||||
local column = archetype.records[PreviousT].column
|
||||
local data = if is_trivial then new else table.clone(new)
|
||||
|
@ -197,62 +132,4 @@ local function ChangeTracker<T>(world, T: Entity<T>): Tracker<T>
|
|||
return tracker
|
||||
end
|
||||
|
||||
local bt
|
||||
do
|
||||
local SUCCESS = 0
|
||||
local FAILURE = 1
|
||||
local RUNNING = 2
|
||||
|
||||
local function SEQUENCE(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == FAILURE or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return SUCCESS
|
||||
end
|
||||
end
|
||||
local function FALLBACK(nodes)
|
||||
return function(...)
|
||||
for _, node in nodes do
|
||||
local status = node(...)
|
||||
if status == SUCCESS or status == RUNNING then
|
||||
return status
|
||||
end
|
||||
end
|
||||
return FAILURE
|
||||
end
|
||||
end
|
||||
bt = {
|
||||
SEQUENCE = SEQUENCE,
|
||||
FALLBACK = FALLBACK,
|
||||
RUNNING = RUNNING
|
||||
}
|
||||
end
|
||||
|
||||
local function interval(s)
|
||||
local pin
|
||||
|
||||
local function throttle()
|
||||
if not pin then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
local elapsed = os.clock() - pin > s
|
||||
if elapsed then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
return elapsed
|
||||
end
|
||||
return throttle
|
||||
end
|
||||
|
||||
return {
|
||||
Scheduler = Scheduler,
|
||||
ChangeTracker = ChangeTracker,
|
||||
interval = interval,
|
||||
BehaviorTree = bt
|
||||
}
|
||||
return ChangeTracker
|
67
demo/src/ReplicatedStorage/std/collect.luau
Normal file
67
demo/src/ReplicatedStorage/std/collect.luau
Normal file
|
@ -0,0 +1,67 @@
|
|||
--!nonstrict
|
||||
|
||||
--[[
|
||||
local signal = Signal.new() :: Signal.Signal<string, string>
|
||||
local events = collect(signal)
|
||||
local function system(world)
|
||||
for id, str1, str2 in events do
|
||||
--
|
||||
end
|
||||
end
|
||||
]]
|
||||
|
||||
--[[
|
||||
original author by @memorycode
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Michael
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
--]]
|
||||
|
||||
type Signal<T...> = { [any]: any }
|
||||
local function collect<T...>(event: Signal<T...>)
|
||||
local storage = {}
|
||||
local mt = {}
|
||||
local iter = function()
|
||||
local n = #storage
|
||||
return function()
|
||||
if n <= 0 then
|
||||
mt.__iter = nil
|
||||
return nil
|
||||
end
|
||||
|
||||
n -= 1
|
||||
return n + 1, unpack(table.remove(storage, 1) :: any)
|
||||
end
|
||||
end
|
||||
|
||||
local disconnect = event:Connect(function(...)
|
||||
table.insert(storage, { ... })
|
||||
mt.__iter = iter
|
||||
end)
|
||||
|
||||
setmetatable(storage, mt)
|
||||
return (storage :: any) :: () -> (number, T...), function()
|
||||
disconnect()
|
||||
end
|
||||
end
|
||||
|
||||
return collect
|
14
demo/src/ReplicatedStorage/std/components.luau
Normal file
14
demo/src/ReplicatedStorage/std/components.luau
Normal file
|
@ -0,0 +1,14 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local world = require(script.Parent.world)
|
||||
|
||||
local components = {
|
||||
Character = world:component(),
|
||||
Mob = world:component(),
|
||||
Model = world:component() :: jecs.Entity<Model>,
|
||||
Player = world:component(),
|
||||
Target = world:component(),
|
||||
Transform = world:component(),
|
||||
Velocity = world:component(),
|
||||
}
|
||||
|
||||
return table.freeze(components)
|
11
demo/src/ReplicatedStorage/std/ctx.luau
Normal file
11
demo/src/ReplicatedStorage/std/ctx.luau
Normal file
|
@ -0,0 +1,11 @@
|
|||
local world = require(script.Parent.world)
|
||||
local handle = require(script.Parent.handle)
|
||||
|
||||
local singleton = world:entity()
|
||||
|
||||
local function ctx()
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return handle(singleton)
|
||||
end
|
||||
|
||||
return ctx
|
52
demo/src/ReplicatedStorage/std/handle.luau
Normal file
52
demo/src/ReplicatedStorage/std/handle.luau
Normal file
|
@ -0,0 +1,52 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local world = require(script.Parent.world)
|
||||
|
||||
type Handle = {
|
||||
has: (self: Handle, id: jecs.Entity) -> boolean,
|
||||
get: <T>(self: Handle, id: jecs.Entity<T>) -> T?,
|
||||
add: <T>(self: Handle, id: jecs.Entity<T>) -> Handle,
|
||||
set: <T>(self: Handle, id: jecs.Entity<T>, value: T) -> Handle
|
||||
}
|
||||
|
||||
local handle: (e: jecs.Entity) -> Handle
|
||||
|
||||
do
|
||||
local e
|
||||
local function has(_, id)
|
||||
return world:has(e, id)
|
||||
end
|
||||
local function get(_, id)
|
||||
return world:get(e, id)
|
||||
end
|
||||
local function set(self, id, value)
|
||||
world:set(e, id, value)
|
||||
return self
|
||||
end
|
||||
local function add(self, id)
|
||||
world:add(e, id)
|
||||
return self
|
||||
end
|
||||
local function clear(self)
|
||||
world:clear(e)
|
||||
return self
|
||||
end
|
||||
local function id()
|
||||
return e
|
||||
end
|
||||
|
||||
local entity = {
|
||||
has = has,
|
||||
get = get,
|
||||
set = set,
|
||||
add = add,
|
||||
clear = clear,
|
||||
id = id,
|
||||
}
|
||||
|
||||
function handle(id)
|
||||
e = id
|
||||
return entity
|
||||
end
|
||||
end
|
||||
|
||||
return handle
|
20
demo/src/ReplicatedStorage/std/init.luau
Normal file
20
demo/src/ReplicatedStorage/std/init.luau
Normal file
|
@ -0,0 +1,20 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
local world = require(script.world)
|
||||
export type World = world.World
|
||||
|
||||
local std = {
|
||||
ChangeTracker = require(script.changetracker),
|
||||
Scheduler = require(script.scheduler),
|
||||
bt = require(script.bt),
|
||||
collect = require(script.collect),
|
||||
components = require(script.components),
|
||||
ctx = require(script.ctx),
|
||||
handle = require(script.handle),
|
||||
interval = require(script.interval),
|
||||
ref = require(script.ref),
|
||||
world = world,
|
||||
pair = jecs.pair,
|
||||
__ = jecs.w,
|
||||
}
|
||||
|
||||
return std
|
19
demo/src/ReplicatedStorage/std/interval.luau
Normal file
19
demo/src/ReplicatedStorage/std/interval.luau
Normal file
|
@ -0,0 +1,19 @@
|
|||
local function interval(s)
|
||||
local pin
|
||||
|
||||
local function throttle()
|
||||
if not pin then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
local elapsed = os.clock() - pin > s
|
||||
if elapsed then
|
||||
pin = os.clock()
|
||||
end
|
||||
|
||||
return elapsed
|
||||
end
|
||||
return throttle
|
||||
end
|
||||
|
||||
return interval
|
18
demo/src/ReplicatedStorage/std/ref.luau
Normal file
18
demo/src/ReplicatedStorage/std/ref.luau
Normal file
|
@ -0,0 +1,18 @@
|
|||
local world = require(script.Parent.world)
|
||||
local handle = require(script.Parent.handle)
|
||||
local refs = {}
|
||||
|
||||
local function ref(key)
|
||||
if not key then
|
||||
return handle(world:entity())
|
||||
end
|
||||
local e = refs[key]
|
||||
if not e then
|
||||
e = world:entity()
|
||||
refs[key] = e
|
||||
end
|
||||
-- Cannot cache handles because they will get invalidated
|
||||
return handle(e)
|
||||
end
|
||||
|
||||
return ref
|
64
demo/src/ReplicatedStorage/std/scheduler.luau
Normal file
64
demo/src/ReplicatedStorage/std/scheduler.luau
Normal file
|
@ -0,0 +1,64 @@
|
|||
local function panic(str)
|
||||
-- We don't want to interrupt the loop when we error
|
||||
task.spawn(error, str)
|
||||
end
|
||||
|
||||
local function Scheduler(...)
|
||||
local systems = { ... }
|
||||
local systemsNames = {}
|
||||
local N = #systems
|
||||
local system
|
||||
local dt
|
||||
|
||||
for i, module in systems do
|
||||
local sys = require(module)
|
||||
systems[i] = sys
|
||||
local file, line = debug.info(2, "sl")
|
||||
systemsNames[sys] = `{file}->::{line}::->{debug.info(sys, "n")}`
|
||||
end
|
||||
|
||||
local function run()
|
||||
local name = systemsNames[system]
|
||||
|
||||
debug.profilebegin(name)
|
||||
debug.setmemorycategory(name)
|
||||
system(dt)
|
||||
debug.profileend()
|
||||
end
|
||||
|
||||
local function loop(sinceLastFrame)
|
||||
debug.profilebegin("loop()")
|
||||
|
||||
for i = N, 1, -1 do
|
||||
system = systems[i]
|
||||
|
||||
dt = sinceLastFrame
|
||||
|
||||
local didNotYield, why = xpcall(function()
|
||||
for _ in run do end
|
||||
end, debug.traceback)
|
||||
|
||||
if didNotYield then
|
||||
continue
|
||||
end
|
||||
|
||||
if string.find(why, "thread is not yieldable") then
|
||||
N -= 1
|
||||
local name = table.remove(systems, i)
|
||||
panic("Not allowed to yield in the systems."
|
||||
.. "\n"
|
||||
.. `System: {name} has been ejected`
|
||||
)
|
||||
else
|
||||
panic(why)
|
||||
end
|
||||
end
|
||||
|
||||
debug.profileend()
|
||||
debug.resetmemorycategory()
|
||||
end
|
||||
|
||||
return loop
|
||||
end
|
||||
|
||||
return Scheduler
|
4
demo/src/ReplicatedStorage/std/world.luau
Normal file
4
demo/src/ReplicatedStorage/std/world.luau
Normal file
|
@ -0,0 +1,4 @@
|
|||
local jecs = require(game:GetService("ReplicatedStorage").ecs)
|
||||
export type World = jecs.WorldShim
|
||||
|
||||
return jecs.World.new()
|
4
demo/src/ServerScriptService/main.server.luau
Normal file
4
demo/src/ServerScriptService/main.server.luau
Normal file
|
@ -0,0 +1,4 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
local std = require(ReplicatedStorage.std)
|
||||
local loop = std.Scheduler(unpack(script.Parent.systems:GetChildren()))
|
||||
game:GetService("RunService").Heartbeat:Connect(loop)
|
54
demo/src/ServerScriptService/systems/mobsMove.luau
Normal file
54
demo/src/ServerScriptService/systems/mobsMove.luau
Normal file
|
@ -0,0 +1,54 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local jecs = require(ReplicatedStorage.ecs)
|
||||
local pair = jecs.pair
|
||||
local __ = jecs.Wildcard
|
||||
|
||||
local std = require(ReplicatedStorage.std)
|
||||
local world = std.world
|
||||
|
||||
local cts = std.components
|
||||
|
||||
local Mob = cts.Mob
|
||||
local Model = cts.Model
|
||||
local Transform = cts.Transform
|
||||
local Velocity = cts.Velocity
|
||||
local Target = cts.Target
|
||||
local Player = cts.Player
|
||||
local Character = cts.Character
|
||||
|
||||
local function mobsMove(dt: number)
|
||||
local players = world:query(Character):with(Player)
|
||||
|
||||
for mob, cf, v in world:query(Transform, Velocity)
|
||||
:with(Mob, Model)
|
||||
do
|
||||
local p = cf.Position
|
||||
|
||||
local target
|
||||
for playerId, character in players do
|
||||
local pos = character.PrimaryPart.Position
|
||||
if true then
|
||||
target = pos
|
||||
break
|
||||
end
|
||||
if not target then
|
||||
target = pos
|
||||
elseif (p - pos).Magnitude < (p - target) then
|
||||
target = pos
|
||||
end
|
||||
end
|
||||
|
||||
if not target then
|
||||
continue
|
||||
end
|
||||
|
||||
local moving = CFrame.new(p + (target - p).Unit * dt * v)
|
||||
--local record = world.entityIndex.sparse[mob]
|
||||
--local archetype = record.archetype
|
||||
--archetype.columns[archetype.records[Transform].column][record.row] = moving
|
||||
world:set(mob, Transform, moving)
|
||||
end
|
||||
end
|
||||
|
||||
return mobsMove
|
29
demo/src/ServerScriptService/systems/move.luau
Normal file
29
demo/src/ServerScriptService/systems/move.luau
Normal file
|
@ -0,0 +1,29 @@
|
|||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local std = require(ReplicatedStorage.std)
|
||||
local world = std.world
|
||||
|
||||
local cts = std.components
|
||||
|
||||
local Model = cts.Model
|
||||
local Transform = cts.Transform
|
||||
|
||||
local function move(dt: number)
|
||||
-- for i, archetype in world:query(Transform, Model):archetypes() do
|
||||
-- local columns = archetype.columns
|
||||
-- local records = archetype.records
|
||||
-- local M = columns[records[Model].column]
|
||||
-- local CF = columns[records[Transform].column]
|
||||
|
||||
-- for row, entity in archetype.entities do
|
||||
-- local model, cf = M[row], CF[row]
|
||||
|
||||
-- model.PrimaryPart.CFrame = cf
|
||||
-- end
|
||||
-- end
|
||||
for _, cf, model in world:query(Transform, Model) do
|
||||
model.PrimaryPart.CFrame = cf
|
||||
end
|
||||
end
|
||||
|
||||
return move
|
37
demo/src/ServerScriptService/systems/players.luau
Normal file
37
demo/src/ServerScriptService/systems/players.luau
Normal file
|
@ -0,0 +1,37 @@
|
|||
local Players = game:GetService("Players")
|
||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||
|
||||
local std = require(ReplicatedStorage.std)
|
||||
local ref = std.ref
|
||||
local collect = std.collect
|
||||
|
||||
local cts = std.components
|
||||
local Player = cts.Player
|
||||
local Character = cts.Character
|
||||
|
||||
local playersAdded = collect(Players.PlayerAdded)
|
||||
local playersRemoved = collect(Players.PlayerRemoving)
|
||||
|
||||
local connections = {}
|
||||
|
||||
local function players()
|
||||
for _, player in playersAdded do
|
||||
local e = ref(player.UserId):set(Player, player)
|
||||
|
||||
connections[e.id()] = player.CharacterAdded:Connect(
|
||||
function(character)
|
||||
while character.Parent ~= workspace do
|
||||
task.wait()
|
||||
end
|
||||
e:set(Character, character)
|
||||
end)
|
||||
end
|
||||
|
||||
for _, player in playersRemoved do
|
||||
local id = ref(player.UserId):clear().id()
|
||||
connections[id]:Disconnect()
|
||||
connections[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return players
|
37
demo/src/ServerScriptService/systems/spawnMobs.luau
Normal file
37
demo/src/ServerScriptService/systems/spawnMobs.luau
Normal file
|
@ -0,0 +1,37 @@
|
|||
local std = require(game:GetService("ReplicatedStorage").std)
|
||||
|
||||
local ref = std.ref
|
||||
local interval = std.interval
|
||||
local cts = std.components
|
||||
|
||||
local Mob = cts.Mob
|
||||
local Model = cts.Model
|
||||
local Transform = cts.Transform
|
||||
local Velocity = cts.Velocity
|
||||
|
||||
local throttle = interval(5)
|
||||
|
||||
local function spawnMobs(world: std.World)
|
||||
if throttle() then
|
||||
local p = Vector3.new(0, 5, 0)
|
||||
local cf = CFrame.new(p)
|
||||
local v = 5
|
||||
local part = Instance.new("Part")
|
||||
part.Anchored = true
|
||||
part.CanCollide = false
|
||||
part.BrickColor = BrickColor.Blue()
|
||||
part.Size = Vector3.one * 5
|
||||
local model = Instance.new("Model")
|
||||
part.Parent = model
|
||||
model.PrimaryPart = part
|
||||
model.Parent = workspace
|
||||
|
||||
ref()
|
||||
:set(Velocity, v)
|
||||
:set(Transform, cf)
|
||||
:set(Model, model)
|
||||
:add(Mob)
|
||||
end
|
||||
end
|
||||
|
||||
return spawnMobs
|
|
@ -1 +0,0 @@
|
|||
print("Hello world, from client!")
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -732,6 +732,9 @@ do
|
|||
replace = noop :: (Query, ...any) -> (),
|
||||
with = Arm,
|
||||
without = Arm,
|
||||
archetypes = function()
|
||||
return {}
|
||||
end
|
||||
}
|
||||
|
||||
setmetatable(EmptyQuery, EmptyQuery)
|
||||
|
|
Loading…
Reference in a new issue