Here's a tiny entity-component-system framework for anyone who might need such a thing! It weighs in at only 108 tokens as of v1.0. This framework assumes (but does not technically require) the strictest definition of the concept, that being that entities and components define no behavior internally but rather are acted upon by externally defined systems that function on the data contained in the entity/components themselves.
The implementation of systems is a slightly refined version of @selfsame's earlier implementation, which treated the "components" simply as keys of the entity, rather than modular objects in their own right. It's also been slightly token-optimized, at 38 instead of 47, but assumes at least the table structure implemented by the entity factory function. I highly recommend @selfsame's version if you need something even more lightweight than this, at the cost of a little flexibility.
If you really need to be stingy about tokens, you can even leave out the component factory function to save 16 tokens, at a minimum of 0 extra tokens per component creation. It's mostly included for simplicity and clarity, but is not strictly required to function.
Copy and paste the code below into a new tab of your project to get started - I'd very much appreciate if you leave the credit comment at the top, but I won't be mad if you don't =)
--tiny ecs v1.1 --by katrinakitten function ent(t) local cmpt={} t=t or {} setmetatable(t,{ __index=cmpt, __add=function(self,cmp) assert(cmp._cn) self[cmp._cn]=cmp return self end, __sub=function(self,cn) self[cn]=nil return self end }) return t end function cmp(cn,t) t=t or {} t._cn=cn return t end function sys(cns,f) return function(ents,...) for e in all(ents) do for cn in all(cns) do if(not e[cn]) goto _ end f(e,...) ::_:: end end end |
From there, you can use the system like so. Keep in mind that the + and - operators on entities are destructive and modify the existing entity, regardless of whether you use += or -=; since they result in the same token count, I highly recommend you still use the assignment variants unless you're really in a performance crunch, for the sake of clarity.
local entity = ent({ ... }) local component = cmp("name", { ... }) local component = { _cn="name", ... } --if cmp is not included entity += component --add a component entity -= "name" --remove a component if entity.name then --executes only if the "name" component exists --access component properties via entity.name.property end local system = sys({ "name", ... }, function(e, ...) --will execute for each entity with all listed components --varargs will be passed through from system call end) system({ entity, ... }) system({ entity, ... }, "some args", ...) |
Here's a simple demo of using this system with the following test code (outdated - v1.0):
function _init() local e1=ent({ x=0,y=0 }) local e2=ent({ x=0,y=0 }) e1+=cmp("test",{state="cmp1"}) e2+=cmp("test",{state="cmp2"}) local s=sys({"test"},function(e,s) ?s..e.cmp.test.state end) s({e1,e2},"before: ") e2-="test" s({e1,e2},"after: ") end |
Changelog:
- v1.1 (BREAKING)
- Changed access pattern from e.cmp.name to e.name
- This reduces access cost but may clobber entity properties, so be careful!
- v1.0
- Initial release
A few hours of work under this system yielded this prototype - doesn't look like much, and please excuse my terrible spritework, but given that my last platformer prototype didn't even reach fully functioning map collisions, I'd say 3-4 hours for all of the standard platformer physics, coyote time, a simple animation system implemented for walking/jumping/falling/crouching, and all of the above wrapped up in an ECS system that should make it quite easy to reuse for other elements of the game, weighing in at only 1033 tokens, all constitutes a fairly glowing recommendation (albeit admittedly a pretty biased one).
If there's interest, I'll share the code for this prototype once I've had a day or two to clean it up a bit and make it at least passably presentable.
Please do share! I'm interested especially in the collision detection and control related systems.
Please, I'm also interested in the prototype. I did make a little shmup game with selfsame ECS implementation https://www.lexaloffle.com/bbs/?tid=38682. It was my first try at using this game programming pattern and I found it very flexible.
Finally I had the chance to start looking at and learning ECS again - yours is a really compact implementation! I think I'll write a blog post to work through (in my own head) how it works :) So far I've drawn a couple of entities: https://github.com/apa64/road-to-ecs/blob/master/01-draw.p8 Next I'll look how to do controls.
edit: Blog here https://www.lexaloffle.com/bbs/?tid=39315
The idea behind this ECS library is fantastic! I love the __add
hack 🤯
I've been using it in my own projects, slowly adding to it over time, and have ended up with something I thought worth sharing back. I call it "PECS" (PICO-8 Entity Component System): https://github.com/jesstelford/pico-8-pecs
[Please log in to post a comment]