Uma maneira legal de fazer o Z-index para componentes XNA?

7

Estou tentando renderizar alguns elementos no meu jogo. Primeiro, renderizo as texturas (as texturas das próprias caixas de texto etc). Então eu renderizo as primitivas (bordas ao redor dos controles etc). Então eu renderizo o texto.

No entanto, ao fazer isso, isso dá um resultado estranho na minha interface do usuário ao mostrar dicas de ferramentas (passando o mouse sobre um campo de texto), como visto na captura de tela abaixo.

Captura de tela

Devido à ordem do desenho, as bordas da janela são desenhadas após as texturas, o que (para a dica de ferramenta que aparece quando o mouse está sobre o campo de texto) faz com que pareça estranho.

Mathias Lykkegaard Lorenzen
fonte

Respostas:

6

Se estes forem separados DrawableGameComponents, você deve definir a DrawOrderpropriedade de cada componente para que eles desenhem na ordem correta.

Para torná-lo mais gerenciável à medida que você adiciona mais componentes, DrawOrderdeve ser enum:

/* DisplayLayer.cs */
public enum DisplayLayer
{
    Background, //back-layer
    Particles,
    Player,
    MenuBack,
    MenuFront //front-layer
}

public class MyComponent : DrawableGameComponent
{
    public MyComponent(Game game) : base(game)
    {
        this.DrawOrder = (int)DisplayLayer.Background;
    }

    /* etc. */
}

Se tudo isso estiver sendo elaborado em um único DrawableGameComponent, você deve seguir os conselhos de @ Andrew e simplesmente desenhá-los na ordem correta. Se, por algum motivo, você não puder fazer isso, ainda poderá ter a clareza de enums ao usá-lo layerDepth, simplesmente normalizando o valor de enumentre 0 e 1:

/* DisplayLayer.cs */
public enum DisplayLayer
{
    MenuFront, //front-layer
    MenuBack,
    Player,
    Particles,
    Background, //back-layer
    MAX_LAYER   //Do not use this as a layer
}

public void Draw()
{
    spriteBatch.Begin(SpriteSortMode.BackToFront);
    foreach(Thingy thingy in thingys)
        spriteBatch.Draw(/* blah blah */, (float)thingy.DisplayLayer/(float)DisplayLayer.MAX_LAYER);
    spriteBatch.End();
}

(A ordem do enumé revertida porque layerDepthusa números mais baixos para a camada frontal em vez da camada posterior)

BlueRaja - Danny Pflughoeft
fonte
6

http://msdn.microsoft.com/en-us/library/ff433989.aspx

public void Draw (
         Texture2D texture,
         Vector2 position,
         Nullable<Rectangle> sourceRectangle,
         Color color,
         float rotation,
         Vector2 origin,
         Vector2 scale,
         SpriteEffects effects,
         float layerDepth
)


layerDepth A profundidade de uma camada. Por padrão, 0 representa a camada frontal e 1 representa a camada traseira. Use SpriteSortMode se desejar que os sprites sejam classificados durante o desenho.
Por exemplo, você tem: formulário e nos rótulos de formulários, botões
Assim, para elementos filhos você pode adicionar ao layerDepth algum valor
Por exemplo: Form A ter filhos: Formulário B e alguns botões, Form B têm apenas botão
Formulário A - 1.0
Botões em formulário A - 1,5
Formulário B no formulário A - 1,5
Botões no formulário B - 2

Para caixas de diálogo - você pode armazenar um valor maior

Piotrek
fonte
4
Algumas advertências para esse método: o intervalo válido de layerDepth(tenho razoavelmente certeza) é de 0 a 1, portanto, você deve permanecer dentro desse intervalo. Além disso, todo o desenho precisará ser feito dentro do lote de um sprite (entre Begine End). Finalmente: vale ressaltar que esse método não oferece benefícios de desempenho em vez de simplesmente desenhar as coisas na ordem correta em primeiro lugar.
Andrew Russell
6

Vou dar a resposta simples e bastante óbvia, e isso é apenas desenhar tudo na ordem correta em primeiro lugar .

Para criar um exemplo do que quero dizer:

// Assuming you have sorted windowList by Z-order, using one of the many
// existing sorting mechanisms provided by the framework such as List<T>.Sort().
foreach(var window in windowList)
    window.Draw();

// in your Window class:
public void Draw()
{
    this.DrawBackground();
    this.DrawBorders();
    this.DrawText();
}

Existe uma razão específica e convincente para você não estar desenhando suas janelas dessa maneira?

Andrew Russell
fonte
Eu acho que, como ele afirma em sua pergunta, é porque ele está fazendo chamadas SpriteBatch e primitivas e, portanto, ele quer agrupá-las, o que não é realmente uma má idéia. Parece que me lembro que, mesmo assim, o SpriteBatch irá agrupar todas as chamadas de qualquer maneira, por isso tenho certeza de que a opção de tour seria a mesma perfeitamente. Também faz mais sentido.
Jonathan Connell
@ 3nixios Eu acho que o motivo dele também é o desempenho - mas eu sugiro imediatamente que é otimização prematura. Sua GUI está realmente se saindo tão mal? E a consolidação de lotes é realmente a escolha correta de otimização? Também SpriteBatchnão pode lote após uma alteração de textura - portanto, meu método é potencialmente mais lento que o método dele, se sua implementação for otimizada corretamente.
Andrew Russell
E seu método é viável - a propósito. Mas é difícil de implementar corretamente - incluindo transformá-lo em uma boa resposta. Portanto, provavelmente não vale a pena o esforço pelo pequeno ganho de desempenho - e, portanto, seria a resposta errada a ser dada. (A versão curta é: usar o Z-tampão.)
Andrew Russell
Eu concordo totalmente :)
Jonathan Connell
Não entendo por que a abordagem acima o impede de SpriteBatchagrupar todas as suas operações. Basta modificar os métodos de desenho para aceitar SpriteBatchcomo parâmetro. Em seguida, no método "Draw" mais básico, primeiro chame SpriteBatch.Begin()- depois chame "Draw" no Windows (passando SpriteBatch, que eles usarão e passará para cada filho) - depois do loop, no mais básico Método "Draw", ligue SpriteBatch.End(). . . (My $ 0,02: Aposto que ele não está usando objetos e toda a coisa é provavelmente uma confusão processual grande em seu principal Game. Class)
BrainSlugs83