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
Sprite
um armazena List<Sprite> Children
e fornece um método para adicionar novos filhos.
- Cada um
Sprite
sabe como calcular um Matrix LocalTransform
que é definido em relação ao pai.
- A chamada
Draw
de um Sprite
també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:
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 LocalTransform
matriz é 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 SpriteBatch
classe é que seu Draw
mé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 Draw
mé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.
- Calcule a transformação global multiplicando a transformação local pela transformação global do pai.
- Adapte a transformação global
SpriteBatch
e renderize o sprite atual.
- 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); }
Você deve ser capaz de agrupá-los em um SpriteBatch e movê-los no lugar e girá-los usando uma matriz.
Códigos não testados e minha multiplicação de matrizes é muito enferrujada, mas a técnica é verdadeira.
fonte
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
-Origin * Scale * Rotation * Translation
(de onde todos esses valores vêm do sprite).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.
fonte