วิธียอดนิยมในการใช้งาน Game Loop มีดังนี้
while (playing) {
advance state by one frame
render the new frame
sleep until it’s time to do the next frame
}
เรื่องนี้มีปัญหาอยู่บ้าง พื้นฐานที่สำคัญที่สุดคือ เกมสามารถกำหนดได้ว่า "เฟรม" จอแสดงผลที่แตกต่างกันจะรีเฟรชในเวลาที่ต่างกัน และอัตราดังกล่าวจึงอาจแตกต่างกันไป เมื่อเวลาผ่านไป หากคุณสร้างเฟรมเร็วกว่า จอแสดงผลจะแสดงได้ คุณจะต้องแสดงหน้าจอเป็นครั้งคราว หากคุณสร้าง ทำงานช้าเกินไป SurfaceFlinger จะไม่หาบัฟเฟอร์ใหม่ ได้มาและจะแสดงเฟรมก่อนหน้าอีกครั้ง ทั้ง 2 สถานการณ์นี้สามารถ ทำให้เกิดข้อบกพร่องที่มองเห็นได้
สิ่งที่ต้องทำคือปรับอัตราเฟรมของจอแสดงผลให้ตรงกันและสถานะของเกมขั้นสูง ตามระยะเวลาที่ผ่านไปนับตั้งแต่เฟรมก่อนหน้า วิธีแก้ปัญหานี้
- ใช้ไลบรารี Android Frame Pacing (แนะนำ)
- ทำให้ BufferQueue เต็มแล้วและใช้ "สลับบัฟเฟอร์" แรงกดดันกลับ
- ใช้ Choreographer (API 16+)
ไลบรารีการกำหนดอัตราการแสดงโฆษณาของ Android Frame
ดูข้อมูลเพิ่มเติมที่ใช้การกำหนดอัตราของเฟรมที่เหมาะสม เกี่ยวกับการใช้ไลบรารีนี้
การยัดเยียดคิว
วิธีการนี้ทำได้ง่ายมาก เพียงสลับบัฟเฟอร์ให้เร็วที่สุดเท่าที่คุณจะทำได้ ในช่วงต้น
ของ Android เวอร์ชันต่างๆ มากขึ้น
จะส่งผลให้มีการลงโทษที่
SurfaceView#lockCanvas()
จะทำให้คุณเข้าสู่โหมดสลีปเป็นเวลา 100 มิลลิวินาที เริ่มเลย
มีการกำหนดอัตราโดย BufferQueue และ BufferQueue ถูกลบทันที
SurfaceFlinger ทำได้แล้ว
ดูตัวอย่างของวิธีการนี้ได้ใน Android เบรกเอาต์ ทั้งนี้
ใช้ GLSurfaceView ซึ่งทำงานแบบวนซ้ำที่เรียกใช้ฟังก์ชัน
onDrawFrame() Callback จากนั้นสลับบัฟเฟอร์ หาก BufferQueue เต็มแล้ว
การเรียกใช้ eglSwapBuffers()
จะรอจนกว่าจะมีบัฟเฟอร์
บัฟเฟอร์จะพร้อมใช้งานเมื่อ SurfaceFlinger ปล่อยบัฟเฟอร์
การได้มาซึ่งหน้าจอใหม่
เพื่อแสดงผล เนื่องจากสิ่งนี้เกิดขึ้นใน VSYNC การวนรอบของคุณ
ช่วงเวลาจะตรงกับอัตราการรีเฟรช ส่วนใหญ่
มีปัญหา 2-3 ข้อสำหรับวิธีนี้ ประการแรก แอปผูกอยู่กับ กิจกรรม SurfaceFlinger ซึ่งจะใช้เวลาแตกต่างกันไป ขึ้นอยู่กับว่าต้องทำมากแค่ไหนและต้องใช้เวลา CPU หรือไม่ กับกระบวนการอื่นๆ เนื่องจากสถานะของเกมจะก้าวล้ำไปตามเวลา ระหว่างการสลับบัฟเฟอร์ ภาพเคลื่อนไหวจะไม่อัปเดตในอัตราที่สม่ำเสมอ วันและเวลา แม้ตอนนี้จะใช้ความเร็ว 60 fps และความไม่สอดคล้องเฉลี่ยในช่วงระยะเวลาหนึ่ง อาจจะไม่สังเกตเห็นกราฟที่ยกตัวขึ้น
อย่างที่สอง การสลับบัฟเฟอร์ 2-3 รายการแรกจะเกิดขึ้นอย่างรวดเร็ว เนื่องจาก BufferQueue ยังไม่เต็ม เวลาที่คำนวณระหว่างเฟรมจะ ใกล้ 0 ดังนั้นเกมจะสร้างเฟรม 2-3 เฟรมที่ไม่มีเหตุการณ์ใดเกิดขึ้น ในเกมอย่าง เบรกเอาต์ ซึ่งอัปเดตหน้าจอทุกครั้งที่รีเฟรช คิวจะ จะเต็มเสมอ ยกเว้นเมื่อเกมเริ่มเล่นครั้งแรก (หรือยกเลิกการหยุดชั่วคราว) ดังนั้น จะไม่สังเกตเห็น เกมที่หยุดภาพเคลื่อนไหวชั่วคราวเป็นครั้งคราวแล้วกลับมาเล่น โหมดเร็วเท่าที่เป็นไปได้อาจเห็นการสะอึกแบบแปลกๆ
Choreographer
การออกแบบท่าเต้นช่วยให้คุณตั้งค่า Callback ที่เริ่มทำงานใน VSYNC ถัดไปได้ เวลา VSYNC จริงจะส่งผ่านเป็นอาร์กิวเมนต์ ดังนั้นแม้ว่าแอปของคุณจะไม่ตื่น คุณก็ยังเห็นภาพที่ถูกต้องของการรีเฟรชหน้าจอ ได้เริ่มต้นขึ้น การใช้ค่านี้ให้ผลลัพธ์เป็น ของเวลาที่สอดคล้องกันสำหรับตรรกะการอัปเดตสถานะเกมของคุณ
การที่คุณจะได้รับ Callback ทุกครั้งที่ VSYNC ไม่ รับประกันว่าการติดต่อกลับของคุณจะได้รับการดำเนินการอย่างทันท่วงที หรือคุณจะ จะสามารถดำเนินการกับการอุทธรณ์ได้อย่างรวดเร็วเพียงพอ แอปของคุณจะต้องตรวจหา ที่ภาพถอยหลังและตกเฟรมด้วยตนเอง
"แอป Record GL" กิจกรรมใน Grafika แสดงตัวอย่างให้เห็นได้จาก ในบางส่วน บนอุปกรณ์ (เช่น Nexus 4 และ Nexus 5) กิจกรรมจะเริ่มทิ้งเฟรมหาก การได้นั่งดูอยู่เฉยๆ การแสดงผล GL นั้นมีความสำคัญเล็กน้อย แต่บางครั้งมุมมอง จะมีการวาดองค์ประกอบใหม่ และการส่งการวัด/เลย์เอาต์อาจใช้เวลานานหาก อุปกรณ์เข้าสู่โหมดลดพลังงาน (จากข้อมูลใน systrace, ใช้เวลา 28 มิลลิวินาทีแทนที่จะเป็น 6 มิลลิวินาทีหลังจากที่นาฬิกาช้าลงบน Android 4.4 หากคุณลาก นิ้วรอบๆ หน้าจอ โมเดลจะคิดว่าคุณกำลังโต้ตอบกับกิจกรรมนั้น เพื่อให้ความเร็วของนาฬิกายังคงสูงอยู่ และคุณก็จะไม่ตกไปในเฟรมทันที)
วิธีแก้ไขง่ายๆ คือให้วางเฟรมใน Callback ของผู้ออกแบบท่าเต้นหาก เวลามากกว่า N มิลลิวินาทีหลังจากเวลา VSYNC ตามหลักการแล้ว ค่า N จะกำหนดตามช่วงเวลา VSYNC ที่สังเกตได้ก่อนหน้านี้ ตัวอย่างเช่น หาก ระยะเวลารีเฟรชคือ 16.7 มิลลิวินาที (60 เฟรมต่อวินาที) คุณอาจทิ้งเฟรมไว้หากใช้ได้มากกว่า ล่าช้ากว่า 15 มิลลิวินาที
หากคุณดู "แอปบันทึก GL" คุณจะเห็นตัวนับของเฟรมที่ตกลงมา และแม้แต่เห็นสีแดงกะพริบที่ขอบเมื่อเฟรมลดลง ยกเว้น ดวงตาของคุณจะดูดีอยู่แล้ว คุณจะไม่เห็นภาพเคลื่อนไหวกระตุก ที่ 60 FPS แอปสามารถวางเฟรมเป็นครั้งคราวโดยที่ไม่มีใครสังเกตเห็น ตราบใดที่ ภาพเคลื่อนไหวมีความคืบหน้าต่อไปในอัตราคงที่ คุณสามารถใช้งานได้มากมายขนาดไหน ขึ้นอยู่กับสิ่งที่คุณวาด ลักษณะของ และดูว่าผู้ที่ใช้แอปตรวจจับการกระตุกได้ดีเพียงใด
การจัดการชุดข้อความ
โดยทั่วไป หากคุณกำลังแสดงผลไปยัง SurfaceView, GLSurfaceView หรือ TextureView คุณต้องการให้แสดงผลในเธรดเฉพาะ ไม่ต้องดำเนินการใดๆ "การยกของหนัก" หรือการดำเนินการใดๆ ที่ใช้เวลาบนอุปกรณ์ ชุดข้อความ UI แต่ให้สร้าง 2 ชุดข้อความสําหรับเกมนั้นแทน ซึ่งก็คือชุดข้อความเกม และเธรดการแสดงภาพ ดูปรับปรุงประสิทธิภาพของเกม เพื่อดูข้อมูลเพิ่มเติม
เบรกเอาต์และ "แอปบันทึก GL" ใช้ชุดข้อความในโหมดแสดงภาพเฉพาะ อัปเดตสถานะภาพเคลื่อนไหวของชุดข้อความนั้น ซึ่งเป็นวิธีที่สมเหตุสมผล สามารถอัปเดตสถานะเกมได้อย่างรวดเร็ว
เกมอื่นๆ ใช้ตรรกะของเกมและแสดงผลอย่างสิ้นเชิง หากคุณมี เกมง่ายๆ ที่ไม่ต้องทำอะไรเลย เพียงแค่ขยับบล็อกทุกๆ 100 มิลลิวินาที คุณสามารถมี ชุดข้อความเฉพาะที่ดำเนินการต่อไปนี้
run() {
Thread.sleep(100);
synchronized (mLock) {
moveBlock();
}
}
(คุณอาจต้องแบ่งเวลานอนให้เท่ากับนาฬิกาตายตัวเพื่อไม่ให้เกิดการลอยตัว -- การนอนหลับ() ไม่ได้สอดคล้องกันอย่างสมบูรณ์ และmoveBlock() ใช้จำนวนที่ไม่ใช่ 0 เวลา แต่คุณก็พอจะมองออกแล้ว)
เมื่อรหัสวาดทำงาน รหัสจะจับล็อก และรับตำแหน่งปัจจุบัน ของบล็อก ปลดล็อก และวาด แทนที่จะทำแบบแบ่งส่วน การเคลื่อนไหวตามเวลาเดลต้าระหว่างเฟรม คุณจะมีชุดข้อความเดียวที่เคลื่อนไหว เรื่องราวต่างๆ ไปพร้อมกับอีกเส้นด้ายที่ชักจูงให้ทุกๆ อย่างเกิดขึ้น เมื่อภาพวาดเริ่มต้นขึ้น
สำหรับฉากที่มีความซับซ้อนมาก คุณก็ควรสร้างรายการเหตุการณ์ที่กำลังจะเกิดขึ้น จัดเรียงตามเวลาตื่นนอน แล้วก็นอนจนกว่ากิจกรรมถัดไปจะครบกำหนด แต่ก็เหมือนกัน ไอเดียของคุณ