Дозвольте мені побачити, якщо, намагаючись зрозуміти, як веб / UI JS Dev, я можу вам допомогти. Крім того, не надто далеко в мовному агностицизмі. Багато моделей, встановлених іншими мовами, варто вивчити, але їх можна застосовувати в JS дуже по-різному через його гнучкість або насправді не потрібні через спритний характер мови. Ви можете створити певні можливості, якщо ви пишете свій код, думаючи про JS, як той, що має той самий набір меж, що і більш класична мова, орієнтована на ООП.
Перш за все, про фактор "не використовувати OOP" пам’ятайте, що об’єкти JavaScript схожі на playdough порівняно з іншими мовами, і ви насправді мусите вийти зі свого шляху, щоб створити кошмарну схему наслідування каскаду, оскільки JS не є класом -на основі та композиції до цього йде набагато природніше. Якщо ви впроваджуєте в своєму JS якусь нерозумну систему класу чи прототипу "руко-мене", подумайте, як це зробити. У JS ми використовуємо закриття, прототипи і передаємо функції навколо, як цукерки. Це огидно, брудно і неправильно, але також потужно, лаконічно, і це нам подобається.
Підходи до успадкування важких підходів насправді прописані як анти-модель у шаблонах дизайну і з поважної причини, як кожен, хто знизив 15+ рівнів, що варті класових або класоподібних структур, щоб спробувати розібратися, куди, до біса, розбита версія методу приїжджав з може сказати вам.
Я не знаю, чому так багато програмістів люблять це робити (особливо хлопці з Java, які чомусь пишуть JavaScript), але це жахливо, нерозбірливо і зовсім нездійсненно, коли використовується надмір. Спадщина тут і там добре, але насправді не потрібно в JS. На мовах, де це більш привабливий ярлик, він дійсно повинен бути зарезервований для більш абстрактних архітектурних проблем, а не для більш буквальних схем моделювання, таких як франкенстенінг реалізації зомбі через ланцюг спадкування, який включав BunnyRabbit, оскільки це трапилось. Це не добре використання коду. Це кошмар технічного обслуговування.
Як двигуни, засновані на JS Entity / Component / System, вражають мене системою / зразком для розв’язання проблем дизайну та складання об'єктів для реалізації на високому рівні. Іншими словами, дитяча гра такою мовою, як JavaScript. Але дозвольте мені побачити, чи спочатку я правильно це вирішу.
Сутність - конкретна річ, яку ви проектуєте. Ми говоримо більше в напрямку власних іменників (але насправді, звичайно). Не "Сцена", а "IntroAreaLevelOne". IntroAreaLevelOne може сидіти в ящику sceneEntity якогось типу, але ми зосереджуємось на чомусь конкретному, що відрізняється від інших пов'язаних речей. У коді суб'єкт справді є лише іменем (або ідентифікатором), пов'язаним з купою речей, які йому потрібно впровадити або встановити (компоненти), щоб бути корисним.
Компоненти - типи речей, які потребує суб'єкт господарювання. Це загальні іменники. Як і WalkingAnimation. У WalkingAnimation ми можемо отримати більш конкретні, наприклад, "Shambling" (хороший вибір для зомбі та монстрів рослин) або "ChickenWalker" (чудово підходить для роботів типу "реверс-спільний" ed-209ish). Примітка: Не впевнений, як це могло б відірватися від візуалізації такої 3D-моделі - тож, можливо, приклад, але я більше скористаюся професіоналом JS, ніж досвідченим розробником гри. У JS я б поставив механізм картографування в одне поле з компонентами. Складові самі по собі, ймовірно, виявляють логіку та більше дорожню карту, яка розповість вашим системам, що потрібно реалізувати, якщо такі системи навіть потрібні (в моїй спробі ECS деякі компоненти - це лише колекції наборів властивостей). Після того, як компонент встановлений, він '
Системи - Справжнє програмне м'ясо тут. Системи AI будуються та пов’язуються, досягається візуалізація, встановлюються послідовності анімації тощо ... Я викидаю їх і залишаю це в основному уяві, але в прикладі System.AI приймає купу властивостей і виділяє функцію, яка використовується для додавання обробників подій до об'єкта, який врешті-решт звикає до реалізації. Ключовим у System.AI є те, що він охоплює кілька типів компонентів. Ви можете розібрати всі елементи AI з одним компонентом, але зробити це - неправильно зрозуміти сенс деталізації речей.
Майте на увазі цілі: Ми хочемо спростити підключення якогось інтерфейсу GUI для недизайнерів, щоб легко налаштувати різні види матеріалів шляхом максимізації та відповідності компонентів у парадигмі, яка має для них сенс, і ми хочемо піти від популярні довільні схеми коду, які писати набагато простіше, ніж їх змінювати або підтримувати.
Тож у JS, можливо, щось подібне. Ігри-розробники, скажіть, будь ласка, чи я зрозумів це жахливо неправильно:
//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);
}
Тепер будь-який час, коли вам потрібен NPC, ви можете створити його npcBuilders.<npcName>();
Графічний інтерфейс може підключатися до об'єктів npcEntities та компонентів і дозволяти дизайнерам налаштовувати старі об'єкти або створювати нові об'єкти шляхом простого змішування та узгодження компонентів (хоча там немає механізму для компонентів, що не замовчуються, але спеціальні компоненти можуть бути додані під час руху в код, поки для нього був визначений компонент.