概念
私はこの問題を 、複合デザインパターンのバリテーションを使用スプライト階層でます。つまり、各スプライトにアタッチされている子スプライトのリストを保存して、親への変更が自動的に反映されるようにします(変換、回転、スケーリングなど)。
私のエンジンでは、次のように実装しています。
- 各
Sprite
格納し、List<Sprite> Children
新しい子を追加するメソッドを提供します。
- それぞれが定義され
Sprite
ているを計算する方法を知っていますMatrix LocalTransform
、親に対して相対的に。
- 呼び出す
Draw
にすることSprite
も、そのすべての子にそれを呼び出します。
- 子供は、自分のローカル変換と親のグローバル変換を掛け合わせます。結果は、レンダリング時に使用するものです。
これにより、コードに他の変更を加えることなく、要求したことを実行できます。次に例を示します。
Sprite tank = new Sprite(tankTexture);
tank.Children.Add(new Sprite(turretTexture) {Position = new Vector2(26, 16) });
spriteBatch.Begin();
tank.Draw(spriteBatch);
spriteBatch.End();
実装
まず、コードを見てそれを理解するだけの場合は、この手法を実装したサンプルプロジェクトをドロップします。
注:ここでは、パフォーマンスではなく明確にすることを選択しました。深刻な実装では、実行できる多くの最適化があり、そのほとんどは、変換をキャッシュして必要に応じて再計算するだけです(たとえば、各スプライトでローカル変換とグローバル変換の両方をキャッシュし、スプライトまたは祖先の1つが変更されます)。また、参照によって値を取得するXNAの行列演算とベクトル演算のバージョンは、ここで使用したものよりも少し高速です。
ただし、このプロセスについては後で詳しく説明します。詳細については、次をお読みください。
ステップ1-Spriteクラスにいくつかの調整を加える
すでにSprite
クラスが配置されている(そしてそうする必要がある)と仮定すると、クラスにいくつかの変更を加える必要があります。特に、子スプライトのリスト、ローカル変換行列、および変換をスプライト階層に伝搬する方法を追加する必要があります。私はそれを描画するときにパラメーターとして渡すだけでそれを行う最も簡単な方法を見つけました。例:
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);
}
ステップ2-LocalTransform行列の計算
LocalTransform
行列は、スプライトの位置、回転、スケール値から構築された普通の世界行列です。原点については、スプライトの中心を想定しました。
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);
}
}
ステップ3-マトリックスをSpriteBatchに渡す方法を知る
SpriteBatch
クラスの1つの問題は、そのDraw
メソッドがワールドマトリックスを直接取得する方法を認識していないことです。この問題を解決するヘルパーメソッドを次に示します。
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);
}
ステップ4-スプライトのレンダリング
注:このDraw
メソッドは、親のグローバル変換をパラメーターとして受け取ります。この情報を伝達する方法は他にもありますが、私はこれが使いやすいことに気づきました。
- ローカル変換に親のグローバル変換を掛けて、グローバル変換を計算します。
- グローバルトランスフォームを
SpriteBatch
現在のスプライトに適合させてレンダリングします。
- 現在のグローバル変換をパラメーターとして渡すすべての子をレンダリングします。
それをコードに変換すると、次のようになります。
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));
}
ルートスプライトを描画する場合、親変換はないため、それを渡しますMatrix.Identity
。この場合に役立つオーバーロードを作成できます。
public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }