Fammi vedere se provando a capire come sviluppatore JS web / UI, posso esserti di aiuto. Inoltre, non andare troppo lontano nell'agnosticismo linguistico. Molti modelli stabiliti in altre lingue meritano di essere studiati, ma possono essere applicati in modo molto diverso in JS a causa della sua flessibilità o in realtà non sono necessari a causa della natura malleabile della lingua. Potresti cogliere alcune opportunità se scrivi il tuo codice pensando a JS di avere lo stesso insieme di confini di un linguaggio più classico orientato verso OOP.
Prima di tutto, sul fattore "non usare OOP", ricorda che gli oggetti JavaScript sono come la pasta madre rispetto ad altre lingue e devi davvero fare di tutto per costruire un incubo a cascata con ereditarietà poiché JS non è di classe a base e il compositing arriva molto più naturalmente ad esso. Se stai implementando qualche stupida classe o prototipo di sistema manuale nel tuo JS, considera di abbandonarlo. In JS utilizziamo chiusure, prototipi e trasmettiamo funzioni come caramelle. È disgustoso, sporco e sbagliato ma anche potente, conciso ed è così che ci piace.
Approcci pesanti di ereditarietà sono in realtà indicati come un anti-pattern in Design Patterns e per una buona ragione come chiunque abbia camminato per oltre 15 livelli di strutture di classe o di classe per cercare di capire dove diavolo la versione rotta di un metodo stava arrivando da te lo posso dire.
Non so perché così tanti programmatori adorino farlo (specialmente i ragazzi java che scrivono JavaScript per qualche motivo), ma è terribile, illeggibile e completamente non mantenibile se usato in eccesso. L'ereditarietà va bene qua e là, ma non è davvero necessaria in JS. Nelle lingue in cui si tratta di una scorciatoia più allettante, dovrebbe davvero essere riservato a preoccupazioni di architettura più astratte piuttosto che a schemi di modellazione più letterali come Frankensteining un'implementazione di zombi attraverso una catena di eredità che includeva un BunnyRabbit perché funzionava. Non è un buon riutilizzo del codice. È un incubo per la manutenzione.
Come sviluppatore di JS, i motori basati su entità / componenti / sistemi mi colpiscono come un sistema / modello per disaccoppiare le preoccupazioni di progettazione e quindi comporre gli oggetti per l'implementazione a un livello altamente granulare. In altre parole, un gioco da ragazzi in una lingua come JavaScript. Ma fammi vedere se sto facendo questo trekking prima.
Entità - La cosa specifica che stai progettando. Stiamo parlando più nella direzione dei nomi propri (ma non in realtà, ovviamente). Non "Scene", ma "IntroAreaLevelOne". IntroAreaLevelOne potrebbe trovarsi all'interno di una scatola di SceneEntity di qualche tipo, ma ci stiamo concentrando su qualcosa di specifico che varia da altre cose correlate. Nel codice, un'entità è in realtà solo un nome (o ID) legato a un mucchio di cose che deve essere implementato o stabilito (i componenti) per essere utile.
Componenti: tipi di cose di cui un'entità ha bisogno. Questi sono nomi generali. Come WalkingAnimation. All'interno di WalkingAnimation possiamo essere più specifici, come "Shambling" (buona scelta per zombi e mostri vegetali), o "ChickenWalker" (ottimo per i tipi di robot ed-209ish a giunto inverso). Nota: non sono sicuro di come questo potrebbe essere disaccoppiato dal rendering di un modello 3D del genere - quindi forse un esempio di merda, ma io sono più un professionista JS che uno sviluppatore di gioco esperto. In JS metterei il meccanismo di mappatura nella stessa scatola con i componenti. I componenti a sé stanti sono probabilmente leggeri sulla logica e più di una roadmap che dice ai tuoi sistemi cosa implementare se i sistemi sono persino necessari (nel mio tentativo di ECS alcuni componenti sono solo raccolte di insiemi di proprietà). Una volta stabilito un componente, esso "
Sistemi - La vera carne programmata è qui. I sistemi di intelligenza artificiale sono costruiti e collegati, il rendering è raggiunto, le sequenze di animazioni stabilite, ecc ... Sto uscendo e lasciandole principalmente all'immaginazione, ma nell'esempio System.AI prende un sacco di proprietà e sputa fuori una funzione che viene utilizzato per aggiungere gestori di eventi all'oggetto che alla fine viene utilizzato nell'implementazione. La cosa fondamentale di System.AI è che copre più tipi di componenti. È possibile risolvere tutte le cose di intelligenza artificiale con un solo componente, ma farlo significa fraintendere il punto di rendere le cose granulari.
Mind the Goals: Vogliamo semplificare il collegamento di una sorta di interfaccia GUI per i non progettisti per modificare facilmente diversi tipi di roba maxing e abbinare i componenti all'interno di un paradigma che ha senso per loro, e vogliamo allontanarci da schemi di codice arbitrario popolari che sono molto più facili da scrivere di quanto non siano da modificare o mantenere.
Quindi in JS, forse qualcosa del genere. Gli sviluppatori di giochi, per favore, dimmi se ho sbagliato in modo orribile:
//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);
}
Ora, ogni volta che hai bisogno di un NPC, puoi costruire con npcBuilders.<npcName>();
Una GUI potrebbe collegarsi agli oggetti npcEntities e componenti e consentire ai progettisti di modificare vecchie entità o creare nuove entità semplicemente mescolando e abbinando i componenti (sebbene non vi sia alcun meccanismo per componenti non predefiniti ma componenti speciali potrebbero essere aggiunti al volo nel codice purché esistesse un componente definito per esso.