XNA หยุดการโทรUpdate
และDraw
ในขณะที่หน้าต่างเกมกำลังถูกปรับขนาดหรือย้าย
ทำไม? มีวิธีป้องกันพฤติกรรมนี้หรือไม่?
(มันทำให้รหัสเครือข่ายของฉันยกเลิกการซิงโครไนซ์เนื่องจากข้อความเครือข่ายไม่ถูกปั๊ม)
XNA หยุดการโทรUpdate
และDraw
ในขณะที่หน้าต่างเกมกำลังถูกปรับขนาดหรือย้าย
ทำไม? มีวิธีป้องกันพฤติกรรมนี้หรือไม่?
(มันทำให้รหัสเครือข่ายของฉันยกเลิกการซิงโครไนซ์เนื่องจากข้อความเครือข่ายไม่ถูกปั๊ม)
คำตอบ:
ใช่. มันเกี่ยวข้องกับการสับสนเล็กน้อยกับ internals ของ XNA ฉันได้สาธิตการแก้ไขในช่วงครึ่งหลังของวิดีโอนี้
พื้นหลัง:
เหตุผลนี้เกิดขึ้นเนื่องจาก XNA ระงับนาฬิกาเกมเมื่อมีการปรับขนาดGame
ต้นแบบForm
(หรือย้ายเหตุการณ์เหมือนกัน) ในทางกลับกันนี่เป็นเพราะมันผลักดันเกมวนมาจากApplication.Idle
มัน เมื่อมีการปรับขนาดหน้าต่าง Windows จะทำบางสิ่งที่วนลูปข้อความ win32ที่ป้องกันไม่ให้Idle
ยิง
ดังนั้นหากGame
ไม่ได้ระงับนาฬิกาหลังจากปรับขนาดแล้วจะมีการสะสมจำนวนมหาศาลซึ่งจะต้องอัปเดตผ่านการระเบิดครั้งเดียว - ไม่ต้องการอย่างชัดเจน
วิธีแก้ไข:
มีโซ่ยาวของเหตุการณ์ใน XNA (ถ้าคุณใช้เป็นGame
นี้ไม่ได้นำไปใช้กับหน้าต่างที่กำหนดเอง) ที่พื้นเชื่อมต่อเหตุการณ์การปรับขนาดของพื้นฐานForm
เพื่อSuspend
และResume
วิธีการสำหรับนาฬิกาเกม
เหล่านี้เป็นส่วนตัวเพื่อให้คุณจะต้องใช้สะท้อนที่จะปลดเหตุการณ์ที่เกิดขึ้น (ที่this
เป็นGame
):
this.host.Suspend = null;
this.host.Resume = null;
นี่คือคาถาที่จำเป็น:
object host = typeof(Game).GetField("host", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(this);
host.GetType().BaseType.GetField("Suspend", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(host, null);
host.GetType().BaseType.GetField("Resume", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(host, null);
(คุณสามารถตัดการเชื่อมต่อที่อื่นคุณสามารถใช้ ILSpy เพื่อค้นหาได้ แต่ไม่มีอะไรอื่นนอกเหนือจากการปรับขนาดหน้าต่างที่หยุดตัวจับเวลา - ดังนั้นเหตุการณ์เหล่านี้จึงดีเหมือนกัน)
แล้วคุณจะต้องระบุแหล่งที่มาเห็บของคุณเองเมื่อ XNA Application.Idle
ไม่ได้ฟ้องจาก ฉันใช้System.Windows.Forms.Timer
:
timer = new Timer();
timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();
ตอนนี้ในที่สุดจะเรียกtimer_Tick
Game.Tick
ตอนนี้นาฬิกาไม่หยุดทำงานเรามีอิสระที่จะใช้รหัสจับเวลาของ XNA ต่อไป ง่าย! ไม่มาก: เราเพียงต้องการให้การฟ้องด้วยตนเองเกิดขึ้นเมื่อการฟ้องร้องในตัวของ XNA ไม่เกิดขึ้น นี่คือรหัสง่ายๆที่จะทำ:
bool manualTick;
int manualTickCount = 0;
void timer_Tick(object sender, EventArgs e)
{
if(manualTickCount > 2)
{
manualTick = true;
this.Tick();
manualTick = false;
}
manualTickCount++;
}
จากนั้นในUpdate
ให้ใส่รหัสนี้เพื่อรีเซ็ตตัวนับหาก XNA ติ๊กตามปกติ
if(!manualTick)
manualTickCount = 0;
โปรดทราบว่าการฟ้องนี้มีความแม่นยำน้อยกว่าของ XNA มาก อย่างไรก็ตามควรมีการเรียงแถวตามเวลาจริงมากขึ้นหรือน้อยลง
ยังมีข้อบกพร่องเล็ก ๆ น้อย ๆ ในรหัสนี้ Win32 อวยพรใบหน้าที่น่าเกลียดโง่ ๆ หยุดข้อความทั้งหมดเป็นเวลาสองสามร้อยมิลลิวินาทีเมื่อคุณคลิกที่แถบชื่อเรื่องของหน้าต่างก่อนที่เมาส์จะขยับ (ดูลิงค์เดิมอีกครั้ง ) สิ่งนี้จะป้องกันไม่ให้เราทำเครื่องหมายTimer
(ซึ่งเป็นข้อความ)
เนื่องจากตอนนี้เราไม่หยุดนาฬิกาเกมของ XNA เมื่อตัวจับเวลาของเราเริ่มต้นฟ้องอีกครั้งคุณจะได้รับการอัปเดตมากมาย และน่าเศร้าที่ XNA จำกัด ระยะเวลาในการสะสมให้เหลือน้อยกว่าระยะเวลาเล็กน้อย Windows จะหยุดข้อความเมื่อคุณคลิกที่แถบชื่อเรื่อง ดังนั้นหากผู้ใช้คลิกที่แถบชื่อเรื่องค้างไว้โดยไม่ย้ายคุณจะได้รับการอัปเดตและทำให้เฟรมไม่สั้นตามเวลาจริง
(แต่นี่ก็ยังดีพอสำหรับกรณีส่วนใหญ่ตัวจับเวลาของ XNA ได้เสียสละความถูกต้องเพื่อป้องกันการพูดติดอ่างดังนั้นถ้าคุณต้องการเวลาที่สมบูรณ์แบบคุณควรทำอย่างอื่น)