英文原文: 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
01 |
#ifndef __ANIMATION_BLENDER_H__
|
02 |
#define __ANIMATION_BLENDER_H__
|
03 |
#include <Ogre.h>
|
04 |
using
namespace
Ogre; |
05 |
class
AnimationBlender |
06 |
{ |
07 |
public :
|
08 |
enum
BlendingTransition |
09 |
{
|
10 |
BlendSwitch,
// stop source and start dest
|
11 |
BlendWhileAnimating,
// cross fade, blend source animation out while blending destination animation in
|
12 |
BlendThenAnimate
// blend source to first frame of dest, when done, start dest anim
|
13 |
};
|
14 |
private :
|
15 |
Entity *mEntity;
|
16 |
AnimationState *mSource;
|
17 |
AnimationState *mTarget;
|
18 |
BlendingTransition mTransition;
|
19 |
bool
loop; |
20 |
~AnimationBlender() {}
|
21 |
public :
|
22 |
Real mTimeleft, mDuration;
|
23 |
bool
complete; |
24 |
void
blend( const
String &animation, BlendingTransition transition, Real duration,
bool
l= true
); |
25 |
void
addTime( Real ); |
26 |
Real getProgress() {
return
mTimeleft/ mDuration; } |
27 |
AnimationState *getSource() {
return
mSource; } |
28 |
AnimationState *getTarget() {
return
mTarget; } |
29 |
AnimationBlender( Entity *);
|
30 |
void
init( const
String &animation, bool
l= true
); |
31 |
}; |
32 |
#endif |
AnimationBlender.cpp
001 |
#include "AnimationBlender.h"
|
002 |
void
AnimationBlender::init( const
String &animation, bool
l) |
003 |
{ |
004 |
// 初始化, 將所有的動畫禁止, 只允許參數中的動畫運行.
|
005 |
AnimationStateSet *set = mEntity->getAllAnimationStates();
|
006 |
AnimationStateIterator it = set->getAnimationStateIterator();
|
007 |
while (it.hasMoreElements())
|
008 |
{
|
009 |
AnimationState *anim = it.getNext();
|
010 |
anim->setEnabled( false );
|
011 |
anim->setWeight(0);
|
012 |
anim->setTimePosition(0);
|
013 |
}
|
014 |
mSource = mEntity->getAnimationState( animation );
|
015 |
mSource->setEnabled( true );
|
016 |
mSource->setWeight(1);
|
017 |
mTimeleft = 0;
|
018 |
mDuration = 1;
|
019 |
mTarget = 0;
|
020 |
complete =
false ;
|
021 |
loop = l;
|
022 |
} |
023 |
void
AnimationBlender::blend( const
String &animation, BlendingTransition transition, Real duration,
bool
l ) |
024 |
{ |
025 |
loop = l;
|
026 |
if ( transition == AnimationBlender::BlendSwitch )
|
027 |
{
|
028 |
if ( mSource != 0 )
|
029 |
mSource->setEnabled( false );
|
030 |
mSource = mEntity->getAnimationState( animation );
|
031 |
mSource->setEnabled( true );
|
032 |
mSource->setWeight(1);
|
033 |
mSource->setTimePosition(0);
|
034 |
mTimeleft = 0;
|
035 |
}
|
036 |
else |
037 |
{
|
038 |
AnimationState *newTarget = mEntity->getAnimationState( animation );
|
039 |
if ( mTimeleft > 0 )
|
040 |
{
|
041 |
// oops, weren't finished yet
|
042 |
if ( newTarget == mTarget )
|
043 |
{
|
044 |
// nothing to do! (ignoring duration here)
|
045 |
}
|
046 |
else
if ( newTarget == mSource )
|
047 |
{
|
048 |
// going back to the source state, so let's switch
|
049 |
mSource = mTarget;
|
050 |
mTarget = newTarget;
|
051 |
mTimeleft = mDuration - mTimeleft;
// i'm ignoring the new duration here
|
052 |
}
|
053 |
else |
054 |
{
|
055 |
// ok, newTarget is really new, so either we simply replace the target with this one, or
|
056 |
// we make the target the new source
|
057 |
if ( mTimeleft < mDuration * 0.5 )
|
058 |
{
|
059 |
// simply replace the target with this one
|
060 |
mTarget->setEnabled( false );
|
061 |
mTarget->setWeight(0);
|
062 |
}
|
063 |
else |
064 |
{
|
065 |
// old target becomes new source
|
066 |
mSource->setEnabled( false );
|
067 |
mSource->setWeight(0);
|
068 |
mSource = mTarget;
|
069 |
}
|
070 |
mTarget = newTarget;
|
071 |
mTarget->setEnabled( true );
|
072 |
mTarget->setWeight( 1.0 - mTimeleft / mDuration );
|
073 |
mTarget->setTimePosition(0);
|
074 |
}
|
075 |
}
|
076 |
else |
077 |
{
|
078 |
// assert( target == 0, "target should be 0 when not blending" )
|
079 |
// mSource->setEnabled(true);
|
080 |
// mSource->setWeight(1);
|
081 |
mTransition = transition;
|
082 |
mTimeleft = mDuration = duration;
|
083 |
mTarget = newTarget;
|
084 |
mTarget->setEnabled( true );
|
085 |
mTarget->setWeight(0);
|
086 |
mTarget->setTimePosition(0);
|
087 |
}
|
088 |
}
|
089 |
} |
090 |
void
AnimationBlender::addTime( Real time
) |
091 |
{ |
092 |
if ( mSource != 0 )
|
093 |
{
|
094 |
if ( mTimeleft > 0 )
|
095 |
{
|
096 |
mTimeleft -=
time ;
|
097 |
if ( mTimeleft < 0 )
|
098 |
{
|
099 |
// finish blending
|
100 |
mSource->setEnabled( false );
|
101 |
mSource->setWeight(0);
|
102 |
mSource = mTarget;
|
103 |
mSource->setEnabled( true );
|
104 |
mSource->setWeight(1);
|
105 |
mTarget = 0;
|
106 |
}
|
107 |
else |
108 |
{
|
109 |
// still blending, advance weights
|
110 |
mSource->setWeight(mTimeleft / mDuration);
|
111 |
mTarget->setWeight(1.0 - mTimeleft / mDuration);
|
112 |
if (mTransition == AnimationBlender::BlendWhileAnimating)
|
113 |
mTarget->addTime( time );
|
114 |
}
|
115 |
}
|
116 |
if
(mSource->getTimePosition() >= mSource->getLength())
|
117 |
{
|
118 |
complete =
true ;
|
119 |
}
|
120 |
else |
121 |
{
|
122 |
complete =
false ;
|
123 |
}
|
124 |
mSource->addTime( time );
|
125 |
mSource->setLoop(loop);
|
126 |
}
|
127 |
} |
128 |
AnimationBlender::AnimationBlender( Entity *entity ) : mEntity(entity)
|
129 |
{ |
130 |
} |
AnimationBlenderDemo.h
01 |
<P>#ifndef __ANIMATIONBLENDER_DEMO_H__
|
02 |
#define __ANIMATIONBLENDER_DEMO_H__
|
03 |
#include "ExampleApplication.h"
|
04 |
#include "AnimationBlender.h"
|
05 |
class
AnimationBlenderDemoFrameListener : public
ExampleFrameListener |
06 |
{ |
07 |
protected :
|
08 |
Entity* mNinjaEnt;
|
09 |
AnimationBlender* mAnimationBlender;
|
10 |
bool
walking, jumping; |
11 |
public :
|
12 |
AnimationBlenderDemoFrameListener(RenderWindow* mWin, Camera* mCam, Entity* ent)
|
13 |
: ExampleFrameListener(mWin, mCam,
false ,
false ), mNinjaEnt(ent)
|
14 |
{
|
15 |
mAnimationBlender =
new
AnimationBlender(mNinjaEnt); |
16 |
mAnimationBlender->init( "Walk" ,
true );
|
17 |
walking =
false ;
|
18 |
jumping =
false ;
|
19 |
}
|
20 |
virtual
~AnimationBlenderDemoFrameListener() |
21 |
{
|
22 |
if (mAnimationBlender)
|
23 |
{
|
24 |
// delete mAnimationBlender;
|
25 |
}
|
26 |
}
|
27 |
bool
frameRenderingQueued( const
FrameEvent& evt) |
28 |
{
|
29 |
if
(!ExampleFrameListener::frameRenderingQueued(evt))
|
30 |
{
|
31 |
return
false ;
|
32 |
}
|
33 |
if
(!walking) |
34 |
{
|
35 |
mAnimationBlender->blend(
"Walk" ,AnimationBlender::BlendWhileAnimating, 0.2,
true
); |
36 |
walking= true ;
|
37 |
}
|
38 |
if
(mKeyboard->isKeyDown( OIS::KC_SPACE ) && !jumping)
|
39 |
{
|
40 |
jumping= true ;
|
41 |
mAnimationBlender->blend( "Jump" ,AnimationBlender::BlendWhileAnimating, 0.2,
false
); |
42 |
}
|
43 |
if
(jumping) |
44 |
{
|
45 |
if
(mAnimationBlender->complete) |
46 |
{
|
47 |
mAnimationBlender->blend(
"Idle1" ,AnimationBlender::BlendWhileAnimating, 0.02,
true
); |
48 |
jumping= false ;
|
49 |
}
|
50 |
}
|
51 |
mAnimationBlender->addTime(evt.timeSinceLastFrame);
|
52 |
return
true ;
|
53 |
}
|
54 |
}; |
55 |
class
AnimationBlenderDemoApp : public
ExampleApplication |
56 |
{ |
57 |
public :
|
58 |
AnimationBlenderDemoApp() {}
|
59 |
protected :
|
60 |
Entity* mNinjaEnt;
|
61 |
void
createScene(); |
62 |
void
createFrameListener() |
63 |
{
|
64 |
mFrameListener =
new
AnimationBlenderDemoFrameListener(mWindow, mCamera, mNinjaEnt);
|
65 |
mRoot->addFrameListener(mFrameListener);
|
66 |
}
|
67 |
}; |
68 |
#endif</P> |
AnimationBlenderDemo.cpp
01 |
<P>#include "AnimationBlenderDemo.h" |
02 |
#include <OgreStringConverter.h>
|
03 |
void
AnimationBlenderDemoApp::createScene() |
04 |
{ |
05 |
mSceneMgr->setAmbientLight(ColourValue(1.0, 1.0, 1.0));
|
06 |
mNinjaEnt = mSceneMgr->createEntity( "Ninja" ,
"ninja.mesh" );
|
07 |
SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode();
|
08 |
node->attachObject(mNinjaEnt);
|
09 |
} |
10 |
#ifdef __cplusplus
|
11 |
extern
"C"
{ |
12 |
#endif
|
13 |
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
|
14 |
#define WIN32_LEAN_AND_MEAN
|
15 |
#include "windows.h"
|
16 |
INT
WINAPI WinMain( HINSTANCE
hInst, HINSTANCE ,
LPSTR
strCmdLine, INT
) |
17 |
#else
|
18 |
int
main( int
argc, char
**argv) |
19 |
#endif
|
20 |
{ |
21 |
AnimationBlenderDemoApp app; |
22 |
try
{ |
23 |
app.go();
|
24 |
} catch ( Ogre::Exception& e ) {
|
25 |
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
|
26 |
MessageBox( NULL, e.getFullDescription().c_str(),
"An exception has occured!" , MB_OK | MB_ICONERROR | MB_TASKMODAL );
|
27 |
#else
|
28 |
std::cerr <<
"An exception has occured: "
<< e.getFullDescription(); |
29 |
#endif
|
30 |
} |
31 |
return
0; |
32 |
} |
33 |
#ifdef __cplusplus
|
34 |
} |
35 |
#endif</P> |
不過由於Ninja.mesh的動畫時間太小, 很難看出混合效果, 以上代碼適合Ogre1.6.5