อย่างไรก็ตามมันมีเหตุผลที่จะสร้างแอพพลิเคชั่นที่ใช้สถาปัตยกรรม Component-Entity-System ทั่วไปในเอ็นจิ้นเกมหรือไม่?
สำหรับฉันอย่างแน่นอน ฉันทำงานใน visual FX และศึกษาระบบที่หลากหลายในสาขานี้สถาปัตยกรรมของพวกเขา (รวมถึง CAD / CAM), หิวสำหรับ SDK และเอกสารใด ๆ ที่จะให้ความรู้สึกถึงข้อดีข้อเสียของการตัดสินใจทางสถาปัตยกรรมที่ไม่มีที่สิ้นสุด สามารถทำได้แม้กระทั่งสิ่งที่บอบบางที่สุดก็ไม่ได้สร้างผลกระทบที่ลึกซึ้งเสมอไป
VFX ค่อนข้างคล้ายกับเกมที่มีแนวคิดหลักเดียวของ "ฉาก" พร้อมวิวพอร์ตที่แสดงผลลัพธ์ที่แสดงผล มีแนวโน้มที่จะมีการประมวลผลแบบวนรอบส่วนกลางจำนวนมากที่หมุนรอบฉากนี้อย่างต่อเนื่องในบริบทของภาพเคลื่อนไหวซึ่งอาจมีฟิสิกส์เกิดขึ้นตัวปล่อยอนุภาควางไข่อนุภาควางตาข่ายเป็นภาพเคลื่อนไหวและเรนเดอร์ภาพเคลื่อนไหวเคลื่อนไหว ฯลฯ และท้ายที่สุด ทั้งหมดให้กับผู้ใช้ในตอนท้าย
อีกแนวคิดที่คล้ายกันกับเอ็นจิ้นเกมที่ซับซ้อนอย่างน้อยก็คือความต้องการมุมมอง "ผู้ออกแบบ" ที่นักออกแบบสามารถออกแบบฉากได้อย่างยืดหยุ่นรวมถึงความสามารถในการเขียนโปรแกรมแบบเบาของตนเอง (สคริปต์และโหนด)
ในช่วงหลายปีที่ผ่านมาฉันพบว่า ECS ทำสิ่งที่ดีที่สุด แน่นอนว่าไม่ได้หย่าขาดจากเรื่องส่วนตัว แต่ฉันอยากบอกว่ามันดูเหมือนจะทำให้เกิดปัญหาน้อยที่สุด มันแก้ไขปัญหาสำคัญอีกมากมายที่เราพยายามอยู่ตลอดเวลาในขณะที่ให้ผู้เยาว์ใหม่เพียงไม่กี่คนกลับมา
OOP แบบดั้งเดิม
วิธีการแบบดั้งเดิมของ OOP แบบดั้งเดิมนั้นมีความแข็งแกร่งมากเมื่อคุณมีความเข้าใจอย่างถ่องแท้เกี่ยวกับข้อกำหนดการออกแบบ แต่ไม่ใช่ข้อกำหนดการดำเนินงาน ไม่ว่าจะด้วยวิธีการอินเตอร์เฟซที่หลากหลายมากกว่าหรือวิธีการลำดับชั้น ABC ที่ซ้อนกันมากขึ้นก็มีแนวโน้มที่จะประสานการออกแบบและทำให้ยากต่อการเปลี่ยนแปลงในขณะที่ทำให้การใช้งานง่ายขึ้นและปลอดภัยขึ้นในการเปลี่ยนแปลง มีความต้องการความไม่แน่นอนในผลิตภัณฑ์ใด ๆ ที่ผ่านรุ่นเดียวดังนั้นแนวทางของ OOP มักจะทำให้เสถียรภาพ (ความยากลำบากในการเปลี่ยนแปลงและการขาดเหตุผลในการเปลี่ยนแปลง) ไปสู่ระดับการออกแบบและความไม่แน่นอน (ความง่ายในการเปลี่ยนแปลงและเหตุผลในการเปลี่ยนแปลง) ถึงระดับการปฏิบัติ
อย่างไรก็ตามกับการพัฒนาข้อกำหนดของผู้ใช้ปลายทางทั้งการออกแบบและการใช้งานอาจจำเป็นต้องเปลี่ยนแปลงบ่อยครั้ง คุณอาจพบบางสิ่งที่แปลกประหลาดเช่นความต้องการของผู้ใช้ที่แข็งแกร่งสำหรับสิ่งมีชีวิตแบบอะนาล็อกที่ต้องเป็นทั้งพืชและสัตว์ในเวลาเดียวกัน วิธีการเชิงวัตถุแบบปกติไม่ได้ปกป้องคุณที่นี่และบางครั้งอาจทำให้การเปลี่ยนแปลงที่ไม่คาดคิดและการทำลายแนวคิดเกิดขึ้นได้ยากขึ้น เมื่อพื้นที่ที่มีความสำคัญต่อประสิทธิภาพเกิดขึ้นเหตุผลในการออกแบบก็เปลี่ยนแปลงทวีคูณมากขึ้น
การรวมอินเทอร์เฟซที่ละเอียดหลายอย่างเข้าด้วยกันเพื่อสร้างอินเทอร์เฟซที่สอดคล้องกันของวัตถุสามารถช่วยได้มากในการรักษาเสถียรภาพของรหัสลูกค้า แต่ก็ไม่ได้ช่วยในการรักษาเสถียรภาพของชนิดย่อยซึ่งบางครั้งอาจ คุณสามารถมีหนึ่งอินเทอร์เฟซที่ถูกใช้โดยส่วนหนึ่งของระบบของคุณเท่านั้นตัวอย่างเช่น แต่มีชนิดย่อยที่แตกต่างกันหลายพันชนิดที่นำอินเตอร์เฟสมา ในกรณีดังกล่าวการรักษาชนิดย่อยที่ซับซ้อน (ซับซ้อนเนื่องจากมีความรับผิดชอบอินเทอร์เฟซที่แตกต่างกันจำนวนมากเพื่อเติมเต็ม) สามารถกลายเป็นฝันร้ายมากกว่ารหัสที่ใช้พวกมันผ่านทางอินเตอร์เฟส OOP มีแนวโน้มที่จะถ่ายโอนความซับซ้อนไปยังระดับวัตถุในขณะที่ ECS ถ่ายโอนไปยังระดับไคลเอนต์ ("ระบบ") และอาจเหมาะเมื่อมีระบบน้อยมาก แต่ทั้งกลุ่มสอดคล้องของ "วัตถุ" ("เอนทิตี้")
ชั้นยังเป็นเจ้าของข้อมูลของตนเองและสามารถรักษาค่าคงที่ทั้งหมดด้วยตัวเอง อย่างไรก็ตามมีค่าคงที่ "หยาบ" ที่จริงยังคงยากที่จะรักษาเมื่อวัตถุมีปฏิสัมพันธ์ซึ่งกันและกัน สำหรับระบบที่ซับซ้อนโดยรวมที่จะอยู่ในสถานะที่ถูกต้องมักจะต้องพิจารณากราฟที่ซับซ้อนของวัตถุแม้ว่าค่าคงที่แต่ละตัวจะได้รับการดูแลอย่างเหมาะสม วิธีการแบบดั้งเดิมของ OOP สามารถช่วยรักษาค่าคงที่เม็ดเล็ก แต่จริง ๆ แล้วสามารถทำให้ยากที่จะรักษาค่าคงที่กว้างและหยาบถ้าวัตถุมุ่งเน้นไปที่แง่มุมเล็ก ๆ ของระบบ
นั่นคือสิ่งที่แนวทางแบบ ECS ของการสร้างบล็อกเลโก้เหล่านี้จะมีประโยชน์มาก นอกจากนี้เมื่อระบบมีการออกแบบที่หยาบกว่าวัตถุทั่วไปมันจะง่ายกว่าที่จะรักษาค่าคงที่แบบหยาบเหล่านั้นไว้ที่มุมมองที่มองด้วยตาของระบบ การโต้ตอบกับวัตถุเล็ก ๆ จำนวนมากกลายเป็นระบบขนาดใหญ่เพียงระบบเดียวที่มุ่งเน้นไปที่งานกว้าง ๆ แทนที่จะเป็นวัตถุเล็ก ๆ ที่เน้นงานเล็ก ๆ น้อย ๆ ด้วยกราฟการพึ่งพาซึ่งจะครอบคลุมกระดาษหนึ่งกิโลเมตร
แต่ฉันต้องมองออกไปนอกสนามของฉันที่อุตสาหกรรมเกมเพื่อเรียนรู้เกี่ยวกับ ECS แม้ว่าฉันจะเป็นหนึ่งในความคิดเชิงข้อมูล นอกจากนี้ยังตลกพอฉันเกือบจะหาทางไปสู่ ECS ด้วยตัวเองแค่วนซ้ำและพยายามหาการออกแบบที่ดีขึ้น ฉันไม่ได้ทำมาตลอดและคิดถึงรายละเอียดที่สำคัญมากซึ่งก็คือการทำให้เป็นส่วนของ "ระบบ" และการบีบอัดส่วนประกอบลงไปจนถึงข้อมูลดิบ
ฉันจะลองดูว่าฉันจะตัดสินใจเกี่ยวกับ ECS ได้อย่างไรและมันสิ้นสุดลงอย่างไรในการแก้ปัญหาทั้งหมดด้วยการทำซ้ำการออกแบบก่อนหน้านี้ ฉันคิดว่ามันจะช่วยเน้นว่าทำไมคำตอบที่นี่อาจเป็น "ใช่" ที่แข็งแกร่งมากซึ่ง ECS นั้นสามารถใช้งานได้ไกลกว่าอุตสาหกรรมเกม
ยุค 80 Brute Force Architecture
สถาปัตยกรรมแรกที่ฉันทำงานในอุตสาหกรรม VFX มีมรดกที่ยาวนานซึ่งผ่านมานับสิบปีตั้งแต่ฉันเข้าร่วม บริษัท มันคือเดรัจฉานบังคับ C ดิบเข้ารหัสตลอดทาง (ไม่เอียงบน C, ฉันรัก C แต่วิธีที่มันถูกใช้ที่นี่เป็นน้ำมันดิบจริงๆ) ชิ้นส่วนขนาดเล็กและขนาดใหญ่คล้ายการขึ้นต่อกันเช่นนี้
และนี่คือไดอะแกรมที่ง่ายขึ้นอย่างมากของชิ้นส่วนเล็ก ๆ ของระบบ ลูกค้าแต่ละรายในแผนภาพ ("การเรนเดอร์", "ฟิสิกส์", "โมชั่น") จะได้รับวัตถุ "ทั่วไป" ซึ่งพวกเขาจะตรวจสอบฟิลด์ประเภทเช่น:
void transform(struct Object* obj, const float mat[16])
{
switch (obj->type)
{
case camera:
// cast to camera and do something with camera fields
break;
case light:
// cast to light and do something with light fields
break;
...
}
}
แน่นอนว่ามีโค้ดที่น่าเกลียดและซับซ้อนกว่านี้มาก บ่อยครั้งที่ฟังก์ชั่นเพิ่มเติมจะถูกเรียกจากกรณีสวิตช์เหล่านี้ซึ่งจะทำสวิตช์ซ้ำซ้ำแล้วซ้ำอีก ไดอะแกรมและรหัสนี้อาจดูเหมือน ECS-lite แต่ไม่มีความแตกต่างขององค์ประกอบเอนทิตีที่แข็งแกร่ง (" นี่คือวัตถุที่กล้องใช่หรือไม่" ไม่ใช่ "วัตถุนี้ให้การเคลื่อนไหวหรือไม่") และไม่มี "ระบบ" ( ฟังก์ชั่นที่ซ้อนกันจำนวนมากไปทั่วสถานที่และผสมความรับผิดชอบ) ในกรณีนั้นทุกอย่างมีความซับซ้อนฟังก์ชั่นใด ๆ อาจเป็นไปได้สำหรับภัยพิบัติที่จะเกิดขึ้น
ขั้นตอนการทดสอบของเราที่นี่มักจะต้องตรวจสอบสิ่งต่าง ๆ เช่นตาข่ายแยกออกจากรายการประเภทอื่น ๆ แม้ว่าสิ่งเดียวกันเกิดขึ้นกับทั้งคู่เนื่องจากลักษณะที่ดุร้ายของการเขียนโค้ดที่นี่ (มักมาพร้อมสำเนาจำนวนมากและวาง) เป็นไปได้มากว่าสิ่งใดที่ตรรกะเดียวกันที่แน่นอนอาจล้มเหลวจากประเภทรายการหนึ่งไปยังอีกรายการหนึ่ง การพยายามขยายระบบเพื่อจัดการกับรายการประเภทใหม่นั้นค่อนข้างสิ้นหวังแม้ว่าจะมีความต้องการของผู้ใช้ที่แสดงออกอย่างมากเนื่องจากมันยากเกินไปเมื่อเราพยายามอย่างมากที่จะจัดการกับรายการประเภทที่มีอยู่
ข้อดีบางประการ:
- เอ่อ ... ไม่ได้มีประสบการณ์ด้านวิศวกรรมเลยฉันเดา? ระบบนี้ไม่ต้องการความรู้ใด ๆ เกี่ยวกับแนวคิดพื้นฐานเช่น polymorphism มันเป็นพลังที่ไร้เดียงสาดังนั้นฉันจึงเดาว่าแม้แต่ผู้เริ่มต้นอาจจะเข้าใจโค้ดบางส่วนได้แม้ว่ามืออาชีพที่ดีบั๊กสามารถรักษามันได้
ข้อเสียบางอย่าง:
- การบำรุงรักษาฝันร้าย ทีมการตลาดของเรารู้สึกว่าจำเป็นที่จะต้องอวดว่าเราแก้ไขข้อผิดพลาดที่ไม่ซ้ำกันกว่า 2,000 รายการในรอบ 3 ปีเดียว สำหรับฉันนั่นเป็นสิ่งที่น่าอายเกี่ยวกับที่เรามีข้อบกพร่องมากมายตั้งแต่แรกและกระบวนการนั้นอาจจะยังคงแก้ไขเพียงประมาณ 10% ของข้อผิดพลาดทั้งหมดซึ่งเติบโตขึ้นเป็นจำนวนมากตลอดเวลา
- เกี่ยวกับวิธีการที่ยืดหยุ่นที่สุด
สถาปัตยกรรมยุค 90 COM
อุตสาหกรรม VFX ส่วนใหญ่ใช้สถาปัตยกรรมรูปแบบนี้จากสิ่งที่ฉันได้รวบรวมอ่านเอกสารเกี่ยวกับการตัดสินใจในการออกแบบของพวกเขาและดูที่ชุดพัฒนาซอฟต์แวร์ของพวกเขา
อาจไม่เป็น COM ที่ระดับ ABI (สถาปัตยกรรมเหล่านี้บางส่วนอาจมีปลั๊กอินที่เขียนโดยใช้คอมไพเลอร์เดียวกันเท่านั้น) แต่ใช้คุณสมบัติที่คล้ายกันจำนวนมากร่วมกับเคียวรีอินเตอร์เฟสที่สร้างขึ้นบนวัตถุเพื่อดูว่าอินเทอร์เฟซสนับสนุนอะไรบ้าง
ด้วยวิธีการเช่นนี้transform
ฟังก์ชันแบบอะนาล็อกด้านบนจะคล้ายกับแบบฟอร์มนี้:
void transform(Object obj, const Matrix& mat)
{
// Wrapper that performs an interface query to see if the
// object implements the IMotion interface.
MotionRef motion(obj);
// If the object supported the IMotion interface:
if (motion.valid())
{
// Transform the item through the IMotion interface.
motion->transform(mat);
...
}
}
นี่คือวิธีการที่ทีมใหม่ของ codebase เก่านั้นตัดสินเพื่อปรับเปลี่ยนในที่สุด และมันก็เป็นการปรับปรุงที่น่าทึ่งกว่าต้นฉบับในแง่ของความยืดหยุ่นและการบำรุงรักษา แต่ก็ยังมีบางประเด็นที่ฉันจะกล่าวถึงในหัวข้อถัดไป
ข้อดีบางประการ:
- มีความยืดหยุ่น / ยืด / บำรุงรักษาได้ดีกว่าสารละลายเดรสแรงก่อนหน้านี้อย่างมาก
- ส่งเสริมความสอดคล้องที่แข็งแกร่งกับหลักการมากมายของ SOLID โดยการทำให้ทุกส่วนต่อประสานเป็นนามธรรมอย่างสมบูรณ์
ข้อเสียบางอย่าง:
- มากมายสำเร็จรูป ส่วนประกอบของเราต้องได้รับการเผยแพร่ผ่านรีจิสตรีเพื่อสร้างอินสแตนซ์ของวัตถุอินเทอร์เฟซที่รองรับนั้นจำเป็นต้องมีทั้งการสืบทอด ("การนำไปใช้" ใน Java) ส่วนต่อประสานและให้รหัสบางอย่างเพื่อระบุว่า
- เลื่อนระดับตรรกะที่ซ้ำกันไปทั่วสถานที่อันเป็นผลมาจากอินเทอร์เฟซบริสุทธิ์ ตัวอย่างเช่นส่วนประกอบทั้งหมดที่นำมาใช้
IMotion
จะมีสถานะที่แน่นอนเหมือนกันและการใช้งานที่เหมือนกันแน่นอนสำหรับฟังก์ชั่นทั้งหมด เพื่อลดสิ่งนี้เราจะเริ่มรวบรวมคลาสพื้นฐานและฟังก์ชันผู้ช่วยทั่วทั้งระบบสำหรับสิ่งต่าง ๆ ที่มีแนวโน้มที่จะนำไปใช้ซ้ำซ้อนในลักษณะเดียวกันสำหรับอินเทอร์เฟซเดียวกันและอาจมีมรดกหลายอย่างเกิดขึ้นหลังฝากระโปรง ยุ่งภายใต้ประทุนแม้ว่ารหัสลูกค้าจะง่าย
- การไม่มีประสิทธิภาพ: เซสชัน vtune มักแสดงให้เห็นว่า
QueryInterface
ฟังก์ชั่นพื้นฐานมักจะแสดงเป็นฮอตสปอตกลางถึงบนและบางครั้งแม้แต่ฮอตสปอตอันดับ 1 เพื่อลดสิ่งนั้นเราจะทำสิ่งต่าง ๆ เช่นมีการเรนเดอร์ส่วนต่าง ๆ ของ codebase cache ซึ่งเป็นรายการของออบเจ็กต์ที่ทราบแล้วว่าให้การสนับสนุนIRenderable
แต่นั่นเพิ่มความซับซ้อนและค่าบำรุงรักษาอย่างมาก ในทำนองเดียวกันนี่เป็นการวัดที่ยากขึ้น แต่เราสังเกตเห็นการชะลอตัวที่แน่นอนบางอย่างเมื่อเทียบกับการเข้ารหัสแบบ C ที่เราทำก่อนหน้านี้เมื่อทุกอินเทอร์เฟซเดียวต้องการการแจกจ่ายแบบไดนามิก สิ่งต่าง ๆ เช่นการคาดคะเนของสาขาและอุปสรรคการปรับให้เหมาะสมนั้นยากที่จะวัดนอกโค้ดเพียงเล็กน้อย แต่ผู้ใช้มักสังเกตเห็นการตอบสนองของอินเทอร์เฟซผู้ใช้และสิ่งต่าง ๆ เช่นแย่ลงโดยการเปรียบเทียบซอฟต์แวร์รุ่นก่อนหน้าและใหม่กว่า ด้านสำหรับพื้นที่ที่ความซับซ้อนของอัลกอริทึมไม่เปลี่ยนแปลงมีเพียงค่าคงที่เท่านั้น
- ยังคงยากที่จะให้เหตุผลเกี่ยวกับความถูกต้องในระดับระบบที่กว้างขึ้น แม้ว่ามันจะง่ายกว่าวิธีการก่อนหน้านี้อย่างมีนัยสำคัญ แต่ก็ยังยากที่จะเข้าใจการโต้ตอบที่ซับซ้อนระหว่างวัตถุต่างๆในระบบนี้
- เรามีปัญหาในการแก้ไขอินเตอร์เฟสให้ถูกต้อง แม้ว่าอาจจะมีเพียงสถานที่กว้าง ๆ แห่งเดียวในระบบที่ใช้อินเทอร์เฟซ แต่ความต้องการของผู้ใช้จะเปลี่ยนไปตามรุ่นและเราจะต้องทำการลดหลั่นการเปลี่ยนแปลงในคลาสทั้งหมดที่ใช้อินเทอร์เฟซเพื่อรองรับฟังก์ชั่นใหม่ อินเทอร์เฟซเช่นถ้ามีบางส่วนที่เป็นนามธรรมชั้นฐานที่รวมศูนย์ตรรกะใต้กระโปรง (บางคนจะปรากฏอยู่ตรงกลางของการเปลี่ยนแปลงเหล่านี้ลดหลั่นลงไปด้วยความหวังว่าจะไม่ทำอย่างนี้ซ้ำแล้วซ้ำอีก)
การตอบสนองในทางปฏิบัติ: องค์ประกอบ
หนึ่งในสิ่งที่เราสังเกตเห็นก่อนหน้านี้ (หรืออย่างน้อยฉันก็เป็น) ที่ก่อให้เกิดปัญหาก็คือIMotion
อาจมีการใช้งานโดยคลาสที่แตกต่างกัน 100 คลาส แต่มีการใช้งานแบบเดียวกันและมีสถานะเกี่ยวข้อง ยิ่งไปกว่านั้นมันจะถูกใช้โดยระบบเพียงไม่กี่อย่างเช่นการเรนเดอร์การเคลื่อนไหวแบบ keyframed และฟิสิกส์
ดังนั้นในกรณีเช่นนี้เราอาจมีความสัมพันธ์แบบ 3 ต่อ 1 ระหว่างระบบโดยใช้ส่วนต่อประสานกับส่วนต่อประสานและความสัมพันธ์แบบ 100 ต่อ 1 ระหว่างชนิดย่อยที่ใช้ส่วนต่อประสานกับส่วนต่อประสาน
ความซับซ้อนและการบำรุงรักษานั้นก็จะถูกบิดเบือนอย่างมากในการดำเนินงานและการบำรุงรักษา 100 ชนิดย่อยแทน 3 IMotion
ระบบไคลเอนต์ที่ขึ้นอยู่กับ สิ่งนี้เปลี่ยนความยากลำบากในการบำรุงรักษาทั้งหมดของเราเป็นการบำรุงรักษา 100 ชนิดย่อยเหล่านี้ไม่ใช่ 3 แห่งที่ใช้อินเทอร์เฟซ การอัปเดต 3 ตำแหน่งในรหัสด้วยน้อยหรือไม่มี "ข้อต่อทางอ้อมที่ออกผล" (เป็นการอ้างอิงผ่านทางอินเทอร์เฟซไม่ใช่แบบพึ่งพาโดยตรง) ไม่ใช่เรื่องใหญ่: การอัปเดต 100 ชนิดย่อยด้วยการโหลดข้อมูล เรื่องใหญ่ *
* ฉันรู้ว่ามันแปลกและผิดที่จะสกรูกับคำจำกัดความของ "ข้อต่อที่ผิดปกติ" ในแง่นี้จากมุมมองของการนำไปใช้ฉันไม่พบวิธีที่ดีกว่าในการอธิบายความซับซ้อนในการบำรุงรักษาที่เกี่ยวข้องเมื่อทั้งสองอินเทอร์เฟซ ต้องเปลี่ยน
ดังนั้นฉันจึงต้องผลักดันอย่างหนัก แต่ฉันเสนอว่าเราพยายามที่จะปฏิบัติได้มากขึ้นและผ่อนคลายแนวคิด "อินเตอร์เฟสบริสุทธิ์" ทั้งหมด ฉันไม่มีเหตุผลที่จะทำสิ่งที่IMotion
เป็นนามธรรมและไร้สัญชาติอย่างสมบูรณ์เว้นแต่ว่าเราเห็นประโยชน์ที่จะได้รับการใช้งานที่หลากหลาย ในกรณีของเราIMotion
การมีการใช้งานที่หลากหลายจะกลายเป็นฝันร้ายในการบำรุงรักษาเพราะเราไม่ต้องการความหลากหลาย แทนที่จะทำเช่นนั้นเราพยายามทำซ้ำการเคลื่อนไหวครั้งเดียวซึ่งดีต่อการเปลี่ยนแปลงความต้องการของลูกค้าและบ่อยครั้งที่ทำงานกับแนวคิดอินเทอร์เฟซที่บริสุทธิ์มากมายที่พยายามบังคับให้ผู้ดำเนินการทุกคนใช้IMotion
การใช้งานแบบเดียวกันและสถานะที่เกี่ยวข้อง เป้าหมายซ้ำซ้อน
การเชื่อมต่อจึงกลายเป็นเหมือนวงกว้างที่Behaviors
เกี่ยวข้องกับเอนทิตี IMotion
ก็จะกลายเป็นMotion
"องค์ประกอบ" (ฉันเปลี่ยนวิธีที่เรากำหนด "องค์ประกอบ" ออกจาก COM เป็นหนึ่งที่อยู่ใกล้กับคำนิยามปกติของชิ้นส่วนที่ทำขึ้นนิติบุคคล "สมบูรณ์")
แทนสิ่งนี้:
class IMotion
{
public:
virtual ~IMotion() {}
virtual void transform(const Matrix& mat) = 0;
...
};
เราพัฒนามันเป็นแบบนี้:
class Motion
{
public:
void transform(const Matrix& mat)
{
...
}
...
private:
Matrix transformation;
...
};
นี่เป็นการละเมิดหลักการการผกผันของการพึ่งพาอาศัยกันอย่างโจ่งแจ้งเพื่อเริ่มเปลี่ยนจากนามธรรมกลับไปเป็นรูปธรรม แต่สำหรับฉันแล้วระดับของนามธรรมนั้นมีประโยชน์เฉพาะในกรณีที่เราสามารถคาดการณ์ความต้องการของแท้ได้ในอนาคตโดยปราศจากข้อสงสัยอันสมเหตุสมผล การใช้สถานการณ์ที่ไร้สาระ "เกิดอะไรขึ้นถ้า" แยกออกจากประสบการณ์ของผู้ใช้อย่างสมบูรณ์ (ซึ่งอาจจะต้องมีการเปลี่ยนแปลงการออกแบบ) เพื่อความยืดหยุ่น
ดังนั้นเราจึงเริ่มพัฒนาการออกแบบนี้ กลายเป็นมากขึ้นเช่นQueryInterface
QueryBehavior
ยิ่งกว่านั้นมันเริ่มไร้ประโยชน์ที่จะใช้การสืบทอดที่นี่ เราใช้การแต่งเพลงแทน วัตถุกลายเป็นชุดของส่วนประกอบที่มีความสามารถในการสอบถามและฉีดที่รันไทม์
ข้อดีบางประการ:
- เป็นเรื่องง่ายกว่าที่เราจะรักษาไว้ในกรณีของเรามากกว่าระบบ COM-style บริสุทธิ์ที่ผ่านมา ความประหลาดใจที่ไม่คาดคิดเช่นการเปลี่ยนแปลงข้อกำหนดหรือข้อร้องเรียนเวิร์กโฟลว์สามารถรองรับได้ง่ายขึ้นด้วยการใช้งานที่เป็นศูนย์กลางและเห็นได้ชัดเจน
Motion
เช่นและไม่แยกย้ายกันไปหลายร้อยชนิดย่อย
- มอบความยืดหยุ่นในระดับใหม่ที่เราต้องการ ในระบบก่อนหน้าของเราเนื่องจากการสืบทอดโมเดลเป็นความสัมพันธ์แบบคงที่เราสามารถกำหนดเอนทิตีใหม่ได้อย่างมีประสิทธิภาพ ณ เวลารวบรวมใน C ++ เราไม่สามารถทำได้จากภาษาสคริปต์เช่นด้วยวิธีการจัดแต่งเราสามารถรวบรวมหน่วยงานใหม่ได้อย่างรวดเร็วในขณะทำงานด้วยการแนบส่วนประกอบเข้ากับรายการและเพิ่มเข้าไปในรายการ "เอนทิตี" กลายเป็นผืนผ้าใบว่างเปล่าซึ่งเราสามารถรวบรวมภาพของสิ่งที่เราต้องการได้ทันทีโดยระบบที่เกี่ยวข้องจะจดจำและประมวลผลเอนทิตีเหล่านี้โดยอัตโนมัติ
ข้อเสียบางอย่าง:
- เรายังคงมีช่วงเวลาที่ยากลำบากในแผนกประสิทธิภาพและการบำรุงรักษาในพื้นที่สำคัญด้านประสิทธิภาพ แต่ละระบบจะยังคงต้องการที่จะแคชองค์ประกอบของเอนทิตีที่ให้พฤติกรรมเหล่านี้เพื่อหลีกเลี่ยงการวนซ้ำพวกเขาทั้งหมดและตรวจสอบสิ่งที่มีอยู่ แต่ละระบบที่ต้องการประสิทธิภาพจะทำสิ่งนี้แตกต่างกันเล็กน้อยและมีแนวโน้มที่จะมีชุดของข้อบกพร่องที่แตกต่างกันในการไม่สามารถอัปเดตรายการแคชนี้และอาจเป็นโครงสร้างข้อมูล (ถ้าการค้นหาบางรูปแบบเกี่ยวข้อง เหตุการณ์เปลี่ยนฉากที่ไม่ชัดเจนเช่น
- ยังมีบางสิ่งที่น่าอึดอัดใจและซับซ้อนที่ฉันไม่สามารถจับนิ้วมือของฉันเกี่ยวกับวัตถุเล็ก ๆ น้อย ๆ ที่เป็นพฤติกรรมง่าย ๆ เหล่านี้ได้ เรายังคงวางเหตุการณ์จำนวนมากเพื่อจัดการกับการโต้ตอบระหว่างวัตถุ "พฤติกรรม" เหล่านี้ซึ่งบางครั้งจำเป็นและผลลัพธ์ก็คือรหัสที่มีการกระจายอำนาจอย่างมาก วัตถุเล็ก ๆ แต่ละชิ้นนั้นง่ายต่อการทดสอบความถูกต้องและถ่ายทีละตัวซึ่งมักจะถูกต้องสมบูรณ์แบบ แต่มันก็ยังรู้สึกเหมือนว่าเรากำลังพยายามรักษาระบบนิเวศขนาดใหญ่ที่ประกอบด้วยหมู่บ้านเล็ก ๆ และพยายามให้เหตุผลเกี่ยวกับสิ่งที่พวกเขาทำและรวมกันเพื่อทำทั้งหมด codebase ยุค 80 ของ C-style ให้ความรู้สึกเหมือนหนึ่งมหากาพย์ megalopolis ที่มีพลเมืองมากเกินไปซึ่งเป็นฝันร้ายของการบำรุงรักษาแน่นอน
- การสูญเสียความยืดหยุ่นเมื่อขาดสิ่งที่เป็นนามธรรม แต่ในพื้นที่ที่เราไม่เคยพบเจอกับความต้องการที่แท้จริงของมันมันแทบจะเป็นข้อปฏิบัติที่ปฏิบัติได้จริง (อย่างน้อยก็เป็นทฤษฎีอย่างหนึ่ง)
- การรักษาความเข้ากันได้ของ ABI นั้นยากเสมอและสิ่งนี้ทำให้มันยากขึ้นโดยต้องการข้อมูลที่มีเสถียรภาพและไม่ใช่แค่ส่วนต่อประสานที่เสถียรที่เกี่ยวข้องกับ "พฤติกรรม" อย่างไรก็ตามเราสามารถเพิ่มพฤติกรรมใหม่ ๆ ได้อย่างง่ายดายและลดค่าใช้จ่ายที่มีอยู่หากต้องการการเปลี่ยนแปลงสถานะและนั่นง่ายกว่าการทำ backflips ใต้ส่วนต่อประสานที่ระดับย่อยเพื่อจัดการข้อกังวลเกี่ยวกับเวอร์ชัน
ปรากฏการณ์หนึ่งที่เกิดขึ้นคือเนื่องจากเราสูญเสียสิ่งที่เป็นนามธรรมในองค์ประกอบพฤติกรรมเหล่านี้เราจึงมีสิ่งเหล่านี้มากขึ้น ตัวอย่างเช่นแทนที่จะเป็นIRenderable
องค์ประกอบนามธรรมเราต้องการแนบวัตถุที่มีรูปธรรมMesh
หรือPointSprites
ส่วนประกอบ ระบบการเรนเดอร์จะรู้วิธีการเรนเดอร์Mesh
และPointSprites
ส่วนประกอบและจะค้นหาเอนทิตีที่จัดเตรียมส่วนประกอบดังกล่าว ในเวลาอื่น ๆ ที่เรามี renderables อื่น ๆ เช่นSceneLabel
ที่เราค้นพบว่าเราจำเป็นย้อนหลังและอื่น ๆ ที่เราต้องการแนบSceneLabel
ในกรณีดังกล่าวไปยังหน่วยงานที่เกี่ยวข้อง (อาจจะนอกเหนือไปMesh
) จากนั้นระบบจะใช้การเรนเดอร์เพื่อรับทราบวิธีการสร้างเอนทิตีที่ให้สิ่งเหล่านั้นและนั่นเป็นการเปลี่ยนแปลงที่ง่ายมาก
ในกรณีนี้เอนทิตีที่ประกอบด้วยส่วนประกอบสามารถใช้เป็นส่วนประกอบของเอนทิตีอื่นได้ เราจะสร้างสิ่งต่าง ๆ ขึ้นมาด้วยการเชื่อมต่อบล็อกเลโก้
ECS: ระบบและส่วนประกอบข้อมูลดิบ
ระบบสุดท้ายนั้นเท่าที่ฉันทำด้วยตัวเองและเราก็ยังคงเลิกกับ COM มันรู้สึกเหมือนว่ามันต้องการที่จะกลายเป็นระบบเอนทิตีส่วนประกอบ แต่ฉันไม่คุ้นเคยกับมันในเวลานั้น ฉันดูตัวอย่างสไตล์ COM ซึ่งทำให้ฟิลด์ของฉันอิ่มตัวเมื่อฉันควรจะดูที่เครื่องมือเกม AAA เพื่อสร้างแรงบันดาลใจทางสถาปัตยกรรม ในที่สุดฉันก็เริ่มทำสิ่งนั้น
สิ่งที่ฉันหายไปคือแนวคิดสำคัญหลายประการ:
- formalization ของ "ระบบ" เพื่อประมวลผล "ส่วนประกอบ"
- "ส่วนประกอบ" เป็นข้อมูลดิบมากกว่าวัตถุพฤติกรรมที่ประกอบเข้าด้วยกันเป็นวัตถุที่ใหญ่กว่า
- เอนทิตีเป็นอะไรมากไปกว่า ID ที่เข้มงวดที่เกี่ยวข้องกับคอลเลกชันของส่วนประกอบ
ในที่สุดฉันก็ออกจาก บริษัท นั้นและเริ่มทำงานกับ ECS ในฐานะอินดี้ (ยังคงทำงานอยู่ในขณะที่ระบายเงินออมของฉัน) และมันเป็นระบบที่ง่ายที่สุดในการจัดการโดยไกล
สิ่งที่ฉันสังเกตเห็นด้วยวิธีการของ ECS ก็คือมันช่วยแก้ไขปัญหาที่ฉันยังต้องดิ้นรนกับปัญหาข้างต้น สิ่งสำคัญที่สุดสำหรับฉันคือรู้สึกเหมือนว่าเรากำลังจัดการ "เมือง" ที่มีสุขภาพดีแทนที่จะเป็นหมู่บ้านเล็ก ๆ ที่มีปฏิสัมพันธ์ที่ซับซ้อน มันไม่ยากที่จะรักษาเหมือนเสาหิน "megalopolis" ซึ่งใหญ่เกินกว่าที่ประชากรจะจัดการได้อย่างมีประสิทธิภาพ แต่ก็ไม่วุ่นวายเหมือนโลกที่เต็มไปด้วยหมู่บ้านเล็ก ๆ ที่มีปฏิสัมพันธ์ซึ่งกันและกันซึ่งแค่คิดถึงเส้นทางการค้าใน ระหว่างพวกเขากลายเป็นกราฟฝันร้าย ECS กลั่นความซับซ้อนทั้งหมดที่มีต่อ "ระบบ" ขนาดใหญ่เช่นระบบการแสดงผลซึ่งเป็น "เมือง" ขนาดใหญ่ที่มีสุขภาพดี แต่ไม่ใช่ "megalopolis ที่มีประชากรมากเกินไป"
ส่วนประกอบที่กลายเป็นข้อมูลดิบรู้สึกแปลก ๆกับฉันในตอนแรกเพราะมันแตกแม้แต่ข้อมูลพื้นฐานที่ซ่อนหลักการของ OOP มันเป็นความท้าทายอย่างหนึ่งในค่านิยมที่ยิ่งใหญ่ที่สุดที่ฉันมีเกี่ยวกับ OOP ซึ่งเป็นความสามารถในการรักษาค่าคงที่ซึ่งต้องใช้การห่อหุ้มและซ่อนข้อมูล แต่มันก็เริ่มที่จะไม่กังวลเพราะเห็นได้อย่างรวดเร็วว่าเกิดอะไรขึ้นกับระบบที่กว้างเพียงโหลหรือมากกว่านั้นเปลี่ยนข้อมูลนั้นแทนที่จะเป็นตรรกะดังกล่าวที่แพร่กระจายไปหลายร้อยถึงพันชนิดย่อยที่ใช้คอมโบของอินเตอร์เฟส ฉันมักจะคิดว่ามันยังคงอยู่ในรูปแบบ OOP ยกเว้นการแพร่กระจายที่ระบบให้การทำงานและการใช้งานที่เข้าถึงข้อมูลส่วนประกอบที่ให้ข้อมูลและหน่วยงานที่ให้บริการส่วนประกอบ
มันง่ายยิ่งขึ้นตอบโต้โดยสังเขปเพื่อให้เหตุผลเกี่ยวกับผลข้างเคียงที่เกิดจากระบบเมื่อมีระบบขนาดใหญ่เพียงไม่กี่ชิ้นที่เปลี่ยนแปลงข้อมูลในวงกว้าง ระบบดังกล่าวกลายเป็น "ประจบ" มากการโทรของฉันก็ตื้นขึ้นกว่าเดิมสำหรับแต่ละเธรด ฉันสามารถคิดเกี่ยวกับระบบในระดับผู้ดูแลนั้นและไม่พบความประหลาดใจแปลก ๆ
ในทำนองเดียวกันมันทำให้แม้กระทั่งพื้นที่ที่สำคัญต่อประสิทธิภาพการทำงานง่าย ๆ ด้วยความเคารพในการกำจัดข้อความค้นหาเหล่านั้น เนื่องจากความคิดของ "ระบบ" เป็นทางการมากระบบสามารถสมัครสมาชิกส่วนประกอบที่สนใจและเพียงแค่ส่งรายการแคชของเอนทิตีที่ตรงตามเกณฑ์นั้น แต่ละคนไม่จำเป็นต้องจัดการการเพิ่มประสิทธิภาพแคชนั้นมันกลายเป็นศูนย์กลางที่เดียว
ข้อดีบางประการ:
- ดูเหมือนว่าจะแก้ปัญหาสถาปัตยกรรมที่สำคัญเกือบทุกอย่างที่ฉันพบในอาชีพการงานของฉันโดยไม่รู้สึกว่าติดอยู่ในมุมการออกแบบเมื่อพบกับความต้องการที่ไม่คาดคิด
ข้อเสียบางอย่าง:
- ฉันยังคงมีเวลาที่ยากลำบากห่อหัวของฉันไปรอบ ๆ มันและมันไม่ใช่กระบวนทัศน์ที่เป็นผู้ใหญ่ที่สุดหรือเป็นที่ยอมรับแม้ในอุตสาหกรรมเกมที่ผู้คนโต้เถียงกันเกี่ยวกับความหมายและวิธีการทำสิ่งต่าง ๆ แน่นอนว่ามันไม่ใช่สิ่งที่ฉันจะทำได้กับทีมเก่าที่ฉันทำงานด้วยซึ่งประกอบด้วยสมาชิกที่ยึดติดกับแนวความคิดแบบ COM หรือแนวความคิด C สไตล์ 1980 ของ codebase ดั้งเดิม ที่ฉันสับสนบางครั้งก็เหมือนกับวิธีการสร้างแบบจำลองความสัมพันธ์ระหว่างองค์ประกอบแบบกราฟ แต่ฉันมักจะพบวิธีแก้ปัญหาที่ไม่น่ากลัวในภายหลังซึ่งฉันสามารถสร้างส่วนประกอบขึ้นอยู่กับอีกองค์ประกอบหนึ่งได้ ("การเคลื่อนไหวนี้ องค์ประกอบขึ้นอยู่กับอีกหนึ่งนี้เป็นผู้ปกครองและระบบจะใช้การบันทึกช่วยจำเพื่อหลีกเลี่ยงการทำซ้ำการคำนวณการเคลื่อนไหวซ้ำซ้ำเช่น ")
- ABI ยังคงเป็นเรื่องยาก แต่จนถึงตอนนี้ฉันยังกล้าที่จะบอกว่ามันง่ายกว่าวิธีการอินเทอร์เฟซที่บริสุทธิ์ มันเป็นการเปลี่ยนความคิด: ความเสถียรของข้อมูลกลายเป็นจุดสนใจเพียงอย่างเดียวของ ABI แทนที่จะเป็นความเสถียรของอินเทอร์เฟซและในบางวิธีการบรรลุความเสถียรของข้อมูลได้ง่ายกว่าความเสถียรของอินเทอร์เฟซ (เช่นไม่มีการล่อลวง สิ่งนั้นเกิดขึ้นในการใช้งานระบบที่หยาบซึ่งไม่ทำลาย ABI)
อย่างไรก็ตามมันมีเหตุผลที่จะสร้างแอพพลิเคชั่นที่ใช้สถาปัตยกรรม Component-Entity-System ทั่วไปในเอ็นจิ้นเกมหรือไม่?
ดังนั้นต่อไปฉันจะบอกว่า "ใช่" แน่นอนด้วยตัวอย่าง VFX ส่วนตัวของฉันในการเป็นผู้สมัครที่แข็งแกร่ง แต่นั่นก็ยังคงคล้ายกับความต้องการของเกม
ฉันไม่ได้นำไปฝึกในพื้นที่ห่างไกลที่แยกออกจากความกังวลของเอ็นจิ้นเกม (VFX ค่อนข้างคล้ายกัน) แต่ดูเหมือนว่าสำหรับฉันแล้วพื้นที่ส่วนใหญ่เป็นผู้สมัครที่ดีสำหรับแนวทาง ECS อาจจะเป็นระบบ GUI ที่เหมาะสำหรับระบบเดียว แต่ฉันยังคงใช้วิธีการแบบ OOP มากกว่านั้น (แต่ไม่มีการสืบทอดแบบลึกซึ่งต่างจาก Qt เช่น)
มันเป็นดินแดนที่ไม่ได้สำรวจอย่างกว้างขวาง แต่ดูเหมือนว่าเหมาะสมสำหรับฉันเมื่อใดก็ตามที่หน่วยงานของคุณสามารถประกอบไปด้วย "ลักษณะ" ที่หลากหลาย (และสิ่งที่พวกเขามีลักษณะการรวมคำสั่งผสมที่เคยเปลี่ยนแปลง) และที่คุณมีทั่วไป ระบบที่ประมวลผลเอนทิตีที่มีคุณลักษณะที่จำเป็น
มันกลายเป็นทางเลือกที่เป็นประโยชน์อย่างมากในกรณีเหล่านั้นกับสถานการณ์ใด ๆ ที่คุณอาจถูกล่อลวงให้ใช้บางสิ่งบางอย่างเช่นการสืบทอดหลายแบบหรือการจำลองแนวคิด (มิกซ์อินเช่น) เพื่อผลิตคอมโบหลายร้อยชุดขึ้นไปในลำดับชั้นการสืบทอดลึก ของคลาสในลำดับชั้นแบนด์ที่ใช้ชุดคำสั่งผสมเฉพาะ แต่ระบบของคุณมีจำนวนน้อย (หลายสิบเช่น)
ในกรณีเหล่านั้นความซับซ้อนของ codebase เริ่มรู้สึกเป็นสัดส่วนกับจำนวนของระบบแทนที่จะเป็นจำนวนชุดค่าผสมเนื่องจากแต่ละประเภทตอนนี้เป็นเพียงส่วนประกอบประกอบเอนทิตีซึ่งไม่มีอะไรมากไปกว่าข้อมูลดิบ ระบบ GUI นั้นเหมาะสมกับสเปคเหล่านี้โดยที่มันอาจมีวิดเจ็ตที่เป็นไปได้หลายร้อยชนิดรวมกันจากประเภทฐานหรืออินเตอร์เฟสอื่น ๆ แต่มีเพียงไม่กี่ระบบที่จะประมวลผล (ระบบเลย์เอาต์ระบบการเรนเดอร์ ฯลฯ ) หากระบบ GUI ใช้ ECS อาจเป็นเรื่องง่ายมากที่จะให้เหตุผลเกี่ยวกับความถูกต้องของระบบเมื่อฟังก์ชั่นทั้งหมดจัดทำโดยระบบเหล่านี้จำนวนหนึ่งแทนที่จะเป็นประเภทวัตถุหลายร้อยชนิดที่มีอินเทอร์เฟซที่สืบทอดมาหรือคลาสพื้นฐาน หากระบบ GUI ใช้ ECS วิดเจ็ตจะไม่มีฟังก์ชันการทำงานมีเพียงข้อมูลเท่านั้น มีเพียงไม่กี่ระบบที่ประมวลผลเอนทิตีวิดเจ็ตจะมีฟังก์ชัน วิธีจัดการเหตุการณ์วิดเจ็ตที่จะจัดการได้นั้นเกินกว่าฉัน แต่เพียงแค่อาศัยประสบการณ์ที่ จำกัด ของฉันจนถึงตอนนี้ฉันยังไม่พบกรณีที่ตรรกะชนิดนั้นไม่สามารถถ่ายโอนจากส่วนกลางไปยังระบบที่กำหนดในแบบที่ การเข้าใจถึงปัญหาย้อนหลังให้ผลลัพธ์ที่ยอดเยี่ยมกว่าที่ฉันเคยคาดไว้
ฉันชอบที่จะเห็นมันใช้งานในสาขามากขึ้นเพราะมันเป็นผู้ช่วยชีวิตในเหมือง แน่นอนว่ามันไม่เหมาะสมถ้าการออกแบบของคุณไม่ทำลายลงด้วยวิธีนี้ตั้งแต่เอนทิตีที่รวมส่วนประกอบไปยังระบบหยาบที่ประมวลผลส่วนประกอบเหล่านั้น แต่ถ้าพวกมันเข้ากับโมเดลแบบนี้ตามธรรมชาติมันเป็นสิ่งที่วิเศษที่สุดที่ฉันเคยพบ .