Позвольте мне понять, могу ли я помочь, пытаясь понять, как разработчик веб-интерфейса или JS-разработчика. Кроме того, не заходите слишком далеко в языковой агностицизм. Многие шаблоны, установленные на других языках, заслуживают изучения, но могут быть применены в JS совсем по-другому из-за его гибкости или действительно не являются необходимыми из-за податливой природы языка. Вы можете упустить некоторые возможности, если напишите свой код, думая, что JS имеет тот же набор границ, что и более классический ООП-ориентированный язык.
Прежде всего, о факторе «не используйте ООП», помните, что объекты JavaScript похожи на playdough по сравнению с другими языками, и вам действительно нужно изо всех сил создать кошмар каскадной схемы наследования, поскольку JS не класс на основе и композитинг приходит гораздо более естественным образом. Если вы внедряете в JS какую-то глупую классную или прототипную систему ручной работы, подумайте об отказе от нее. В JS мы используем замыкания, прототипы и передаем функции, как конфеты. Это отвратительно, грязно и неправильно, но также мощно, кратко, и так нам нравится.
Подходы с тяжелым наследованием на самом деле прописаны в шаблонах проектирования как анти-шаблон, и на это есть веская причина, как если бы каждый, кто прошел класс более 15 уровней или классоподобных структур, попытался выяснить, где, черт возьми, взломанная версия метода приходил от могу вам сказать.
Я не знаю, почему так много программистов любят это делать (особенно Java-парни, пишущие JavaScript по какой-то причине), но это ужасно, неразборчиво и совершенно не поддерживается, когда используется в избытке. Наследование хорошо здесь и там, но не обязательно в JS. В языках, где это более заманчивый ярлык, он действительно должен быть зарезервирован для более абстрактных архитектурных задач, а не для более буквальных схем моделирования, таких как frankensteining реализация зомби через цепочку наследования, которая включала BunnyRabbit, потому что это работало. Это не очень хорошее повторное использование кода. Это кошмар обслуживания.
Как движок на основе JS-разработчика, основанный на сущностях / компонентах / системах, я представляю собой систему / шаблон для разделения проблем проектирования и последующей компоновки объектов для реализации на высоком уровне детализации. Другими словами, детская игра на языке, подобном JavaScript. Но позвольте мне посмотреть, правильно ли я начинаю это делать.
Сущность - конкретная вещь, которую вы разрабатываете. Мы говорим больше в направлении имен собственных (но не на самом деле, конечно). Не «сцена», а «IntroAreaLevelOne». IntroAreaLevelOne может находиться в каком-либо блоке sceneEntity, но мы концентрируемся на чем-то конкретном, что отличается от других связанных вещей. В коде сущность на самом деле является просто именем (или идентификатором), связанным с кучей вещей, которые необходимо внедрить или установить (компоненты), чтобы быть полезными.
Компоненты - типы вещей, в которых нуждается организация. Это общие существительные. Нравится WalkingAnimation. В рамках WalkingAnimation мы можем получить более конкретную информацию, например, «Shabing» (хороший выбор для зомби и растительных монстров) или «ChickenWalker» (отлично подходит для роботов с обратной связью ed-209ish). Примечание: Не уверен, как это могло бы отделить от рендеринга 3D-модели, подобной этой, так что, может быть, это дерьмовый пример, но я скорее профессионал JS, чем опытный разработчик игр. В JS я бы поместил механизм сопоставления в одну коробку с компонентами. Компоненты сами по себе, вероятно, будут лёгкими в логике и большей части дорожной карты, сообщающей вашим системам, что следует реализовывать, если системы даже необходимы (в моей попытке ECS некоторые компоненты являются просто наборами наборов свойств). Как только компонент установлен, он '
Системы - Настоящая программа мяса здесь. Системы ИИ построены и связаны, рендеринг достигнут, последовательности анимаций установлены и т. Д. Я собираюсь выделить их, оставляя в основном воображению, но в примере System.AI принимает множество свойств и выплевывает функцию, которая используется для добавления обработчиков событий к объекту, который в конечном итоге используется в реализации. Ключевым моментом в System.AI является то, что он охватывает несколько типов компонентов. Вы могли бы разобрать все вещи ИИ с одним компонентом, но сделать это - неправильно понять смысл детализации.
Помните о целях: мы хотим упростить подключение некоторого графического интерфейса для тех, кто не является дизайнером, чтобы легко настраивать различные виды контента, максимизируя и сопоставляя компоненты в рамках парадигмы, которая им понятна, и мы хотим уйти от популярные схемы произвольного кода, которые гораздо проще написать, чем модифицировать или поддерживать.
Так что в 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 и компонентов и позволяет дизайнерам настраивать старые объекты или создавать новые, просто смешивая и сопоставляя компоненты (хотя там нет механизма для компонентов не по умолчанию, но специальные компоненты могут быть добавлены на лету в код, пока существует определенный компонент для него.