2つ以上のスプライトをグループ化する簡単な方法はありますか?そのため、それらすべてが互いに依存しますか?


8

私はこの質問が非常によく似ていると思い、この 1、私は答えが普遍的なものであるかはわかりません。

だから、私の目標は:

  • 2つのスプライトを固定位置に配置します。たとえば、プレーヤーと彼の目
  • プレーヤーが回転するたびに、目のスプライトも回転し、体から同じ相対位置に到達するようにしてください(目がプレーヤーの後ろにないようにします)。したがって、彼らはグループとして作業します。-このステップは自動化する必要があります。それが私の目標です!

たとえば、今度はユーザーの手に銃を置きたいと思います。だから今私は、そのプレイヤーが配置されVector2(0, 0)、銃が配置されていると言いVector2(26, 16)ます。次に、それらをグループ化したいので、プレイヤーが回転するたびに銃も回転します。

ここに画像の説明を入力してください

現在この例では大丈夫ですが、y軸(のみ)に銃を移動する必要がある場合は、迷っています

回答:


10

概念

私はこの問題を 複合デザインパターンのバリテーションを使用スプライト階層でます。つまり、各スプライトにアタッチされている子スプライトのリストを保存して、親への変更が自動的に反映されるようにします(変換、回転、スケーリングなど)。

私のエンジンでは、次のように実装しています。

  • 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メソッドは、親のグローバル変換をパラメーターとして受け取ります。この情報を伝達する方法は他にもありますが、私はこれが使いやすいことに気づきました。

  1. ローカル変換に親のグローバル変換を掛けて、グローバル変換を計算します。
  2. グローバルトランスフォームをSpriteBatch現在のスプライトに適合させてレンダリングします。
  3. 現在のグローバル変換をパラメーターとして渡すすべての子をレンダリングします。

それをコードに変換すると、次のようになります。

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); }

現在対応している問題があります。現在の設定では、カメラを使用しているため、spriteBatchを呼び出すときにMatrix変換を作成します。カメラが500,50にあり、プレーヤーのスプライトが500,50にある場合、プレーヤーは中央にあるはずです。ただし、この場合、どこにも描画されていません
Martin。

@Martinカメラマトリックスを使用するかどうかに関係なく、同じように機能します。この例では、カメラを使用しませんでしたが、エンジンではカメラを使用しており、通常どおり機能します。カメラマトリックス(およびカメラマトリックスのみ)をSpriteBatch.Beginに渡しますか?また、中央に配置したい場合は、カメラマトリックスを作成するときに画面サイズの半分を考慮していますか?
David Gouveia、2012年

ここでは同じクラスのカメラを使用していますが、カメラメトリックを取得しています。これで動作します

1
パーフェクト!素晴らしいパーフェクト!そして、ほとんどの場合私が問題を抱えていたように、それは私のせいでした。見てください!i.imgur.com/2CDRP.png
マーティン。

この質問は本当に古いので、私はここでのパーティーに遅れていることを知っています。しかし、これは機能しますが、xとyのスケールが同じでない場合、位置がめちゃくちゃになります:/
Erik Skoglund

2

それらをSpriteBatchにグループ化して、それらを所定の位置に移動し、行列を使用して回転できるようにする必要があります。

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();

テストされていないコードと私の行列乗算は非常に錆びていますが、この手法は当てはまります。


カメラのspriteBatchで実際にMatrixを使用する場合はどうすればよいですか? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
マーティン。

Begin with matrix * cam.get_transformation(graphics.GraphicsDevice)を使用できるはずです。
ClassicThunder 2012年

完璧です。私はそんなに素晴らしい答えをそんなに速くはなかった。
マーティン。

簡単な解決策ですが、1回の呼び出しでできるだけ多くのスプライトを描画するという、スプライトのバッチ処理の目的全体を少し無効にします。私はこれを単純なデモで使用するかもしれませんが、スプライト集中型のゲームでは決定的には使用しません。
David Gouveia 2012年

@Martin私の答えには、スプライトの完全なワールドマトリックスを作成する方法の例があります。基本的に順序は次のとおりです-Origin * Scale * Rotation * Translation(これらの値はすべてスプライトから取得されます)。
David Gouveia 2012年

0

これは少し違った方法で実装します。

スプライトクラスにその子のリストを与えます。次に、スプ​​ライトの位置と変換を更新するときに、同じ変換をfor-eachループで子に適用します。子スプライトの座標は、ワールド空間ではなくモデル空間で定義する必要があります。

私は2つの回転を使用したいと思います。1つは子の原点の周り(スプライトを所定の位置にスピンさせるため)と、もう1つは親の原点の周り(子供が本質的に親を周回させるため)です。

描画するには、スプライトバッチを開始し、player.draw()を呼び出します。これにより、すべての子がループされ、同様に描画されます。

この種の階層では、親を移動すると、すべての子も一緒に移動します。また、子を個別に移動することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.