JGE 游戏引擎动画模块分析
作者: 刘鹏
日期: 2009-05-12
本文详细分析了 JGE 游戏引擎的动画模块,包括该模块的各个类,类之间的关系,并给出了类图和序列图,最后分析了一个播放动画的例子。

简介

JGE 引擎提供了一个基于 frame 的动画模块。动画各个 frame 的参数和播放顺 序存在一个 XML 文件中。播放动画时动画模块根据时间顺序显示各个 frame。

记录动画的 XML 文件如下所示:


<?xml version="1.0" standalone="no" ?>

<script name="abc" type="ANIMATION_TYPE_ONCE_AND_STAY" framerate="20">

/* "type" can be ANIMATION_TYPE_LOOPING,
 *               ANIMATION_TYPE_ONCE_AND_STAY,
 *               ANIMATION_TYPE_ONCE_AND_BACK,
 *               ANIMATION_TYPE_ONCE_AND_GONE,r
 *               ANIMATION_TYPE_PINGPONG
 *
 * "framerate" is the rate of playback in frames per seconds.
 */

<frame id="1">
    <obj name="head">
        <settings quad="head" x="10" y="10" hsize="1.0" vsize="1.0"
                  rotation="0.0" r="255" g="255" b="255" a="255" />
    </obj>
    <obj name="body">
        <settings quad="body" />
    </obj>
</frame>

/*
 * Each frame contains one or more frame objects.
 * Each object is a quad with various settings.
 * "quad" is the name of the quad.
 * "x" and "y" is the position.
 * "hsize" is the horizontal scaling.
 * "vsize" is the vertical scaling.
 * "rotation" is the angle that the quad will be rotated in radians.
 * You can also specify the color and alpha of the quad with the "r", "g", "b" and "a" parameters.
 */

<frame id="2" time="0.20">
   <obj name="head">
      <settings quad="head" x="10" y="10" hsize="1.0" vsize="1.0"
       rotation="0.0" r="255" g="255" b="255" a="255" />
   </obj>
   <obj name="body">
      <settings quad="body" a="128" />
   </obj>
   </frame>


</script>


动画模块

JGE 动画的动画模块由三个类组成:

  • JAnimatior
  • JAnimatorFrame
  • JAnimatorObject

动画模块的类图如下所示:

播放动画总体流程

播放动画分如下几步:

 - 装入资源文件;  - 创在动画对象,并指定记录 frame 信息的 xml 文件;  - 设置动画对象位置;  - start 动画;  - 进入循环,每次循环调用动画对象的 Update 方法和 Render 方法,分别处 理动画逻辑和渲染。

播放一个 frame 的序列图如下所示:

示例代码如下所示:


 /* Load resource. */

 mResourceMgr = new JResourceManager();
 mResourceMgr->LoadResource("animation.res");

 /* Create animator. */
 mMyAnimator = new JAnimator(mResourceMgr);
 mMyAnimator->Load("left.anm");

 /* Specify animator position.*/
 mMyAnimator->SetHotSpot(79,126);
 mMyAnimator->SetPosition(240,220);

 /* Start animation */
 mMyAnimator->Start();

 while(!done) {

    mMyAnimator->Update(dt);

    mMyAnimator->Render();

 }


JAnimator 类

JAnimator 的核心成员是 mFrames,它是 JAnimatorFrame 类型的指针数组, 记录了该动画包含的所有 frame。

JAnimator 的核心方法是

  • Load:装入 xml 文件;
  • Start:启动动画 ;
  • Update:根据时间的推进及时更新当前 frame ;
  • Render: 绘制当前 frame。

class JAnimator
{
public:

 /*
   * Constructor.
   *
   * @param resourceMgr - ResourceManager to look for images (JQuads)
   *
   */
 JAnimator(JResourceManager* resourceMgr);

 /*
  * Destructor.
  *
  */
 ~JAnimator();

 /*
  * Load animation sequence from a script file.
  *
  *  @param scriptFile - Animation script.
  *
  *  @return True if no problem during loading. False otherwise.
  *
  */
 bool Load(const char* scriptFile);

 /*
  * Start animation.
  *
  */
 void Start();

 /*
  * Update animation.
  *
  * @param dt - Time elapsed since last update (in second).
  *
  */
 void Update(float dt);

 /*
  * Render animation.
  *
  */
 void Render();

 /*
  * Check if animation is playing or not.
  *
  * @return True if playing animation.
  *
  */
 bool IsAnimating();

private:

 JResourceManager* mResource;
 vector<JAnimatorFrame *> mFrames;

 bool mAnimating;
 bool mActive;
 int mCurrentFrame;
 int mAnimationType;
 int mFrameDelta;

 float mX;
 float mY;
 float mHotSpotX;
 float mHotSpotY;

};

核心方法 Update 和 Render 的实现如下所示:


void JAnimator::Update(float dt)
{
    if (!mAnimating) return;

    if (mFrames[mCurrentFrame]->Update(dt))
    {
        mCurrentFrame+=mFrameDelta;

        int frameCount = mFrames.size();
        if  (mCurrentFrame >= frameCount)
        {
            /* All frames have been played. */
            if (mAnimationType == JSprite::ANIMATION_TYPE_LOOPING)
                mCurrentFrame = 0;
            else if (mAnimationType == JSprite::ANIMATION_TYPE_ONCE_AND_GONE)
            {
                mAnimating = false;
                mActive = false;
            }
            else if (mAnimationType == JSprite::ANIMATION_TYPE_ONCE_AND_STAY)
            {
                mCurrentFrame = frameCount-1;
                mAnimating = false;
            }
            else if (mAnimationType == JSprite::ANIMATION_TYPE_ONCE_AND_BACK)
            {
                mCurrentFrame = 0;
                mAnimating = false;
            }
            else    // ping pong
            {
                mFrameDelta *= -1;
                mCurrentFrame += mFrameDelta;
            }
        }
        else if (mCurrentFrame < 0)
        {
            if (mAnimationType == JSprite::ANIMATION_TYPE_PINGPONG)
            {
                mFrameDelta *= -1;
                mCurrentFrame += mFrameDelta;
            }
        }

        /* Play next frame.*/
        if (mAnimating)
            mFrames[mCurrentFrame]->Start();
    }

}


void JAnimator::Render()
{
    if (!mActive)
        return;

    mFrames[mCurrentFrame]->Render(mX-mHotSpotX, mY-mHotSpotY);

}



JAnimatorFrame 类

JAnimatorFrame 的核心成员是 mObjects,该成员是 JAnimatorObject 类型的 指针数组,保存了构成该 frame 的所有对象。

JAnimatorFrame 的核心方法是

  • Start:启动该 frame;
  • Update:根据时间的推进及时更新 frame 中的对象;
  • Render: 绘制当前 frame。

/*
 * A single frame of an animation.
 *
 */
class JAnimatorFrame
{
public:
 /*
  * Constructor.
  *
  * @param parent - Parent of the frame.
  *
  */
 JAnimatorFrame(JAnimator* parent);

 /*
  * Destructor.
  *
  */
 ~JAnimatorFrame();

 /*
  * Add a new object into the frame.
  *
  * @param obj - New animation object.
  *
  */
 void AddObject(JAnimatorObject *obj);


 /*
  * Frame update.
  *
  * @param dt - Time elapsed since last update (in second).
  *
  * @return True if the frame is done.
  *
  */
 bool Update(float dt);

 /*
  * Render frame.
  *
  * @param x - X position for rendering.
  * @param y - Y position for rendering.
  *
  */
 void Render(float x, float y);

 /*
  * Start playing the frame.
  *
  */
 void Start();


private:
 float mTimer;
 float mFrameTime;
 JAnimator* mAnimator;
 vector<JAnimatorObject *> mObjects;

};

核心方法 Update 和 Render 的实现如下所示:


bool JAnimatorFrame::Update(float dt)
{
    mTimer += dt;
    if (mTimer >= mFrameTime)
        return true;
    else
    {
        int size = mObjects.size();
        for (int i=0;i<size;i++)
            mObjects[i]->Update(dt);

        return false;
    }
}

void JAnimatorFrame::Render(float x, float y)
{
    int size = mObjects.size();
    for (int i=0;i<size;i++)
        mObjects[i]->Render(x, y);
}


JAnimatorObject 类


/*
 * Animation object (image quad) in a frame.
 *
 */
class JAnimatorObject
{
public:

 /*
  * Constructor.
  *
  */
 JAnimatorObject();

 /*
  * Destructor.
  *
  */
 ~JAnimatorObject();

 /*
  * Update object.
  *
  * @param dt - Time elapsed since last update (in second).
  *
  */
 void Update(float dt);

 /*
  * Render object.
  *
  * @param x - X position for rendering.
  * @param y - Y position for rendering.
  *
  */
 void Render(float x, float y);


private:
 JRenderer* mRenderer;
 JQuad* mQuad;

 float mX;
 float mY;
 float mRotation;
 float mHScale;
 float mVScale;
 PIXEL_TYPE mColor;
 bool mFlipped;
};


Update 和 Render 方法的实现:


void JAnimatorObject::Update(float dt)
{
}


void JAnimatorObject::Render(float x, float y)
{
    mQuad->SetHFlip(mFlipped);
    mQuad->SetColor(mColor);
    mRenderer->RenderQuad(mQuad, x+mX, y+mY, mRotation, mHScale, mVScale);
}


JGE Tutorial: Animator

这是 JGE 提供的一个动画例子,该例子创建了两个动画,一个是小人脸朝左跳 舞、另一个是脸朝右跳舞,两个动画交替播放。

GameApp.h 如下所示:


class GameApp:  public JApp
{
private:
    JResourceManager* mResourceMgr;
    JAnimator* mLeft;
    JAnimator* mRight;

    JAnimator* mCurr;

public:
    GameApp();
    virtual ~GameApp();
    virtual void Create();
    virtual void Destroy();
    virtual void Update();
    virtual void Render();
    virtual void Pause();
    virtual void Resume();

};


GameApp.cpp 如下所示:


void GameApp::Create()
{
    // JAnimator needs to get its resource from a JResourceManager
    mResourceMgr = new JResourceManager();
    mResourceMgr->LoadResource("animation.res");

    // create the first animator
    mLeft = new JAnimator(mResourceMgr);
    mLeft->Load("left.anm");

    // create the second animator
    mRight = new JAnimator(mResourceMgr);
    mRight->Load("right.anm");

    mLeft->SetHotSpot(79,126);
    mLeft->SetPosition(240,220);

    mRight->SetHotSpot(79,126);
    mRight->SetPosition(240,220);

    // start the animation
    mCurr = mLeft;
    mCurr->Start();
}


void GameApp::Update()
{

    JGE* engine = JGE::GetInstance();

    ......

    float dt = engine->GetDelta();
    mCurr->Update(dt);

    // change to the other animation when the current one is done
    if (!mCurr->IsAnimating())
    {
        if (mCurr == mLeft)
            mCurr = mRight;
        else
            mCurr = mLeft;

        mCurr->Start();
    }
}

void GameApp::Render()
{

    JRenderer* renderer = JRenderer::GetInstance();

    // turn off bilinear filtering to have sharp image
    renderer->EnableTextureFilter(false);

    // render background
    renderer->RenderQuad(mResourceMgr->GetQuad("bg"), 480>>1, 272>>1);

    // render animation
    mCurr->Render();

}