Вторник, 19 Марта 2024, 07:40

Приветствую Вас Гость

Меню сайта
Категории каталога
Создание игр [354]
Статьи об общих понятиях связанных с созданием игр.
Программирование [82]
Гайды по программированию на разных ЯП.
Движки и Гейммейкеры [144]
Статьи о программах для создания игр, уроки и описания.
Софт [39]
Различные программы, в том числе в помощь игроделам.
2D-графика [14]
Уроки по рисованию, растр, пиксель-арт, создание спрайтов и пр.
3D-графика [16]
Уроки по моделированию, ландшафт, модели, текстурирование и пр.
Моддинг игр [5]
Модификация компьютерных игр, создание дополнений, перевод, хакинг.
Игры [160]
Статьи об играх, в том числе и сделанных на гейммейкерах.
Разное [128]
Статьи, которые не вошли в определённые разделы.
Наш опрос
Какой ЯП вы знаете?
Всего ответов: 27889
Главная » Статьи » Создание игр

XNA для начинающих: рисование спрайтов, анимация и бег. Часть вторая.


Часть 2. Анимирование спрайта.

Немного теории:

Итак, для начала давайте разберемся, как же создается анимация в XNA GameStudio. Здесь нет ничего сложного. В игру загружается изображение, в котором содержатся все кадры анимации на одинаковом расстоянии друг от друга (это самое важное). Вот как будет выглядеть файл с изображением анимации моего героя:



Т.е. на одном изображении нарисованы все кадры анимации (кадр анимации так же называется фреймом, и в будущем я буду использовать это понятие). Вы спросите, как же проигрывается анимация, если все нарисовано на одном изображении? Очень просто.

Мы создаем прямоугольник, который размерами совпадает с одним фреймом анимации (размеры всех фреймов должны быть одинаковы!). Главная мысль в том, что если прямоугольник выделит первый кадр (фрейм) анимации, то другие кадры будут не видны. Т.е. если прямоугольник станет так:



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

Итак, если Вы разобрались с теорией, то пора переходить к практике (если же нет, можете задать вопрос мне в ЛС, я на все отвечу =) ).

Анимирование спрайта:

Итак, для начала перейдем в класс Sprites и добавим несколько вспомогательных переменных.

После строки

Code
public Vector2 spritePosition;


запишем:

Code
int FrameCount; //Количество всех фреймов в изображении (у нас это 10)
int frame;//какой фрейм нарисован в данный момент
float TimeForFrame;//Сколько времени нужно показывать один фрейм (скорость)
float TotalTime;//сколько времени прошло с показа предыдущего фрейма


Здесь все понятно из комментариев. Единственное, что может вызвать у Вас вопрос, так это последняя переменная. Эта переменная не дает выйти значению «TimeForFrame» за рамки, и если оно все же выйдет, то последняя переменная обновляет фрейм. Например, если у нас один фрейм должен быть показан на экране ровно одну секунду, а уже прошло времени 1 секунда и одна милисекунда, то переменная TotalTime сигнализирует, что пора показывать новый фрейм. И так, продолжим.

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

Code
public Sprites(int speedAnimation)
{
  frame = 0;
  TimeForFrame = (float)1 / speedAnimation;
  TotalTime = 0;
}


Теперь давайте разберем все это по порядку.

Как я уже сказал, в скобках (в параметрах) написана переменная, которая вычесляет скорость анимации из времени, которое выделяется на один фрейм.

Первая строчка считает, какой фрейм сейчас показан на экране. Так анимация еще не начала проигрываться, на экране показан нолевой фрейм (т.е. ничего).

Вторая переменная высчитывает скорость анимации. Это число будет зависеть от введенной Вами скорости.

И последняя переменная так же равна нулю, так как анимация еще не проигрывается.

Теперь давайте добавим новый метод в наш класс. Пусть это будет метод Udate(), с параметром gameTime. Сразу после второго конструктора напишите:

Code
public void Update(GameTime gameTime)
{

}


Вы спросите, зачем же нам gameTime? Очень просто. Как видно из названия, эта переменная отвечает за игровое время. Это единственная известная мне переменная, с помощью которой мы можем раситать время на какое-то действие.
В gameTime хранятся все значения времени нашего игрового проекта. Она считает время вызова метода Update() (класса Game1), время продолжительновти игры и все тому подобное. Так же выводить это время наша переменная может в виде милисекунд, секунд, минут и т.д.
Именно gameTime поможет нам расчитать скорость нашей анимации.

Итак, в первую очередь в методе Update мы расчитаем TotalTime. Для этого сделаем так, что бы при каждом вызове метода Update, эта переменная увеличивалась на определенное количество времени (в нашем случае это время, за которое полностью выполняется метод Update). Для этого пропишем в нашем методе:

Code
TotalTime += (float)gameTime.ElapsedGameTime.TotalSeconds;


Далее проверим уловие, если TotalTime больше TimeForFrame (т.е. время показа фрейма закончилось), мы увеличим фрейм на один (покажем следующий фрейм), а так же уменьшим TotalTime на TimeForFrame, что бы для нового фрейма значение TotalTime было первоначальным. Вот так будет выглядеть наш метод после редактирования:

Code
public void Update(GameTime gameTime)
  {
  FrameCount = spriteTexture.Width / spriteTexture.Height;

  TotalTime += (float)gameTime.ElapsedGameTime.TotalSeconds;

  if (TotalTime > TimeForFrame)
  {
  frame++;

  frame = frame % (FrameCount - 1);

  TotalTime -= TimeForFrame;
  }

  }


Первая строчка этого метода вычисляет количество фреймов в одной текстуре (если помните, у нас на текстуре нарисовано десять фреймов (кадров анимации)). Как я говорил, все фреймы должны быть одинакового размера, и желательно квадратными. Так как мои фреймы квадратные, то высота одного из них, равна его ширине.
Но, высота одного фрейма равна высоте всей текстуры. Короче говоря, из всего вышеизложенного понятно, что высота всей текстуры (spriteTexture.Height)равна ширине и высоте одного фрейма. И, так как фреймы оlинаковые, то можно разделить ширину всей текстуры на ширину одного фрейма. Тем самым мы получим количесто фреймов.

В остальном все понятно все понятно, кроме строчки

Code
frame = frame % (FrameCount - 1);


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

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

Code
public void DrawAnimation(SpriteBatch spriteBatch)
  {

  }


Теперь давайте просчитаем ширину одного фрейма. Впрочем, если кадры анимации квадаратные, то этого можно не делать, но что бы Вам было понятней, все же проститаем. Для этого нужно просто разделить всю ширину текстуры на количество фреймов. Представим, что наша текстура шириной 300 пикселей. И если мы разделим эти 300 пикселей на те 10 фреймов, которые в них нарисованы (а фреймы одинаковы), то получится что ширина каждого фрейма равна 30 пикселей. Но, это я привел не реальные значения нашей текстуры, а выдуманные (на самом деле ширина моей текстуры 960 пикселей, а у Вас она сожет быть любой другой). Короче говоря, такая будет первая строчка нового метода:

Code
int frameWidth = spriteTexture.Width / FrameCount;


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

Code
Rectangle rectanglе = new Rectangle(frameWidth * frame, 0, frameWidth, spriteTexture.Height);



Здесь мы создали прямоугольник rectangle класса Rectangle. В его параметрах мы прописали положение каждого фрейма в текстуре (а точнее, перемещение по текстуре, по координате X, т.е. вбок), перемещение по кординате Y (так как все спрайты нарисованы слева направо, то по координате Y нам перемещаться не надо, т.е. укажем во втором параметре 0), ширину и высоту каждого кадра.

Все что осталось, это нарисовать нашу анимацию. Для этого далее запишем:

Code
spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White);


Как Вы видите, единственное, что добавилось (по отношению к предыдущему вызову метода spriteBatch.Draw) это наш прямоугольник – rectangle.

Все, работу с этим классом мы закончили!

Теперь перейдем к классу Game1 и создадим второй объект класса Sprites. Для этого сразу после строки (в начале класса)

Code
Sprites hero;


Запишем:

Code
Sprites runAnimation;


Этот объект будет отвечать за анимацию бега в нашей игре. Теперь давайте его инициализируем в конструкторе Game1. Сразу после строки

Code
hero = new Sprites();


Запишите:

Code
runAnimation = new Sprites(10);


Цифра 10 в аргументе означает скорость нашей анимации. Здесь Вы можете поэксперементировать и сделать ту скорось, которая подходит Вам.

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

Code
hero.spritePosition = new Vector2(300, 300);


Запишем:

Code
runAnimation.spritePosition = new Vector2(300, 300);


Итак, теперь давайте добавим наш файл с анимацией в папку Content/Textures и назовем его run. Как я уже и говорил, мой файл выглядит так:



Итак, после добавления файла запишем в метод LoadContent такие строки:

Code
runAnimation.LoadContent(Content, "Textures//run");


Тем самым мы загрузили нашу анимацию в игру. Осталось вызвать метод Update (класса Sprites), в котором мы как раз и описывали технологию воспроизведения анимации, и нарисовать нашу анимацию. Для начала в методе Update (класса Game1) перед строкой:

Code
base.Update(gameTime);


Code
runAnimation.Update(gameTime);


Теперь напишите в методе Draw(), после строки

Code
hero.Draw(spriteBatch);


Напишите

Code
runAnimation.DrawAnimation(spriteBatch);


Мы сделали практически все. Но, запускать проект не спешите. Если запустить нашу игру сейчас, то на экране будут нарисованы сразу два изображения: idle и run. Нам нужно сделать так, что бы необходимая картинка (или анимация) вызывалась в необходимое время. Например, пусть наш персонаж бежит при нажатии кнопки «влево» и стоит, если мы не нажимаем ни на какую кнопку. Основы пользовательского ввода описаны в моей первой статье, поэтому сейчас я подробно останавливаться на них не буду. Просто расскажу, что и куда нужно вписать. Для начала перед конструктором класса Game1 запишем

Code
KeyboardState states;


Затем в методе Update (класса Game1):

Code
states = Keyboard.GetState();


И наконец в методе Draw (класса Game1) заменим код

Code
hero.Draw(spriteBatch);
runAnimation.DrawAnimation(spriteBatch);


На

Code
if(states.IsKeyDown(Keys.Left))
{
  runAnimation.DrawAnimation(spriteBatch);
}
else
  hero.Draw(spriteBatch);



Этот код означает: [/i]«Если нажата клавиша влево, то анимация проигрывается. Если клавиша влево не нажата, то анимация останавливается и проигрывается текстура отдыха».

Теперь можете запустить нашу игру. Если Вы все сделали правильно, то сначала увидите нашу предыдущую текстуру отдыха главного героя. Но, если Вы нажмете кнопку влево, тогда герой начнет бежать (правда, пока на месте).

Вот что у меня получилось:



А если я нажму клавишу влево, то проиграется анимация:



Итак, самое сложное мы, считайте, уже сделали. Правда, сейчас наш герой бежит только влево, но в будущем мы это исправим. На этом, пожалуй, пора закончить данную часть статьи.
Категория: Создание игр | Добавил: Stalker_Shooter (06 Августа 2011) | Автор: Максим
Просмотров: 21297 | Комментарии: 7 | Рейтинг: 3.9/7 |
Теги: 2d, C#, программирование, анимация, класс, XNA Game Studio, фрейм, пользовательский ввод, спрайт, кодинг
Дополнительные опции:
Также если вы считаете, что данный материал мог быть интересен и полезен кому-то из ваших друзей, то вы бы могли посоветовать его, отправив сообщение на e-mail друга:

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

Всего комментариев: 7
+1-
6 BasilCat   (30 Ноября 2020 12:58) [Материал]
Такое впечатление что на https://gcup.ru все вымерли...

+0-
5 BasilCat   (27 Ноября 2020 11:43) [Материал]
Извините что код кусками, но у Вас форма не принимает большие тексты.
Game1.cs
Код

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace DrawSprite
{
  public class Game1 : Microsoft.Xna.Framework.Game
  {
  GraphicsDeviceManager graphics;
  SpriteBatch spriteBatch;
  Sprites hero;
  Sprites runAnimation;
  KeyboardState states;
  public Game1()  // Конструктор
  {
  graphics = new GraphicsDeviceManager(this);
  Content.RootDirectory = "Content";
  hero = new Sprites();
  runAnimation = new Sprites(10);
  graphics.PreferredBackBufferHeight = 600; //Ширина экрана
  graphics.PreferredBackBufferWidth = 600; //Высота экрана
  }
  protected override void Initialize()
  {
  hero.spritePosition = new Vector2(300, 300);
  runAnimation.spritePosition = new Vector2(300, 300);
  base.Initialize();
  }
  protected override void LoadContent()
  {
  // Create a new SpriteBatch, which can be used to draw textures.
  spriteBatch = new SpriteBatch(GraphicsDevice);
  hero.LoadContent(Content, "idle");
  runAnimation.LoadContent(Content, "run");
  }
  protected override void UnloadContent()
  {
  }
  protected override void Update(GameTime gameTime)
  {
  if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
  this.Exit();
  states = Keyboard.GetState();
  runAnimation.Update(gameTime);
  base.Update(gameTime);
  }
  protected override void Draw(GameTime gameTime)
  {
  GraphicsDevice.Clear(Color.CornflowerBlue);
  spriteBatch.Begin();
  if (states.IsKeyDown(Keys.Left))
  {
  runAnimation.DrawAnimation(spriteBatch);
  }
  else
  hero.Draw(spriteBatch);
  spriteBatch.End();
  base.Draw(gameTime);
  }
  }
}

+-1-
7 TLT   (01 Декабря 2020 20:26) [Материал]
TLTМог бы отдельный материал опубликовать, а не лепить комментариями...

+1-
4 BasilCat   (27 Ноября 2020 11:30) [Материал]
Ссори. Тексты:
Sprites.cs
Код

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework;

namespace DrawSprite
{
  class Sprites
  {
  public Texture2D spriteTexture;
  public Vector2 spritePosition;
  int FrameCount; //Количество всех фреймов в изображении (у нас это 10)
  int frame;//какой фрейм нарисован в данный момент
  float TimeForFrame;//Сколько времени нужно показывать один фрейм (скорость)
  float TotalTime;//сколько времени прошло с показа предыдущего фрейма
//  private Rectangle? rectangle;

  public Sprites()
  {
  }

  public Sprites(int speedAnimation)
  {
  frame = 0;
  TimeForFrame = (float)1 / speedAnimation;
  TotalTime = 0;
  }

  public void Update(GameTime gameTime)
  {
  FrameCount = spriteTexture.Width / spriteTexture.Height;
  TotalTime += (float)gameTime.ElapsedGameTime.TotalSeconds;
  if (TotalTime > TimeForFrame)
  {
  frame++;

  frame = frame % (FrameCount - 1);

  TotalTime -= TimeForFrame;
  }

  }

  public void DrawAnimation(SpriteBatch spriteBatch)
  {
  int frameWidth = spriteTexture.Width / FrameCount;
  Rectangle rectanglе = new Rectangle(frameWidth * frame, 0, frameWidth, spriteTexture.Height);
  spriteBatch.Draw(spriteTexture, rectanglе, Color.White);
//  spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White);

  }

  public void LoadContent(ContentManager Content, String texture)
  {
  spriteTexture = Content.Load<Texture2D>(texture);
  }
  public void Draw(SpriteBatch spriteBatch)
  {
  spriteBatch.Draw(spriteTexture, spritePosition, Color.White);
  }

  }
}

+1-
3 BasilCat   (27 Ноября 2020 11:21) [Материал]
Позавчера начал изучать C# с надстройкой XNA. Извините если что не так понимаю. spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White); - неверно по формату и выдаёт ошибку - .
Ошибка 1 Элемент "rectangle" не существует в текущем контексте.

Делал вроде как писано у Вас. Исправил на spriteBatch.Draw(spriteTexture, rectanglе, Color.White); заработало, но почему-то бегает вверху от 0,0 и слева направо узкими полосками от кадров. Т.е. бежит кадр, 96х96 разбиты на 10 узких полос с сжатыми по Х изображениеями. Что у меня не так.
кадры содрал у Вас. 96х96 - 10 кадров.

+3-
1 ApostolDK   (09 Июня 2012 21:39) [Материал]
Quote
TotalTime += gameTime.ElapsedGameTime.TotalSeconds;

Ошибка - пишет про преобразования типов. решается
Code
TotalTime += (float)gameTime.ElapsedGameTime.TotalSeconds;


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

Влево он бежит пока:D
Спасибо за статью, очень интересно почитать happy

+1-
2 Stalker_Shooter   (20 Июля 2012 12:13) [Материал]
Stalker_ShooterДа, благодарю за найденные недочеты.

Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск по сайту
10 случ. движков
  • Luxe Engine
  • FIFE
  • LuxRender
  • Fifteen Sliding Puzzle maker
  • NaaLaa
  • EDuke32
  • Silent Walk FPS Creator
  • RuneSword
  • Core
  • MANU
  • Друзья сайта
    Игровой форум GFAQ.ru Перевод консольных игр
    Все права сохранены. GcUp.ru © 2008-2024 Рейтинг