Existe uma maneira simples de agrupar dois ou mais sprites, para que todos eles dependam um do outro?

8

Penso que esta pergunta é muito semelhante a esta , mas não tenho certeza se as respostas são universais.

Então, meu objetivo é:

  • Coloque dois sprites em posição fixa, por exemplo, jogador e seus olhos
  • Certifique-se de que sempre que o jogador girar, os olhos girem também e cheguem à mesma posição relativa do corpo (para que os olhos não fiquem nas costas do jogador). Então eles estarão trabalhando como um grupo. - Essa etapa deve ser automatizada, esse é o meu objetivo!

Então, por exemplo, agora quero colocar uma arma nas mãos do usuário. Então agora eu digo, aquele jogador está em posição Vector2(0, 0)e a arma está em posição Vector2(26, 16). Agora eu quero agrupá-los, então sempre que o jogador gira, a arma também gira.

insira a descrição da imagem aqui

Atualmente, neste exemplo, está bem, mas no caso de eu precisar mover minha arma no eixo y (apenas), estou perdido

Martin.
fonte

Respostas:

10

Conceito

Eu resolveria esse problema com uma hierarquia de sprites usando uma variação do padrão de design composto . Isso significa que cada sprite armazene uma lista dos sprites filhos anexados a ele, para que quaisquer modificações no pai sejam refletidas automaticamente neles (incluindo translação, rotação e dimensionamento).

No meu mecanismo, eu o implementei assim:

  • Cada Spriteum armazena List<Sprite> Childrene fornece um método para adicionar novos filhos.
  • Cada um Spritesabe como calcular um Matrix LocalTransformque é definido em relação ao pai.
  • A chamada Drawde um Spritetambém chama todos os seus filhos.
  • Os filhos multiplicam sua transformação local pela transformação global de seus pais . O resultado é o que você usa ao renderizar.

Com isso, você poderá fazer o que pediu sem outras modificações no seu código. Aqui está um exemplo:

Sprite tank = new Sprite(tankTexture);
tank.Children.Add(new Sprite(turretTexture) {Position = new Vector2(26, 16) });

spriteBatch.Begin();
tank.Draw(spriteBatch);
spriteBatch.End();

Implementação

Para iniciantes, mostrarei apenas um projeto de amostra com essa técnica implementada, caso você prefira apenas olhar para o código e descobrir:

insira a descrição da imagem aqui

Nota: optei por clareza em vez de desempenho aqui. Em uma implementação séria, há muitas otimizações que podem ser feitas, a maioria das quais envolve transformações de cache e apenas recalculá-las conforme necessário (por exemplo, armazene em cache transformações locais e globais em cada sprite e recalcule-as somente quando o sprite ou um de seus ancestrais muda). Além disso, as versões das operações matriciais e vetoriais do XNA que recebem valores por referência são um pouco mais rápidas que as que usei aqui.

Mas descreverei o processo com mais detalhes abaixo, então continue lendo para obter mais informações.


Etapa 1 - Faça alguns ajustes na classe Sprite

Supondo que você já tenha um Sprite classe em funcionamento (e você deve), precisará fazer algumas modificações nela. Em particular, você precisará adicionar a lista de sprites filhos, a matriz de transformação local e uma maneira de propagar as transformações na hierarquia do sprite. Eu achei a maneira mais fácil de fazer isso apenas para passá-los como parâmetro ao desenhar. Exemplo:

public class Sprite
{
    public Vector2 Position { get; set; } 
    public float Rotation { get; set; }
    public Vector2 Scale { get; set; }
    public Texture2D Texture { get; set; }

    public List<Sprite> Children { get; }
    public Matrix LocalTransform { get; }
    public void Draw(SpriteBatch spriteBatch, Matrix parentTransform);
}

Etapa 2 - Cálculo da matriz LocalTransform

A LocalTransformmatriz é apenas uma matriz mundial regular construída a partir dos valores de posição, rotação e escala do sprite. Para a origem, assumi o centro do sprite:

public Matrix LocalTransform
{
    get 
    {
        // Transform = -Origin * Scale * Rotation * Translation
        return Matrix.CreateTranslation(-Texture.Width/2f, -Texture.Height/2f, 0f) *
               Matrix.CreateScale(Scale.X, Scale.Y, 1f) *
               Matrix.CreateRotationZ(Rotation) *
               Matrix.CreateTranslation(Position.X, Position.Y, 0f);
   }
}

Etapa 3 - Saber como passar uma Matrix para o SpriteBatch

Um problema com a SpriteBatchclasse é que seu Drawmétodo não sabe como obter uma matriz mundial diretamente. Aqui está um método auxiliar para solucionar esse problema:

public static void DecomposeMatrix(ref Matrix matrix, out Vector2 position, out float rotation, out Vector2 scale)
{
    Vector3 position3, scale3;
    Quaternion rotationQ;
    matrix.Decompose(out scale3, out rotationQ, out position3);
    Vector2 direction = Vector2.Transform(Vector2.UnitX, rotationQ);
    rotation = (float) Math.Atan2(direction.Y, direction.X);
    position = new Vector2(position3.X, position3.Y);
    scale = new Vector2(scale3.X, scale3.Y);
}

Etapa 4 - Renderizando o Sprite

Nota: O Drawmétodo usa a transformação global do pai como parâmetro. Existem outras maneiras de propagar essas informações, mas achei fácil de usar.

  1. Calcule a transformação global multiplicando a transformação local pela transformação global do pai.
  2. Adapte a transformação global SpriteBatche renderize o sprite atual.
  3. Renderize todos os filhos passando a transformação global atual como parâmetro.

Ao traduzir isso em código, você terá algo como:

public void Draw(SpriteBatch spriteBatch, Matrix parentTransform)
{
    // Calculate global transform
    Matrix globalTransform = LocalTransform * parentTransform;

    // Get values from GlobalTransform for SpriteBatch and render sprite
    Vector2 position, scale;
    float rotation;
    DecomposeMatrix(ref globalTransform, out position, out rotation, out scale);
    spriteBatch.Draw(Texture, position, null, Color.White, rotation, Vector2.Zero, scale, SpriteEffects.None, 0.0f);

    // Draw Children
    Children.ForEach(c => c.Draw(spriteBatch, globalTransform));
}

Ao desenhar o sprite raiz, não há transformação pai, então você a passa Matrix.Identity. Você pode criar uma sobrecarga para ajudar neste caso:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }
David Gouveia
fonte
Estou com um problema no qual estou lidando atualmente. Na minha configuração atual, crio a transformação Matrix ao chamar spriteBatch, porque estou usando a câmera. Portanto, caso minha câmera esteja com 500,50 e o sprite do meu jogador com 500,50, o jogador deve estar no centro. No entanto, neste caso, não está atraindo nenhum lugar
Martin.
@ Martin Se você usa ou não uma matriz de câmera, ela deve funcionar da mesma forma. Neste exemplo, eu não usei uma câmera, mas no meu mecanismo eu uso uma e ela funciona normalmente. Você está passando a matriz da câmera (e somente a matriz da câmera) para o SpriteBatch.Begin? E se você deseja que as coisas fiquem centralizadas, está levando em consideração metade do tamanho da tela ao criar a matriz da câmera?
David Gouveia
Estou usando a mesma classe que aqui para câmera, obtendo sua metrix de câmera, sim. Agora funciona, vou tentar ajustá-lo agora e informá-lo
Martin.
11
PERFEITO! Incrível PERFEITO! E como na maioria dos casos tive problemas, a culpa foi minha. Dê uma olhada ! i.imgur.com/2CDRP.png
Martin.
Eu sei que estou atrasado para a festa aqui, já que essa pergunta é muito antiga. Mas enquanto isso funciona, ele bagunça as posições quando as escalas x e y não são as mesmas: /
Erik Skoglund
2

Você deve ser capaz de agrupá-los em um SpriteBatch e movê-los no lugar e girá-los usando uma matriz.

var matrix = 
    Matrix.CreateRotationZ(radians) *
    Matrix.CreateTranslation(new Vector3(x, y, 0));

SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, matrix);
SpriteBatch.Draw(player, Vector2.Zero, null, Color.White);
SpriteBatch.Draw(gun, Vector2(handDistance, 0), null, Color.White);
SpriteBatch.End();

Códigos não testados e minha multiplicação de matrizes é muito enferrujada, mas a técnica é verdadeira.

ClassicThunder
fonte
O que fazer caso eu realmente use Matrix no spriteBatch para câmera? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Martin.
Eu acho que você deve poder usar Begin with matrix * cam.get_transformation (graphics.GraphicsDevice).
ClassicThunder
PERFEITO. Eu não respondi tão bem tão rápido.
Martin.
Solução simples, mas meio que derrota todo o objetivo do lote de sprites, que é desenhar o maior número possível de sprites em uma chamada. Eu poderia usar isso em uma demonstração simples, mas definitivamente não em um jogo intensivo de sprites.
David Gouveia
@ Martin Na minha resposta, tenho um exemplo de como criar uma matriz mundial completa para o sprite. Basicamente, a ordem deve ser -Origin * Scale * Rotation * Translation(de onde todos esses valores vêm do sprite).
David Gouveia
0

Eu implementaria isso de maneira um pouco diferente.

Dê à sua classe de sprite uma lista para seus filhos. Então, quando você atualizar a posição e as transformações do sprite, aplique as mesmas traduções aos filhos com um loop for-each. Os sprites filhos devem ter suas coordenadas definidas no espaço do modelo em vez do espaço do mundo.

Eu gosto de usar duas rotações - uma em torno da origem da criança (para girar o sprite no lugar) e outra em torno da origem do pai (para fazer o filho orbitar essencialmente o pai).

Para desenhar, basta iniciar seu spritebatch, chame seu player.draw (), que desenharia depois percorreria todos os seus filhos e os desenharia também.

Com esse tipo de hierarquia, quando você move o pai, todos os filhos se movem com ele - permitindo também que você mova os filhos independentemente.

Suds
fonte