среда, 3 октября 2012 г.

5. Анимируем и композируем

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

#!/usr/bin/env mkb
...
includepath
{
    ...
    ./source/Animate
}
files
{
    ...
    [Animate]
    (source/Animate)
    IAnimatedSprite.h
    AnimatedSprite.cpp
    AnimatedSprite.h
    CompositeSprite.cpp
    CompositeSprite.h
    ...
}
...
Далее, создадим новый интерфейс для анимированных спрайтов. Он будет содержать всего два метода:

#ifndef _IANIMATEDSPRITE_H_
#define _IANIMATEDSPRITE_H_

#include <string>

#include "Desktop.h"

using namespace std;

class IAnimatedSprite {
    public:
      virtual bool isValidMessage(int msg)                                     = 0;
      virtual void doMessage(int msg, void* data = NULL, uint64 timestamp = 0) = 0;
};

#endif    // _IANIMATEDSPRITE_H_


Метод isValidMessage позволит определить, обрабатывает ли спрайт сообщения с заданным кодом. doMessage, позволит передать сообщение на обработку. В параметре data могут передаваться произвольные данные. Для чего нужен timestamp, мы рассмотрим в следующей статье. Поскольку мы заговорили о сообщениях, сразу добавим новые коды сообщений в Desktop.h:

#ifndef _DESKTOP_H_
#define _DESKTOP_H_

#include <set>
#include "s3eKeyboard.h"
#include "Scene.h"

using namespace std;

enum EMessageType {
    emtNothing                                                      = 0x00,

    emtHide                                                         = 0x01,
    emtShadow                                                       = 0x02,
    emtShow                                                         = 0x03,
    emtSwitch                                                       = 0x04,
    emtInit                                                         = 0x05,
    emtFix                                                          = 0x08,

    emtStartAnimation                                               = 0x06,
    emtStopAnimation                                                = 0x07,
    emtActivate                                                     = 0x09,

    emtSystemMessage                                                = 0x0F,

    emtTouchEvent                                                   = 0x10,
    emtTouchIdMask                                                  = 0x03,
    emtTouchMask                                                    = 0x78,
    emtMultiTouch                                                   = 0x14,
    emtTouchOut                                                     = 0x18,
    emtTouchDown                                                    = 0x30,
    emtTouchUp                                                      = 0x50,
    emtTouchOutUp                                                   = 0x58,
    emtTouchMove                                                    = 0x70,
    emtSingleTouchDown                                              = 0x30,
    emtSingleTouchUp                                                = 0x50,
    emtSingleTouchMove                                              = 0x70,
    emtMultiTouchDown                                               = 0x34,
    emtMultiTouchUp                                                 = 0x54,
    emtMultiTouchMove                                               = 0x74,

    emtKeyEvent                                                     = 0x80,
    emtKeyAction                                                    = 0x82,
    emtKeyDown                                                      = 0x81,
    emtKeyPressed                                                   = 0x83,
    emtKeyReleased                                                  = 0x82
};
...

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

AnimatedSprite.h:

#ifndef _ANIMATEDSPRITE_H_
#define _ANIMATEDSPRITE_H_

#include <map>
#include <vector>

#include "Sprite.h"
#include "IAnimatedSprite.h"
#include "ResourceManager.h"

#define REFRESH_CNT 2

using namespace std;

class AnimatedSprite: public Sprite,
                      public IAnimatedSprite {
    protected:
        int state;
        map<int, ResourceHolder*> images;
        uint64 lastTimestamp;
        bool isAnimated;
        int refreshCnt;
    public:
        AnimatedSprite(ISpriteOwner* scene, int x, int y, int zOrder = 0);
        AnimatedSprite(ISpriteOwner* scene, const char* res, int x, int y, 

                       int zOrder = 0, int loc = elImage);
        virtual void addImage(const char*res, int id = 0, int loc = 0);
        virtual CIw2DImage* getImage(int id = 0);
        virtual int  getState();
        virtual bool setState(int newState);
        virtual void refresh();
        virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL);
        virtual bool isBuzy() {return false;}
        virtual bool isValidMessage(int msg) {return (msg <= emtSystemMessage);}
        virtual void doMessage(int msg, void* data = NULL, uint64 timestamp = 0);
        virtual void unload();

    typedef map<int,  ResourceHolder*>::iterator IIter;
    typedef pair<int, ResourceHolder*> IPair;
};

#endif    // _ANIMATEDSPRITE_H_


AnimatedSprite.cpp:

#include "AnimatedSprite.h"
#include "Desktop.h"
#include "Locale.h"

AnimatedSprite::AnimatedSprite(ISpriteOwner* scene, int x, int y, int zOrder): Sprite(scene, x, y, zOrder)
                                            , state(0)
                                            , images()
                                            , lastTimestamp(0)
                                            , isAnimated(false)
                                            , refreshCnt(REFRESH_CNT) {}

AnimatedSprite::AnimatedSprite(ISpriteOwner* scene, const char* res, int x, int y, int zOrder, int loc): Sprite(scene, x, y, zOrder)
                                            , state(0)
                                            , images()
                                            , lastTimestamp(0)
                                            , isAnimated(false)
                                            , refreshCnt(REFRESH_CNT) {
    AnimatedSprite::addImage(res, 0, loc);
}

void AnimatedSprite::unload() {
    for (IIter p = images.begin(); p != images.end(); ++p) {
        p->second->unload();
    }
}

void AnimatedSprite::addImage(const char*res, int id, int loc) {
    ResourceHolder* img = rm.load(res, loc);
    images.insert(IPair(id, img));
}

bool AnimatedSprite::setState(int newState) {
    IIter p = images.find(newState);
    if (p == images.end()) {
        return false;
    }
    state = newState;
    return true;
}
       
CIw2DImage* AnimatedSprite::getImage(int id) {
    IIter p = images.find(id);
    if (p == images.end()) {
        return NULL;
    }
    return p->second->getData();
}

int AnimatedSprite::getState() {
    return state;
}

void AnimatedSprite::doMessage(int msg, void* data, uint64 timestamp) {
    init();
    int s = getState();
    switch (msg) {
        case emtStartAnimation:
            isAnimated = true;
            break;
        case emtStopAnimation:
            isAnimated = false;
            break;
        case emtSwitch:
            s++;
            if (getImage(s) == NULL) {
                s = 0;
            }
            setState(s);
            return;
        case emtHide:
            isVisible = false;
            return;
        case emtShadow:
            isVisible = true;
            alpha = IW_2D_ALPHA_HALF;
            return;
        case emtShow:
            isVisible = true;
            alpha = IW_2D_ALPHA_NONE;
            return;
    };
    if (timestamp == 0) {
        timestamp = s3eTimerGetMs();
    }
}

bool AnimatedSprite::sendMessage(int msg, uint64 timestamp, void* data) {
    if (!isValidMessage(msg)) {
        return false;
    }
    if (timestamp <= lastTimestamp) {
        doMessage(msg, data);
        return true;
    }
    return false;
}

void AnimatedSprite::refresh() {
    if (isAnimated) {
        if (--refreshCnt <= 0) {
            refreshCnt = REFRESH_CNT;
            doMessage(emtSwitch);
        }
    }
    Sprite::refresh();
}


Главным отличием этого класса от Sprite является возможность загрузки нескольких излбражений, ассоциированных с числовыми кодами статусов. Кроме того, AnimatedSprite обрабатывает служебные сообщения, позволяющие управлять его видимостью, переключать текущее изображение и т.п.

CompositeSprite.h:

#ifndef _COMPOSITESPRITE_H_
#define _COMPOSITESPRITE_H_

#include "AnimatedSprite.h"
#include "AbstractSpriteOwner.h"
#include "Scene.h"

class CompositeSprite: public AnimatedSprite
                     , public AbstractSpriteOwner {
    protected:
        ISpriteOwner* owner;   
    public:
        CompositeSprite(ISpriteOwner* scene, int x, int y, int zOrder);
        virtual int  getXSize(int xSize);
        virtual int  getYSize(int ySize);
        virtual int  getXPos(int x);
        virtual int  getYPos(int y);
        virtual bool setState(int newState);
        virtual void refresh();
        virtual void update(uint64 timestamp);
        virtual bool isBuzy();
        virtual bool sendMessage(int msg, uint64 timestamp = 0, void* data = NULL);
        virtual bool sendMessage(int msg, int x, int y);
        virtual void unload();
};

#endif    // _COMPOSITESPRITE_H_


CompositeSprite.cpp:

#include "CompositeSprite.h"

CompositeSprite::CompositeSprite(ISpriteOwner* scene, int x, int y, int zOrder): AnimatedSprite(scene, x, y, zOrder), owner(scene), AbstractSpriteOwner() {}

int CompositeSprite::getXSize(int xSize) {
    return owner->getXSize(xSize);
}
       
int CompositeSprite::getYSize(int ySize) {
    return owner->getYSize(ySize);
}

int CompositeSprite::getXPos(int x) {
    return AbstractScreenObject::getXPos() + owner->getXPos(x);
}
   
int CompositeSprite::getYPos(int y) {
    return AbstractScreenObject::getYPos() + owner->getYPos(y);
}

void CompositeSprite::refresh() {
    if (isVisible) {
        init();
        AbstractSpriteOwner::refresh();
    }
}

void CompositeSprite::update(uint64 timestamp) {
    AnimatedSprite::update(timestamp);
    AbstractSpriteOwner::update(timestamp);
}

bool CompositeSprite::isBuzy() {
    return AnimatedSprite::isBuzy();
}

bool CompositeSprite::sendMessage(int msg, uint64 timestamp, void* data) {
    return AnimatedSprite::sendMessage(msg, timestamp, data) ||
        owner->sendMessage(msg, timestamp, data);
}

bool CompositeSprite::sendMessage(int msg, int x, int y) {
    if (!isVisible) return false;
    return AbstractSpriteOwner::sendMessage(msg, x, y);
}

bool CompositeSprite::setState(int newState) {
    state = newState;
    return true;
}

void CompositeSprite::unload() {
    AbstractSpriteOwner::unload();
}


Можно видеть, что CompositeSprite представляет собой контейнер спрайтов с минимальной функциональностью. Пожалуй, единственные методы, заслуживающие внимания, здесь - это getXPos и getYPos, вычисляющие координаты, с учетом координат самого CompositeSprite. Изменив координаты CompositeSprite, мы переместим на экране все включенные в него спрайты (в том числе и композитные). Вследующей статье, мы заставим картинки двигаться.

Как обычно, полные исходники проекта можно загрузить здесь.

Комментариев нет:

Отправить комментарий