Pencere yeniden boyutlandırılırken / taşınırken XNA'nın Güncellemeleri duraklatmasını nasıl önleyebilirim?


15

XNA aramayı durdurur Updateve Drawoyun penceresi yeniden boyutlandırılırken veya taşınırken.

Neden? Bu davranışı önlemenin bir yolu var mı?

(Ağ mesajlarının pompalanmaması nedeniyle ağ kodumun senkronize olmamasına neden oluyor.)

Yanıtlar:


20

Evet. XNA'nın iç kısımları ile az miktarda karışıklık içerir. Bu videonun ikinci yarısında bir düzeltme gösterdim .

Arka fon:

Bunun nedeni, XNA'nın Gamealtında yatan Formboyut yeniden boyutlandırıldığında (veya taşındığında, olaylar aynı olduğunda) oyun saatini askıya almasıdır . Bu da oyun döngüsünü harekete geçirmesidir Application.Idle. Bir pencere yeniden boyutlandırıldığında, Windows, ateş etmeyi önleyen çılgın win32 mesaj döngüsü öğelerini yaparIdle .

Yani, Gamesaatini askıya almadıysa, bir yeniden boyutlandırmadan sonra, tek bir patlamada güncellemesi gereken muazzam bir süre birikmiş olacak - açıkça istenmiyor.

Nasıl düzeltilir?

XNA olaylar uzun zincirli (kullanırsanız Orada Gametemelde yatan resize olayı birbirine bağlayan bu özel pencereler için geçerli değildir) Form, karşı Suspendve Resumeoyun saatinin yöntemleri.

Eğer gerekir böylece Bunlar, özeldir yansıması kullanmak (burada olayları unhook thisbir olduğunu Game):

this.host.Suspend = null;
this.host.Resume = null;

İşte gerekli büyüleme:

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);

(Zincirin başka bir yerde bağlantısını kesebilirsiniz, bunu anlamak için ILSpy'yi kullanabilirsiniz. Ama pencereyi yeniden boyutlandırmanın yanında zamanlayıcıyı askıya alan başka bir şey yok - bu yüzden bu olaylar herhangi bir şey kadar iyi.)

O zaman XNA işaretlemediği zaman kendi kene kaynağınızı sağlamanız gerekir Application.Idle. Ben sadece bir System.Windows.Forms.Timer:

timer = new Timer();
timer.Interval = (int)(this.TargetElapsedTime.TotalMilliseconds);
timer.Tick += new EventHandler(timer_Tick);
timer.Start();

Şimdi, timer_Ticksonunda arayacak Game.Tick. Artık saat askıya alınmadığından, XNA'nın kendi zamanlama kodunu kullanmaya devam etmekte özgürüz. Kolay! Tam olarak değil: manuel işaretlememizin yalnızca XNA'nın yerleşik işaretlemesi gerçekleşmediğinde olmasını istiyoruz. İşte bunu yapmak için bazı basit kod:

bool manualTick;
int manualTickCount = 0;
void timer_Tick(object sender, EventArgs e)
{
    if(manualTickCount > 2)
    {
        manualTick = true;
        this.Tick();
        manualTick = false;
    }

    manualTickCount++;
}

Ve sonra, UpdateXNA normal olarak işaretliyorsa sayacı sıfırlamak için bu kodu girin.

if(!manualTick)
    manualTickCount = 0;

Bu işaretlemenin XNA'lardan çok daha az doğru olduğuna dikkat edin. Bununla birlikte, gerçek zamanlı olarak aşağı yukarı dizilmiş olarak kalmalıdır.


Bu kodda hala küçük bir kusur var. Aptal çirkin yüzünü korusun Win32, fare gerçekten hareket etmeden önce bir pencerenin başlık çubuğunu tıklattığınızda tüm mesajları birkaç yüz milisaniye boyunca durdurur ( aynı bağlantıya tekrar bakın ). Bu, Timer(mesaj tabanlı olan) işaretlememizi önler .

Şimdi XNA'nın oyun saatini askıya almadığımız için, zamanlayıcımız sonunda tekrar işaretlemeye başladığında, bir güncelleme patlaması alacaksınız. Ve ne yazık ki, XNA, biriktirilebilir süreyi , başlık çubuğunu tıklattığınızda Windows'un iletileri durdurma süresinden biraz daha düşük bir miktarla sınırlar . Dolayısıyla, bir kullanıcı başlık çubuğunuzu tıklayıp basılı tutarsa, taşımadan güncellemeler patlar ve gerçek zamanlı olarak birkaç kare düşer.

(Ancak bu çoğu durumda hala yeterince iyi olmalıdır . XNA'nın zamanlayıcısı, kekemeliği önlemek için doğruluktan zaten fedakarlık yapar, bu nedenle mükemmel zamanlamaya ihtiyacınız varsa , başka bir şey yapmalısınız.)


2
Güzel kapsamlı cevap.
MichaelHouse

1
Neden tam olarak soruyu sordunuz, sonra anında cevapladınız mı?
Alastair Pitts

4
Adil soru. Bu oluyor açıkça teşvik . Soru arayüzünde "Kendi sorunuzu cevaplayın" kutusu bile var. Başlangıçta bu soruya bir cevap verecektim, ama bu sadece eski bir cevabın bir düzenlemesi olurdu ve çözümüm bu sorunun bir kısmını çözmüyor. Böylece daha iyi görünürlük elde edilir (GDSE'de SO yerine yeni olmak ve XNA olmak). Ve bilgi blogumdan ziyade buraya daha uygun.
Andrew Russell
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.