กลยุทธ์สำหรับการย้ายตรรกะการเรนเดอร์ออกจากคลาส GameObject


10

เมื่อสร้างเกมคุณมักจะสร้างวัตถุเกมต่อไปนี้ซึ่งหน่วยงานทั้งหมดได้รับมา:

public class GameObject{
    abstract void Update(...);
    abstract void Draw(...);
}

ดังนั้นในการอัพเดทลูปคุณวนซ้ำทุกออบเจ็กต์เกมและให้โอกาสพวกเขาเปลี่ยนสถานะจากนั้นในการวาดลูปถัดไปให้คุณวนซ้ำอีกครั้งกับออบเจ็กต์เกมทั้งหมดและให้โอกาสพวกเขาวาดตัวเอง

แม้ว่าวิธีนี้จะทำงานได้ค่อนข้างดีในเกมง่ายๆที่มีตัวเรนเดอร์แบบง่ายมันมักจะนำไปสู่วัตถุเกมขนาดยักษ์สองสามตัวที่จำเป็นต้องจัดเก็บแบบจำลองพื้นผิวที่หลากหลายและแย่ที่สุดของวิธีการวาดไขมันทั้งหมด กลยุทธ์การเรนเดอร์ปัจจุบันและคลาสการเรนเดอร์ใด ๆ ที่เกี่ยวข้อง

ถ้าฉันจะเปลี่ยนกลยุทธ์การเรนเดอร์จากไปข้างหน้าเป็นการเลื่อนออกไปฉันจะต้องอัปเดตออบเจกต์เกมจำนวนมาก และวัตถุเกมที่ฉันทำไม่สามารถนำกลับมาใช้ใหม่ได้อย่างที่ควรจะเป็น แน่นอนว่าการสืบทอดและ / หรือการแต่งเพลงสามารถช่วยฉันต่อสู้กับการทำสำเนารหัสและทำให้มันง่ายขึ้นเล็กน้อยในการเปลี่ยนการใช้งาน แต่ก็ยังรู้สึกขาด

วิธีที่ดีกว่าอาจจะเป็นการลบวิธีการดึงออกจากคลาส GameObject ทั้งหมดและสร้างคลาส Renderer GameObject ยังคงต้องมีข้อมูลบางอย่างเกี่ยวกับการมองเห็นของมันเช่นสิ่งที่รูปแบบที่จะเป็นตัวแทนด้วยและสิ่งที่ควรจะทาสีพื้นผิวในรูปแบบ แต่วิธีการทำจะถูกปล่อยให้แสดง อย่างไรก็ตามมีกรณีชายแดนจำนวนมากในการเรนเดอร์ดังนั้นแม้ว่านี่จะเป็นการลบการเชื่อมต่ออย่างแน่นหนาจาก GameObject ไปยัง Renderer Renderer จะยังคงต้องรู้เกี่ยวกับวัตถุทั้งหมดของเกมซึ่งจะทำให้อ้วนทุกคนรู้และ แน่นคู่ สิ่งนี้จะละเมิดแนวปฏิบัติที่ดีไม่กี่อย่าง บางที Data-Oriented-Design อาจทำเรื่องหลอกลวงได้ วัตถุในเกมจะเป็นข้อมูลแน่นอน แต่ตัวแสดงผลจะได้รับแรงผลักดันจากสิ่งนี้อย่างไร ฉันไม่แน่ใจ.

ดังนั้นฉันจึงสูญเสียและไม่สามารถคิดทางออกที่ดีได้ ฉันพยายามใช้หลักการของ MVC และในอดีตฉันมีความคิดบางอย่างเกี่ยวกับวิธีการใช้สิ่งนั้นในเกม แต่เมื่อเร็ว ๆ นี้ดูเหมือนจะไม่เหมาะสมเท่าที่ฉันคิด ฉันชอบที่จะรู้ว่าคุณแก้ไขปัญหานี้อย่างไร

อย่างไรก็ตามขอปะยางรถฉันสนใจในการบรรลุเป้าหมายการออกแบบต่อไปนี้

  • ไม่มีตรรกะการแสดงผลในวัตถุเกม
  • ข้อต่อหลวมระหว่างวัตถุในเกมและเอ็นจิ้นการเรนเดอร์
  • ไม่มี renderer ที่รู้ทั้งหมด
  • การสลับรันไทม์ระหว่างโปรแกรมแสดงผลดีกว่า

การตั้งค่าโครงการในอุดมคติจะเป็น 'เกมลอจิก' แยกต่างหากและแสดงโครงการลอจิกที่ไม่จำเป็นต้องอ้างอิงซึ่งกันและกัน

รถไฟความคิดนี้เริ่มต้นขึ้นเมื่อฉันได้ยิน John Carmack พูดใน twitter ว่าเขามีระบบที่ยืดหยุ่นได้เขาสามารถสลับเอ็นจิ้นการเรนเดอร์ในเวลาทำงานและยังสามารถบอกระบบของเขาให้ใช้ตัวเรนเดอร์ทั้งสอง ในเวลาเดียวกันเพื่อให้เขาสามารถตรวจสอบความแตกต่าง ระบบที่ฉันตั้งโปรแกรมจนถึงขณะนี้ยังไม่ยืดหยุ่นแม้แต่น้อย

คำตอบ:


7

ขั้นตอนแรกที่รวดเร็วในการแยกส่วน:

วัตถุในเกมอ้างอิงถึงตัวระบุว่าภาพของพวกเขาเป็นอะไร แต่ไม่ใช่ข้อมูลลองมาพูดถึงสิ่งที่ง่ายเหมือนสตริง ตัวอย่าง: "human_male"

Renderer รับผิดชอบในการโหลดและบำรุงรักษาการอ้างอิง "human_male" และส่งกลับไปยังวัตถุที่จับเพื่อใช้

ตัวอย่างในรหัสเทียมที่น่ากลัว:

GameObject( initialization parameters )
  me.render_handle = Renderer_Create( parameters.render_string )

- elsewhere
Renderer_Create( string )

  new data handle = Resources_Load( string );
  return new data handle

- some time later
GameObject( something happens to me parameters )
  me.state = something.what_happens
  Renderer_ApplyState( me.render_handle, me.state.effect_type )

- some time later
Renderer_Render()
  for each renderable thing
    for each rendering back end
        setup graphics for thing.effect
        render it

- finally
GameObject_Destroy()
  Renderer_Destroy( me.render_handle )

ขออภัยในความยุ่งเหยิงนั้นเงื่อนไขของคุณจะถูกเปลี่ยนแปลงอย่างง่าย ๆ จาก OOP บริสุทธิ์โดยดูจากสิ่งต่าง ๆ เช่นวัตถุในโลกแห่งความเป็นจริงและเข้าสู่ OOP ตามความรับผิดชอบ

  • ไม่มีตรรกะการเรนเดอร์ในวัตถุเกม (ทำแล้ววัตถุทั้งหมดรู้ว่าเป็นจุดจับเพื่อให้สามารถใช้เอฟเฟกต์กับตัวเองได้)
  • การมีเพศสัมพันธ์อย่างหลวม ๆ ระหว่างวัตถุในเกมและเอ็นจิ้นการเรนเดอร์ (เสร็จสิ้นการติดต่อทั้งหมดจะดำเนินการผ่านการจัดการแบบนามธรรมรัฐที่สามารถนำไปใช้และไม่เกี่ยวข้องกับสถานะเหล่านั้น)
  • ไม่มีตัวรู้ภาพทั้งหมด (เสร็จแล้วรู้เพียงเกี่ยวกับตัวเองเท่านั้น)
  • การสลับรันไทม์ระหว่างเอ็นจิ้นการเรนเดอร์ (ทำได้ดีกว่าในระยะ Renderer_Render () คุณต้องเขียนทั้งสองด้านหลัง

คำหลักที่คุณสามารถค้นหาได้นอกเหนือไปจากการปรับโครงสร้างชั้นเรียนอย่างง่ายคือ "เอนทิตี้ / ระบบส่วนประกอบ" และ "การพึ่งพาการฉีด" และรูปแบบ GUI "MVC" GUI เพียงเพื่อให้สมองหมุนรอบตัวเอง


สิ่งนี้แตกต่างอย่างมากจากสิ่งที่ฉันเคยทำมาก่อนดูเหมือนว่ามันจะมีศักยภาพค่อนข้างน้อย โชคดีที่ฉันไม่ได้ถูก จำกัด ด้วยเครื่องมือใด ๆ ที่มีอยู่ดังนั้นฉันจึงสามารถแก้ไขได้ ฉันจะค้นหาคำศัพท์ที่คุณพูดถึงแม้ว่าการฉีดพึ่งพาจะทำให้สมองของฉันเจ็บเสมอ: P.
รอยต

2

สิ่งที่ฉันทำสำหรับเอ็นจิ้นของตัวเองคือจัดกลุ่มทุกอย่างเป็นโมดูล ดังนั้นฉันมีGameObjectชั้นเรียนของฉันและมันมีที่จับ:

  • ModuleSprite - สไปรต์การวาด
  • ModuleWeapon - ปืนที่ยิง
  • ModuleScriptingBase - สคริปต์
  • ModuleParticles - ผลกระทบของอนุภาค
  • ModuleCollision - การตรวจจับการชนและการตอบสนอง

ดังนั้นฉันจึงมีPlayerชั้นเรียนและBulletชั้นเรียน ทั้งสองมาจากและมีการเพิ่มGameObject Sceneแต่Playerมีโมดูลต่อไปนี้:

  • ModuleSprite
  • ModuleWeapon
  • ModuleParticles
  • ModuleCollision

และBulletมีโมดูลเหล่านี้:

  • ModuleSprite
  • ModuleCollision

วิธีการจัดสิ่งนี้หลีกเลี่ยง "เพชรแห่งความตาย" ที่คุณมีVehicleเป็นVehicleLandและและตอนนี้คุณต้องการVehicleWater VehicleAmphibiousแต่คุณมีVehicleและมันสามารถมีและModuleWaterModuleLand

โบนัสที่เพิ่มเข้ามา: คุณสามารถสร้างวัตถุโดยใช้ชุดของคุณสมบัติ สิ่งที่คุณต้องรู้ก็คือ basetype (Player, Enemy, Bullet, ฯลฯ ) จากนั้นสร้างหมายเลขอ้างอิงสำหรับโมดูลที่คุณต้องการสำหรับประเภทนั้น

ในฉากของฉันฉันทำสิ่งต่อไปนี้:

  • เรียกการจัดการUpdateทั้งหมดGameObject
  • ทำการตรวจสอบการชนกันของข้อมูลและการตอบสนองการชนกันของข้อมูลสำหรับผู้ที่มีที่ModuleCollisionจับ
  • โทรหาที่จัดการUpdatePostทั้งหมดGameObjectเพื่อแจ้งให้ทราบเกี่ยวกับตำแหน่งสุดท้ายของพวกเขาหลังจากฟิสิกส์
  • ทำลายวัตถุที่มีการตั้งค่าสถานะของพวกเขา
  • เพิ่มวัตถุใหม่จากm_ObjectsCreatedรายการลงในm_Objectsรายการ

และฉันสามารถจัดระเบียบเพิ่มเติม: โดยโมดูลแทนโดยวัตถุ แล้วฉันจะทำให้รายการModuleSpriteอัปเดตพวงของและจะชนกับรายการModuleScriptingBaseModuleCollision


ฟังดูเหมือนองค์ประกอบสูงสุด! ดีมาก. ฉันไม่เห็นเคล็ดลับเฉพาะการเรนเดอร์ที่นี่ คุณจะจัดการกับมันได้อย่างไรเพียงแค่เพิ่มโมดูลที่แตกต่างกัน?
รอยต

โอ้ใช่. นั่นคือข้อเสียของระบบนี้: หากคุณมีข้อกำหนดเฉพาะสำหรับGameObject(เช่นวิธีการแสดง "งู" ของสไปรต์) คุณจะต้องสร้างลูกModuleSpriteสำหรับฟังก์ชันเฉพาะนั้น ( ModuleSpriteSnake) หรือเพิ่มโมดูลใหม่ทั้งหมด ( ModuleSnake) โชคดีที่พวกเขากำลังชี้เพียง แต่ผมเคยเห็นรหัสที่GameObjectไม่แท้จริงทุกสิ่งที่วัตถุสามารถทำได้
knight666
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.