จะออกแบบ AssetManager ได้อย่างไร


26

อะไรคือวิธีที่ดีที่สุดในการออกแบบ AssestManager ที่จะอ้างอิงถึงกราฟิกเสียง ฯลฯ ของเกม

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

โดยเฉพาะฉันเขียนเกม Android / Java แต่คำตอบได้ทั่วไป

คำตอบ:


16

มันขึ้นอยู่กับขอบเขตของเกมของคุณ ผู้จัดการสินทรัพย์มีความจำเป็นอย่างยิ่งสำหรับเกมที่มีขนาดใหญ่กว่าดังนั้นสำหรับเกมเล็ก ๆ

สำหรับหนังสือขนาดใหญ่คุณต้องจัดการปัญหาต่าง ๆ ดังต่อไปนี้:

  • สินทรัพย์ที่ใช้ร่วมกัน - มีการใช้พื้นผิวอิฐหลายรุ่นหรือไม่
  • อายุการใช้งานเนื้อหา - คุณไม่โหลดสินทรัพย์นั้นเมื่อ 15 นาทีก่อนหรือไม่ การอ้างอิงการนับสินทรัพย์ของคุณเพื่อให้แน่ใจว่าคุณรู้เมื่อบางสิ่งบางอย่างเสร็จสิ้นด้วย ฯลฯ
  • ใน DirectX 9 หากโหลดเนื้อหาบางประเภทและอุปกรณ์กราฟิกของคุณได้รับ 'หลงทาง' (สิ่งนี้จะเกิดขึ้นหากคุณกด Ctrl + Alt + Del ท่ามกลางสิ่งอื่น ๆ ) - เกมของคุณจะต้องสร้างใหม่
  • การโหลดเนื้อหาล่วงหน้าก่อนที่จะต้องการมัน - คุณไม่สามารถสร้างเกมเปิดโลกใบใหญ่ได้
  • การโหลดเนื้อหาจำนวนมาก - เรามักจะบรรจุทรัพย์สินจำนวนมากไว้ในไฟล์เดียวเพื่อปรับปรุงเวลาในการโหลด - การค้นหารอบดิสก์นั้นใช้เวลานานมาก

สำหรับชื่อเรื่องเล็ก ๆ สิ่งเหล่านี้มีปัญหาน้อยกว่ากรอบการทำงานเช่น XNA มีผู้จัดการสินทรัพย์อยู่ในนั้น - มีจุดเล็ก ๆ น้อย ๆ ในการคิดค้นมันขึ้นมาใหม่

หากคุณพบว่าคุณต้องการผู้จัดการสินทรัพย์ไม่มีวิธีแก้ปัญหาที่เหมาะกับทุกขนาดจริงๆ แต่ฉันพบว่าแผนที่แฮชที่มีคีย์เป็นแฮช * ของชื่อไฟล์ (ลดลงและแยกทั้งหมดคงที่) ทำงานได้ดีสำหรับโครงการที่ฉันทำงาน

โดยทั่วไปจะไม่แนะนำให้ใช้ชื่อไฟล์ hardcode ในแอปของคุณโดยปกติแล้วจะดีกว่าถ้ามีรูปแบบข้อมูลอื่น (เช่น xml) แสดงถึงชื่อไฟล์เป็น 'ID'

  • ในฐานะที่เป็นบันทึกย่อที่น่าสนุกคุณมักจะได้รับหนึ่งการชนกันของแฮช

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

2
@Joe Wreschnig - คุณจะจัดการกับข้อกำหนดห้าข้อที่ icStatic กล่าวถึงโดยไม่ใช้ผู้จัดการสินทรัพย์ได้อย่างไร
antinome

8

(พยายามหลีกเลี่ยง "อย่าใช้ผู้จัดการสินทรัพย์" - พูดคุยที่นี่เนื่องจากฉันคิดว่ามันเป็นเรื่องที่ผิดปกติ)

คีย์แผนที่ / ค่าเป็นวิธีที่ใช้งานได้ดีมาก

เรามี ResourceManager ที่ใช้งานซึ่งโรงงานสำหรับประเภททรัพยากรที่แตกต่างกันสามารถลงทะเบียนได้

วิธี "getResource" ใช้เทมเพลตเพื่อค้นหา Factory ที่ถูกต้องสำหรับประเภททรัพยากรที่ต้องการและส่งกลับ ResourceHandle เฉพาะ (อีกครั้งโดยใช้แม่แบบเพื่อส่งกลับ SpecificResourceHandle)

ทรัพยากรถูก refcounted โดย ResourceManager (ภายใน ResourceHandle) และปล่อยเมื่อไม่ต้องการอีกต่อไป

แอดออนแรกที่เราเขียนคือวิธี "รีโหลด (XYZ)" ซึ่งช่วยให้เราสามารถเปลี่ยนทรัพยากรจากภายนอกเครื่องยนต์ที่กำลังทำงานอยู่โดยไม่ต้องเปลี่ยนรหัสใด ๆ หรือโหลดเกมใหม่ (นี่เป็นสิ่งสำคัญเมื่อศิลปินทำงานบนคอนโซล))

เวลาส่วนใหญ่เรามีเพียงอินสแตนซ์ของ ResourceManager แต่บางครั้งเราสร้างอินสแตนซ์ใหม่สำหรับระดับหรือแผนที่ วิธีนี้เราสามารถเรียก "ปิด" บน levelResourceManager และตรวจสอบให้แน่ใจว่าไม่มีการรั่วไหล

ตัวอย่าง (สั้น ๆ )

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource

6

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

Steve Yegge (ในหมู่คนอื่น ๆ อีกหลายคน) ได้เขียนเรื่องราวที่ดีเกี่ยวกับวิธีการจัดการเรียนที่ไร้ประโยชน์โดยวิธีการของรูปแบบซิงเกิลจบลงด้วยการเป็น http://sites.google.com/site/steveyegge2/singleton-considered-stupid


2
โอเคแน่นอน แต่ในกรณีเช่น Android (หรือเกมอื่น ๆ ) คุณต้องโหลดกราฟิก / เสียงจำนวนมากในหน่วยความจำก่อนที่คุณจะเริ่มเกมไม่ใช่ในระหว่าง ฉันจะใช้สิ่งที่คุณพูด (โรงงาน) เพื่อทำสิ่งนี้ในระหว่างโหลดหน้าจอได้อย่างไร เพียงแค่กดวัตถุทุกชิ้นในโรงงานบนหน้าจอโหลดเพื่อที่จะแคชพวกเขา?
ไบรอันนี่นี่

ฉันไม่คุ้นเคยกับรายละเอียดของ Android แต่ฉันไม่รู้ว่าคุณหมายถึงอะไรโดย "ก่อนที่คุณจะเริ่มเกม" เป็นไปไม่ได้หรือไม่ที่จะโหลดทรัพยากรเมื่อคุณต้องการ (หรือเมื่อคุณต้องการ 'เร็ว ๆ นี้') แทนที่จะเป็นเมื่อคุณเริ่มโปรแกรม ฉันพบว่าไม่น่าเป็นไปได้อย่างมากเช่นคุณไม่สามารถมีพื้นผิวได้มากกว่า RAM ขนาดเล็กของ Android

@Joe ลองดูคำถามอื่น ๆ ของฉันเกี่ยวกับ "หน้าจอการโหลด": gamedev.stackexchange.com/questions/1171/…การกดปุ่มแคชเปล่าหมายถึงการไปที่ดิสก์เป็นเวลานานและอาจส่งผลให้ประสิทธิภาพการทำงานของ FPS บนสายแรก . หากคุณรู้อยู่แล้วว่าคุณกำลังจะไปถึงเวลาใดก่อนหน้านี้คุณอาจโดนมันในระหว่างการโหลดเพื่อแคชล่วงหน้า
ไบรอันนี่นี่

อีกครั้งฉันไม่สามารถพูดคุยกับ Android ได้ แต่โดยปกติแล้วการไปที่ดิสก์เป็นสิ่งที่คุณสามารถทำได้โดยไม่ต้องใช้ FPS ยอดนิยมเนื่องจากเธรดที่ไปยังดิสก์จะไม่ใช้ CPU ใด ๆ เลย คุณเพียงแค่ต้องใช้งบประมาณในการทำมันให้มากพอล่วงหน้าซึ่งคุณจะไม่ได้รับป๊อปอัป หากคุณจะแคชล่วงหน้าทุกอย่างเพราะคุณรู้ล่วงหน้าว่าคุณต้องการอะไรคุณไม่จำเป็นต้องมี AssetManager เพราะคุณไม่จำเป็นต้องจัดการสินทรัพย์เลย - พวกเขาพร้อมแล้ว

1
@ โจไม่ได้เป็นโรงงาน "ผู้จัดการเฉพาะ" หรือไม่
MSN

2

ฉันคิดเสมอว่าผู้จัดการสินทรัพย์ที่ดีควรมีหลายโหมดการทำงาน โหมดเหล่านี้น่าจะเป็นโมดูลแหล่งแยกต่างหากที่ยึดติดกับอินเตอร์เฟสทั่วไป โหมดการทำงานพื้นฐานสองโหมดคือ:

  • โหมดการผลิต - สินทรัพย์ทั้งหมดอยู่ภายในเครื่องและดึงข้อมูลเมตาทั้งหมด
  • โหมดการพัฒนา - การทดสอบจะถูกเก็บไว้ในฐานข้อมูล (เช่น MySQL, ฯลฯ ) พร้อมข้อมูลเมตาเพิ่มเติม ฐานข้อมูลจะเป็นระบบสองชั้นที่มีฐานข้อมูลภายในแคชฐานข้อมูลที่ใช้ร่วมกัน ผู้สร้างเนื้อหาจะสามารถแก้ไขและอัปเดตฐานข้อมูลที่ใช้ร่วมกันและอัปเดตโดยอัตโนมัติไปยังนักพัฒนา / ระบบควบคุมคุณภาพ มันควรจะเป็นไปได้ที่จะสร้างเนื้อหาตัวแทน เนื่องจากทุกอย่างอยู่ในฐานข้อมูลจึงสามารถสร้างคิวรีบนฐานข้อมูลและรายงานที่สร้างขึ้นเพื่อวิเคราะห์สถานะของการผลิตได้

คุณต้องการเครื่องมือที่สามารถรับการทดสอบทั้งหมดจากฐานข้อมูลที่แชร์และสร้างชุดข้อมูลการผลิต

ในปีที่ผ่านมาในฐานะนักพัฒนาฉันไม่เคยเห็นอะไรแบบนี้มาก่อนแม้ว่าฉันจะได้ทำงานให้กับ บริษัท เพียงไม่กี่แห่งเท่านั้นดังนั้นมุมมองของฉันจึงไม่ได้เป็นตัวแทนจริงๆ

ปรับปรุง

ตกลงโหวตลบ ฉันจะขยายการออกแบบนี้

ประการแรกคุณไม่ต้องการคลาสจากโรงงานเพราะถ้าคุณมี:

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

คุณรู้จักประเภทดังนั้นเพียงทำ:

TextureHandle tex = new TextureHandle ("test.otx");

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

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream ใช้พารามิเตอร์ resource_id เพื่อค้นหาตำแหน่งของข้อมูล วิธีการนี้จะขึ้นอยู่กับสภาพแวดล้อมที่คุณใช้:

ในการพัฒนา: สตรีมค้นหา ID ในฐานข้อมูล (โดยใช้ SQL เป็นต้น) เพื่อรับชื่อไฟล์จากนั้นเปิดไฟล์ไฟล์อาจถูกแคชในเครื่องหรือดึงออกจากเซิร์ฟเวอร์หากไม่มีไฟล์ในตัวเครื่องหรือเป็น ตกยุค.

ในรีลีส: สตรีมจะค้นหา ID ในตารางคีย์ / ค่าเพื่อรับออฟเซ็ต / ขนาดเป็นไฟล์ขนาดใหญ่ที่บรรจุ (เช่นไฟล์ WAD ของ Doom)


ฉันโหวตให้คุณเพราะคุณแนะนำให้ใส่ทุกอย่างลงในตาราง SQL ด้วยคีย์หลักแทนที่จะใช้ VCS จริง นอกจากนี้ฉันพิจารณาใช้ opaque ID แทนการเพิ่มประสิทธิภาพชื่อสตริง ฉันใช้สตริงในสองโครงการขนาดใหญ่สำหรับสินทรัพย์ทั้งหมดนอกเหนือจากคีย์การแปลซึ่งเรามีคีย์สตริงที่ยาวมากนับแสน (จากนั้นไปที่พอร์ตไปยังคอนโซลเท่านั้น) พวกเขามักจะถูกทำให้เป็นมาตรฐานดังนั้นเราจึงสามารถใช้ตัวชี้การเปรียบเทียบแทนการเปรียบเทียบสตริง แต่การเปรียบเทียบสตริงมักจะถูกครอบงำโดยค่าใช้จ่ายของการดึงหน่วยความจำและไม่ใช่การเปรียบเทียบจริง

@ โจ: ฉันให้ SQL เป็นตัวอย่างเท่านั้นจากนั้นในสภาพแวดล้อมการพัฒนาคุณสามารถใช้ VCS ได้อย่างเท่าเทียมกัน ฉันแนะนำเฉพาะฐานข้อมูล SQL เนื่องจากคุณสามารถเพิ่มข้อมูลพิเศษไปยังวัตถุที่เก็บไว้และใช้ฟังก์ชัน SQL เพื่อสืบค้นข้อมูลจากฐานข้อมูล (ได้รับการจัดการมากกว่าสิ่งอื่นใด) สำหรับรหัสทึบแสงเป็นการปรับให้เหมาะสมก่อนกำหนด - บางคนอาจเห็นว่าเป็นแบบนั้นฉันเดา แต่ฉันคิดว่ามันจะง่ายกว่าถ้าจะเริ่มต้นด้วยสิ่งนั้นแทนที่จะแสดงมันไว้ในภายหลังในการพัฒนา ฉันไม่คิดว่ามันจะส่งผลกระทบต่อการพัฒนามากนักถ้าคุณใช้ ID หรือสตริง
Skizz

2

สิ่งที่ฉันชอบที่จะทำสำหรับสินทรัพย์คือการตั้งค่าผู้จัดการก้อน แรงบันดาลใจจาก Doom engine, lumps คือชิ้นส่วนของข้อมูลที่มีสินทรัพย์เก็บไว้ในไฟล์ lumpที่ประกาศชื่อ lumps 'ความยาวชนิด (บิตแมปเสียง shader ฯลฯ ) และประเภทเนื้อหา (ไฟล์ก้อนอีกก้อนหนึ่งภายใน ไฟล์ก้อนตัวเอง) เมื่อเริ่มต้นก้อนเหล่านี้จะถูกป้อนลงในต้นไม้ไบนารี แต่ยังไม่ได้โหลด แต่ละแผนที่ (ซึ่งเป็นก้อน) มีรายการของการพึ่งพาซึ่งเป็นเพียงชื่อของก้อนที่แผนที่จำเป็นต้องทำงาน ก้อนเหล่านี้นอกจากจะถูกโหลดไปแล้วจะถูกโหลดในเวลาที่โหลดแผนที่ นอกจากนี้ยังมีการโหลดแผนที่ก้อนที่อยู่ติดกันของแผนที่ไม่ใช่ในเวลาเดียวกัน แต่เมื่อเอ็นจิ้นไม่ทำงานด้วยเหตุผลบางประการ สิ่งนี้สามารถทำให้แผนที่ราบรื่นและไม่มีหน้าจอโหลด

วิธีการของฉันสมบูรณ์แบบสำหรับแผนที่แบบเปิดโล่ง แต่เกมระดับพื้นฐานจะไม่ได้รับประโยชน์จากความราบรื่นของวิธีการนี้ที่ทำให้คุณได้รับ หวังว่านี่จะช่วยได้!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.