IrrlichtСтатьи

Простой спрайт для Irrlicht

Автор:

Простая реализация спрайта для Irrlicht. В примере используется только 2d графика.
Спрайт способен плавно перемещаться и изменять свою картинку.
Загрузить

Пример показывает как создать спрайт для Irrlicht.
Пример использует библиотеку irrlicht 1.4.1 поставляемую в исходных кодах.
Нажатие мышки на экране приводит к перемещению спрайта в точку нажатия мышки.
На экране отображается 3 спрайта: неподвижный, подвижный и управляемый мышкой.
Для работы примера, надо скопировать файлы из папки Docs, так как написано в readme.txt

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

typedef std::vector < rect <s32> > FrameRects;
class SpriteData
{
  public:
  ITexture*    texture;  // Указатель на текстуру с картинками спрайтов
  FrameRects*    frames;    // Указатель на список координат картинок спрайта в текстуре
  u32        hight;    // Высота кадра спрайта  в пикселях  
  u32        width;    // Ширина кадра спрайта в пикселях
  u32        period;     // Скорость смены кадров
  SColor      color;    // Цвет подкрашивания
};

frames - это список, каждый элемент которого содержит координаты верхнего левого и нижнего правого углов картинки со спрайтом

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

class ActiveFrames
{
  public:
  u32      frameBegin;    // Первый активный кадр 
  u32      frameCount;    // Количество активных кадров
  ActiveFrames (u32 begin = 0, u32 count = 1)
  {
    frameBegin = begin;
    frameCount = count;
  }
};

Рассмотрим основные члены базового класса Sprite
Базовый класс спрайта содержит поля с указателями на экземпляры классов SpriteData и ActiveFrames, что даёт возможность создавать огромное количество похожих спрайтов, не тратя память на повторяющиеся данные (кроме самих указателей):

protected:
SpriteData*    spriteData;    // Данные для рисования спрайта
ActiveFrames*  activeFrames;  // Активные кадры
position2d<s32> position;    // Координаты левого верхнего угла

Каждый экземпляр класса содержит своё поле position, т.к. это поле хранит положение спрайта на экране.

В соответствии с постулатами ООП, спрайт должен рисовать себя сам:

virtual void draw (IVideoDriver* driver, u32 time = 0)
{
  driver->draw2DImage (spriteData->texture, getPosition (time), getFrame (time)
                       ,0 ,getColor (),true);
}

Типы аргументов draw2DImage определяют, типы данных возвращаемых методами getPosition и getFrame:

virtual position2d < s32 > getPosition (u32 time = 0)
{
  return position;
}
virtual irr::core::rect < s32 > getFrame (u32 time = 0)
{
    return spriteData->frames->at(((time / this->spriteData->period) % activeFrames->frameCount) + activeFrames->frameBegin);
}

В методе getFrame, создаётся анимация, т.е. метод возвращает координаты прямоугольника кадра который надо нарисовать в данный момент. Номер текущего кадра вычисляется исходя из текущего тик-времени, передаваемого в метод getFrame. Таким образом, кадры будут сменяться c постоянной скоростью, не зависящей от FPS.

Теперь нам надо научить спрайт двигаться равномерно, с заданной скоростью. Создадим класс наследник MovebleSprite от базового класса Sprite:

class MovebleSprite : public Sprite
{/*...*/};

Рассмотрим его основные члены

irr::f32 speed;      // Скорость движения пиксель/мс
position2d<s32> targetPosition;  // Целевая позиция

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

// Возвращает текущую позицию на экране
// u32 time - текущее тик-время
virtual position2d<s32> getPosition (u32 time)
{
  // Проверить достигнута ли целевая позиция
  if (position == targetPosition)
  {
    // целевая позиция достигнута, спрайт не движется
    speedX = 0;
    speedY = 0;
    return position;
  }
  
  // Целевая позиция не достигнута, вычислить необходимое перемещение в пикселях
  // исходя из прошедшего времени и скорости
  irr::f32 deltaTime = (irr::f32) (time - prevTime);  // Время, затраченное на перемещение
  irr::f32 deltaX = speedX * deltaTime;        // Перемещение по оси X
  irr::f32 deltaY = speedY * deltaTime;        // Перемещение по оси Y
  
  // переместить точную текущую точную позицию на соотв. перемещение по осям X и Y
  curPosition.X += deltaX;
  curPosition.Y += deltaY;
  
  // присвоить текущей позиции округлённую точную текущую позицию
  position.X = (s32) (curPosition.X + 0.5); // + 0.5 - для округления до ближайшего целого

  position.Y = (s32) (curPosition.Y + 0.5);

  // Проверить на "перелёт". Если "перелёт", значит целевая позиция достигнута,
  // значит надо завершить движение, а текущую позицию сделать равной целевой.
  // "перелёт" возникнет, если знак разности целевой и текущей позиций изменится
  if (((targetPosition.X - position.X) > 0) != sigX)
  {
    speedX = 0;
    position.X = targetPosition.X;
  }
  if (((targetPosition.Y - position.Y) > 0) != sigY)
  {
    speedY = 0;
    position.Y = targetPosition.Y;
  }
    // Записать текущее время
  prevTime = time;
  
  // Вернуть текущую позицию
  return position;
}

Вычисление текущей позиции, происходит исходя из скорости, и времени потраченного на перемещение. Скорость вычисляется отдельно для осей X и Y, из скорости движения спрайта speed, и расстояния которое надо преодолеть по осям X и Y. Вычисления объясняются на рисунке 1.
пояснение к вычислениям | Простой спрайт для Irrlicht
    Скорости движения по осям, вычисляются один раз в методе moveToPosition:

void moveToPosition(position2d<s32> _position, u32 _startTime)
{
  prevTime = _startTime;                // сохранить текущее время
  targetPosition = _position;              // сохранить целевую позицию
  curPosition.X = (irr::f32)position.X;        // установить точную текущую позицию X
  curPosition.Y = (irr::f32)position.Y;        // установить точную текущую позицию Y
  distX = (irr::f32) (targetPosition.X - position.X); // расстояние перемещения по оси X
  distY = (irr::f32) (targetPosition.Y - position.Y); // расстояние перемещения по оси Y
  irr::f32 dist = sqrt(distX * distX + distY * distY);// расстояние от текущей точки до целевой
  speedX = speed * (distX / dist);          // скорость перемещения по оси X пиксель/мс
  speedY = speed * (distY / dist);          // скорость перемещения по оси Y пиксель/мс
  sigX = distX > 0;                  // сохранить знак разности позиций X
  sigY = distY > 0;                  // сохранить знак разности позиций Y
}

    Поле curPosition имеет тип irr:f32 (float) и используется для хранения точной текущей позиции, поскольку position2d<s32> имеет целый формат. Шаг с которым происходит изменение текущей позиции, не всегда приведёт к целевой позиции, для чего была введена проверка на "перелёт",которая заключается в проверке знака разности текущей и целевой позиции. Если этот знак изменился значит произошёл "перелёт".
   
    Хранить точную текущую позицию в отдельном поле нужно для того, что перемещение и скорость могут быть меньше единицы. Перед тем как вернуть результат, метод getPosition заносит округлённое значение curPosition в position. Для обеспечения необходимой точности не обязательно использовать float, т.к. диапазона значений unsigned int достаточно для отображения и вычисления скорости в пределах экрана, но это вопрос оптимизации и он не входит в рамки этой статьи.
 
    Теперь для того чтобы переместить спрайт в нужную позицию, надо вызвать метод moveToPosition, и спрайт сам будет двигаться в заданном направлении. Переопределив метод getPosition(), можно задавать различные траектории движения спрайта, сделав их функцией времени и расстояния.


Правка:
1. Готовлю дополнения.
2. Дополнения.
3. Дополнения завершены.
4. Код примера в архиве изменён.
5. Статья завешена
6. Ссылка с архивом исходных кодов примера обновлена

26 июля 2008 (Обновление: 13 ноя 2010)

Комментарии [4]