英文原文: 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
view source print ?
01 | #ifndef __ANIMATION_BLENDER_H__ |
02 | #define __ANIMATION_BLENDER_H__ |
05 | class AnimationBlender |
08 | enum BlendingTransition |
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 |
16 | AnimationState *mSource; |
17 | AnimationState *mTarget; |
18 | BlendingTransition mTransition; |
20 | ~AnimationBlender() {} |
22 | Real mTimeleft, mDuration; |
24 | void blend( const String &animation, BlendingTransition transition, Real duration, bool l= true ); |
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 ); |
AnimationBlender.cpp
view source print ?
001 | #include "AnimationBlender.h" |
002 | void AnimationBlender::init( const String &animation, bool l) |
004 | // 初始化, 将所有的動畫禁止, 隻允許參數中的動畫運作. |
005 | AnimationStateSet *set = mEntity->getAllAnimationStates(); |
006 | AnimationStateIterator it = set->getAnimationStateIterator(); |
007 | while (it.hasMoreElements()) |
009 | AnimationState *anim = it.getNext(); |
010 | anim->setEnabled( false ); |
012 | anim->setTimePosition(0); |
014 | mSource = mEntity->getAnimationState( animation ); |
015 | mSource->setEnabled( true ); |
016 | mSource->setWeight(1); |
023 | void AnimationBlender::blend( const String &animation, BlendingTransition transition, Real duration, bool l ) |
026 | if ( transition == AnimationBlender::BlendSwitch ) |
029 | mSource->setEnabled( false ); |
030 | mSource = mEntity->getAnimationState( animation ); |
031 | mSource->setEnabled( true ); |
032 | mSource->setWeight(1); |
033 | mSource->setTimePosition(0); |
038 | AnimationState *newTarget = mEntity->getAnimationState( animation ); |
041 | // oops, weren't finished yet |
042 | if ( newTarget == mTarget ) |
044 | // nothing to do! (ignoring duration here) |
046 | else if ( newTarget == mSource ) |
048 | // going back to the source state, so let's switch |
051 | mTimeleft = mDuration - mTimeleft; // i'm ignoring the new duration here |
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 ) |
059 | // simply replace the target with this one |
060 | mTarget->setEnabled( false ); |
061 | mTarget->setWeight(0); |
065 | // old target becomes new source |
066 | mSource->setEnabled( false ); |
067 | mSource->setWeight(0); |
071 | mTarget->setEnabled( true ); |
072 | mTarget->setWeight( 1.0 - mTimeleft / mDuration ); |
073 | mTarget->setTimePosition(0); |
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; |
084 | mTarget->setEnabled( true ); |
085 | mTarget->setWeight(0); |
086 | mTarget->setTimePosition(0); |
090 | void AnimationBlender::addTime( Real time ) |
100 | mSource->setEnabled( false ); |
101 | mSource->setWeight(0); |
103 | mSource->setEnabled( true ); |
104 | mSource->setWeight(1); |
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 ); |
116 | if (mSource->getTimePosition() >= mSource->getLength()) |
124 | mSource->addTime( time ); |
125 | mSource->setLoop(loop); |
128 | AnimationBlender::AnimationBlender( Entity *entity ) : mEntity(entity) |
AnimationBlenderDemo.h
view source print ?
01 | <P>#ifndef __ANIMATIONBLENDER_DEMO_H__ |
02 | #define __ANIMATIONBLENDER_DEMO_H__ |
03 | #include "ExampleApplication.h" |
04 | #include "AnimationBlender.h" |
05 | class AnimationBlenderDemoFrameListener : public ExampleFrameListener |
09 | AnimationBlender* mAnimationBlender; |
10 | bool walking, jumping; |
12 | AnimationBlenderDemoFrameListener(RenderWindow* mWin, Camera* mCam, Entity* ent) |
13 | : ExampleFrameListener(mWin, mCam, false , false ), mNinjaEnt(ent) |
15 | mAnimationBlender = new AnimationBlender(mNinjaEnt); |
16 | mAnimationBlender->init( "Walk" , true ); |
20 | virtual ~AnimationBlenderDemoFrameListener() |
22 | if (mAnimationBlender) |
24 | // delete mAnimationBlender; |
27 | bool frameRenderingQueued( const FrameEvent& evt) |
29 | if (!ExampleFrameListener::frameRenderingQueued(evt)) |
35 | mAnimationBlender->blend( "Walk" ,AnimationBlender::BlendWhileAnimating, 0.2, true ); |
38 | if (mKeyboard->isKeyDown( OIS::KC_SPACE ) && !jumping) |
41 | mAnimationBlender->blend( "Jump" ,AnimationBlender::BlendWhileAnimating, 0.2, false ); |
45 | if (mAnimationBlender->complete) |
47 | mAnimationBlender->blend( "Idle1" ,AnimationBlender::BlendWhileAnimating, 0.02, true ); |
51 | mAnimationBlender->addTime(evt.timeSinceLastFrame); |
55 | class AnimationBlenderDemoApp : public ExampleApplication |
58 | AnimationBlenderDemoApp() {} |
62 | void createFrameListener() |
64 | mFrameListener = new AnimationBlenderDemoFrameListener(mWindow, mCamera, mNinjaEnt); |
65 | mRoot->addFrameListener(mFrameListener); |
AnimationBlenderDemo.cpp
view source print ?
01 | <P>#include "AnimationBlenderDemo.h" |
02 | #include <OgreStringConverter.h> |
03 | void AnimationBlenderDemoApp::createScene() |
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); |
13 | #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32 |
14 | #define WIN32_LEAN_AND_MEAN |
16 | INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE , LPSTR strCmdLine, INT ) |
18 | int main( int argc, char **argv) |
21 | AnimationBlenderDemoApp app; |
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 ); |
28 | std::cerr << "An exception has occured: " << e.getFullDescription(); |
不過由于Ninja.mesh的動畫時間太小, 很難看出混合效果, 以上代碼适合Ogre1.6.5