让我看看通过尝试理解为Web / UI JS开发人员是否对我有帮助。另外,在语言不可知论方面也不要太过分。许多其他语言建立的模式值得研究,但由于其灵活性而可以在JS中非常不同地应用,或者由于该语言的可塑性而实际上并不是必需的。如果您在编写代码时考虑到JS具有与更传统的面向OOP的语言相同的边界集,则可能会吹牛。
首先,在“不要使用OOP”因素上,请记住,与其他语言相比,JavaScript对象就像橡皮泥一样,由于JS不属于类,您实际上必须竭尽全力构建级联继承方案的噩梦基于和合成自然而然。如果您要在JS中实现一些愚蠢的类或原型递减系统,请考虑放弃它。在JS中,我们使用闭包,原型,然后像糖果一样传递函数。这令人作呕,肮脏和错误,但又功能强大,简洁,这就是我们喜欢的方式。
实际上,继承繁重的方法在设计模式中被明确说明是一种反模式,这是有充分理由的,因为凡是走过15个或15个以上级别的类或类结构的人,都可以尝试找出方法的无效版本在哪里。来自可以告诉你。
我不知道为什么有这么多的程序员喜欢这样做(尤其是出于某种原因而编写JavaScript的Java程序员),但是使用起来太糟糕了,难以理解并且完全无法维护。在这里和那里都可以继承,但是在JS中并不是必须的。在使用更诱人的快捷方式的语言中,它实际上应该保留给更多的抽象体系结构问题,而不是更多的字面化建模方案,例如通过包含BunnyRabbit的继承链来弗兰肯斯坦僵尸实现,因为它确实起作用了。这不是很好的代码重用。这是一场维护噩梦。
作为JS开发人员,基于实体/组件/系统的引擎将我作为系统/模式来解决设计问题,然后将对象进行组合以实现高度粒度的实现。换句话说,儿童游戏采用JavaScript之类的语言。但是让我看看我是否先正确地做过这个。
实体-您正在设计的特定事物。我们正在更多地谈论专有名词的方向(当然,实际上不是)。不是“场景”,而是“ IntroAreaLevelOne”。IntroAreaLevelOne可能位于某种SceneEntity框内,但我们关注的是与其他相关事物不同的特定事物。在代码中,实体实际上只是一个名称(或ID),它与一堆需要实现或建立的东西(组件)相关,才能有用。
组件-实体需要的事物类型。这些是通用名词。像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>();
GUI可以插入npcEntities和components对象,并允许设计人员通过简单地混合和匹配组件来调整旧实体或创建新实体(尽管其中没有用于非默认组件的机制,但可以在运行时动态添加特殊组件)代码,只要有一个定义的组件即可。