2D KeyFrame Animation für XNA

Abgelegt unter: Game Design, Programmierung am: 8. März 2009 | Schlagwörter:, , | Kommentare (2)

Im Rahmen eines 2D Projekt bei mir auf der Uni schreiben wir zurzeit ein 2D Spiel, da ich meist der Meinung bin ich mach alles selber werkele ich deswegen an einem Editor für 2D Games rum.

Der Plan ist, 2D Sprites zu animieren, z. Bsp. Für Menüs oder Hintergründe in den Spielen und so weiter, dafür eignen sich so gennanten KeyFramed Animations bzw. Inbetween Animations

Hier ist das Modell meines Ansatzes.


Die IKeyFrameable Schnittstelle setzt einige Eigenschaften voraus mit den die TimeLine die Objekte z. Bsp. Sprites verändern kann.

public interface IKeyFrameable
{
    Vector2 Position { get; set; }
    Vector2 Origin { get; set; }
    Vector2 Scale { get; set; }
    float Rotation { set; }
    byte AlphaValue { set; }
}

Ein TimeLineItem enthält eine Liste von Kurven korrespondierend zu einer der Eigenschaften des Objekts (IKeyFrameable), diese wird mit Hilfe des KeyValueType Enums und einem Dictionary bewerkstelligt.

public class TimeLineItem
{
    public TimeLineItem()
    {
        this.KeyValues = new Dictionary<KeyValueType, Curve>();
    }
 
    public IKeyFrameable KeyFrameable;
    public Dictionary<KeyValueType, Curve> KeyValues;
}
public enum KeyValueType
{
    PositionX,
    PositionY,
    ScaleX,
    ScaleY,
    OriginX,
    OriginY,
    AlphaValue,
    Rotation
}

Nun das Herz von dem ganzen die TimeLine Klasse, hier kann man einstellen wie lang die TimeLine ist also Frames und wie lange ein Frame ist . In der Update Methode rennt die TimeLine über alle TimeLineItems und berechnet den aktuellen Wert der jeweiligen Eigenschaft des IKeyFrameable Objects anhand der zugewiesenen Kurve und weist diesen zu.

public class TimeLine
{
    #region Private Fields
 
    private double _intervalBuffer;
    private TimeSpan _interval;
    private Int32 _framesCount;
    private Int32 _currentFrameIndex;
    private bool _active;
    private List<TimeLineItem> _items;
 
    #endregion
 
    public TimeLine(TimeSpan interval, Int32 framesCount)
    {
        this._interval = interval;
        this._framesCount = framesCount;
        this._active = true;
        this._items = new List<TimeLineItem>();
    }
 
    #region Public Properties
 
    public List<TimeLineItem> Items
    {
        get { return this._items; }
    }
 
    public TimeSpan UpdateInterval
    {
        get { return this._interval; }
        set { this._interval = value; }
    }
 
    public Int32 FramesCount
    {
        get { return this._framesCount; }
        set { this._framesCount = value; }
    }
 
    public int CurrentFrameIndex
    {
        get { return this._currentFrameIndex; }
        set { this._currentFrameIndex = value; }
    }
 
    #endregion
 
    // TODO: Speed up optimization
    public void Update(GameTime gameTime)
    {
        if (!this._active) return;
 
        this._intervalBuffer += gameTime.ElapsedGameTime.TotalMilliseconds;
 
        if (this._intervalBuffer >= this._interval.TotalMilliseconds)
        {
            int itemLength = this._items.Count;
            for (int j = 0; j < itemLength; j++)
            {
                TimeLineItem item = _items[j];
                float time = (float)this._currentFrameIndex;
 
                #region Set Alpha Value
 
                if (item.KeyValues.ContainsKey(KeyValueType.AlphaValue))
                    item.KeyFrameable.AlphaValue = 
                        (byte)item.KeyValues[KeyValueType.AlphaValue].
                            Evaluate(time);
 
                #endregion
 
                #region Set Rotation
 
                if (item.KeyValues.ContainsKey(KeyValueType.Rotation))
                    item.KeyFrameable.Rotation = 
                        item.KeyValues[KeyValueType.Rotation].Evaluate(time);
 
                #endregion
 
                #region Set Position
 
                Vector2 newPosition = item.KeyFrameable.Position;
 
                if (item.KeyValues.ContainsKey(KeyValueType.PositionX))
                    newPosition.X = 
                        item.KeyValues[KeyValueType.PositionX].Evaluate(time);
 
                if (item.KeyValues.ContainsKey(KeyValueType.PositionY))
                    newPosition.Y = 
                        item.KeyValues[KeyValueType.PositionY].Evaluate(time);
 
                item.KeyFrameable.Position = newPosition;
 
                #endregion
 
                #region Set Origin
 
                Vector2 newOrigin = item.KeyFrameable.Origin;
 
                if (item.KeyValues.ContainsKey(KeyValueType.OriginX))
                    newOrigin.X = 
                        item.KeyValues[KeyValueType.OriginX].Evaluate(time);
 
                if (item.KeyValues.ContainsKey(KeyValueType.OriginY))
                    newOrigin.Y = 
                        item.KeyValues[KeyValueType.OriginY].Evaluate(time);
 
                item.KeyFrameable.Origin = newOrigin;
 
                #endregion
 
                #region Set Scale
 
                Vector2 newScale = item.KeyFrameable.Scale;
 
                if (item.KeyValues.ContainsKey(KeyValueType.ScaleX))
                    newScale.X = 
                        item.KeyValues[KeyValueType.ScaleX].Evaluate(time);
 
                if (item.KeyValues.ContainsKey(KeyValueType.ScaleY))
                    newScale.Y = 
                        item.KeyValues[KeyValueType.ScaleY].Evaluate(time);
 
                item.KeyFrameable.Scale = newScale;
 
                #endregion
            }
 
            #region Update Current KeyFrame Position
 
            if (this._currentFrameIndex == this._framesCount - 1)
                this._active = false;
            else
                this._currentFrameIndex++;
 
            this._intervalBuffer = 0;
 
            #endregion
        }
    }
}

Nun kann man das ganze auch benutzen. Als erstes braucht man eine TimeLine Instanz und einige IKeyFrameable Instanzen sei es das oben genannte Sprite oder irgendetwas anderes was die IkeyFrameable Schnittstelle implementiert.

private Sprite[] _sprites;
private TimeLine _timeLine;

In der LoadContent Methode lädt man die einzelnen Sprites und konfiguriert die TimeLine, später wird das Konfigurieren der TimeLine ein Editor übernehmen und das ganze als XML exportieren welches dann als Content geladen werden kann.

this._sprites = new Sprite[1];
this._sprites[0] = new Sprite();
this._sprites[0] .Texture = this.Content.Load<Texture2D>(@"bitmap");
this._sprites[0] .Position = new Vector2(100, 250);
 
this._timeLine = new TimeLine(new TimeSpan(0, 0, 0, 0, 33), 600);
 
TimeLineItem itemA = new TimeLineItem();
itemA.KeyFrameable = this._sprites[0];
 
Curve rotationValue = new Curve();
rotationValue.Keys.Add(new CurveKey(200, 0));
rotationValue.Keys.Add(new CurveKey(400, 120));
itemA.KeyValues.Add(KeyValueType.Rotation, rotationValue);
 
Curve positionX = new Curve();
positionX.Keys.Add(new CurveKey(0, 100));
positionX.Keys.Add(new CurveKey(150, 400));
 
itemA.KeyValues.Add(KeyValueType.PositionX, positionX);
 
Curve positionY = new Curve();
positionY.Keys.Add(new CurveKey(0, 100));
positionY.Keys.Add(new CurveKey(600, 200));
itemA.KeyValues.Add(KeyValueType.PositionY, positionY);
 
Curve scaleX = new Curve();
scaleX.Keys.Add(new CurveKey(0, 1));
scaleX.Keys.Add(new CurveKey(200, 2));
scaleX.Keys.Add(new CurveKey(300, 1));
scaleX.Keys.Add(new CurveKey(400, 0.5f));
scaleX.Keys.Add(new CurveKey(500, 1));
itemA.KeyValues.Add(KeyValueType.ScaleX, scaleX);
 
this._timeLine.Items.Add(itemA);

Die TimeLine muss dann in der Update Methode upgedatet werden.

this._timeLine.Update(gameTime);

Und zum Schluss zeichnet man ganz normal wie vorher seine Sprites.

this._spriteBatch.Begin();
int length = this._sprites.Length;
for (int i = 0; i < length; i++)
{
    Sprite sprite = this._sprites[i];
    this._spriteBatch.Draw(sprite.Texture, 
        new Rectangle(
            (int)sprite.Position.X,
            (int)sprite.Position.Y,
            (int)(sprite.Texture.Width * sprite.Scale.X),
            (int)(sprite.Texture.Height * sprite.Scale.Y)),
        null,
        Color.White,
        sprite.Rotation, 
        new Vector2(
            (float)(sprite.Texture.Width / 2),
            (float)(sprite.Texture.Height / 2)),
        SpriteEffects.None, 0); 
}
 
this._spriteBatch.End();

Soo, damit lassen sich ganz einfach schicke Animationen gestalten. :D

2 Kommentare »

  1. Thanks for the tip, works great! I’ll make some tweaks like animation loops, thanks a lot!

    Kommentar von Niko — 8. April 2009 @ %H:%M

  2. Ich bemerke gerade in diesem Moment, dass ich diesen Blog wesentlich ofter aufrufen sollte – da komme ich wirklich auf Ideen

    Kommentar von Geld — 27. März 2011 @ %H:%M

RSS-Feed für Kommentare zu diesem Artikel. TrackBack-URL

Einen Kommentar hinterlassen