Permettez-moi de voir si en essayant de comprendre en tant que développeur JS Web / UI, je peux être utile. N'allez pas trop loin dans l'agnosticisme linguistique. De nombreux modèles établis dans d'autres langues méritent d'être étudiés, mais peuvent être appliqués très différemment dans JS en raison de sa flexibilité ou ne sont vraiment pas nécessaires en raison de la nature malléable de la langue. Vous pourriez souffler quelques opportunités si vous écrivez votre code en pensant que JS a le même ensemble de frontières qu'un langage orienté OOP plus classique.
Tout d'abord, sur le facteur "ne pas utiliser OOP", rappelez-vous que les objets JavaScript sont comme de la pâte à modeler par rapport à d'autres langages et vous devez en fait faire tout votre possible pour construire un cauchemar de schéma d'héritage en cascade puisque JS n'est pas une classe et le compositing lui viennent beaucoup plus naturellement. Si vous implémentez une classe idiote ou un prototype de système manuel dans votre JS, envisagez de l'abandonner. Dans JS, nous utilisons des fermetures, des prototypes et nous transmettons des fonctions comme des bonbons. C'est dégoûtant et sale et faux mais aussi puissant, concis et c'est ainsi que nous l'aimons.
Les approches lourdes d'héritage sont en fait énoncées comme un anti-modèle dans les modèles de conception et pour une bonne raison, toute personne qui a parcouru plus de 15 niveaux de classe ou des structures de type classe pour essayer de comprendre où diable la version éclatée d'une méthode venait de peut vous le dire.
Je ne sais pas pourquoi tant de programmeurs aiment faire cela (en particulier les gars de Java qui écrivent JavaScript pour une raison quelconque), mais c'est horrible, illisible et complètement impossible à entretenir lorsqu'il est utilisé à outrance. L'héritage est correct ici et là, mais pas vraiment nécessaire dans JS. Dans les langues où c'est un raccourci plus attrayant, il devrait vraiment être réservé à des préoccupations d'architecture plus abstraites plutôt qu'à des schémas de modélisation plus littéraux comme frankensteining une implémentation de zombie via une chaîne d'héritage qui comprenait un BunnyRabbit parce que cela fonctionnait. Ce n'est pas une bonne réutilisation du code. C'est un cauchemar d'entretien.
En tant que moteur JS dev Entity / Component / System, les moteurs me semblent être un système / modèle pour découpler les problèmes de conception, puis pour composer des objets à implémenter à un niveau très granulaire. En d'autres termes, un jeu d'enfant dans un langage comme JavaScript. Mais permettez-moi de voir si je suis en train de faire ça correctement en premier.
Entité - La chose spécifique que vous concevez. Nous parlons plus dans le sens des noms propres (mais pas en fait, bien sûr). Pas «Scene», mais «IntroAreaLevelOne». IntroAreaLevelOne peut s'asseoir dans une boîte sceneEntity d'une certaine sorte, mais nous nous concentrons sur quelque chose de spécifique qui diffère d'autres choses connexes. Dans le code, une entité n'est en fait qu'un nom (ou ID) lié à un tas de choses qu'elle doit avoir implémenté ou établi (les composants) pour être utile.
Composants - types de choses dont une entité a besoin. Ce sont des noms généraux. Comme WalkingAnimation. Dans WalkingAnimation, nous pouvons être plus précis, comme "Shambling" (bon choix pour les zombies et les monstres végétaux), ou "ChickenWalker" (idéal pour les types de robots ed-209ish à articulation inversée). Remarque: Je ne sais pas comment cela pourrait se dissocier du rendu d'un modèle 3D comme celui-ci - alors peut-être un exemple de merde, mais je suis plus un JS pro qu'un développeur de jeu expérimenté. Dans JS, je mettrais le mécanisme de mappage dans la même boîte avec les composants. Les composants à part entière sont susceptibles d'être légers sur la logique et plus d'une feuille de route indiquant à vos systèmes ce qu'il faut implémenter si des systèmes sont même nécessaires (dans ma tentative d'ECS, certains composants ne sont que des collections d'ensembles de propriétés). Une fois qu'un composant est établi, il '
Systèmes - La vraie viande programmée est ici. Les systèmes d'IA sont construits et liés, le rendu est réalisé, les séquences d'animations établies, etc ... Je les supprime et les laisse principalement à l'imagination mais dans l'exemple System.AI prend un tas de propriétés et crache une fonction qui est utilisé pour ajouter des gestionnaires d'événements à l'objet qui est finalement utilisé dans l'implémentation. L'essentiel sur System.AI est qu'il couvre plusieurs types de composants. Vous pouvez trier tous les éléments de l'IA avec un seul composant, mais le faire est de mal comprendre le point de rendre les choses granulaires.
Gardez à l'esprit les objectifs: nous voulons faciliter la connexion d'une sorte d'interface graphique pour les non-concepteurs afin de modifier facilement différents types de choses en maximisant et en faisant correspondre les composants dans un paradigme qui leur convient, et nous voulons nous éloigner de des schémas de code arbitraires populaires qui sont beaucoup plus faciles à écrire qu'à modifier ou à maintenir.
Donc, dans JS, peut-être quelque chose comme ça. Les développeurs de jeu, s'il vous plaît, dites-moi si je me trompe horriblement:
//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);
}
Maintenant, chaque fois que vous avez besoin d'un PNJ, vous pouvez construire avec npcBuilders.<npcName>();
Une interface graphique pourrait se connecter aux objets npcEntities et composants et permettre aux concepteurs de modifier d'anciennes entités ou de créer de nouvelles entités en mélangeant simplement et en faisant correspondre les composants (bien qu'il n'y ait pas de mécanisme pour les composants non par défaut, mais des composants spéciaux pourraient être ajoutés à la volée dans le code tant qu'il y avait un composant défini pour lui.