First project-specific commit.
Hardly a game yet, but extremely basic ball movements work!
After Width: | Height: | Size: 772 B |
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 901 B |
After Width: | Height: | Size: 889 B |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 857 B |
After Width: | Height: | Size: 857 B |
After Width: | Height: | Size: 915 B |
After Width: | Height: | Size: 726 B |
|
@ -5,4 +5,5 @@ require("../systems/decay")
|
|||
require("../systems/draw")
|
||||
require("../systems/gravity")
|
||||
require("../systems/input")
|
||||
require("../systems/rounds")
|
||||
require("../systems/velocity")
|
||||
|
|
|
@ -1,11 +1,47 @@
|
|||
-- GENERATED FILE - DO NOT EDIT
|
||||
-- Instead, edit the source file directly: assets.lua2p.
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
Ball = love.graphics.newImage("assets/images/Ball.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
Flag = love.graphics.newImage("assets/images/Flag.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
GolferRightActivated = love.graphics.newImage("assets/images/GolferRightActivated.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
GolferRight = love.graphics.newImage("assets/images/GolferRight.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
Grass = love.graphics.newImage("assets/images/Grass.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
SandTrap = love.graphics.newImage("assets/images/SandTrap.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
SmallSandTrapActivated = love.graphics.newImage("assets/images/SmallSandTrapActivated.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
SmallSandTrap = love.graphics.newImage("assets/images/SmallSandTrap.png")
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type love.Texture
|
||||
StartButton = love.graphics.newImage("assets/images/StartButton.png")
|
||||
|
||||
|
||||
|
||||
|
||||
-- luacheck: ignore
|
||||
---@type FontData
|
||||
---@type fun(fontSize: number | nil): love.Font
|
||||
EtBt7001Z0xa = function(fontSize)
|
||||
return love.graphics.newFont("assets/fonts/EtBt7001Z0xa.ttf", fontSize)
|
||||
end
|
||||
|
|
|
@ -25,9 +25,11 @@ function generatedFileWarning()
|
|||
return "-- GENERATED FILE - DO NOT EDIT\n-- Instead, edit the source file directly: assets.lua2p."
|
||||
end)!!(generatedFileWarning())
|
||||
|
||||
!!(dirLookup('assets/images', 'png', 'love.graphics.newImage', 'Image'))
|
||||
!!(dirLookup('assets/sounds', 'wav', 'love.sound.newSoundData', 'SoundData'))
|
||||
!!(dirLookup('assets/music', 'wav', 'love.sound.newSoundData', 'SoundData'))
|
||||
!!(dirLookup('assets/fonts', 'ttf', 'love.graphics.newFont', 'FontData', function(varName, newFunc, file)
|
||||
!!(dirLookup('assets/images', 'png', 'love.graphics.newImage', 'love.Texture'))
|
||||
!!(dirLookup('assets/sounds', 'ogg', 'love.audio.newSource', 'love.Source', function(varName, newFunc, file)
|
||||
return varName .. ' = ' .. newFunc .. '("' .. file .. '", "static")'
|
||||
end))
|
||||
!!(dirLookup('assets/music', 'wav', 'love.sound.newSoundData', 'love.SoundData'))
|
||||
!!(dirLookup('assets/fonts', 'ttf', 'love.graphics.newFont', 'fun(fontSize: number | nil): love.Font', function(varName, newFunc, file)
|
||||
return varName .. ' = function(fontSize)\n return ' .. newFunc .. '("' .. file .. '", fontSize)\nend'
|
||||
end))
|
||||
|
|
1088
lib/luaunit.lua
|
@ -175,14 +175,14 @@ Handler messages:
|
|||
]]
|
||||
--==============================================================
|
||||
|
||||
|
||||
|
||||
local startTime = os.time()
|
||||
local startClock = os.clock()
|
||||
|
||||
local args = arg
|
||||
|
||||
if not args[0] then error("Expected to run from the Lua interpreter.") end
|
||||
if not args[0] then
|
||||
error("Expected to run from the Lua interpreter.")
|
||||
end
|
||||
local pp = dofile((args[0]:gsub("[^/\\]+$", "preprocess.lua")))
|
||||
|
||||
-- From args:
|
||||
|
@ -217,7 +217,7 @@ local function formatBytes(n)
|
|||
elseif n >= 1024 * 1024 then
|
||||
return F("%.2f MiB", n / (1024 * 1024))
|
||||
elseif n >= 1024 then
|
||||
return F("%.2f KiB", n/(1024))
|
||||
return F("%.2f KiB", n / 1024)
|
||||
elseif n == 1 then
|
||||
return F("1 byte", n)
|
||||
else
|
||||
|
@ -246,9 +246,13 @@ local loadLuaFile = (
|
|||
end
|
||||
or function(path, env)
|
||||
local chunk, err = loadfile(path)
|
||||
if not chunk then return nil, err end
|
||||
if not chunk then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if env then setfenv(chunk, env) end
|
||||
if env then
|
||||
setfenv(chunk, env)
|
||||
end
|
||||
|
||||
return chunk
|
||||
end
|
||||
|
@ -270,59 +274,46 @@ local pathsIn = {}
|
|||
local pathsOut = {}
|
||||
|
||||
for _, arg in ipairs(args) do
|
||||
if processOptions and (arg:find"^%-%-?help$" or arg == "/?" or arg:find"^/[Hh][Ee][Ll][Pp]$") then
|
||||
if processOptions and (arg:find("^%-%-?help$") or arg == "/?" or arg:find("^/[Hh][Ee][Ll][Pp]$")) then
|
||||
print("LuaPreprocess v" .. pp.VERSION)
|
||||
print((help:gsub("\t", " ")))
|
||||
os.exit()
|
||||
|
||||
elseif not (processOptions and arg:find"^%-.") then
|
||||
elseif not (processOptions and arg:find("^%-.")) then
|
||||
local paths = (hasOutputPaths and #pathsOut < #pathsIn) and pathsOut or pathsIn
|
||||
table.insert(paths, arg)
|
||||
|
||||
if arg == "-" and (not hasOutputPaths or paths == pathsOut) then
|
||||
silent = true
|
||||
end
|
||||
|
||||
elseif arg == "--" then
|
||||
processOptions = false
|
||||
|
||||
elseif arg:find"^%-%-data=" or arg:find"^%-d=" then
|
||||
elseif arg:find("^%-%-data=") or arg:find("^%-d=") then
|
||||
customData = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--backtickstrings" then
|
||||
allowBacktickStrings = true
|
||||
|
||||
elseif arg == "--debug" then
|
||||
isDebug = true
|
||||
outputMeta = outputMeta or true
|
||||
|
||||
elseif arg:find"^%-%-handler=" or arg:find"^%-h=" then
|
||||
elseif arg:find("^%-%-handler=") or arg:find("^%-h=") then
|
||||
messageHandlerPath = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--jitsyntax" then
|
||||
allowJitSyntax = true
|
||||
|
||||
elseif arg == "--linenumbers" then
|
||||
addLineNumbers = true
|
||||
|
||||
elseif arg == "--meta" then
|
||||
outputMeta = true
|
||||
elseif arg:find"^%-%-meta=" then
|
||||
elseif arg:find("^%-%-meta=") then
|
||||
outputMeta = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--nonil" then
|
||||
canOutputNil = false
|
||||
|
||||
elseif arg == "--novalidate" then
|
||||
validate = false
|
||||
|
||||
elseif arg:find"^%-%-outputextension=" then
|
||||
elseif arg:find("^%-%-outputextension=") then
|
||||
if hasOutputPaths then
|
||||
errorLine("Cannot specify both --outputextension and --outputpaths")
|
||||
end
|
||||
hasOutputExtension = true
|
||||
outputExtension = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--outputpaths" or arg == "-o" then
|
||||
if hasOutputExtension then
|
||||
errorLine("Cannot specify both --outputpaths and --outputextension")
|
||||
|
@ -330,38 +321,27 @@ for _, arg in ipairs(args) do
|
|||
errorLine(arg .. " must appear before any input path.")
|
||||
end
|
||||
hasOutputPaths = true
|
||||
|
||||
elseif arg:find"^%-%-saveinfo=" or arg:find"^%-i=" then
|
||||
elseif arg:find("^%-%-saveinfo=") or arg:find("^%-i=") then
|
||||
processingInfoPath = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--silent" then
|
||||
silent = true
|
||||
|
||||
elseif arg == "--faststrings" then
|
||||
fastStrings = true
|
||||
|
||||
elseif arg == "--nogc" then
|
||||
collectgarbage("stop")
|
||||
|
||||
elseif arg:find"^%-%-macroprefix=" then
|
||||
elseif arg:find("^%-%-macroprefix=") then
|
||||
macroPrefix = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg:find"^%-%-macrosuffix=" then
|
||||
elseif arg:find("^%-%-macrosuffix=") then
|
||||
macroSuffix = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--release" then
|
||||
releaseMode = true
|
||||
|
||||
elseif arg:find"^%-%-loglevel=" then
|
||||
elseif arg:find("^%-%-loglevel=") then
|
||||
maxLogLevel = arg:gsub("^.-=", "")
|
||||
|
||||
elseif arg == "--version" then
|
||||
io.stdout:write(pp.VERSION)
|
||||
os.exit()
|
||||
|
||||
elseif arg == "--nostrictmacroarguments" then
|
||||
strictMacroArguments = false
|
||||
|
||||
else
|
||||
errorLine("Unknown option '" .. arg:gsub("=.*", "") .. "'.")
|
||||
end
|
||||
|
@ -380,26 +360,19 @@ if hasOutputPaths and #pathsOut < #pathsIn then
|
|||
errorLine("Missing output path for " .. pathsIn[#pathsIn])
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Prepare metaEnvironment.
|
||||
pp.metaEnvironment.dataFromCommandLine = customData -- May be nil.
|
||||
|
||||
|
||||
|
||||
-- Load message handler.
|
||||
local messageHandler = nil
|
||||
|
||||
local function hasMessageHandler(message)
|
||||
if not messageHandler then
|
||||
return false
|
||||
|
||||
elseif type(messageHandler) == "function" then
|
||||
return true
|
||||
|
||||
elseif type(messageHandler) == "table" then
|
||||
return messageHandler[message] ~= nil
|
||||
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
|
@ -408,18 +381,17 @@ end
|
|||
local function sendMessage(message, ...)
|
||||
if not messageHandler then
|
||||
return
|
||||
|
||||
elseif type(messageHandler) == "function" then
|
||||
local returnValues = pp.pack(messageHandler(message, ...))
|
||||
return pp.unpack(returnValues, 1, returnValues.n)
|
||||
|
||||
elseif type(messageHandler) == "table" then
|
||||
local _messageHandler = messageHandler[message]
|
||||
if not _messageHandler then return end
|
||||
if not _messageHandler then
|
||||
return
|
||||
end
|
||||
|
||||
local returnValues = pp.pack(_messageHandler(...))
|
||||
return pp.unpack(returnValues, 1, returnValues.n)
|
||||
|
||||
else
|
||||
assert(false)
|
||||
end
|
||||
|
@ -450,8 +422,6 @@ if messageHandlerPath ~= "" then
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Init stuff.
|
||||
sendMessage("init", pathsIn, (hasOutputPaths and pathsOut or nil)) -- @Incomplete: Use pcall and format error message better?
|
||||
|
||||
|
@ -471,17 +441,23 @@ local pathsSetIn = {}
|
|||
local pathsSetOut = {}
|
||||
|
||||
for i = 1, #pathsIn do
|
||||
if pathsSetIn [pathsIn [i]] then errorLine("Duplicate input path: " ..pathsIn [i]) end
|
||||
if pathsSetOut[pathsOut[i]] then errorLine("Duplicate output path: "..pathsOut[i]) end
|
||||
if pathsSetIn[pathsIn[i]] then
|
||||
errorLine("Duplicate input path: " .. pathsIn[i])
|
||||
end
|
||||
if pathsSetOut[pathsOut[i]] then
|
||||
errorLine("Duplicate output path: " .. pathsOut[i])
|
||||
end
|
||||
|
||||
pathsSetIn[pathsIn[i]] = true
|
||||
pathsSetOut[pathsOut[i]] = true
|
||||
|
||||
if pathsIn [i] ~= "-" and pathsSetOut[pathsIn [i]] then errorLine("Path is both input and output: "..pathsIn [i]) end
|
||||
if pathsOut[i] ~= "-" and pathsSetIn [pathsOut[i]] then errorLine("Path is both input and output: "..pathsOut[i]) end
|
||||
if pathsIn[i] ~= "-" and pathsSetOut[pathsIn[i]] then
|
||||
errorLine("Path is both input and output: " .. pathsIn[i])
|
||||
end
|
||||
if pathsOut[i] ~= "-" and pathsSetIn[pathsOut[i]] then
|
||||
errorLine("Path is both input and output: " .. pathsOut[i])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Process files.
|
||||
|
||||
|
@ -507,7 +483,7 @@ for i, pathIn in ipairs(pathsIn) do
|
|||
pathMeta = nil
|
||||
end
|
||||
|
||||
local info, err = pp.processFile{
|
||||
local info, err = pp.processFile({
|
||||
pathIn = pathIn,
|
||||
pathMeta = pathMeta,
|
||||
pathOut = pathOut,
|
||||
|
@ -549,17 +525,20 @@ for i, pathIn in ipairs(pathsIn) do
|
|||
sendMessage("beforemeta", pathIn, lua)
|
||||
end,
|
||||
|
||||
onAfterMeta = messageHandler and function(lua)
|
||||
onAfterMeta = messageHandler
|
||||
and function(lua)
|
||||
local luaModified = sendMessage("aftermeta", pathIn, lua)
|
||||
|
||||
if type(luaModified) == "string" then
|
||||
lua = luaModified
|
||||
|
||||
elseif luaModified ~= nil then
|
||||
error(F(
|
||||
error(
|
||||
F(
|
||||
"%s: Message handler did not return a string for 'aftermeta'. (Got %s)",
|
||||
messageHandlerPath, type(luaModified)
|
||||
))
|
||||
messageHandlerPath,
|
||||
type(luaModified)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
return lua
|
||||
|
@ -577,7 +556,7 @@ for i, pathIn in ipairs(pathsIn) do
|
|||
end)
|
||||
os.exit(1)
|
||||
end,
|
||||
}
|
||||
})
|
||||
assert(info, err) -- The onError() handler above should have been called and we should have exited already.
|
||||
|
||||
byteCount = byteCount + info.processedByteCount
|
||||
|
@ -586,18 +565,14 @@ for i, pathIn in ipairs(pathsIn) do
|
|||
tokenCount = tokenCount + info.tokenCount
|
||||
|
||||
if processingInfoPath ~= "" then
|
||||
|
||||
-- :SavedInfo
|
||||
table.insert(processingInfo.files, info) -- See 'ProcessInfo' in preprocess.lua for what more 'info' contains.
|
||||
|
||||
end
|
||||
|
||||
printfNoise("Processing '%s' successful! (%.3fs)", pathIn, os.clock() - startClockForPath)
|
||||
printfNoise(("-"):rep(#header))
|
||||
end
|
||||
|
||||
|
||||
|
||||
-- Finalize stuff.
|
||||
if processingInfoPath ~= "" then
|
||||
printfNoise("Saving processing info to '%s'.", processingInfoPath)
|
||||
|
@ -614,17 +589,18 @@ end
|
|||
printfNoise(
|
||||
"All done! (%.3fs, %.0f file%s, %.0f LOC, %.0f line%s, %.0f token%s, %s)",
|
||||
os.clock() - startClock,
|
||||
#pathsIn, (#pathsIn == 1) and "" or "s",
|
||||
#pathsIn,
|
||||
(#pathsIn == 1) and "" or "s",
|
||||
lineCountCode,
|
||||
lineCount, (lineCount == 1) and "" or "s",
|
||||
tokenCount, (tokenCount == 1) and "" or "s",
|
||||
lineCount,
|
||||
(lineCount == 1) and "" or "s",
|
||||
tokenCount,
|
||||
(tokenCount == 1) and "" or "s",
|
||||
formatBytes(byteCount)
|
||||
)
|
||||
|
||||
sendMessage("alldone") -- @Incomplete: Use pcall and format error message better?
|
||||
|
||||
|
||||
|
||||
--[[!===========================================================
|
||||
|
||||
Copyright © 2018-2022 Marcus 'ReFreezed' Thunström
|
||||
|
@ -648,4 +624,3 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
SOFTWARE.
|
||||
|
||||
==============================================================]]
|
||||
|
||||
|
|
1497
lib/preprocess.lua
67
lib/tiny.lua
|
@ -30,7 +30,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
---@field indices table<any, any> field is a table of Entity keys to their indices in the entities list. Most Systems can ignore this.
|
||||
---@field modified boolean indicator for if the System has been modified in the last update. If so, the onModify callback will be called on the System in the next update, if it has one. This is usually managed by tiny-ecs, so users should mostly ignore this, too.
|
||||
|
||||
|
||||
--- @module tiny-ecs
|
||||
-- @author Calvin Rose
|
||||
-- @license MIT
|
||||
|
@ -103,14 +102,13 @@ local filterJoin
|
|||
-- A helper function to filters from string
|
||||
local filterBuildString
|
||||
|
||||
|
||||
local function filterJoinRaw(invert, joining_op, ...)
|
||||
local _args = { ... }
|
||||
|
||||
return function(system, e)
|
||||
local acc
|
||||
local args = _args
|
||||
if joining_op == 'or' then
|
||||
if joining_op == "or" then
|
||||
acc = false
|
||||
for i = 1, #args do
|
||||
local v = args[i]
|
||||
|
@ -119,7 +117,7 @@ local function filterJoinRaw(invert, joining_op, ...)
|
|||
elseif type(v) == "function" then
|
||||
acc = acc or v(system, e)
|
||||
else
|
||||
error 'Filter token must be a string or a filter function.'
|
||||
error("Filter token must be a string or a filter function.")
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -131,7 +129,7 @@ local function filterJoinRaw(invert, joining_op, ...)
|
|||
elseif type(v) == "function" then
|
||||
acc = acc and v(system, e)
|
||||
else
|
||||
error 'Filter token must be a string or a filter function.'
|
||||
error("Filter token must be a string or a filter function.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -146,69 +144,68 @@ local function filterJoinRaw(invert, joining_op, ...)
|
|||
end
|
||||
|
||||
do
|
||||
|
||||
function filterJoin(...)
|
||||
local state, value = pcall(filterJoinRaw, ...)
|
||||
if state then return value else return nil, value end
|
||||
if state then
|
||||
return value
|
||||
else
|
||||
return nil, value
|
||||
end
|
||||
end
|
||||
|
||||
local function buildPart(str)
|
||||
local accum = {}
|
||||
local subParts = {}
|
||||
str = str:gsub('%b()', function(p)
|
||||
str = str:gsub("%b()", function(p)
|
||||
subParts[#subParts + 1] = buildPart(p:sub(2, -2))
|
||||
return ('\255%d'):format(#subParts)
|
||||
return ("\255%d"):format(#subParts)
|
||||
end)
|
||||
for invert, part, sep in str:gmatch('(%!?)([^%|%&%!]+)([%|%&]?)') do
|
||||
if part:match('^\255%d+$') then
|
||||
for invert, part, sep in str:gmatch("(%!?)([^%|%&%!]+)([%|%&]?)") do
|
||||
if part:match("^\255%d+$") then
|
||||
local partIndex = tonumber(part:match(part:sub(2)))
|
||||
accum[#accum + 1] = ('%s(%s)')
|
||||
:format(invert == '' and '' or 'not', subParts[partIndex])
|
||||
accum[#accum + 1] = ("%s(%s)"):format(invert == "" and "" or "not", subParts[partIndex])
|
||||
else
|
||||
accum[#accum + 1] = ("(e[%s] %s nil)")
|
||||
:format(make_safe(part), invert == '' and '~=' or '==')
|
||||
accum[#accum + 1] = ("(e[%s] %s nil)"):format(make_safe(part), invert == "" and "~=" or "==")
|
||||
end
|
||||
if sep ~= '' then
|
||||
accum[#accum + 1] = (sep == '|' and ' or ' or ' and ')
|
||||
if sep ~= "" then
|
||||
accum[#accum + 1] = (sep == "|" and " or " or " and ")
|
||||
end
|
||||
end
|
||||
return table.concat(accum)
|
||||
end
|
||||
|
||||
function filterBuildString(str)
|
||||
local source = ("return function(_, e) return %s end")
|
||||
:format(buildPart(str))
|
||||
local source = ("return function(_, e) return %s end"):format(buildPart(str))
|
||||
local loader, err = loadstring(source)
|
||||
if err then
|
||||
error(err)
|
||||
end
|
||||
return loader()
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Makes a Filter that selects Entities with all specified Components and
|
||||
-- Filters.
|
||||
function tiny.requireAll(...)
|
||||
return filterJoin(false, 'and', ...)
|
||||
return filterJoin(false, "and", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that selects Entities with at least one of the specified
|
||||
-- Components and Filters.
|
||||
function tiny.requireAny(...)
|
||||
return filterJoin(false, 'or', ...)
|
||||
return filterJoin(false, "or", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that rejects Entities with all specified Components and
|
||||
-- Filters, and selects all other Entities.
|
||||
function tiny.rejectAll(...)
|
||||
return filterJoin(true, 'and', ...)
|
||||
return filterJoin(true, "and", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter that rejects Entities with at least one of the specified
|
||||
-- Components and Filters, and selects all other Entities.
|
||||
function tiny.rejectAny(...)
|
||||
return filterJoin(true, 'or', ...)
|
||||
return filterJoin(true, "or", ...)
|
||||
end
|
||||
|
||||
--- Makes a Filter from a string. Syntax of `pattern` is as follows.
|
||||
|
@ -224,7 +221,11 @@ end
|
|||
-- @param pattern
|
||||
function tiny.filter(pattern)
|
||||
local state, value = pcall(filterBuildString, pattern)
|
||||
if state then return value else return nil, value end
|
||||
if state then
|
||||
return value
|
||||
else
|
||||
return nil, value
|
||||
end
|
||||
end
|
||||
|
||||
--- System functions.
|
||||
|
@ -463,8 +464,7 @@ function tiny.world(...)
|
|||
entities = {},
|
||||
|
||||
-- List of Systems
|
||||
systems = {}
|
||||
|
||||
systems = {},
|
||||
}, worldMetaTable)
|
||||
|
||||
tiny_add(ret, ...)
|
||||
|
@ -673,7 +673,6 @@ end
|
|||
|
||||
-- Adds, removes, and changes Entities that have been marked.
|
||||
function tiny_manageEntities(world)
|
||||
|
||||
local e2r = world.entitiesToRemove
|
||||
local e2c = world.entitiesToChange
|
||||
|
||||
|
@ -793,7 +792,6 @@ end
|
|||
-- Systems. If `filter` is not supplied, all Systems are updated. Put this
|
||||
-- function in your main loop.
|
||||
function tiny.update(world, dt, filter)
|
||||
|
||||
tiny_manageSystems(world)
|
||||
tiny_manageEntities(world)
|
||||
|
||||
|
@ -809,8 +807,7 @@ function tiny.update(world, dt, filter)
|
|||
onModify(system, dt)
|
||||
end
|
||||
local preWrap = system.preWrap
|
||||
if preWrap and
|
||||
((not filter) or filter(world, system)) then
|
||||
if preWrap and ((not filter) or filter(world, system)) then
|
||||
preWrap(system, dt)
|
||||
end
|
||||
end
|
||||
|
@ -853,12 +850,10 @@ function tiny.update(world, dt, filter)
|
|||
for i = 1, #systems do
|
||||
local system = systems[i]
|
||||
local postWrap = system.postWrap
|
||||
if postWrap and system.active and
|
||||
((not filter) or filter(world, system)) then
|
||||
if postWrap and system.active and ((not filter) or filter(world, system)) then
|
||||
postWrap(system, dt)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Removes all Entities from the World.
|
||||
|
@ -924,11 +919,11 @@ worldMetaTable = {
|
|||
clearSystems = tiny.clearSystems,
|
||||
getEntityCount = tiny.getEntityCount,
|
||||
getSystemCount = tiny.getSystemCount,
|
||||
setSystemIndex = tiny.setSystemIndex
|
||||
setSystemIndex = tiny.setSystemIndex,
|
||||
},
|
||||
__tostring = function()
|
||||
return "<tiny-ecs_World>"
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
_G.tiny = tiny
|
||||
|
|
190
main.lua
|
@ -9,25 +9,177 @@ require("generated/filter-types")
|
|||
require("generated/assets")
|
||||
require("generated/all-systems")
|
||||
|
||||
local scenarios = {
|
||||
default = function()
|
||||
-- TODO: Add default entities
|
||||
end,
|
||||
textTestScenario = function()
|
||||
local width, height = love.graphics.getWidth(), love.graphics.getHeight()
|
||||
|
||||
local squareSize = 80
|
||||
local tileSize = math.floor(squareSize * 1.2)
|
||||
local marginSize = 10
|
||||
|
||||
CursorMask = 1
|
||||
BallMask = 2
|
||||
|
||||
local function emptyTile(unhighlightSiblings, gridPosition)
|
||||
local size = { x = squareSize, y = squareSize }
|
||||
return {
|
||||
unhighlightSiblings = unhighlightSiblings,
|
||||
canBeCollidedBy = CursorMask,
|
||||
highlightOnMouseOver = T.marker,
|
||||
size = size,
|
||||
gridPosition = gridPosition,
|
||||
drawAsRectangle = { size = size },
|
||||
}
|
||||
end
|
||||
|
||||
local function replaceAt(grid, y, x, entity)
|
||||
local current = grid[y][x]
|
||||
grid[y][x] = entity
|
||||
|
||||
current.unhighlightSiblings[entity] = true
|
||||
current.unhighlightSiblings[current] = nil
|
||||
entity.unhighlightSiblings = current.unhighlightSiblings
|
||||
|
||||
-- entity.position = current.position
|
||||
entity.gridPosition = current.gridPosition
|
||||
entity.toLeft = current.toLeft
|
||||
entity.toRight = current.toRight
|
||||
entity.above = current.above
|
||||
entity.below = current.below
|
||||
|
||||
current.toLeft.toRight = current.toRight
|
||||
current.toRight.toLeft = current.toLeft
|
||||
current.above.below = current.below
|
||||
current.below.above = current.above
|
||||
|
||||
World:removeEntity(current)
|
||||
World:addEntity(entity)
|
||||
end
|
||||
|
||||
function PositionAtGridXy(x, y)
|
||||
return {
|
||||
x = 20 + ((x - 1) * tileSize),
|
||||
y = 60 + ((y - 1) * tileSize),
|
||||
}
|
||||
end
|
||||
|
||||
Scenarios = {
|
||||
---@return table xyGrid Where grid[y][x] is an Entity
|
||||
emptyGrid = function()
|
||||
local cursorSize = { x = 5, y = 5 }
|
||||
World:addEntity({
|
||||
position = { x = 0, y = 600 },
|
||||
drawAsText = {
|
||||
text = "Hello, world!",
|
||||
style = TextStyle.Inverted,
|
||||
size = cursorSize,
|
||||
moveWithCursor = T.marker,
|
||||
canCollideWith = bit.bor(CursorMask, BallMask),
|
||||
position = { x = -999, y = -999 },
|
||||
highlightsCollided = T.marker,
|
||||
drawAsRectangle = {
|
||||
size = cursorSize,
|
||||
mode = "fill",
|
||||
},
|
||||
})
|
||||
World:addEntity({
|
||||
position = { x = 0, y = 0 },
|
||||
drawAsSprite = Grass,
|
||||
z = 0,
|
||||
})
|
||||
|
||||
-- Temporary storages for connecting grid items
|
||||
local previous
|
||||
local unhighlightSiblings = {}
|
||||
local yxGrid = {}
|
||||
|
||||
local xCount = 8
|
||||
local yCount = 5
|
||||
|
||||
for y = 1, yCount do
|
||||
yxGrid[y] = {}
|
||||
for x = 1, xCount do
|
||||
local current = World:addEntity(emptyTile(unhighlightSiblings, { x = x, y = y }))
|
||||
yxGrid[y][x] = current
|
||||
unhighlightSiblings[current] = true
|
||||
current.toLeft = previous
|
||||
if previous ~= nil then
|
||||
if previous.toRight == nil then
|
||||
previous.toRight = current
|
||||
end
|
||||
else
|
||||
current.canReceiveButtons = T.marker
|
||||
current.highlighted = T.marker
|
||||
end
|
||||
if yxGrid[y - 1] and yxGrid[y - 1][x] then
|
||||
current.above = yxGrid[y - 1][x]
|
||||
yxGrid[y - 1][x].below = current
|
||||
end
|
||||
if y == yCount then
|
||||
-- Connect last row to first row
|
||||
yxGrid[1][x].above = current
|
||||
current.below = yxGrid[1][x]
|
||||
end
|
||||
if x == xCount then
|
||||
-- Connect last entry to first Entry
|
||||
current.toRight = yxGrid[y][1]
|
||||
yxGrid[y][1].toLeft = current
|
||||
end
|
||||
previous = current
|
||||
end
|
||||
end
|
||||
return yxGrid
|
||||
end,
|
||||
|
||||
firstLevel = function()
|
||||
local yxGrid = Scenarios.emptyGrid()
|
||||
replaceAt(yxGrid, 4, 7, {
|
||||
drawAsSprite = Flag,
|
||||
z = 1,
|
||||
effectsToApply = {
|
||||
endOfRound = T.marker,
|
||||
}
|
||||
})
|
||||
replaceAt(yxGrid, 2, 2, {
|
||||
drawAsSprite = SmallSandTrap,
|
||||
z = 1,
|
||||
spriteAfterEffect = SmallSandTrapActivated,
|
||||
effectsToApply = {
|
||||
slowsBy = { x = 1, y = 1 },
|
||||
movement = { x = 0, y = 0, },
|
||||
},
|
||||
})
|
||||
replaceAt(yxGrid, 1, 1, {
|
||||
pickedUpOnClick = T.marker,
|
||||
drawAsSprite = GolferRight,
|
||||
z = 1,
|
||||
spriteAfterEffect = GolferRightActivated,
|
||||
effectsToApply = {
|
||||
movement = { x = 6, y = 0, },
|
||||
},
|
||||
})
|
||||
replaceAt(yxGrid, 1, 7, {
|
||||
pickedUpOnClick = T.marker,
|
||||
drawAsSprite = GolferRight,
|
||||
z = 1,
|
||||
spriteAfterEffect = GolferRightActivated,
|
||||
effectsToApply = {
|
||||
movement = { x = 0, y = 3 },
|
||||
},
|
||||
})
|
||||
World:addEntity({
|
||||
ballEffects = {},
|
||||
drawAsSprite = Ball,
|
||||
gridPosition = { x = 1, y = 1 },
|
||||
z = 2,
|
||||
})
|
||||
-- TODO: Make the start button start
|
||||
World:addEntity({
|
||||
drawAsSprite = StartButton,
|
||||
z = 3,
|
||||
position = {
|
||||
x = width - 120,
|
||||
y = height - 50,
|
||||
},
|
||||
velocity = { x = 240, y = -500 },
|
||||
mass = 1,
|
||||
decayAfterSeconds = 10,
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
scenarios.textTestScenario()
|
||||
Scenarios.firstLevel()
|
||||
|
||||
function love.load()
|
||||
love.graphics.setBackgroundColor(1, 1, 1)
|
||||
|
@ -35,5 +187,15 @@ function love.load()
|
|||
end
|
||||
|
||||
function love.draw()
|
||||
World:update(love.timer.getDelta())
|
||||
local dt = love.timer.getDelta()
|
||||
if love.keyboard.isDown("r") then
|
||||
World:clearEntities()
|
||||
Scenarios.firstLevel()
|
||||
end
|
||||
World:update(dt, function(_, system)
|
||||
return not system.isDrawSystem
|
||||
end)
|
||||
World:update(dt, function(_, system)
|
||||
return system.isDrawSystem
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -1,11 +1,24 @@
|
|||
collidingEntities = filteredSystem("collidingEntitites", {
|
||||
velocity = T.XyPair,
|
||||
position = T.XyPair,
|
||||
size = T.XyPair,
|
||||
canCollideWith = T.BitMask,
|
||||
isSolid = Maybe(T.bool),
|
||||
})
|
||||
|
||||
local function intersects(rect, rectOther)
|
||||
local left = rect.position.x
|
||||
local right = rect.position.x + rect.size.x
|
||||
local top = rect.position.y
|
||||
local bottom = rect.position.y + rect.size.y
|
||||
|
||||
local leftOther = rectOther.position.x
|
||||
local rightOther = rectOther.position.x + rectOther.size.x
|
||||
local topOther = rectOther.position.y
|
||||
local bottomOther = rectOther.position.y + rectOther.size.y
|
||||
|
||||
return leftOther < right and left < rightOther and topOther < bottom and top < bottomOther
|
||||
end
|
||||
|
||||
filteredSystem(
|
||||
"collisionDetection",
|
||||
{ position = T.XyPair, size = T.XyPair, canBeCollidedBy = T.BitMask, isSolid = Maybe(T.bool) },
|
||||
|
@ -18,19 +31,7 @@ filteredSystem(
|
|||
and e.canBeCollidedBy
|
||||
and bit.band(collider.canCollideWith, e.canBeCollidedBy) ~= 0
|
||||
then
|
||||
local colliderTop = collider.position.y
|
||||
local colliderBottom = collider.position.y + collider.size.y
|
||||
local entityTop = e.position.y
|
||||
local entityBottom = entityTop + e.size.y
|
||||
|
||||
local withinY = (entityTop > colliderTop and entityTop < colliderBottom)
|
||||
or (entityBottom > colliderTop and entityBottom < colliderBottom)
|
||||
|
||||
if
|
||||
withinY
|
||||
and collider.position.x < e.position.x + e.size.x
|
||||
and collider.position.x + collider.size.x > e.position.x
|
||||
then
|
||||
if intersects(e, collider) then
|
||||
system.world:addEntity({ collisionBetween = { e, collider } })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,23 @@
|
|||
filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, _, system)
|
||||
Collisions = filteredSystem("collisionResolution", { collisionBetween = T.Collision }, function(e, _, system)
|
||||
local collidedInto, collider = e.collisionBetween[1], e.collisionBetween[2]
|
||||
if collider.highlightsCollided then
|
||||
if collidedInto.unhighlightSiblings then
|
||||
for sibling in pairs(collidedInto.unhighlightSiblings) do
|
||||
sibling.highlighted = nil
|
||||
sibling.canReceiveButtons = nil
|
||||
system.world:addEntity(sibling)
|
||||
end
|
||||
end
|
||||
if collidedInto.highlightOnMouseOver ~= nil then
|
||||
collidedInto.highlighted = T.marker
|
||||
collidedInto.canReceiveButtons = T.marker
|
||||
system.world:addEntity(collidedInto)
|
||||
end
|
||||
if collidedInto.pickedUpOnClick ~= nil then -- and #HeldByCursor.entities == 0 and love.mouse.isDown(1) then
|
||||
print("Click!")
|
||||
collidedInto.moveWithCursor = T.marker
|
||||
system.world:addEntity(collidedInto)
|
||||
end
|
||||
end
|
||||
system.world:removeEntity(e)
|
||||
end)
|
||||
|
|
|
@ -1,23 +1,52 @@
|
|||
local floor = math.floor
|
||||
local gfx = love.graphics
|
||||
---
|
||||
---@generic T
|
||||
---@param shape T | fun()
|
||||
---@param process fun(entity: T, dt: number, system: System) | nil
|
||||
---@return System | { entities: T[] }
|
||||
local function drawSystem(name, shape, process)
|
||||
local system = filteredSystem(name, shape, process, function(_, a, b)
|
||||
if a.z ~= nil and b.z ~= nil then
|
||||
return a.z < b.z
|
||||
end
|
||||
if a.z ~= nil then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
system.isDrawSystem = true
|
||||
return system
|
||||
end
|
||||
|
||||
filteredSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _)
|
||||
gfx.fillRect(e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y)
|
||||
filteredSystem("mapGridPositionToRealPosition", { gridPosition = T.XyPair }, function(e, _, system)
|
||||
e.position = PositionAtGridXy(e.gridPosition.x, e.gridPosition.y)
|
||||
system.world:addEntity(e)
|
||||
end)
|
||||
|
||||
filteredSystem("drawSprites", { position = T.XyPair, drawAsSprite = T.pd_image }, function(e)
|
||||
if e.position.y < Camera.pan.y - 240 or e.position.y > Camera.pan.y + 480 then
|
||||
local spriteDrawSystem = drawSystem(
|
||||
"drawSprites",
|
||||
{ position = T.XyPair, drawAsSprite = T.Drawable, rotation = Maybe(T.number) },
|
||||
function(e)
|
||||
if not e.drawAsSprite then
|
||||
return
|
||||
end
|
||||
e.drawAsSprite:draw(e.position.x, e.position.y)
|
||||
end)
|
||||
local width, height = e.drawAsSprite:getDimensions()
|
||||
gfx.draw(e.drawAsSprite, e.position.x, e.position.y)
|
||||
end
|
||||
)
|
||||
|
||||
function spriteDrawSystem:preProcess()
|
||||
gfx.setColor(1, 1, 1)
|
||||
end
|
||||
|
||||
local margin = 8
|
||||
|
||||
filteredSystem(
|
||||
drawSystem(
|
||||
"drawText",
|
||||
{ position = T.XyPair, drawAsText = { text = T.str, style = Maybe(T.str), font = Maybe(T.pd_font) } },
|
||||
function(e)
|
||||
local font = gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold
|
||||
local font = e.font or gfx.getFont() -- e.drawAsText.font or AshevilleSans14Bold
|
||||
local textHeight = font:getHeight()
|
||||
local textWidth = font:getWidth(e.drawAsText.text)
|
||||
|
||||
|
@ -40,3 +69,9 @@ filteredSystem(
|
|||
gfx.print(e.drawAsText.text, bgLeftEdge + margin, bgTopEdge + margin)
|
||||
end
|
||||
)
|
||||
|
||||
drawSystem("drawRectangles", { position = T.XyPair, drawAsRectangle = { size = T.XyPair } }, function(e, _, _)
|
||||
gfx.setColor(1, 1, 1, 0.5)
|
||||
local mode = e.drawAsRectangle.mode or (e.highlighted and "fill" or "line")
|
||||
gfx.rectangle(mode, e.position.x, e.position.y, e.drawAsRectangle.size.x, e.drawAsRectangle.size.y)
|
||||
end)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
local isDown = love.keyboard.isDown
|
||||
|
||||
---@type ButtonState
|
||||
local buttonState = {}
|
||||
|
||||
|
@ -6,8 +8,105 @@ buttonInputSystem = filteredSystem("buttonInput", { canReceiveButtons = T.marker
|
|||
system.world:addEntity(e)
|
||||
end)
|
||||
|
||||
HeldByCursor = filteredSystem("HeldByCursor", { pickedUpOnClick = T.marker, moveWithCursor = T.marker })
|
||||
|
||||
function buttonInputSystem:preProcess()
|
||||
if #self.entities == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
function love.keypressed(key, _, _)
|
||||
buttonState[key] = true
|
||||
end
|
||||
|
||||
function ClearButtonState()
|
||||
for key in pairs(buttonState) do
|
||||
buttonState[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
---@type System
|
||||
local cursorTracking
|
||||
|
||||
local mouseX, mouseY = -9999, -9999
|
||||
local mouseInControl = false
|
||||
|
||||
function love.mousemoved(x, y)
|
||||
mouseInControl = true
|
||||
mouseX, mouseY = x, y
|
||||
end
|
||||
|
||||
local keyDebounceSec = 0.1
|
||||
local delay = 0
|
||||
|
||||
local menuSystem = filteredSystem(
|
||||
"menu",
|
||||
{ canReceiveButtons = T.marker, highlighted = T.marker },
|
||||
function(e, dt, system)
|
||||
if delay > 0 then
|
||||
delay = delay - dt
|
||||
return
|
||||
end
|
||||
|
||||
local function tryShiftMenu(target, keys)
|
||||
if target == nil then
|
||||
return false
|
||||
end
|
||||
for _, key in ipairs(keys) do
|
||||
if isDown(key) then
|
||||
e.highlighted = nil
|
||||
e.canReceiveButtons = nil
|
||||
target.canReceiveButtons = T.marker
|
||||
target.highlighted = T.marker
|
||||
system.world:addEntity(e)
|
||||
system.world:addEntity(target)
|
||||
e = target
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local pressed = tryShiftMenu(e.toRight, { "right", "d" })
|
||||
pressed = tryShiftMenu(e.toLeft, { "left", "a" }) or pressed
|
||||
pressed = tryShiftMenu(e.below, { "down", "s" }) or pressed
|
||||
pressed = tryShiftMenu(e.above, { "up", "w" }) or pressed
|
||||
|
||||
if isDown("return") then
|
||||
pressed = true
|
||||
system.world:addEntity({
|
||||
round = "start",
|
||||
})
|
||||
end
|
||||
|
||||
if pressed then
|
||||
mouseInControl = false
|
||||
mouseX, mouseY = -9999, -9999
|
||||
for _, tracker in pairs(cursorTracking.entities) do
|
||||
tracker.position.x = -9999
|
||||
tracker.position.y = -9999
|
||||
end
|
||||
delay = keyDebounceSec
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
cursorTracking = filteredSystem("cursorTracking", { moveWithCursor = T.marker, position = T.XyPair }, function(e)
|
||||
if mouseInControl then
|
||||
e.position.x = mouseX
|
||||
e.position.y = mouseY
|
||||
end
|
||||
end)
|
||||
|
||||
-- local allHighlighted = filteredSystem("allHighlighted", { highlighted = T.marker })
|
||||
|
||||
function cursorTracking:postProcess()
|
||||
-- if mouseInControl and #Collisions.entities == 0 then
|
||||
-- -- If the cursor is not colliding with anything, wipe all highlighted components
|
||||
-- for _, highlighted in pairs(allHighlighted.entities) do
|
||||
-- highlighted.highlighted = nil
|
||||
-- self.world:addEntity(highlighted)
|
||||
-- end
|
||||
-- end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
local gridElements = filteredSystem("gridElements", { gridPosition = T.XyPair, effectsToApply = T.AnyComponent })
|
||||
|
||||
local roundRunning = false
|
||||
|
||||
filteredSystem("timers", { timerSec = T.number, callback = T.SelfFunction }, function(e, dt, system)
|
||||
e.timerSec = e.timerSec - dt
|
||||
if e.timerSec < 0 then
|
||||
e:callback()
|
||||
system.world:removeEntity(e)
|
||||
end
|
||||
end)
|
||||
|
||||
local function sign(n)
|
||||
return n > 0 and 1 or n < 0 and -1 or 0
|
||||
end
|
||||
|
||||
local roundSystem = filteredSystem("rounds", { round = T.str })
|
||||
|
||||
local activeBallEffects = filteredSystem("activeBallEffects", { ballEffects = T.AnyComponent, gridPosition = T.XyPair }, function(e, dt, system)
|
||||
local roundActive = false
|
||||
for _, state in pairs(roundSystem.entities) do
|
||||
if state.round == "start" then
|
||||
roundActive = true
|
||||
end
|
||||
end
|
||||
if not roundActive then
|
||||
return
|
||||
end
|
||||
local gridPosition, effects = e.gridPosition, e.ballEffects
|
||||
|
||||
-- Search for new effects from the current tile
|
||||
for _, gridElement in pairs(gridElements.entities) do
|
||||
if
|
||||
gridPosition.x == gridElement.gridPosition.x
|
||||
and gridPosition.y == gridElement.gridPosition.y
|
||||
then
|
||||
-- More direct-mutation-y than I'd like,
|
||||
-- but offers a simple way to overwrite existing effects.
|
||||
-- We're "setting InRelations" :D
|
||||
for key, value in pairs(gridElement.effectsToApply) do
|
||||
effects[key] = value
|
||||
end
|
||||
if gridElement.spriteAfterEffect then
|
||||
gridElement.drawAsSprite = gridElement.spriteAfterEffect
|
||||
gridElement.spriteAfterEffect = nil
|
||||
end
|
||||
gridElement.effectsToApply = nil
|
||||
system.world:addEntity(gridElement)
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply any effects currently connected to this ball
|
||||
if effects.movement ~= nil then
|
||||
gridPosition.x = gridPosition.x + sign(effects.movement.x)
|
||||
gridPosition.y = gridPosition.y + sign(effects.movement.y)
|
||||
|
||||
effects.movement.x = effects.movement.x - sign(effects.movement.x)
|
||||
effects.movement.y = effects.movement.y - sign(effects.movement.y)
|
||||
|
||||
if effects.movement.x == 0 and effects.movement.y == 0 then
|
||||
effects.movement = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Trigger the round end
|
||||
-- for _, _ in pairs(effects) do
|
||||
-- -- Return if there are any effects left
|
||||
-- return
|
||||
-- end
|
||||
|
||||
-- system.world:addEntity({ round = "end" })
|
||||
end)
|
||||
|
||||
local stepTimeSec = 0.3
|
||||
local stepTimer = stepTimeSec
|
||||
|
||||
function activeBallEffects:preProcess(dt)
|
||||
stepTimer = stepTimer - dt
|
||||
if stepTimer <= 0 then
|
||||
stepTimer = stepTimeSec
|
||||
return
|
||||
end
|
||||
return tiny.SKIP_PROCESS
|
||||
end
|
||||
|
|
@ -1,13 +1,15 @@
|
|||
---@generic T
|
||||
---@param shape T | fun()
|
||||
---@param process fun(entity: T, dt: number, system: System) | nil
|
||||
---@param compare nil | fun(system: System, entityA: T, entityB: T): boolean
|
||||
---@return System | { entities: T[] }
|
||||
function filteredSystem(name, shape, process)
|
||||
function filteredSystem(name, shape, process, compare)
|
||||
assert(type(name) == "string")
|
||||
assert(type(shape) == "table" or type(shape) == "function")
|
||||
assert(process == nil or type(process) == "function")
|
||||
|
||||
local system = tiny.processingSystem()
|
||||
local system = compare and tiny.sortedProcessingSystem() or tiny.processingSystem()
|
||||
system.compare = compare
|
||||
system.name = name
|
||||
if type(shape) == "table" then
|
||||
local keys = {}
|
||||
|
|