Lassen Sie mich sehen, ob ich durch den Versuch, als Web- / UI-JS-Entwickler zu verstehen, hilfreich sein kann. Gehen Sie auch nicht zu weit in der Sprachunabhängigkeit. Viele Muster, die in anderen Sprachen erstellt wurden, sind es wert, studiert zu werden, können jedoch aufgrund ihrer Flexibilität in JS sehr unterschiedlich angewendet werden oder sind aufgrund der Formbarkeit der Sprache nicht unbedingt erforderlich. Sie könnten einige Möglichkeiten nutzen, wenn Sie Ihren Code so schreiben, dass JS dieselben Grenzen hat wie eine klassischere OOP-orientierte Sprache.
Denken Sie beim Faktor "OOP nicht verwenden" zunächst daran, dass JavaScript-Objekte im Vergleich zu anderen Sprachen wie Knetmasse sind und Sie sich wirklich alle Mühe geben müssen, um einen Alptraum für ein Kaskadenvererbungsschema zu erstellen, da JS keine Klasse ist -basiert und Compositing kommt viel natürlicher dazu. Wenn Sie ein albernes Klassen- oder Prototyp-Hand-Me-Down-System in Ihrem JS implementieren, sollten Sie es fallen lassen. In JS verwenden wir Verschlüsse, Prototypen und geben Funktionen wie Süßigkeiten weiter. Es ist widerlich und schmutzig und falsch, aber auch kraftvoll, prägnant und so mögen wir es.
Vererbungsintensive Ansätze werden in Design Patterns tatsächlich als Anti-Pattern formuliert, und das aus gutem Grund für alle, die Klassen oder klassenähnliche Strukturen im Wert von mehr als 15 Stufen durchlaufen haben, um herauszufinden, wo zum Teufel die kaputte Version einer Methode ist kam herein von kann dir sagen.
Ich weiß nicht, warum so viele Programmierer dies lieben (besonders Java-Leute, die aus irgendeinem Grund JavaScript schreiben), aber es ist schrecklich, unleserlich und völlig unbrauchbar, wenn man es an Übermaß gewöhnt. Vererbung ist hier und da in Ordnung, aber in JS nicht wirklich notwendig. In Sprachen, in denen es sich um eine verlockendere Abkürzung handelt, sollte sie eher abstrakteren Architekturproblemen vorbehalten sein als wörtlicheren Modellierungsschemata wie dem Frankensteining einer Zombie-Implementierung durch eine Vererbungskette, die ein BunnyRabbit enthielt, weil es zufällig funktionierte. Das ist keine gute Wiederverwendung von Code. Es ist ein Alptraum für die Instandhaltung.
Als JS-Entwickler erscheinen mir Entity / Component / System-basierte Engines als System / Muster, um Designprobleme zu entkoppeln und dann Objekte für die Implementierung auf einer sehr detaillierten Ebene zusammenzusetzen. Mit anderen Worten, ein Kinderspiel in einer Sprache wie JavaScript. Aber lassen Sie mich sehen, ob ich das zuerst richtig mache.
Entität - Die spezifische Sache, die Sie entwerfen. Wir sprechen mehr in Richtung der Eigennamen (aber natürlich nicht wirklich). Nicht 'Scene', sondern 'IntroAreaLevelOne'. IntroAreaLevelOne befindet sich möglicherweise in einer SzeneEntity-Box, aber wir konzentrieren uns auf etwas Bestimmtes, das sich von anderen verwandten Dingen unterscheidet. Im Code ist eine Entität eigentlich nur ein Name (oder eine ID), der an eine Reihe von Dingen gebunden ist, die implementiert oder eingerichtet werden müssen (die Komponenten), um nützlich zu sein.
Komponenten - Arten von Dingen, die eine Entität benötigt. Dies sind allgemeine Substantive. Wie WalkingAnimation. Innerhalb von WalkingAnimation können wir spezifischer werden, wie "Shambling" (gute Wahl für Zombies und Pflanzenmonster) oder "ChickenWalker" (ideal für Robotertypen mit umgekehrten Gelenken). Hinweis: Ich bin mir nicht sicher, wie sich das vom Rendern eines solchen 3D-Modells abkoppeln könnte - also vielleicht ein Mistbeispiel, aber ich bin eher ein JS-Profi als ein erfahrener Spieleentwickler. In JS würde ich den Mapping-Mechanismus mit den Komponenten in dieselbe Box setzen. Eigene Komponenten sind wahrscheinlich logisch und eher eine Roadmap, die Ihren Systemen sagt, was zu implementieren ist, wenn Systeme überhaupt benötigt werden (bei meinem Versuch, ECS zu verwenden, sind einige Komponenten nur Sammlungen von Eigenschaftssätzen). Sobald eine Komponente eingerichtet ist, wird sie
Systeme - Das echte Programmfleisch ist da. KI-Systeme werden gebaut und verknüpft, Rendering wird erreicht, Animationssequenzen werden erstellt usw. Ich überlasse diese und überlasse sie hauptsächlich der Vorstellungskraft, aber im Beispiel System.AI nimmt eine Reihe von Eigenschaften auf und spuckt eine Funktion aus, die wird verwendet, um dem Objekt Ereignishandler hinzuzufügen, die letztendlich in der Implementierung verwendet werden. Das Wichtigste an System.AI ist, dass es mehrere Komponententypen abdeckt. Sie könnten alle KI-Dinge mit einer Komponente aussortieren, aber dies zu tun, bedeutet, den Sinn der Granularisierung der Dinge falsch zu verstehen.
Beachten Sie die Ziele: Wir möchten es Nicht-Designern einfach machen, eine Art GUI-Oberfläche einzubinden, um verschiedene Arten von Dingen zu optimieren, indem wir Komponenten innerhalb eines für sie sinnvollen Paradigmas maximieren und aufeinander abstimmen, und wir möchten davon wegkommen Beliebte beliebige Codeschemata, die viel einfacher zu schreiben sind als zu ändern oder zu warten.
Also in JS vielleicht so etwas. Spielentwickler sagen mir bitte, wenn ich es schrecklich falsch verstanden habe:
//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);
}
Jetzt können Sie jederzeit mit einem NPC bauen npcBuilders.<npcName>();
Eine grafische Benutzeroberfläche kann in die Objekte npcEntities und Komponenten eingebunden werden und es Designern ermöglichen, alte Entitäten zu optimieren oder neue Entitäten zu erstellen, indem sie einfach Komponenten mischen und abgleichen (obwohl es dort keinen Mechanismus für nicht standardmäßige Komponenten gibt, sondern spezielle Komponenten im laufenden Betrieb hinzugefügt werden könnten Code, solange es eine definierte Komponente dafür gab.