Getting Started
Tecs is a typed, archetype-based ECS for LuaJIT and Teal. Tecs is the core of Tecs2D, a Love2D engine.
Tecs is in preview
Tecs is not yet stable and may change as development progresses.
Installation
First, install LuaJIT and LuaRocks if you haven't already:
brew install luajit luarockssudo apt install luajit libluajit-5.1-dev luarockssudo pacman -S luajit luarocksscoop install luajit luarocks# LuaJIT build guide: https://luajit.org/install.html
git clone https://github.com/LuaJIT/LuaJIT.git
cd LuaJIT && make && sudo make install
# Then install LuaRocks:
# https://github.com/luarocks/luarocks/blob/main/docs/download.mdInstall Tecs via LuaRocks. For typical gamedev, you'll want a self-contained build:
luarocks install --dev --tree=vendor --lua-version=5.1 tecsWhile Tecs is in preview, --dev is required. There are no tagged releases yet.
Require Tecs in your code:
local tecs = require("tecs")Building a game?
If you are building a Love2D game and want the engine layer as well, install tecs2d instead. It depends on tecs automatically and provides rendering, audio, input, physics, UI, and the Love2D loop integration. See Tecs2D Getting Started for the starter template and build commands.
Tecs in a nutshell
World
A World contains all the entities, components, systems, plugins, and resources of a game.
local world = tecs.newWorld()See the World reference for more information
Entity
A unique ID that represents an object in the game world. Entities themselves have no data or behavior; only the components attached to them define what they are.
-- Create an entity with two components and get the entity ID
local entityId = world:spawn(
tecs.builtins.Name("Hello Tecs"),
tecs.builtins.Transform(100, 100)
)Component
Components describe traits like position, velocity, or health, and are the building blocks of game state.
-- Get the Name component of the entity
local name = world:get(entityId, tecs.builtins.Name)
print(name.value)Tecs is strongly typed
By passing in the type of the component to get, the Teal type system knows you are getting back a component of the same type.
Creating a component
You can create a new component by defining a Teal record:
local record Sprite is tecs.Component
texture: love.graphics.Texture
metamethod __call: function(self, love.graphics.Texture): self
endNext, pass a configuration table to tecs.newComponent to wire up the necessary metatables to make it a component.
tecs.newComponent({
name = "Sprite",
container = Sprite,
fields = {"texture"},
init = function(instance: Sprite, texture: love.graphics.Texture)
instance.texture = texture
end
})
-- Set the Sprite component on the entity
local image = love.graphics.newImage("cactus.png")
world:set(entityId, Sprite(image))See the Components reference for more information
System
A system is a function that runs game logic by operating on entities with specific components. Add behavior through systems. Add systems to phases to run at specific parts of the game loop.
world:addSystem({
phase = tecs.phases.Update,
run = function(dt: number)
print("Time since last frame: " .. dt)
end
})See the Systems reference for more information
Plugin
Use plugins to configure the world: add systems, register components, set up resources, and hook up event observers. Plugins bundle parts of a game into modular units.
world:addPlugin(function(world: tecs.World)
-- register components, systems, spawn entities, add resources, ...
end)Plugins power everything
You'll use plugins extensively to write your game logic, include plugins from other libraries like tecs2d, and install systems.
Queries
Create systems inside plugins. For systems to be useful, they typically use a query to find entities in the world. Systems can use zero or more queries. Create queries in plugins outside the system scope, then reuse them over the system's lifetime.
local Transform = tecs.builtins.Transform
local spritePlugin = function(world: tecs.World)
-- Find entities with Transform and Sprite components
local spriteQuery = world:query({
include = {Transform, Sprite}
})
-- Draw the sprites in the render phase
world:addSystem({
phase = tecs.phases.Render,
run = function()
-- Iterate over entities with both Transform and Sprite.
for archetype, len, entities in spriteQuery:iter() do
local transforms = archetype:get(Transform)
local sprites = archetype:get(Sprite)
for row = 1, len do
local id = entities[row]
local tx = transforms[row]
local sprite = sprites[row]
love.graphics.draw(sprite.texture, tx.x, tx.y)
end
end
end
})
end
-- Register the plugin with the world
world:addPlugin(spritePlugin)See the Query reference for more information
Archetypes
Every unique combination of components applied to entities forms an archetype. An entity belongs to exactly one archetype. Archetypes give you fast access to entity IDs and components of the entities stored in the archetype. You'll interact with archetypes primarily through queries.
-- Grab "columns" using archetype:get (read) or archetype:getMut (mark dirty)
local transforms = archetype:get(Transform)
local sprites = archetype:get(Sprite)
for row = 1, len do
-- Index into the archetype columns by row
local tx = transforms[row]
local sprite = sprites[row]
endSee the Archetype reference for more information
Archetypes and queries
Archetypes organize entities into groups based on their components. Queries find matching archetypes, then iterate their component columns by row. This is why query examples bind columns once per archetype instead of fetching components one entity at a time.
Phases
To progress the game, call world:update(dt) with the time since last update. This invokes every phase of the Tecs game loop and runs each system in those phases. When you add a system to a world, you specify its phase. Access phases with tecs.phases.<X>:
world:addSystem({
phase = tecs.phases.Startup,
run = function()
print("The game is starting up!")
end
})See the Phases reference for a list of phases
TIP
tecs2d handles phase execution timing automatically when using Love2D. See Love2D integration.
Resources
Resources in Tecs are the built-in way to share variables globally across your game, but without globals.
To add resources to a world, you first need to create a strongly typed key.
local FONT: tecs.Key<love.graphics.Font> = tecs.newKey()This tells the Teal type system that FONT contains a love.graphics.Font. Now you can assign a value to the resource:
world.resources[FONT] = love.graphics.newFont(filename, glyphs)You can access the resource using the key too:
local font = world.resources[FONT]Store keys in modules
Store your keys in a module because you need to refer to the exact same key when trying to access the resource.
local record MyModule
FONT: tecs.Key<love.graphics.Font>
end
MyModule.FONT = tecs.newKey()