ให้ฉันดูด้วยการพยายามทำความเข้าใจในฐานะเว็บ / UI JS dev ฉันสามารถช่วยได้ นอกจากนี้อย่าไปไกลเกินไปในเรื่องความไม่เชื่อเรื่องภาษา รูปแบบจำนวนมากที่จัดตั้งขึ้นในภาษาอื่น ๆ นั้นมีค่าในการเรียน แต่สามารถนำไปใช้ใน JS แตกต่างกันมากเนื่องจากมีความยืดหยุ่นหรือไม่จำเป็นจริง ๆ เนื่องจากลักษณะของภาษาที่ละเอียดอ่อน คุณสามารถสร้างโอกาสบางอย่างได้ถ้าคุณเขียนโค้ดที่คิดว่า JS มีขอบเขตชุดเดียวกันกับที่ภาษา OOP เชิงดั้งเดิมจะทำ
ก่อนอื่นบนปัจจัย "ไม่ใช้ OOP" โปรดจำไว้ว่าวัตถุ JavaScript นั้นเป็นเหมือน playdough เมื่อเทียบกับภาษาอื่นและคุณต้องออกจากวิธีการสร้างโครงร่างการสืบทอด - มรดกสืบทอดเนื่องจาก JS ไม่ใช่คลาส - เบสและคอมโพสิตมาเป็นธรรมชาติมากขึ้นกับมัน หากคุณกำลังใช้คลาส hand-me-down ที่โง่เขลาหรือระบบต้นแบบใน JS ของคุณให้ลองทำดู ใน JS เราใช้การปิดต้นแบบและเราผ่านฟังก์ชั่นรอบ ๆ เหมือนขนม มันน่าขยะแขยงและสกปรกและไม่ถูกต้อง แต่ก็มีประสิทธิภาพรัดกุมและนั่นคือวิธีที่เราชอบ
วิธีการที่สืบทอดมาอย่างหนักนั้นถูกสะกดออกมาเป็นรูปแบบต่อต้านในรูปแบบการออกแบบและด้วยเหตุผลที่ดีเช่นเดียวกับใครก็ตามที่เดินลงไป 15+ ระดับของคลาสหรือสิ่งก่อสร้างที่มีลักษณะคล้ายชั้นเรียน กำลังเข้ามาจากสามารถบอกคุณได้
ฉันไม่รู้ว่าทำไมโปรแกรมเมอร์จำนวนมากถึงชอบทำสิ่งนี้ (โดยเฉพาะอย่างยิ่งพวกจาวาที่เขียน JavaScript ด้วยเหตุผลบางอย่าง) แต่มันแย่มากอ่านไม่ออกและไม่ย่อท้ออย่างสมบูรณ์เมื่อเคยเกิน การรับมรดกไม่เป็นไรที่นี่และที่นั่น แต่ไม่จำเป็นจริงๆใน JS ในภาษาที่มีทางลัดที่น่าดึงดูดใจมากขึ้นควรสงวนไว้สำหรับข้อกังวลทางสถาปัตยกรรมที่เป็นนามธรรมมากกว่าการสร้างแบบจำลองตามตัวอักษรมากขึ้นเช่นการสร้างการใช้งานผีดิบผ่านเครือข่ายการสืบทอดที่รวม BunnyRabbit เพราะมันทำงานได้จริง นั่นไม่ใช่การนำโค้ดที่ดีมาใช้ซ้ำ มันเป็นฝันร้ายของการบำรุงรักษา
ในฐานะที่เป็นเอ็นจิ้นที่ใช้ JS dev Entity / Component / System ทำให้ฉันเป็นระบบ / รูปแบบสำหรับการแยกข้อกังวลในการออกแบบและจากนั้นรวบรวมวัตถุสำหรับการนำไปใช้ในระดับที่ละเอียดมาก กล่าวอีกนัยหนึ่งการเล่นของเด็กในภาษาเช่น JavaScript แต่ให้ฉันดูก่อนว่าฉันกำลังทำแบบนี้ถูกต้องก่อนไหม
เอนทิตี - สิ่งเฉพาะที่คุณกำลังออกแบบ เรากำลังพูดถึงคำนามที่เหมาะสมมากกว่า (แต่ไม่ใช่ตามจริง) ไม่ใช่ 'ฉาก' แต่ 'IntroAreaLevelOne' IntroAreaLevelOne อาจนั่งอยู่ในกล่องฉากเหตุการณ์บางอย่าง แต่เรามุ่งเน้นไปที่บางสิ่งที่แตกต่างจากสิ่งที่เกี่ยวข้องอื่น ๆ ในรหัสหน่วยงานนั้นเป็นเพียงชื่อ (หรือ ID) ที่เชื่อมโยงกับสิ่งต่าง ๆ ที่จำเป็นต้องมีการใช้งานหรือสร้างขึ้น (ส่วนประกอบ) เพื่อให้มีประโยชน์
ส่วนประกอบ - ประเภทของสิ่งที่องค์กรต้องการ นี่เป็นคำนามทั่วไป เช่นเดียวกับ WalkingAnimation ภายใน WalkingAnimation เราสามารถเจาะจงมากขึ้นเช่น "Shambling" (ทางเลือกที่ดีสำหรับซอมบี้และมอนสเตอร์พืช) หรือ "ChickenWalker" (ยอดเยี่ยมสำหรับประเภทหุ่นยนต์ ed-209ish แบบย้อนกลับ) หมายเหตุ: ไม่แน่ใจว่าจะแยกแยะจากการแสดงผลของโมเดล 3 มิติแบบนั้นได้อย่างไร - อาจเป็นตัวอย่างที่น่ารำคาญ แต่ฉันเป็นโปร 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 และส่วนประกอบและอนุญาตให้นักออกแบบปรับแต่งเอนทิตีเก่าหรือสร้างเอนทิตีใหม่โดยเพียงแค่ผสมและจับคู่ส่วนประกอบ (แม้ว่าจะไม่มีกลไกในส่วนประกอบที่ไม่ใช่ค่าเริ่มต้น แต่ส่วนประกอบพิเศษสามารถเพิ่มได้ทันที รหัสตราบใดที่มีองค์ประกอบที่กำหนดไว้สำหรับมัน