posts - 311, comments - 0, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

英文原文: http://test.ogitor.org/tiki/AnimationBlender

动画混合 -- 实现两个动画的切换, 一个动画逐渐消逝, 另一个动画逐渐显示来实现. 主要通过动画状态的权重来实现
通过三种方式来实现两个动画的混合:
    - BlendSwitch - 直接切换至目标动画
    - BlendWhileAnimating - 混合的过程中目标动画也更新帧, 实现动画
    - BlendThenAnimate - 用源动画的当前帧混合目标动画的第一个帧

源码理解, 主要代码位于下面两个函数
AnimationBlender::blend函数 根据传入的参数设置新转换的源动画和目标动画
AnimationBlender::add函数则更新源动画和目标动画(如存在)的状态和权重

AnimationBlender::blend函数
1. 如果动画转换类型为 AnimationBlender::BlendSwitch
    直接将源动画转换成目标动画
2. 如果不是这个转换类型 AnimationBlender::BlendSwitch
    如果上次的动画转换还未完成
        如果新的目标动画等于上次转换的源动画
            则反向转换
        如果新的目标动画不等于上次转换的源动画和目标动画
            根据上次转换的剩余时间设置是显示上次转换的源动画还是目标动画, 并将其设置为新转换的源动画
            显示新转换的目标动画, 并设置其权重
    假设上次的动画转换已经完成了转换
        设置转换时间, 转换类型, 目标动画和其权重

AnimationBlender::addTime 函数
1. 如有动画在运行
    如果存在转换
        更新剩余时间
            如剩余时间小于0
                禁止源动画
                将目标动画设置为源动画, 继续运行
            如剩余时间不小于0
                更新源动画和目标动画的权重
                如转换类型为AnimationBlender::BlendWhileAnimating
                    更新目标动画
    判断动画是否完成
        更新源动画

完整代码和Demo代码

AnimationBlender.h

 

#ifndef __ANIMATION_BLENDER_H__   
#define __ANIMATION_BLENDER_H__   
#include 
<Ogre.h>   
using namespace Ogre;   
class AnimationBlender   
{   
public:   
    
enum BlendingTransition   
    {   
        BlendSwitch,         
// stop source and start dest   
        BlendWhileAnimating,   // cross fade, blend source animation out while blending destination animation in   
        BlendThenAnimate      // blend source to first frame of dest, when done, start dest anim   
    };   
private:   
    Entity 
*mEntity;   
    AnimationState 
*mSource;   
    AnimationState 
*mTarget;   
    BlendingTransition mTransition;   
    
bool loop;   
    
~AnimationBlender() {}   
public:    
    Real mTimeleft, mDuration;   
    
bool complete;   
    
void blend( const String &animation, BlendingTransition transition, Real duration, bool l=true );   
    
void addTime( Real );   
    Real getProgress() { 
return mTimeleft/ mDuration; }   
    AnimationState 
*getSource() { return mSource; }   
    AnimationState 
*getTarget() { return mTarget; }   
    AnimationBlender( Entity 
*);   
    
void init( const String &animation, bool l=true );   
};   
#endif 

AnimationBlender.cpp

#include "AnimationBlender.h"   
void AnimationBlender::init(const String &animation, bool l)   
{   
    
// 初始化, 将所有的动画禁止, 只允许参数中的动画运行.   
    AnimationStateSet *set = mEntity->getAllAnimationStates();   
    AnimationStateIterator it 
= set->getAnimationStateIterator();   
    
while(it.hasMoreElements())   
    {   
        AnimationState 
*anim = it.getNext();   
        anim
->setEnabled(false);   
        anim
->setWeight(0);   
        anim
->setTimePosition(0);   
    }   
    mSource 
= mEntity->getAnimationState( animation );   
    mSource
->setEnabled(true);   
    mSource
->setWeight(1);   
    mTimeleft 
= 0;   
    mDuration 
= 1;   
    mTarget 
= 0;   
    complete 
= false;   
    loop 
= l;   
}    
void AnimationBlender::blend( const String &animation, BlendingTransition transition, Real duration, bool l )   
{   
    loop 
= l;   
    
if( transition == AnimationBlender::BlendSwitch )   
    {   
        
if( mSource != 0 )   
            mSource
->setEnabled(false);   
        mSource 
= mEntity->getAnimationState( animation );   
        mSource
->setEnabled(true);   
        mSource
->setWeight(1);   
        mSource
->setTimePosition(0);   
        mTimeleft 
= 0;   
    }    
    
else    
    {    
        AnimationState 
*newTarget = mEntity->getAnimationState( animation );   
        
if( mTimeleft > 0 )   
        {   
            
// oops, weren't finished yet   
            if( newTarget == mTarget )   
            {   
                
// nothing to do! (ignoring duration here)   
            }   
            
else if( newTarget == mSource )   
            {   
                
// going back to the source state, so let's switch   
                mSource = mTarget;   
                mTarget 
= newTarget;   
                mTimeleft 
= mDuration - mTimeleft; // i'm ignoring the new duration here   
            }   
            
else  
            {   
                
// ok, newTarget is really new, so either we simply replace the target with this one, or   
                
// we make the target the new source   
                if( mTimeleft < mDuration * 0.5 )   
                {   
                    
// simply replace the target with this one   
                    mTarget->setEnabled(false);   
                    mTarget
->setWeight(0);   
                }   
                
else  
                {   
                    
// old target becomes new source   
                    mSource->setEnabled(false);   
                    mSource
->setWeight(0);   
                    mSource 
= mTarget;   
                }    
                mTarget 
= newTarget;   
                mTarget
->setEnabled(true);   
                mTarget
->setWeight( 1.0 - mTimeleft / mDuration );   
                mTarget
->setTimePosition(0);   
            }   
        }   
        
else  
        {   
            
// assert( target == 0, "target should be 0 when not blending" )   
            
// mSource->setEnabled(true);   
            
// mSource->setWeight(1);   
            mTransition = transition;   
            mTimeleft 
= mDuration = duration;   
            mTarget 
= newTarget;   
            mTarget
->setEnabled(true);   
            mTarget
->setWeight(0);   
            mTarget
->setTimePosition(0);   
        }   
    }   
}   
void AnimationBlender::addTime( Real time )   
{   
    
if( mSource != 0 )   
    {   
        
if( mTimeleft > 0 )   
        {   
            mTimeleft 
-= time;   
            
if( mTimeleft < 0 )   
            {   
                
// finish blending   
                mSource->setEnabled(false);   
                mSource
->setWeight(0);   
                mSource 
= mTarget;   
                mSource
->setEnabled(true);   
                mSource
->setWeight(1);   
                mTarget 
= 0;   
            }   
            
else  
            {   
                
// still blending, advance weights   
                mSource->setWeight(mTimeleft / mDuration);   
                mTarget
->setWeight(1.0 - mTimeleft / mDuration);   
                
if(mTransition == AnimationBlender::BlendWhileAnimating)   
                    mTarget
->addTime(time);   
            }   
        }   
        
if (mSource->getTimePosition() >= mSource->getLength())   
        {   
            complete 
= true;   
        }   
        
else  
        {   
            complete 
= false;   
        }   
        mSource
->addTime(time);   
        mSource
->setLoop(loop);   
    }   
}   
AnimationBlender::AnimationBlender( Entity 
*entity ) : mEntity(entity)    
{   

AnimationBlenderDemo.h

#ifndef __ANIMATIONBLENDER_DEMO_H__   
#define __ANIMATIONBLENDER_DEMO_H__   
#include 
"ExampleApplication.h"   
#include 
"AnimationBlender.h"   
class AnimationBlenderDemoFrameListener : public ExampleFrameListener   
{   
protected:   
    Entity
* mNinjaEnt;   
    AnimationBlender
* mAnimationBlender;   
    
bool walking, jumping;   
public:   
    AnimationBlenderDemoFrameListener(RenderWindow
* mWin, Camera* mCam, Entity* ent)   
        : ExampleFrameListener(mWin, mCam, 
falsefalse), mNinjaEnt(ent)   
    {   
        mAnimationBlender 
= new AnimationBlender(mNinjaEnt);   
        mAnimationBlender
->init("Walk"true);   
        walking 
= false;   
        jumping 
= false;   
    }   
    
virtual ~AnimationBlenderDemoFrameListener()   
    {   
        
if(mAnimationBlender)   
        {   
//          delete mAnimationBlender;   
        }   
    }   
    
bool frameRenderingQueued(const FrameEvent& evt)   
    {   
        
if (!ExampleFrameListener::frameRenderingQueued(evt))   
        {   
            
return false;   
        }   
        
if (!walking)   
        {   
            mAnimationBlender
->blend( "Walk",AnimationBlender::BlendWhileAnimating, 0.2true );   
            walking
=true;   
        }   
        
if (mKeyboard->isKeyDown( OIS::KC_SPACE ) && !jumping)   
        {   
            jumping
=true;   
            mAnimationBlender
->blend("Jump",AnimationBlender::BlendWhileAnimating, 0.2false );   
        }   
        
if (jumping)   
        {   
            
if (mAnimationBlender->complete)   
            {   
                mAnimationBlender
->blend( "Idle1",AnimationBlender::BlendWhileAnimating, 0.02true );   
                jumping
=false;   
            }   
        }   
        mAnimationBlender
->addTime(evt.timeSinceLastFrame);   
        
return true;   
    }   
};   
class AnimationBlenderDemoApp : public ExampleApplication   
{   
public:   
    AnimationBlenderDemoApp() {}   
protected:   
    Entity
* mNinjaEnt;   
    
void createScene();   
    
void createFrameListener()   
    {   
        mFrameListener 
= new AnimationBlenderDemoFrameListener(mWindow, mCamera, mNinjaEnt);   
        mRoot
->addFrameListener(mFrameListener);   
    }   
};   
#endif  


AnimationBlenderDemo.cpp

#include "AnimationBlenderDemo.h"   
#include <OgreStringConverter.h>   
void AnimationBlenderDemoApp::createScene()   
{   
    mSceneMgr->setAmbientLight(ColourValue(1.0, 1.0, 1.0));   
    mNinjaEnt = mSceneMgr->createEntity("Ninja", "ninja.mesh");   
    SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();   
    node->attachObject(mNinjaEnt);   
}   
#ifdef __cplusplus   
extern "C" {   
#endif   
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32   
#define WIN32_LEAN_AND_MEAN   
#include "windows.h"   
    INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR strCmdLine, INT )   
#else   
    int main(int argc, char **argv)   
#endif   
    {   
        AnimationBlenderDemoApp app;   
        try {   
            app.go();   
        } catch( Ogre::Exception& e ) {   
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32   
            MessageBox( NULL, e.getFullDescription().c_str(), "An exception has occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL );   
#else   
            std::cerr << "An exception has occured: " << e.getFullDescription();   
#endif   
        }   
        return 0;   
    }   
#ifdef __cplusplus   
}   
#endif