Skip to main content
1 of 4

Let me see if by trying to understand this myself as a JS dev, I can be of help.

First of all, on the "don't use OOP" factor, remember that JavaScript objects are like playdough compared to other languages and you actually have to go out of your way to build a cascading-inheritance scheme nightmare since JS isn't class-based and compositing comes much more naturally to it. If you are implementing some silly class or prototype hand-me-down system in your JS, consider ditching it. In JS we use closures, prototypes, and we pass functions around like candy. It's disgusting and filthy and wrong but also powerful, concise and that's the way we like it.

Inheritance heavy approaches are actually spelled out as an anti-pattern in Design Patterns and for good reason as anyone can tell you who has walked down 15+ levels worth of class or class-like structures to try and figure out where the heck the busted version of a method was coming in from. I don't know why so many programmers love doing this (especially java guys writing JavaScript for some reason), but it's awful, illegible, unmaintainable and the point of this system appears to be to avoid that by doing something that is technically OOP-ish that doesn't have the same limitations/encourage bad inheritance behavior as a lot of the more class-oriented language's object systems might. Inheritance is okay here and there, but not really necessary in JS and should really be more reserved for architecture concerns and not frankensteining a zombie from a clever series of inheriting/overriding classes that include a bunny rabbit simply because it worked. That's not good code reuse. That's a maintenance nightmare.

What it resembles to me is basically a system for decoupling design concerns and then compositing objects for implementation on a highly granular level. In other words, child's play in a language like JavaScript. But let me see if I'm grokking this correctly first.

  • Entity - The specific thing you are designing. We're talking proper nouns. Not 'Scene', but 'IntroAreaLevelOne' possibly sitting inside the sceneEntities list/dictionary/object-literal. In the code this is essentially a list of all the dependency types (components) for the entity, which in themselves are broader categories that we use the entity ID to map to the specific variety of component of that type that we want.

  • Components - types of things an entity needs. These are general nouns. Like WalkingAnimation. Within WalkingAnimation we can get more specific, like "Shambling" (good choice for zombies and plant monsters), or "ChickenWalker" (great for reverse-joint ed-209ish robot-types). Note: Not sure how that could decouple from the rendering of a 3D model like that - so maybe a crap example but I'm more of a JS pro than an experienced game dev. In JS I would put the mapping mechanism in the same box with the components. Components in their own right are likely to be light on logic and more of a roadmap telling your systems what to implement. Once a component is established, it's easy enough to tweak slightly and stash under a new name if necessary but more likely you would want to avoid duping and check some property attached to the entity instead in order to vary behavior from a component. In my attempted example these do ultimately become or assign (if functions) properties and event handlers to implementation-ready objects.

  • Systems - The real programmey meat is here. AI systems are built and linked, Rendering is achieved, animations sequences established, etc... I'm copping out and leaving these mostly to the imagination but in the example System.AI takes a bunch of properties in and spits out a function which is used to add event handlers to the object that ultimately gets used in implementation

So in JS, maybe something like this. Game devs please tell me if I've got it horribly wrong because I'm interested:

//I'm going with simple objects of flags over arrays of component names
//easier to read and can provide an opt-out default
//Assume a genre-bending stealth assassin game

//new (function etc... is a lazy way to define a constructor and auto-instantiate
var npcEntities = new (function NpcEntities(){
    
    //note: {} in JS is an object literal, a simple obj namespace (a dictionary)
    //plain ol' internal var in JS is akin to a private member
    var default={ //most NPCs are humanoids and critters - why repeat things?
        speedAttributes:true,
        maneuverAttributes:true,
        combatAttributes:true,
        walkingAnimation:true,
        runningAnimation:true,
        combatAnimation:true,
        aiOblivious:true,
        aiAggro:true,
        aiWary:true, //"I heard something!"
        aiFearful:true
    };
    
    //this. exposes as public
    
    this.zombie={ //zombies are slow, but keep on coming so don't need these
        runningAnimation:false,
        aiFearful:false
    };
    
    this.laserTurret={ //most defaults are pointless so ignore 'em
        ignoreDefault:true,
        combatAttributes:true,
        maneuverAttrubtes:true, //turning speed only
    };
    //also this.nerd, this.lawyer and on and on...
    
    //loop runs on instantiation which we're forcing on the spot
    
    //note: it would be silly to repeat this loop in other entity collections
    //but I'm spelling it out to keep things straight-forward.
    //Probably a good example of a place where one-level inheritance from
    //a more general entity class might make sense with hurting the pattern.
    //In JS, of course, that would be completely unnecessary. I'd just build a
    //constructor factory with a looping function new objects could access via
    //closure.
    
    for(var x in npcEntities){
        
        var thisEntity = npcEntities[x];
        
        if(!thisEntity.ignoreDefaults){
        
            thisEntity = someObjectXCopyFunction(defaults,thisEntity);
            //copies entity properties over defaults
        
        }
        else {
            //remove nonComponent property since we loop again later
            delete thisEntity.ignoreDefaults;
        }
    }
})() //end of entity instantiation

var npcComponents = {
    //all components should have public entityMap properties

    //No systems in use here. Just bundles of related attributes
    speedAttributes: new (function SpeedAttributes(){
        var shamblingBiped = {
            walkingAcceleration:1,
            topWalking:3
        },
        averageMan = {
            walkingAcceleration:3,
            runningAcceleration:4,
            topWalking: 4,
            topRunning: 6
        },
        programmer = {
            walkingAcceleration:1,
            runningAcceleration:100,
            topWalking:2
            topRunning:2000
        }; //end local/private vars
        
        //left is entity names | right is the component subcategory
        this.entityMap={
            zombie:shamblingBiped,
            lawyer:averageMan,
            nerd:programmer,
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(), //end speedAttributes
    
    //Now an example of an AI component - maps to function used to set eventHandlers
    //functions which, because JS is awesome we can pass around like candy
    //I'll just use some imaginary systems on this one

    aiFearful: new (function AiFearful(){
        var averageMan = Systems.AI({ //builds and returns eventSetting function
            fearThreshold:70, //%hitpoints remaining
            fleeFrom:'lastAttacker',
            tactic:'avoidIntercept',
            hazardAwareness:'distracted'
        }),
        programmer = Systems.AI({
            fearThreshold:95,
            fleeFrom:'anythingMoving',
            tactic:'beeline',
            hazardAwareness:'pantsCrappingPanic'
        });//end local vars/private members

        
         this.entityMap={
            lawyer:averageMan,
            nerd:averageMan, //nerds can run like programmers but are less cowardly
            gCostanza:programmer //makes a cameo during the fire-in-nursery stage
        }
    })(),//and more components...
    
    //Systems.AI is general and would get called for all the AI components.
    //It basically spits out functions used to set events on NPC objects that
    //determine their behavior. You could do it all in one shot but
    //the idea is to keep it granular enough for designers to actually tweak stuff
    //easily without tugging on developer pantlegs constantly.
    //e.g. SuperZombies, zombies, but slightly tougher, faster, smarter
}//end npcComponents

function createNPCConstructor(npcType){

    var components = npcEntities[npcType],
    
    //objConstructor is returned but components is still accessible via closure.

    objConstructor = function(){
        for(var x in components){
            //object iteration <property> in <object>

            var thisComponent = components[x];

            if(typeof thisComponent === 'function'){
                thisComponent.apply(this);
                //fires function as if it were a property of instance
                //would allow the function to add additional properties and set
                //event handlers via the 'this' keyword
            }
            else {
                objConstructor.prototype[x] = thisComponent;
                //public property accessed via reference to constructor prototype
                //good for low memory footprint among other things
            }
        }
    }
    return objConstructor;
}

var npcBuilders= {}; //empty object literal
for (var x in npcEntities){
    npcConstructors[x] = createNPCConstructor(x);
}

Now any time you need an NPC, you could build with npcBuilders.<npcName>();

A GUI could plug into the npcEntities and components objects and allow designers to tweak old entities or create new entities by simply mixing and matching components (although there's no mechanism in there for non-default components but special components could be added on the fly in the code as long as there was a defined component for it.