Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

ECS (Entity Component System) frameworks and uses

A topic by harebrained created Nov 01, 2023 Views: 188 Replies: 3
Viewing posts 1 to 4
Submitted (2 edits) (+2)

Many submissions this time around made use of some form of ECS. I think it would be great to discuss implementation strategies and libraries. 

I will start: Components have a symbol as key and any other type as value, implemented as a scheme record. An entity is a list of components. All entities (e.g. list of components) are stored in a vector. They are represented by an integer (their index into the vector), however this is never exposed to the user. Instead a query macro is provided with which to access entities with particular component types (cannot query on component values). This is backed by a  hashtable mapping the components to a bitset of entity ids (to answer the question which entity implement what component). A system then applies a query and acts on the components of the entities return by the query. 

I took great care that there is no mutation to allow a functional programming style. Thus at each step of the game loop a system is applied to the game world and returns a new game world.

Brief example of defining a world with a single entity with 3 components and a system to render to the screen


(define world (create-world (list (component 'myentity) (component 'postition (cons 1 1)) (component 'sprite "path/to/sprite"))))
(define (render world)
    (let-values (((_ pos sprite) (get-components world (query 'position 'sprite) 'position 'sprite)))                 (for-each (lambda(p s) (draw-sprite (at (car p) (cdr p) s))) pos sprite)))



Code for the library: https://github.com/sam-d/klecs

EDIT:  code formatting

Submitted

hoping to have @Andrew (cl-fast-ecs library), @oofoe and @Jummit comment here as well. You can read about their approach to ECS  in their devlogs https://awkravchuk.itch.io/cl-fast-ecs/devlog/622054/gamedev-in-lisp-part-1-ecs-and-metalinguistic-abstraction.    https://oofoe.itch.io/rfk2k/devlog/624237/handling-entities and https://jummit.itch.io/tic80-access-battlers/devlog/622949/pre-jam-preparations

Submitted

I think you’re describing an array of structures, and you’re one step away from greatly increasing the performance — if you just reverse the storage and have a struct of arrays with the same semantics, the whole thing would be much more cache friendly (unless, of course, there is value boxing in play, which could be avoided at least for some values in Common Lisp, but is generally a PITA for performance).

The comparison of the syntax for defintions also might be fruitful, I still haven’t figured the perfect way to define entities, components and systems in code. Here’s how your example will look using my library now:

(ecs:bind-storage)

;; provided myentity, position and sprite components are defined elsewhere
(ecs:make-object `((:myentity) (:position :x 1 :y 1) (:sprite "path/to/sprite")))

(defsystem render
 (:components-ro (position sprite))
 (draw-sprite :x position-x :y position-y sprite-path))

Note how in defsystem macro I just write the body of code to process the single entity with no looping logic (it is kinda embeeded into macro itself). I also use quasiqoute to build an object spec to be passed to make-object (kinda similar to your create-world, but for single object). I can’t remember, does Scheme has quasiquoting?

cannot query on component values

This is correct for most of ECS implementations there are, but I went ahead and implemented hashtable-based indices in my library, akin to the ones in relational databases. They allow to answer the question “which entity or entities has this specific value as this component’ slot?” I think it might come in handy in implementing things like entity names or prefabs. I’m already using those to e.g. distinguish different images in texture atlas: https://github.com/lockie/mana-break/blob/0.0.1/src/atlas.lisp#L13-L18

Submitted (1 edit) (+1)

I found a pretty ergonomic way to implement ECS in Fennel:

Entities are Lua tables, components keys/value pairs. Systems make use of the powerful pattern matching:

(fn draw [world]
  "System which draws textures."
  (each [_ entity (ipairs world)]
    (case entity
      {: x : y : texture}
      (draw-texture texture x y))))

The “framework” is just 40 lines of glue to allow removing entities inside systems.

Obviously this doesn’t give you the performance benefits for which ECS was originally designed, but it makes implementing some gameplay mechanics easier. I originally chose ECS because I knew it would be easy to maintain (the data/functionality split makes understanding codebases easier).