MadByte wrote:
The idea is good and I would be very proud if we can establish something that epic [...]
I'm not sure if my base structure of the project fit to such an ambitious project. We'll see.
I'm still working on it (it needs a lot of polish and testing!), but it IS currently functional (sans the content pack system). If you'd like to play around with a preview, here's my current TLECS.lua:
Code: Select all
-- TLECS v0.1b, an extensible Entity-Component System implementation in Lua
-- by Taehl (email.Taehl@gmail.com)
-- TODO: content pack system
-- it'll have to be able to add/override nodes and systems
-- when adding a node, also add components[node] = {}!
local TLECS = {} -- namespace
TLECS.nodes = {} -- nodes are prototypes for components, representing default state
TLECS.ents = {} -- entities are numerical indices that link components
-- later, the ent itself may do more than isDead. Ideally, I'd like to be able to add tags to ents for better cross-content support
TLECS.components = {} -- components are instanced per-ent, holding their state
-- each TLECS.components lists all ents possessing that component (much faster to iterate with rather than all ents!) (may possibly be replaced by the tag system?)
TLECS.systems = {} -- systems are functions which iterate on components
TLECS.contents = {} -- content populates nodes/components with data and systems with code
for node, v in pairs( TLECS.nodes ) do TLECS.components[node] = {} end
-- import contents into ECS
-- if fresh is true, all existing TLECS data will be replaced by ONLY contents (good when starting a new game)
-- otherwise, all content will be overlaid (best for adding new content, expanding others' content, etc.)
function TLECS.importContents( contents, fresh )
contents = contents or TLECS.contents
if fresh then
TLECS.nodes = {}
TLECS.ents = {}
TLECS.components = {}
TLECS.systems = {}
for node, v in pairs( TLECS.nodes ) do TLECS.components[node] = {} end
end
for kind, adds in pairs( contents ) do
for new, data in pairs( adds ) do
TLECS[kind][new] = data
end
end
end
-- spawn an entity with given parameters
-- example: spawnEnt( { pos={ x=2, y=5 }, draw{ img=robot.png, scale= 2} } )
function TLECS.spawnEnt( comps )
if not comps then error"Can't spawnEnt with no components!" end
local ent = {}
for component,data in pairs( comps ) do
TLECS.addComponent( ent, component, data )
end
table.insert( TLECS.ents, {} ) -- ent, if ents are to list their components...
return ent
end
-- adds a component to an existing object
-- returns the new component
function TLECS.addComponent( ent, component, data )
if not (ent and component) then error"Can't addComponent without ent and component!" end
-- instance the components from their nodes
local comp = { parent = ent }
for k,v in pairs( TLECS.nodes[component] ) do
v = data[k] or v -- if data was specified then add it, else use default
comp[k] = v
end
ent[component] = comp
table.insert( TLECS.components[component], comp )
return comp
end
-- removes a component from an existing ent
-- returns true if it was removed, false if ent didn't have it
function TLECS.removeComponent( ent, component )
if not (ent and component) then error"Can't removeComponent without ent and component!" end
ent[component] = nil
for k,v in TLECS.components do
if v == ent then
table.remove( TLECS.components[component], v )
return true
end
end
end
-- systems execution and ent clean-up
function TLECS.update(dt)
-- run each system on TLECS.ents with the requisite component
for k,system in pairs( TLECS.systems ) do
for k, comp in ipairs( TLECS.components[system.requires] or {} ) do
system.run( comp, dt ) -- todo: support return values? (where would they be sent? callbacks?)
end
end
-- remove dead TLECS.ents
for k = #TLECS.ents,1, -1 do
local ent = TLECS.ents[k]
if ent.isDead then
table.remove(TLECS.ents, k)
-- remove ent from all components' lists
for component, entlist in pairs( TLECS.components ) do
for k, v in pairs( entlist ) do
if v == ent then
table.remove( component, k )
break
end
end
end
end
end
end
return TLECS
Content is put in tables following naming convention, like so:
Code: Select all
content = {
nodes = {
-- engine nodes
draw = { img = whatever, m=0, color={255,255,255} },
pos = { x=0, y=0, },
move = { vx=0, vy=0, g=.98, airf=.001, groundf=2.99, },
-- character nodes
stats = { topSpeed=200, accel=50, },
state = { is = "standing", running = false, dreaming = true },
-- level nodes
wall = { x1=0, y1=0, x2=0, y2=0 }
}
systems = {
draw = {
requires = nil, -- don't run in TLECS.update(), since we'd probably want this in love.draw()
run = function( draw )
local ent = draw.parent
local lg = love.graphics
if not ( ent.draw and ent.pos ) then return end
if ent.move then draw.m = math.sign( ent.move.vx ) end -- if it moves left, flip it
lg.setColor( draw.color )
lg.draw( draw.img, ent.pos.x, ent.pos.y, 0, 1*draw.m, 1, 16, 16 )
end
},
lamePhysics = {
requires = "move",
run = function( move, dt )
local ent = move.parent
local pos = ent.pos
pos.x, pos.y = pos.x + move.vx*dt, pos.y + (move.vy + move.g)*dt
end
},
}
}