Анимация спрайтов

Последнее обновление: 14.11.2015

Нередко для отрисовки спрайтов используются целые спрайт-листы, которые представляют набор изображений в рамках одного спрайта. Плавное перемещение подобных изображений рождает иллюзию движения. Например, у нас есть следующий спрайт:

Данный спрайт представляет набор связанных изображений или фреймов. Если мы быстро прокрутим эти фреймы, то у нас возникнет ощущение бегущего человечка. Подобные спрайт-листы позволяют зафиксировать состояние игрового персонажа и затем выводить его в зависимости от условий. Что гораздо удобнее применения статичных спрайтов с одним изображением.

Загрузим этот спрайт в проект и попробуем его анимировать. Приложение обновляет свое состояние 60 раз в секунду, то есть чтобы анимировать спрайт и перемещаться по его изображениям, нам надо изменять текущие координаты отрисовки части спрайта.

Определим следующий код игры:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        Vector2 position = Vector2.Zero;

        int frameWidth = 108;
        int frameHeight = 140;
        Point currentFrame = new Point(0, 0);
        Point spriteSize = new Point(8, 2);

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            TargetElapsedTime = new System.TimeSpan(0, 0, 0, 0, 400);
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = Content.Load<Texture2D>("scottpilgrim_multiple");
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            ++currentFrame.X;
            if (currentFrame.X >= spriteSize.X)
            {
                currentFrame.X = 0;
                ++currentFrame.Y;
                if (currentFrame.Y >= spriteSize.Y)
                    currentFrame.Y = 0;
            }

            base.Update(gameTime);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);

            spriteBatch.Draw(texture, position,
                new Rectangle(currentFrame.X * frameWidth,
                    currentFrame.Y * frameHeight,
                    frameWidth, frameHeight),
                Color.White, 0, Vector2.Zero,
                1, SpriteEffects.None, 0);

            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}

Ключевыми для отрисовки нужного фрейма из спрайта являются следующие переменные:

int frameWidth = 108;
int frameHeight = 140;
Point currentFrame = new Point(0, 0);
Point spriteSize = new Point(8, 2);

Переменные frameWidth и frameHeight указывают соответственно на ширину и высоту фрейма, то есть отдельного изображения на спрайте. Переменная currentFrame, представляющая структуру Point, задает положение текущего фрейма. По умолчанию мы устанавливаем значение new Point(0, 0), то есть самый первый фрейм на спрайте. Размер спрайта задается через переменную spriteSize. Ее значение new Point(8, 2) указывает, что на спрайте 8 фреймов по ширине и 2 фрейма по высоте.

В методе Update с каждым новым оборотом игрового цикла переменная, указывающая на текущий фрейм, будет увеличивать свое значение, пока не достигнет предельных значений:

++currentFrame.X;
if (currentFrame.X >= spriteSize.X)
{
    currentFrame.X = 0;
    ++currentFrame.Y;
    if (currentFrame.Y >= spriteSize.Y)
        currentFrame.Y = 0;
}

После этого происходит отрисовка спрайта с помощью перегрузки метода spriteBatch.Draw():

spriteBatch.Draw(texture, position,
    new Rectangle(currentFrame.X * frameWidth,
        currentFrame.Y * frameHeight,
        frameWidth, frameHeight),
    Color.White, 0, Vector2.Zero,
	1, SpriteEffects.None, 0);

При запуске приложения начнется анимация фреймов в спрайт-листов. Однако мы можем столкнуться, что анимация работает слишком быстро. В этом случае мы можем установить явным образом частоту смены кадров. Для этого добавим в конструктор класса Game1 следующую строку:

TargetElapsedTime = new System.TimeSpan(0, 0, 0, 0, 50);

Свойство TargetElapsedTime задает частоту смены кадров. В данном случае в качестве времени игрового цикла устанавливается 50 миллисекунд. То есть кадры будут меняться с частотой в 1000 / 50 = 20 раз в секунду, что меньше стандартного значения (60 раз в секунду).

При задании частоты кадров в игре нам надо учитывать, что если частота будет слишком высокой, то процессор может не успевать обновлять состояние игры и производить перерисовку. Если же, наоборот, сделать небольшую частоту, то игра будет нединамичной, так как обновления будут производиться очень медленно.

Однако данный метод установки скорости анимации имеет один недостаток: если у нас в приложении используются несколько спрайтов, которые должны обновляться с разной частотой, то установка одной и той же частоты глобально будет не лучшим решением.

Поэтому удалим из конструктора строку:

TargetElapsedTime = new System.TimeSpan(0, 0, 0, 0, 50);

И добавим в класс Game1 две глобальные переменные:

int currentTime = 0; // сколько времени прошло
int period = 50; // период обновления в миллисекундах

И после этого изменим метод Update:

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
        Exit();
    // добавляем к текущему времени прошедшее время
	currentTime += gameTime.ElapsedGameTime.Milliseconds;
	// если текущее время превышает период обновления спрайта
    if (currentTime > period)
    {
        currentTime -= period; // вычитаем из текущего времени период обновления
        ++currentFrame.X; // переходим к следующему фрейму в спрайте
        if (currentFrame.X >= spriteSize.X)
        {
            currentFrame.X = 0;
            ++currentFrame.Y;
            if (currentFrame.Y >= spriteSize.Y)
                currentFrame.Y = 0;
        }
    }
    base.Update(gameTime);
}

С помощью свойства gameTime.ElapsedGameTime.Milliseconds мы можем получить время игрового цикла, по умолчанию это примерно 1000/60 = 16 миллисекунд. И затем это время добавляем переменной currentTime, которое содержит время, прошедшее после последнего обновления фреймов. Если это время окажется больше интервала обновления фреймов, то переходим к новому фрейму.

Но имитация человечка, бегущего на месте, не очень выразительна. Объединим анимацию фрейма с перемещением спрайта по экрану. Полный код программы:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace Game1
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        Vector2 position = Vector2.Zero;
        float speed = 5f;

        int currentTime = 0; // сколько времени прошло
        int period = 50; // частота обновления в миллисекундах
        
        int frameWidth = 108;
        int frameHeight = 140;
        Point currentFrame = new Point(0, 0);
        Point spriteSize = new Point(8, 2);

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            texture = Content.Load<Texture2D>("scottpilgrim_multiple");
        }

        protected override void UnloadContent()
        {
            
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            currentTime += gameTime.ElapsedGameTime.Milliseconds;
            if (currentTime > period)
            {
                currentTime -= period;

                position.X += speed;
                if (position.X > Window.ClientBounds.Width - frameWidth || position.X < 0)
                {
                    speed *= -1;
                    ++currentFrame.Y;
                    if (currentFrame.Y >= spriteSize.Y)
                        currentFrame.Y = 0;
                }

                ++currentFrame.X;
                if (currentFrame.X >= spriteSize.X)
                {
                    currentFrame.X = 0;
                }
            }

            base.Update(gameTime);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);

            spriteBatch.Draw(texture, position,
                new Rectangle(currentFrame.X * frameWidth,
                    currentFrame.Y * frameHeight,
                    frameWidth, frameHeight),
                Color.White, 0, Vector2.Zero,
                1, SpriteEffects.None, 0);

            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850