แผนที่ที่มีไทล์ 20 ล้านแผ่นทำให้เกมหน่วยความจำหมดฉันจะหลีกเลี่ยงได้อย่างไร


11

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

UPD:คำถามที่นี่คือวิธีทำให้เกมหยุดทำงานล้มเหลวเมื่อยังมีหน่วยความจำว่างสำหรับให้ใช้ ส่วนแผนที่แยกเป็นชิ้นมันเป็นวิธีที่ดี แต่ไม่ใช่สิ่งที่ฉันต้องการในกรณีของฉัน

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

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


8
โหลดพื้นที่โดยรอบผู้เล่นเท่านั้น
MichaelHouse

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

วิธีการโหลดทำอะไร รหัสไปรษณีย์คุณใช้ไปป์ไลน์เนื้อหาหรือไม่ มันโหลดพื้นผิวไปยังหน่วยความจำหรือเพียงแค่เมตาดาต้า? ข้อมูลเมตาคืออะไร โดยทั่วไปประเภทเนื้อหา XNA จะอ้างอิงสิ่งที่ไม่มีการจัดการ DirectX ดังนั้นวัตถุที่ได้รับการจัดการจึงมีขนาดเล็กมาก แต่ในด้านที่ไม่มีการจัดการอาจมีสิ่งของมากมายที่คุณไม่เห็นเว้นแต่คุณกำลังเรียกใช้ตัวสร้างโปรไฟล์ DirectX เช่นกัน stackoverflow.com/questions/3050188/…
Oskar Duveborn

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

3
อะไรที่ทำให้คุณคิดว่าคุณมีความจำว่าง คุณใช้กระบวนการแบบ 32 บิตในระบบปฏิบัติการ 64 บิตหรือไม่?
Dalin Seivewright

คำตอบ:


58

แอปล่มเมื่อถึง 1.5Gb

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

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

ยกตัวอย่าง Terraria Terraria ที่เล็กที่สุดในโลกใช้กระเบื้อง 4200x1200 ซึ่งเป็น 5 ล้านแผ่น ทีนี้หน่วยความจำใช้เวลาเท่าไหร่ในการเป็นตัวแทนโลกใบนี้?

ดีแต่ละกระเบื้องมีเลเยอร์เบื้องหน้าเลเยอร์พื้นหลัง (ผนังพื้นหลัง), "เลเยอร์ลวด" ที่สายไปและ "เลเยอร์เฟอร์นิเจอร์" ที่รายการเฟอร์นิเจอร์ไป หน่วยความจำแต่ละไฟล์ใช้เวลาเท่าใด อีกครั้งเรากำลังพูดถึงแนวคิดไม่ใช่มองเห็น

ไทล์พื้นหน้าสามารถจัดเก็บได้อย่างง่ายดายในช่วงสั้น ๆ มีประเภทไทน์เดอร์พื้นหน้าไม่เกิน 65536 ดังนั้นจึงไม่มีประโยชน์ในการใช้หน่วยความจำมากกว่านี้ แผ่นพื้นหลังอาจอยู่ในไบต์ที่ไม่ได้ลงนามได้ง่ายเนื่องจากมีแผ่นพื้นหลังน้อยกว่า 256 ชนิด ชั้นลวดเป็นไบนารีล้วนๆ: ทั้งกระเบื้องมีลวดอยู่ในนั้นหรือไม่ นั่นคือหนึ่งบิตต่อแผ่น และเลเยอร์ของเฟอร์นิเจอร์อาจเป็นไบต์ที่ไม่ได้ลงนามอีกครั้งทั้งนี้ขึ้นอยู่กับจำนวนชิ้นส่วนของเฟอร์นิเจอร์ที่เป็นไปได้

ขนาดหน่วยความจำทั้งหมดต่อไทล์: 2 ไบต์ + 1 ไบต์ + 1 บิต + 1 ไบต์: 4 ไบต์ + 1 บิต ดังนั้นขนาดโดยรวมของแผนที่ Terraria ขนาดเล็กคือ 20790000 ไบต์หรือ ~ 20MB (หมายเหตุ: การคำนวณเหล่านี้มีพื้นฐานมาจาก Terraria 1.1 เกมมีการขยายตัวมากตั้งแต่นั้นมา แต่แม้ Terraria สมัยใหม่อาจมีขนาดไม่เกิน 8 ไบต์ต่อตำแหน่งที่ตั้งของกระเบื้องหรือประมาณ 40MB แต่ก็ยังทนได้อยู่)

คุณไม่ควรจัดเก็บการแสดงนี้เป็นอาร์เรย์ของคลาส C # ควรเป็นอาร์เรย์ของจำนวนเต็มหรือบางอย่างที่คล้ายกัน AC # struct จะทำงานเช่นกัน

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

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

ตอนนี้พื้นที่ที่มองเห็นได้ของกระเบื้องจะต้องแตกออกเป็นชิ้น ๆ หลาย ๆ ชิ้นดังนั้นคุณจะไม่ต้องสร้างแผ่นสไปรต์ทุกครั้งที่มีการเคลื่อนไหวของกล้อง ดังนั้นคุณอาจมี 64x64 ชิ้นของโลกเป็นแผ่นผีสาง ไม่ว่าชิ้นส่วนใดของโลกที่มองเห็นได้จากตำแหน่งกล้องปัจจุบันของผู้เล่นคือชิ้นส่วนที่คุณวาด ส่วนอื่น ๆ ไม่มีแผ่นผีสาง หากชิ้นส่วนหลุดออกจากหน้าจอคุณจะโยนแผ่นงานนั้นออก (หมายเหตุ: คุณไม่ได้ลบมันจริงๆคุณเก็บมันไว้รอบ ๆ และให้ความช่วยเหลือกับชิ้นใหม่ที่อาจปรากฏให้เห็นในภายหลัง)

ฉันต้องการให้มีการประมวลผลแผนที่ทั้งหมดอย่างน้อยในแอปเซิร์ฟเวอร์ (และลูกค้าถ้าเป็นไปได้)

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


4
+1 คำตอบที่ยอดเยี่ยม ต้องมีการโหวตมากกว่าฉัน
MichaelHouse

22

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

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

คุณสามารถค้นหาแนวคิดเพิ่มเติมได้ที่นี่:

พวกเขาจะทำอย่างไร: กระเบื้องนับล้านใน Terraria

'Zoning' พื้นที่บนแผนที่ขนาดใหญ่เช่นเดียวกับดันเจี้ยน


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

6
แต่ละแผ่นประกอบด้วยอะไร? หน่วยความจำมีการใช้งานเท่าใดต่อหนึ่งไทล์ แม้จะอยู่ที่ 10 ไบต์ละคุณจะอยู่ที่ 190 เมกะไบต์เท่านั้น เซิร์ฟเวอร์ไม่ควรโหลดพื้นผิวหรือข้อมูลอื่น ๆ ที่ไม่จำเป็น หากคุณต้องการการจำลองสำหรับไพ่ 20 ล้านใบคุณจะต้องตัดส่วนที่เหลือให้มากที่สุดเท่าที่จะทำได้เพื่อสร้างชุดการจำลองที่มีเฉพาะข้อมูลที่จำเป็นสำหรับปฏิกิริยาลูกโซ่
MichaelHouse

5

คำตอบของ Byte56 เป็นสิ่งที่ดีและฉันเริ่มเขียนสิ่งนี้เป็นความคิดเห็น แต่มันยาวเกินไปและมีเคล็ดลับบางอย่างที่อาจมีประโยชน์มากกว่าในคำตอบ

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

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

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


3

วิธีที่มีประสิทธิภาพที่ฉันใช้ในเกม XNA คือ:

  • ใช้ spritesheets ไม่ใช่รูปภาพสำหรับแต่ละไทล์ / วัตถุและเพียงโหลดสิ่งที่คุณต้องการบนแผนที่นั้น
  • แบ่งแผนที่ออกเป็นส่วนย่อย ๆ พูดแผนที่ชิ้นขนาดเล็ก ๆ ขนาด 500 x 200 แผ่นหากแผนที่ของคุณมีขนาด 4 เท่าหรือบางสิ่งที่คุณสามารถลวกได้
  • โหลดก้อนข้อมูลนี้ในหน่วยความจำและทาสีเฉพาะส่วนที่มองเห็นได้ของแผนที่ (เช่นแผ่นกระเบื้องที่มองเห็นได้และอีก 1 แผ่นสำหรับแต่ละทิศทางที่ผู้เล่นกำลังเคลื่อนที่) ในบัฟเฟอร์หน้าจอ
  • เคลียร์วิวพอร์ตและคัดลอกพิกเซลจากบัฟเฟอร์ทับ (ซึ่งเรียกว่าบัฟเฟอร์คู่และทำให้ภาพเคลื่อนไหวราบรื่นขึ้น)
  • เมื่อคุณย้ายถ่านให้ติดตาม bouds ของแผนที่และโหลดอันถัดไปเมื่อมันเข้าใกล้มันและผนวกเข้ากับอันปัจจุบัน
  • เมื่อผู้เล่นเข้าร่วมแผนที่ใหม่นี้แล้วคุณสามารถยกเลิกการโหลดแผนที่ล่าสุดได้

คุณสามารถใช้แผนที่ขนาดใหญ่ด้วยวิธีนี้ถ้ามันไม่ใหญ่และใช้ตรรกะที่นำเสนอ

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

คุณจะต้องวนซ้ำเฉพาะส่วนที่เกี่ยวข้องของแผนที่นี้ทุกครั้งและแสดงเฉพาะสิ่งที่จำเป็น วิธีนี้คุณจะปรับปรุงประสิทธิภาพได้มาก

เซิร์ฟเวอร์สามารถประมวลผลแผนที่ขนาดใหญ่ได้เร็วขึ้นเพราะมันไม่จำเป็นต้องเรนเดอร์ (หนึ่งในการดำเนินการที่ช้าที่สุด) เกมดังนั้นตรรกะสำหรับมันที่สามารถใช้งานได้จากชิ้นส่วนที่ใหญ่กว่าหรือแม้แต่แผนที่ทั้งหมด แต่ประมวลผลเฉพาะตรรกะ รอบผู้เล่นเช่นใน 2 เท่าของผู้เล่นดูพื้นที่รอบ ๆ ผู้เล่น

คำแนะนำที่นี่คือฉันไม่เคยมีความเชี่ยวชาญในเกม dev และเกมของฉันถูกสร้างขึ้นสำหรับโครงการสุดท้ายของการสำเร็จการศึกษาของฉัน (ซึ่งฉันมีคะแนนดีที่สุด) ดังนั้นฉันอาจไม่ถูกต้องในทุกจุด แต่ฉันได้ ค้นคว้าเว็บและเว็บไซต์เช่นhttp://www.gamasutra.comและ XNA creators site creators.xna.com (ในเวลานี้http://create.msdn.com ) เพื่อรวบรวมความรู้และทักษะและทำงานได้ดีสำหรับฉัน .


2

ทั้ง

  • ซื้อหน่วยความจำเพิ่มเติม
  • แสดงโครงสร้างข้อมูลของคุณให้กระชับยิ่งขึ้น
  • อย่าเก็บข้อมูลทั้งหมดไว้ในหน่วยความจำพร้อมกัน

ฉันมี 8 Gb ram บน win7 64 บิตและแอปล่มเมื่อถึง 1.5Gb ดังนั้นจึงไม่มีประเด็นที่จะซื้อ ram มากกว่านี้เว้นแต่ฉันจะพลาดอะไรบางอย่าง
user1306322

1
@ user1306322: หากคุณมี 20 ล้านกระเบื้องและมันจะขึ้นประมาณ ~ 1.5GB ของหน่วยความจำที่หมายถึงกระเบื้องของคุณจะอยู่ที่ประมาณ80 ไบต์ต่อกระเบื้อง คุณเก็บอะไรไว้ในสิ่งเหล่านี้อย่างแน่นอน?
Nicol Bolas

@ NicolBolas อย่างที่ฉันพูดไปแล้วสองสาม ints, ไบต์และ texture2d นอกจากนี้ยังไม่ได้ทั้งหมดใช้ 1.5GB แอปขัดข้องเมื่อถึงต้นทุนหน่วยความจำนี้
user1306322

2
@ user1306322: นั่นยังไกลเกินกว่าที่จำเป็น ใหญ่แค่texture2dไหน? แต่ละไทล์มีอันที่ไม่ซ้ำกันหรือพวกเขาแบ่งปันพื้นผิว? นอกจากนี้ที่มีคุณกล่าวนี้หรือไม่? แน่นอนคุณไม่ได้ใส่ไว้ในคำถามของคุณซึ่งเป็นข้อมูลที่ควร โปรดอธิบายสถานการณ์เฉพาะของคุณให้ดีขึ้นได้โปรด
Nicol Bolas

@ user1306322: ตรงไปตรงมาฉันไม่ค่อยเข้าใจว่าทำไมคำตอบของฉันจึงถูกลดระดับลง ฉันได้ระบุวิธีแก้ไขสามอย่างสำหรับสถานการณ์ความจำไม่ได้ - นั่นไม่ได้หมายความว่าทุกอย่างจำเป็นต้องใช้ แต่ฉันก็ยังคิดว่ามันถูกต้อง ฉันควรจะเขียน "ขยายหน่วยความจำของคุณ" แทน "ซื้อ" เพื่อรวมกรณีของเครื่องเสมือนแม้ว่า ฉันถูกลดระดับลงเพราะคำตอบของฉันสั้นกระชับแทนที่จะเป็นคำตอบ? ฉันคิดว่าพวกเขามีคะแนนตรงไปตรงมาพอที่จะเข้าใจโดยไม่ต้องอธิบายเพิ่มเติม!
โทมัส

1

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

ในการตอบกลับการอัปเดตของคุณคุณไม่สามารถจัดสรรอาร์เรย์ที่Int32.MaxValueมีขนาดเกิน คุณต้องแยกมันออกเป็นชิ้น ๆ คุณสามารถแค็ปซูลในคลาส wrapper ที่แสดงหน้าตาแบบอาเรย์:

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