2010-11-20 12 views
6

Per i menu nel mio gioco, li disegno una volta sullo schermo, quindi li ridisegno solo se sono stati considerati sporchi. Questo viene gestito tramite un set booleano su true ogni volta che l'utente esegue un'azione che dovrebbe causare un ridisegno, e quindi il ciclo di disegno controllerà quel valore prima di disegnare il menu. Questa logica ha funzionato perfettamente in 3.1, ma in 4.0 il menu sfarfalla (disegnato per 1 fotogramma) quindi mostra una schermata viola fino a quando non viene nuovamente disegnato.XNA 3.1 a 4.0 richiede un ridisegno costante o visualizzerà una schermata viola

Ho creato un semplice gioco di prova in 4.0 per dimostrare il problema illustrato di seguito. Noterai che lo schermo sembra solo viola. Se rimuovi l'impostazione di riga _isDirty su false, vedrai lo sfondo bluette.

public class Game1 : Microsoft.Xna.Framework.Game 
{ 
    GraphicsDeviceManager graphics; 
    bool _isDirty = true; 

    public Game1() 
    { 
     graphics = new GraphicsDeviceManager(this); 
    } 
    protected override void Draw(GameTime gameTime) 
    { 
     if (_isDirty) 
     { 
      GraphicsDevice.Clear(Color.CornflowerBlue); 
      _isDirty = false; 
     } 
     base.Draw(gameTime); 
    } 
} 

Come posso ottenere il comportamento da XNA 3.1? Ho visto diverse persone menzionare PreserveContents, ma questo non sembra avere alcun effetto in 4.0 a meno che non lo stia applicando in modo errato.

risposta

13

Ecco una panoramica approssimativa di ciò che viene chiamato in un gioco di XNA:

Update 
BeginDraw 
Draw 
EndDraw 

L'implementazione predefinita di EndDraw in ultima analisi, chiamate GraphicsDevice.Present. Con il doppio buffer attivato, scambia i buffer posteriore e anteriore.

Quindi, ciò che sta accadendo è:

  • prima volta intorno: si sta disegnando la scena al buffer posteriore e poi lasciare XNA di swap in primo piano.

  • Seconda volta: non si disegna nulla, lasciando la superficie riempita con il colore viola a cui DirectX inizializza queste superfici, e scambiando con in primo piano!

  • Tempi successivi: non si disegna nulla, quindi vedrete lo sfarfallio dello schermo tra queste due superfici.

Esistono diversi modi per sopprimere il disegno in XNA. Li controllo su this answer to a similar question. Come in quella risposta, vi consiglio di ignorare BeginDraw, in questo modo:

protected override bool BeginDraw() 
{ 
    if(_isDirty) 
     return base.BeginDraw(); 
    else 
     return false; 
} 

Quando BeginDraw restituisce false, Draw e EndDraw (e così Present) non sarà chiamato quel fotogramma. Non si disegnerà nulla e i buffer fronte/retro non si scambieranno.

+1

Perfetto! Grazie per aver dedicato del tempo al dettaglio del funzionamento interno di Draw, è esattamente ciò di cui avevo bisogno. –

+0

Nota: come ho scoperto [qui sopra] (http://stackoverflow.com/a/16910684/165500), XNA contrassegna effettivamente un buffer come non inizializzato (cancella in viola scuro) durante "Presente", quindi il bit in la mia risposta sullo sfarfallio è sbagliata, anche se la soluzione è sempre la stessa. (Pentti [risposta] (http://stackoverflow.com/a/6283242/165500) qui ha dettagli sul meccanismo, anche se eviterei di spegnerlo con il martello che è riflesso. Molto più ragionevole usare correttamente l'API , come da mia risposta.) –

+0

Ricevo "Il nome '_isDirty' non esiste nel contesto attuale" Errore. Usando XNA 4.0 e mettendo questo metodo nella classe Game1. Qualche idea? – Xonatron

2

Recentemente sono incappato anche in questo; Non penso che sia un problema con l'inversione dei buffer. Questo dovrebbe essere lo stesso in 3.1 e 4.0. Ho guardato GraphicsDevice con ILSpy e ho scoperto questo:

Ciò che è cambiato è che in 4.0, il target di rendering corrente viene prima cancellato in GraphicsDevice.Present() se è impostato un flag privato. Per impostazione predefinita, questo flag verrà impostato inizialmente (render target clean). Il disegno nel target di rendering cancella questo flag (l'obiettivo di rendering è ora sporco). Dopo aver eseguito il suo compito, GraphicsDevice.Present() imposta nuovamente il flag (la destinazione del rendering è stata "svuotata" alla tua finestra ed è ora nuovamente considerata pulita).

Per ottenere risultati da GraphicsDevice.Present(), è necessario chiamare Draw() e EndDraw() in sequenza rigorosa.

Se si chiama EndDraw() senza una chiamata precedente a Draw(), non si otterrà il contenuto del target di rendering (che viene cancellato), ma solo lo schermo viola.

Sfortunatamente, la bandiera è privata e non esiste un modo pulito per arrivarci. È possibile utilizzare la reflection per cancellare il flag in questo modo, costringendo l'obiettivo sporco render senza disegnare qualsiasi cosa in esso:

graphicsDevice.GetType() getField ("lazyClearFlags", BindingFlags.NonPublic | BindingFlags.Instance) .SetValue (GraphicsDevice. , 0);

(GraphicsDevice è l'istanza GraphicsDevice corrente)

Posizionare la linea di cui sopra prima della chiamata a EndDraw(), e il comportamento torna a quello che era in 3.1

+0

C'è un altro modo, potenzialmente di copiare il contenuto della schermata corrente (dall'ultimo buffer utilizzato) al nuovo buffer che verrà utilizzato ogni volta? – Xonatron

0

Nel mio caso particolare, ho un macchina di stato che cambia tra gli stati responsabili del disegno. Quindi, quando effettuo la transizione tra due stati di gioco, non c'è nulla da disegnare e lo schermo diventa viola fino a quando lo stato di gioco successivo non è attivo.

Quello che ho fatto per risolverlo era semplicemente, nell'Aggiornamento, se non ho stato, chiamare il metodo SuppressDraw() nell'oggetto di gioco. Ciò impedisce che il disegno avvenga fino al successivo aggiornamento.

Suppongo che non vi sia nulla che impedisca di chiamarlo sempre in Aggiornamento, tranne quando si imposta un flag isDirty. Ma dovresti essere preparato a gestire altri eventi/casi in cui lo schermo potrebbe essere distrutto.

Problemi correlati