英文原文: http://test.ogitor.org/tiki/AnimationBlender
動畫混合 -- 實現(xiàn)兩個動畫的切換, 一個動畫逐漸消逝, 另一個動畫逐漸顯示來實現(xiàn). 主要通過動畫狀態(tài)的權(quán)重來實現(xiàn)
通過三種方式來實現(xiàn)兩個動畫的混合:
- BlendSwitch - 直接切換至目標(biāo)動畫
- BlendWhileAnimating - 混合的過程中目標(biāo)動畫也更新幀, 實現(xiàn)動畫
- BlendThenAnimate - 用源動畫的當(dāng)前幀混合目標(biāo)動畫的第一個幀
源碼理解, 主要代碼位于下面兩個函數(shù)
AnimationBlender::blend函數(shù) 根據(jù)傳入的參數(shù)設(shè)置新轉(zhuǎn)換的源動畫和目標(biāo)動畫
AnimationBlender::add函數(shù)則更新源動畫和目標(biāo)動畫(如存在)的狀態(tài)和權(quán)重
AnimationBlender::blend函數(shù)
1. 如果動畫轉(zhuǎn)換類型為 AnimationBlender::BlendSwitch
直接將源動畫轉(zhuǎn)換成目標(biāo)動畫
2. 如果不是這個轉(zhuǎn)換類型 AnimationBlender::BlendSwitch
如果上次的動畫轉(zhuǎn)換還未完成
如果新的目標(biāo)動畫等于上次轉(zhuǎn)換的源動畫
則反向轉(zhuǎn)換
如果新的目標(biāo)動畫不等于上次轉(zhuǎn)換的源動畫和目標(biāo)動畫
根據(jù)上次轉(zhuǎn)換的剩余時間設(shè)置是顯示上次轉(zhuǎn)換的源動畫還是目標(biāo)動畫, 并將其設(shè)置為新轉(zhuǎn)換的源動畫
顯示新轉(zhuǎn)換的目標(biāo)動畫, 并設(shè)置其權(quán)重
假設(shè)上次的動畫轉(zhuǎn)換已經(jīng)完成了轉(zhuǎn)換
設(shè)置轉(zhuǎn)換時間, 轉(zhuǎn)換類型, 目標(biāo)動畫和其權(quán)重
AnimationBlender::addTime 函數(shù)
1. 如有動畫在運行
如果存在轉(zhuǎn)換
更新剩余時間
如剩余時間小于0
禁止源動畫
將目標(biāo)動畫設(shè)置為源動畫, 繼續(xù)運行
如剩余時間不小于0
更新源動畫和目標(biāo)動畫的權(quán)重
如轉(zhuǎn)換類型為AnimationBlender::BlendWhileAnimating
更新目標(biāo)動畫
判斷動畫是否完成
更新源動畫
完整代碼和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)
{
// 初始化, 將所有的動畫禁止, 只允許參數(shù)中的動畫運行.
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, false, false), 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.2, true );
walking=true;
}
if (mKeyboard->isKeyDown( OIS::KC_SPACE ) && !jumping)
{
jumping=true;
mAnimationBlender->blend("Jump",AnimationBlender::BlendWhileAnimating, 0.2, false );
}
if (jumping)
{
if (mAnimationBlender->complete)
{
mAnimationBlender->blend( "Idle1",AnimationBlender::BlendWhileAnimating, 0.02, true );
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