ฉันขอแนะนำให้เริ่มต้นด้วยการอ่านคำโกหกที่ยิ่งใหญ่ของ Mike Acton เพราะคุณละเมิดทั้งสอง ฉันจริงจังนี่จะเปลี่ยนวิธีที่คุณออกแบบรหัสของคุณ: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html
แล้วคุณทำอะไรละเมิด?
โกหก # 3 - รหัสสำคัญกว่าข้อมูล
คุณพูดคุยเกี่ยวกับการฉีดพึ่งพาซึ่งอาจมีประโยชน์ในบางกรณี (และบางส่วนเท่านั้น) แต่ควรกดกริ่งสัญญาณเตือนภัยขนาดใหญ่หากคุณใช้มันโดยเฉพาะอย่างยิ่งในการพัฒนาเกม! ทำไม? เพราะมันเป็นสิ่งที่เป็นนามธรรมที่ไม่จำเป็น และ abstractions ในสถานที่ที่ไม่ถูกต้องน่ากลัว ดังนั้นคุณมีเกม เกมนี้มีผู้จัดการสำหรับส่วนประกอบต่าง ๆ ส่วนประกอบทั้งหมดถูกกำหนดไว้ ดังนั้นให้เรียนที่ไหนสักแห่งในรหัสลูปเกมหลักของคุณที่ "มี" ผู้จัดการ ชอบ:
private CollissionManager _collissionManager;
private BulletManager _bulletManager;
ให้ฟังก์ชัน getter เพื่อรับคลาสผู้จัดการแต่ละคลาส (getBulletManager ()) บางทีคลาสนี้อาจเป็น Singleton หรือสามารถเข้าถึงได้จากที่หนึ่ง (คุณอาจมี Singleton เกมกลางอยู่ที่ไหนสักแห่ง) ไม่มีอะไรผิดปกติกับข้อมูลและพฤติกรรมที่กำหนดรหัสยาก
อย่าสร้าง ManagerManager ซึ่งให้คุณลงทะเบียน Managers โดยใช้รหัสซึ่งสามารถดึงได้โดยใช้คีย์นั้นโดยคลาสอื่นที่ต้องการใช้ตัวจัดการ มันเป็นระบบที่ยอดเยี่ยมและมีความยืดหยุ่นสูง แต่เมื่อพูดถึงเกมที่นี่ คุณรู้ว่าระบบใดในเกม ทำไมแกล้งเหมือนคุณไม่? เพราะนี่เป็นระบบสำหรับคนที่คิดว่ารหัสสำคัญกว่าข้อมูล พวกเขาจะพูดว่า "รหัสมีความยืดหยุ่นข้อมูลเพียงแค่เติม" แต่รหัสเป็นเพียงข้อมูล ระบบที่ฉันอธิบายนั้นง่ายกว่าเชื่อถือได้มากขึ้นง่ายต่อการดูแลรักษาและมีความยืดหยุ่นมากขึ้น (ตัวอย่างเช่นหากพฤติกรรมของผู้จัดการรายหนึ่งแตกต่างจากผู้จัดการคนอื่นคุณต้องเปลี่ยนเพียงไม่กี่บรรทัดแทนที่จะเปลี่ยนระบบทั้งหมด)
Lie # 2 - รหัสควรได้รับการออกแบบรอบ ๆ แบบจำลองของโลก
ดังนั้นคุณมีเอนทิตีในโลกของเกม เอนทิตีมีจำนวนขององค์ประกอบที่กำหนดพฤติกรรมของมัน ดังนั้นคุณต้องสร้างคลาส Entity ด้วยรายการของออบเจกต์ Component และฟังก์ชัน Update () ซึ่งเรียกใช้ฟังก์ชัน Update () ของแต่ละ Component ขวา?
Nope :) นั่นคือการออกแบบรอบ ๆ แบบจำลองของโลก: คุณมีสัญลักษณ์แสดงหัวข้อย่อยในเกมของคุณดังนั้นคุณจึงเพิ่มคลาสกระสุน จากนั้นคุณอัปเดตกระสุนแต่ละอันและไปยังกระสุนถัดไป สิ่งนี้จะทำให้ประสิทธิภาพการทำงานของคุณแย่ลงและมันจะช่วยให้คุณมี codebase ที่ซับซ้อนที่น่ากลัวด้วยรหัสที่ซ้ำกันได้ทุกที่ (ตรวจสอบคำตอบของฉันที่นี่สำหรับคำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับสาเหตุที่การออกแบบ OO แบบดั้งเดิมแย่ลงหรือค้นหา Data Oriented Design)
ลองดูสถานการณ์โดยไม่มีอคติ OO ของเรา เราต้องการสิ่งต่อไปนี้ไม่มากไม่น้อย (โปรดทราบว่าไม่มีข้อกำหนดในการสร้างคลาสสำหรับเอนทิตีหรือวัตถุ):
- คุณมีเอนทิตี้มากมาย
- เอนทิตีประกอบด้วยจำนวนขององค์ประกอบที่กำหนดพฤติกรรมของกิจการ
- คุณต้องการอัปเดตแต่ละองค์ประกอบในเกมแต่ละเฟรมโดยเฉพาะอย่างยิ่งในวิธีการควบคุม
- นอกเหนือจากการระบุส่วนประกอบที่เป็นของร่วมกันไม่มีสิ่งใดที่เอนทิตีเองต้องทำ มันเป็นลิงค์ / ID สำหรับส่วนประกอบสองสามอย่าง
ลองดูที่สถานการณ์ ระบบส่วนประกอบของคุณจะอัพเดตพฤติกรรมของทุกวัตถุในเกมทุกเฟรม นี่เป็นระบบที่สำคัญอย่างยิ่งสำหรับเครื่องยนต์ของคุณ ประสิทธิภาพเป็นสิ่งสำคัญที่นี่!
หากคุณคุ้นเคยกับสถาปัตยกรรมคอมพิวเตอร์หรือ Data Oriented Design คุณจะรู้ว่าประสิทธิภาพการทำงานที่ดีที่สุดนั้นเป็นอย่างไร: หน่วยความจำที่บรรจุแน่นและการประมวลผลรหัสกลุ่ม หากคุณเรียกใช้ตัวอย่างโค้ด A, B และ C ดังนี้: ABCABCABC คุณจะไม่ได้รับประสิทธิภาพเช่นเดียวกับเมื่อคุณเรียกใช้งานเช่นนี้: AAABBBCCC นี่ไม่ใช่เพียงเพราะการเรียนการสอนและแคชข้อมูลจะถูกใช้อย่างมีประสิทธิภาพมากขึ้น แต่ยังเพราะถ้าคุณดำเนินการ "A" ทั้งหมดหลังจากนั้นอีกครั้งมีพื้นที่มากมายสำหรับการปรับให้เหมาะสม: การลบรหัสที่ซ้ำกัน ทั้งหมด "A" s เป็นต้น
ดังนั้นหากเราต้องการที่จะอัพเดทส่วนประกอบทั้งหมดอย่าทำให้มันเป็นคลาส / วัตถุด้วยฟังก์ชั่นการอัพเดท อย่าเรียกฟังก์ชันการอัปเดตนั้นสำหรับแต่ละองค์ประกอบในแต่ละเอนทิตี นั่นคือโซลูชัน "ABCABCABC" ลองจัดกลุ่มการอัพเดทองค์ประกอบที่เหมือนกันทั้งหมดเข้าด้วยกัน จากนั้นเราสามารถอัปเดตส่วนประกอบ A ทั้งหมดตามด้วย B เป็นต้นเราต้องทำอะไรบ้าง
อันดับแรกเราต้องมีผู้จัดการองค์ประกอบ สำหรับองค์ประกอบทุกประเภทในเกมเราต้องมีคลาสผู้จัดการ มันมีฟังก์ชั่นการอัพเดทซึ่งจะอัพเดทองค์ประกอบทั้งหมดของประเภทนั้น มันมีฟังก์ชั่นสร้างที่จะเพิ่มองค์ประกอบใหม่ของประเภทนั้นและฟังก์ชั่นลบที่จะทำลายองค์ประกอบที่ระบุ อาจมีฟังก์ชั่นตัวช่วยอื่น ๆ ในการรับและตั้งค่าข้อมูลเฉพาะสำหรับส่วนประกอบนั้น (เช่น: ตั้งค่าโมเดล 3 มิติสำหรับ Model Component) โปรดทราบว่าผู้จัดการอยู่ในบางวิธีกล่องดำสู่โลกภายนอก เราไม่ทราบว่าข้อมูลของแต่ละองค์ประกอบถูกจัดเก็บอย่างไร เราไม่ทราบว่าแต่ละองค์ประกอบได้รับการปรับปรุงอย่างไร เราไม่สนใจตราบใดที่องค์ประกอบทำงานตามที่ควร
ต่อไปเราต้องการเอนทิตี คุณสามารถทำให้ชั้นเรียนนี้ แต่มันไม่ค่อยจำเป็น เอนทิตีอาจเป็นอะไรมากกว่า ID เลขจำนวนเต็มเฉพาะหรือสตริงที่แฮช (เช่นเดียวกับจำนวนเต็ม) เมื่อคุณสร้างส่วนประกอบสำหรับ Entity คุณจะส่ง ID เป็นอาร์กิวเมนต์ไปยังผู้จัดการ เมื่อคุณต้องการลบส่วนประกอบคุณจะต้องส่ง ID อีกครั้ง อาจมีข้อได้เปรียบบางประการในการเพิ่มข้อมูลอีกเล็กน้อยใน Entity แทนที่จะทำให้เป็น ID แต่สิ่งเหล่านั้นจะทำหน้าที่เป็นตัวช่วยเท่านั้นเพราะอย่างที่ฉันได้ระบุไว้ในข้อกำหนดพฤติกรรมเอนทิตีทั้งหมดจะถูกกำหนดโดยส่วนประกอบเอง มันเป็นเครื่องยนต์ของคุณ แต่ทำในสิ่งที่สมเหตุสมผลสำหรับคุณ
สิ่งที่เราจำเป็นต้องมีก็คือ Entity Manager คลาสนี้จะสร้าง ID ที่ไม่ซ้ำกันถ้าคุณใช้วิธีแก้ปัญหา ID เท่านั้นหรือสามารถใช้เพื่อสร้าง / จัดการวัตถุ Entity นอกจากนี้ยังสามารถเก็บรายการของเอนทิตีทั้งหมดในเกมหากคุณต้องการ Entity Manager อาจเป็นคลาสกลางของระบบส่วนประกอบของคุณจัดเก็บข้อมูลอ้างอิงไปยัง ComponentManagers ทั้งหมดในเกมของคุณและเรียกใช้ฟังก์ชั่นการอัพเดทตามลำดับที่ถูกต้อง วิธีนี้ทำให้วนรอบเกมต้องทำคือเรียก EntityManager.update () และระบบทั้งหมดจะถูกแยกออกจากส่วนที่เหลือของเครื่องยนต์ของคุณ
นั่นคือมุมมองแบบเบิร์ดลองมาดูกันว่าผู้จัดการองค์ประกอบทำงานอย่างไร นี่คือสิ่งที่คุณต้องการ:
- สร้างข้อมูลองค์ประกอบเมื่อสร้าง (entityID) ถูกเรียก
- ลบข้อมูลส่วนประกอบเมื่อมีการลบ (entityID) เรียก
- อัปเดตข้อมูลส่วนประกอบทั้งหมด (ใช้งานได้) เมื่อเรียกใช้อัปเดต () (เช่นไม่ใช่ส่วนประกอบทั้งหมดที่จำเป็นต้องอัปเดตแต่ละเฟรม)
อันสุดท้ายคือตำแหน่งที่คุณกำหนดพฤติกรรม / ตรรกะของส่วนประกอบและขึ้นอยู่กับประเภทของส่วนประกอบที่คุณเขียน AnimationComponent จะอัปเดตข้อมูลแอนิเมชั่นตามเฟรมของเฟรม DragableComponent จะอัปเดตเฉพาะส่วนประกอบที่เมาส์ลากอยู่ PhysicsComponent จะอัปเดตข้อมูลในระบบฟิสิกส์ แต่ถึงกระนั้นเนื่องจากคุณอัปเดตส่วนประกอบทั้งหมดในประเภทเดียวกันในครั้งเดียวคุณสามารถเพิ่มประสิทธิภาพบางอย่างที่ไม่สามารถทำได้เมื่อแต่ละองค์ประกอบเป็นวัตถุแยกต่างหากที่มีฟังก์ชั่นอัปเดตที่สามารถเรียกใช้ได้ตลอดเวลา
โปรดทราบว่าฉันยังไม่เคยเรียกร้องให้มีการสร้างคลาส XxxComponent เพื่อเก็บข้อมูลส่วนประกอบ ขึ้นอยู่กับคุณ คุณชอบ Data Oriented Design หรือไม่? จากนั้นจัดโครงสร้างข้อมูลในอาร์เรย์แยกกันสำหรับแต่ละตัวแปร คุณชอบการออกแบบเชิงวัตถุหรือไม่ (ฉันจะไม่แนะนำมันก็จะฆ่าประสิทธิภาพของคุณในหลาย ๆ สถานที่) จากนั้นสร้างวัตถุ XxxComponent ที่จะเก็บข้อมูลของแต่ละองค์ประกอบ
สิ่งที่ยอดเยี่ยมเกี่ยวกับผู้จัดการคือการห่อหุ้ม ตอนนี้การห่อหุ้มเป็นหนึ่งในปรัชญาที่ถูกใช้อย่างผิด ๆ ในโลกแห่งการเขียนโปรแกรม นี่คือวิธีการใช้งาน มีเพียงผู้จัดการเท่านั้นที่ทราบว่ามีการจัดเก็บข้อมูลส่วนประกอบไว้ที่ใดตรรกะการทำงานของส่วนประกอบอย่างไร มีฟังก์ชั่นบางอย่างในการรับ / ตั้งค่าข้อมูล คุณสามารถเขียนผู้จัดการทั้งหมดและคลาสพื้นฐานของมันใหม่และถ้าคุณไม่เปลี่ยนอินเทอร์เฟซสาธารณะ เครื่องยนต์ฟิสิกส์เปลี่ยนไปหรือไม่ เพียงแค่เขียน PhysicsComponentManager และคุณก็เสร็จแล้ว
จากนั้นมีสิ่งหนึ่งที่สุดท้ายคือการสื่อสารและการแบ่งปันข้อมูลระหว่างส่วนประกอบ ตอนนี้มันยุ่งยากและไม่มีวิธีแก้ปัญหาที่เหมาะกับทุกขนาด คุณสามารถสร้างฟังก์ชั่น get / set ในตัวจัดการเพื่ออนุญาตให้อินสแตนซ์ส่วนประกอบการชนกันเพื่อรับตำแหน่งจากองค์ประกอบตำแหน่ง (เช่น PositionManager.getPosition (เอนทิตี)) คุณสามารถใช้ระบบเหตุการณ์ คุณสามารถจัดเก็บข้อมูลที่ใช้ร่วมกันบางอย่างในเอนทิตี (ทางออกที่น่าเกลียดที่สุดในความคิดของฉัน) คุณสามารถใช้ (มักใช้) ระบบการส่งข้อความ หรือใช้การรวมกันของหลาย ๆ ระบบ! ฉันไม่มีเวลาหรือประสบการณ์ในการเข้าสู่แต่ละระบบเหล่านี้ แต่การค้นหา google และ stackoverflow เป็นเพื่อนของคุณ