Déjame ver si al tratar de entender como un desarrollador web / UI JS, puedo ser de ayuda. Además, no vaya demasiado lejos en el agnosticismo lingüístico. Vale la pena estudiar muchos patrones establecidos en otros idiomas, pero se pueden aplicar de manera muy diferente en JS debido a su flexibilidad o realmente no son necesarios debido a la naturaleza maleable del lenguaje. Podría aprovechar algunas oportunidades si escribe su código pensando que JS tiene el mismo conjunto de límites que un lenguaje más clásico orientado a OOP.
En primer lugar, en el factor "no usar OOP", recuerde que los objetos de JavaScript son como plastilina en comparación con otros lenguajes y que realmente debe hacer todo lo posible para construir una pesadilla de esquema de herencia en cascada ya que JS no es clase de composición y composición es mucho más natural. Si está implementando algún tipo de sistema tonto de clase o prototipo en su JS, considere deshacerse de él. En JS usamos cierres, prototipos y pasamos funciones como dulces. Es asqueroso, sucio e incorrecto, pero también poderoso, conciso y así nos gusta.
En realidad, los enfoques pesados de la herencia se explican como un antipatrón en los Patrones de diseño y, por una buena razón, como cualquiera que haya caminado por más de 15 niveles de clase o estructuras similares a la clase para tratar de descubrir dónde diablos la versión reventada de un método venía de puedo decirte.
No sé por qué a tantos programadores les encanta hacer esto (especialmente los chicos de Java que escriben JavaScript por alguna razón), pero es horrible, ilegible y completamente imposible de mantener cuando se usa en exceso. La herencia está bien aquí y allá, pero no es realmente necesaria en JS. En los lenguajes donde es un atajo más atractivo, realmente debería reservarse para preocupaciones de arquitectura más abstracta en lugar de esquemas de modelado literales como frankensteining una implementación zombie a través de una cadena de herencia que incluía un BunnyRabbit porque funcionó. Eso no es buena reutilización de código. Es una pesadilla de mantenimiento.
Como JS dev Entity / Component / System, los motores basados me parecen un sistema / patrón para desacoplar problemas de diseño y luego componer objetos para su implementación en un nivel altamente granular. En otras palabras, un juego de niños en un lenguaje como JavaScript. Pero déjame ver si estoy asimilando esto correctamente primero.
Entidad: lo específico que está diseñando. Estamos hablando más en la dirección de los nombres propios (pero no en realidad, por supuesto). No 'Scene', sino 'IntroAreaLevelOne'. IntroAreaLevelOne podría estar dentro de un cuadro sceneEntity de algún tipo, pero nos estamos centrando en algo específico que varía de otras cosas relacionadas. En el código, una entidad es realmente solo un nombre (o ID) vinculado a un montón de cosas que necesita haber implementado o establecido (los componentes) para ser útil.
Componentes: tipos de cosas que una entidad necesita. Estos son sustantivos generales. Como caminar, animación. Dentro de WalkingAnimation podemos ser más específicos, como "Shambling" (buena opción para zombies y monstruos de plantas), o "ChickenWalker" (ideal para los tipos de robot ed-209ish de articulación inversa). Nota: No estoy seguro de cómo eso podría desacoplarse de la representación de un modelo 3D como ese, por lo que tal vez sea un ejemplo de mierda, pero soy más un JS profesional que un desarrollador de juegos experimentado. En JS, pondría el mecanismo de mapeo en el mismo cuadro con los componentes. Es probable que los componentes por derecho propio sean ligeros en lógica y más en una hoja de ruta que le indique a sus sistemas qué implementar si los sistemas son necesarios (en mi intento de ECS, algunos componentes son solo colecciones de conjuntos de propiedades). Una vez que se establece un componente, '
Sistemas: el verdadero programa de carne está aquí. Los sistemas de inteligencia artificial se construyen y se vinculan, se logra el renderizado, se establecen secuencias de animaciones, etc. Estoy recortando y dejando estos principalmente a la imaginación, pero en el ejemplo System.AI toma un montón de propiedades y escupe una función que se usa para agregar controladores de eventos al objeto que finalmente se usa en la implementación. La clave de System.AI es que cubre varios tipos de componentes. Podría ordenar todas las cosas de IA con un componente, pero hacerlo es malinterpretar el punto de hacer que las cosas sean granulares.
Tenga en cuenta los objetivos: queremos facilitar la conexión de algún tipo de interfaz GUI para que los no diseñadores puedan modificar fácilmente diferentes tipos de cosas maximizando y combinando componentes dentro de un paradigma que tenga sentido para ellos, y queremos alejarnos de esquemas de código arbitrario populares que son mucho más fáciles de escribir que modificar o mantener.
Entonces, en JS, tal vez algo como esto. Los desarrolladores de juegos, por favor, dime si me equivoqué terriblemente:
//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);
}
Ahora, cada vez que necesite un NPC, puede construir con npcBuilders.<npcName>();
Una GUI podría conectarse a los objetos npcEntities y componentes y permitir a los diseñadores ajustar entidades antiguas o crear nuevas entidades simplemente mezclando y combinando componentes (aunque no hay ningún mecanismo para componentes no predeterminados, pero se pueden agregar componentes especiales sobre la marcha). código siempre que haya un componente definido para él.