วิธีที่ดีในการสร้างลูปเกมใน OpenGL


31

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

ดังนั้นความพยายามครั้งแรกของฉันคือการให้ฟังก์ชั่นของฉันฉันส่งไปที่glutIdleFunc( myIdle()) เพื่อติดตามเวลาที่ผ่านไปนับตั้งแต่การโทรครั้งก่อนและอัปเดตฟิสิกส์ (และกราฟิกปัจจุบัน) ทุกมิลลิวินาที ฉันเคยtimeGetTime()ทำสิ่งนี้ (โดยใช้<windows.h>) และนี่ทำให้ฉันคิดว่าการใช้ฟังก์ชั่นว่างเป็นวิธีที่ดีในการวนรอบเกมหรือไม่?

คำถามของฉันคือวิธีที่ดีกว่าในการใช้ลูปเกมใน OpenGL คืออะไร ฉันควรหลีกเลี่ยงการใช้ฟังก์ชันว่างหรือไม่



ผมรู้สึกว่าคำถามนี้เป็นเฉพาะเจาะจงมากขึ้นเพื่อ OpenGL และไม่ว่าจะใช้จำนวนที่มากเกินไปสำหรับวงแนะนำ
เจฟฟ์

1
นี่คือคนที่ไม่ได้ใช้จำนวนที่มากเกินไปสำหรับโครงการขนาดใหญ่ มันดีสำหรับคนเล็ก ๆ
bobobobo

คำตอบ:


29

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

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

คำอธิบายที่ดีที่สุดและแบบฝึกหัดที่ฉันพบในนี้คือ Glenn Fiedler's Fix Your Timestep

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

// The amount of time we want to simulate each step, in milliseconds
// (written as implicit frame-rate)
timeDelta = 1000/30
timeAccumulator = 0
while ( game should run )
{
  timeSimulatedThisIteration = 0
  startTime = currentTime()

  while ( timeAccumulator >= timeDelta )
  {
    stepGameState( timeDelta )
    timeAccumulator -= timeDelta
    timeSimulatedThisIteration += timeDelta
  }

  stepAnimation( timeSimulatedThisIteration )

  renderFrame() // OpenGL frame drawing code goes here

  handleUserInput()

  timeAccumulator += currentTime() - startTime 
}

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

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

  • GLFWซึ่งให้ความสามารถในการรับอินพุตอินไลน์เหตุการณ์ (ในลูปหลักของคุณเอง)
  • SDLอย่างไรก็ตามการเน้นของมันไม่ใช่ OpenGL โดยเฉพาะ

บทความที่ดี ดังนั้นเพื่อทำสิ่งนี้ใน OpenGL ฉันจะไม่ใช้ glutMainLoop แต่แทนที่จะเป็นของตัวเอง? ถ้าฉันทำอย่างนั้นฉันจะสามารถใช้ glutMouseFunc และฟังก์ชั่นอื่น ๆ ได้หรือไม่? ฉันหวังว่ามันสมเหตุสมผลฉันยังค่อนข้างใหม่กับสิ่งนี้ทั้งหมด
เจฟฟ์

แต่น่าเสียดายที่ไม่ได้. การเรียกกลับทั้งหมดที่ลงทะเบียนใน GLUT ถูกไล่ออกจากภายใน glutMainLoop เนื่องจากคิวเหตุการณ์ถูกระบายออกและวนซ้ำ ฉันเพิ่มลิงก์ไปยังทางเลือกอื่นในเนื้อหาของคำตอบ
charstar

1
+1 เพื่อทำ GLUT เพื่อสิ่งอื่น เท่าที่ฉันรู้ glut ไม่ได้มีความหมายอะไรเลยนอกจากการทดสอบแอปพลิเคชัน
Jari Komppa

คุณยังคงต้องจัดการกับเหตุการณ์ของระบบ แต่ไม่? ความเข้าใจของฉันคือว่าโดยการใช้ glutIdleFunc มันจัดการกับ (ใน Windows) GetMessage-TranslateMessage-DispatchMessage วนและเรียกใช้ฟังก์ชันของคุณเมื่อใดก็ตามที่ไม่มีข้อความที่จะได้รับ คุณต้องทำด้วยตัวคุณเองหรือไม่งั้นความอดทนของระบบปฏิบัติการในการตั้งค่าสถานะแอปพลิเคชันของคุณไม่ตอบสนองใช่ไหม?
Ricket

@Ricket ในทางเทคนิค glutMainLoopEvent () จัดการกับเหตุการณ์ที่เข้ามาด้วยการโทรกลับลงทะเบียน glutMainLoop () มีประสิทธิภาพ {glutMainLoopEvent (); IdleCallback (); } การพิจารณาที่สำคัญที่นี่คือการวาดใหม่ก็เป็นโทรกลับจัดการจากภายในวงเหตุการณ์ คุณสามารถทำให้การทำงานของห้องสมุดเหตุการณ์ที่ขับเคลื่อนด้วยเช่นเวลาที่ขับเคลื่อนด้วยหนึ่ง แต่ Maslow ค้อนและสิ่งที่ ...
charstar

4

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

แน่นอนคุณสามารถมีmyIdleฟังก์ชั่นที่คุณผ่านไปglutIdleFuncและภายในนั้นวัดเวลาที่ผ่านไปแล้วหารมันด้วยการประทับเวลาคงที่ของคุณและเรียกฟังก์ชั่นอื่นที่หลายต่อหลายครั้ง

นี่คือสิ่งที่ไม่ต้องทำ:

void myFunc() {
    // (calculate timeDifference here)
    int numOfTimes = timeDifference / 10;
    for(int i=0; i<numOfTimes; i++) {
        fixedTimestep();
    }
}

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

ให้ทำสิ่งนี้แทน:

long queuedMilliseconds; // static or whatever, this must persist outside of your loop

void myFunc() {
    // (calculate timeDifference here)
    queuedMilliseconds += timeDifference;
    while(queuedMilliseconds >= 10) {
        fixedTimestep();
        queuedMilliseconds -= 10;
    }
}

ตอนนี้โครงสร้างลูปนี้ทำให้มั่นใจได้ว่าfixedTimestepฟังก์ชันของคุณจะถูกเรียกหนึ่งครั้งต่อ 10 มิลลิวินาทีโดยไม่สูญเสียมิลลิวินาทีใด ๆ เป็นเศษซากหรืออะไรทำนองนั้น

เนื่องจากลักษณะการทำงานแบบมัลติทาสกิ้งของระบบปฏิบัติการคุณไม่สามารถพึ่งพาฟังก์ชั่นที่เรียกว่าทุก ๆ 10 มิลลิวินาที; แต่การวนรอบด้านบนจะเกิดขึ้นเร็วพอที่หวังว่าจะได้ใกล้พอและคุณสามารถสันนิษฐานได้ว่าการเรียกแต่ละครั้งfixedTimestepจะเพิ่มมูลค่าการจำลอง 10 มิลลิวินาทีของคุณ

โปรดอ่านคำตอบนี้และโดยเฉพาะลิงก์ที่ให้ไว้ในคำตอบ

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