Entity-Component-Systems (or Entity-Systems)


So, I'm going to talk about Entity-Systems, but first a post mortem on the recent jam:

Hum. Didn't work out quite the way I hoped. Needed more bling.

; - )

So, Entity-Systems. Let's talk about that. They're more commonly referred to as Entity-Component-Systems, however as best I can tell, the "Component" part is used to deal with the fact that C++ can't do associative arrays. I can do associative arrays (dictionaries in JavaScript), so I say just Entity-System. And the "System" part doesn't mean the actual implementation (that would be the "Entity-System system") but rather "system" as a general class of things (usually functions) that do things with the Entities.

So, entities, ahem, Entities first. Here's some Entities:

(define STORE (list 
     (object [name 'game] [score 0] [layout "XXX..."])
     (object [name 'hero] [xy (16 22)] [state 'spawn])
     (object [name dopey] [xy (16 16)] [strategy s-dopey] [state 'spawn] [tag 'ghost] [hit hit-ghost])
     (object [name sleepy] [xy (16 16)] [strategy s-sleepy] [state 'spawn] [tag 'ghost] [hit hit-ghost])
     (object [tag 'biskit] [xy (14 22)] [value 1])
     (object [tag 'pill] [xy (4 4)] [value 10] [time 10]))

In case it's not obvious, the above is a definition for a particularly low rent version of Pac Man. The "object" translates to a JavaScript dictionary. Even "real" objects in JavaScript are dictionaries, but here they're just dictionaries being used as dictionaries. The keys and values you see are just examples. A real version of Pac Man (even a low rent one) would have other things in the dictionary too -- pointers to sprite data, counters, animation actions, whatever. But we can do quite a lot with this.

Now that we have some Entities, let's do a System. I'll call this the "sys-eat", and it will check to see if our hero nommed down on anything tasty:

(define (sys-eat)
    (do-for x (select (lambda (e) (and ((dot (list 'biskit pill) includes) x.tag)  (= x.xy player.xy))))
        (inc (ref (selec 'game) 'score) x.value)
        (when (= 'ghost x.tag) (do-for g (select 'tag 'ghost) (:= x.state 'flee)))
        (:= x.die #t)))

In brief, this "system" selects all objects in the STORE tagged with "biskit" or "pill" and share the same xy coordinates with the hero (player). This means that our hero has walked into that set of coordinates and (implicitly) consumed what it found there. Add the value of the pill or biskit to the game score. Then, if it was a pill, set the ghost states to run away. Finally, mark the entity for death because it's been consumed.

"select" is basically a filter for the STORE. Give it a single argument, it will look for entities with that in the name property. Two arguments (key and value), it looks for a key that matches value. A function argument gets applied against all entities -- like I said, a filter.

Because I don't have any hard structure here, I can add new keys at any time to support new functionality or requirements. It also means that as long as I have the properties needed by any particular system to operate, then it doesn't matter what kind of thing it is -- we're not object oriented here!

So this gives a lot of flexibility and can be pretty straightforward to use. I made a few false starts in RFK, but this is close to what I came up with.

References

Leave a comment

Log in with itch.io to leave a comment.