-
简介
本示例主要使用了osgAnimation中的timeline时间线的方式来管理场景中的动画。时间线的概念有点像把三维的渲染过程分成一帧一帧,打个比喻:timeline有点像在看动画片的时候用相机把正在播放的动画片场景按固定间隔拍摄成一张张的照片,最后把这一张张的照片管理起来,有点像反向模拟动画的过程。
-
示例
这个示例只能用Nathan.osg这个模型来运行,因为代码中的Animation动画依赖这个文件的内容,我们可以打开Nathan.osg看看,可以发现这样几段内容:
osgAnimation::Animation {
UniqueID uniqid_Animation_698
name "Idle_Main"
num_channels 66
Vec3LinearChannel {
name "scale"
target "Foot_IK.L"
Keyframes 51 {
key 0.00000 1.00000 1.00000 1.00000
key 0.04000 1.00000 1.00000 1.00000
key 0.08000 1.00000 1.00000 1.00000
key 0.12000 1.00000 1.00000 1.00000
key 0.16000 1.00000 1.00000 1.00000
key 0.20000 1.00000 1.00000 1.00000
key 0.24000 1.00000 1.00000 1.00000
......
可以看到这个Idle_Main正好是Nathan.osg中的一段关键帧动画,同样源码中的Idle_Head_Scratch.02以及Idle_Nose_Scratch.01也可以在Nathan.osg中找到它们对应的动画
- Callback定义
struct NoseBegin : public osgAnimation::Action::Callback
{
virtual void operator()(osgAnimation::Action* action, osgAnimation::ActionVisitor* nv)
{
std::cout << "sacrebleu, it scratches my nose, let me scratch it" << std::endl;
std::cout << "process NoseBegin call back " << action->getName() << std::endl << std::endl;
}
};
struct NoseEnd : public osgAnimation::Action::Callback
{
virtual void operator()(osgAnimation::Action* action, osgAnimation::ActionVisitor* nv)
{
std::cout << "shhhrt shrrrrt shhhhhhrrrrt, haaa it's better"<< std::endl;
std::cout << "process NoseEnd call back " << action->getName() << std::endl << std::endl;
}
};
这两段Callback定义在Nose_Scratch Action下面,它们会在某一个时刻被调用。Callback定义在Action之中,它们为的是实现在某一个Action中某一时刻被调用。而这些Action会在场景动画的某一个时间线上被调用(有点像我们之前打的比喻,在某一张拍摄照片绑定了一个Action,在这一时间到来时这个Action会被触发来运行它所包含的动画和回调)
_scratchNose->setCallback(0.0, new NoseBegin);
_scratchNose->setCallback(_scratchNose->getNumFrames()-1, new NoseEnd);
可以知道它们在_scratchNose开始时(0.0)以及_scratchNose结束时被调用。
- 定义Action
在代码中Action定义在NodeCallback(ExampleTimelineUsage)之中
_releaseKey = false;
_manager = manager;
const osgAnimation::AnimationList& list = _manager->getAnimationList();
osgAnimation::AnimationMap map;
for (osgAnimation::AnimationList::const_iterator it = list.begin(); it != list.end(); it++)
map[(*it)->getName()] = *it;
_mainLoop = new osgAnimation::ActionStripAnimation(map["Idle_Main"].get(),0.0,0.0);
_mainLoop->setLoop(0); // means forever
map["Idle_Head_Scratch.02"]->setDuration(4.0);
_scratchHead = new osgAnimation::ActionStripAnimation(map["Idle_Head_Scratch.02"].get(),0.2,0.3);
_scratchHead->setLoop(1); // one time
map["Idle_Nose_Scratch.01"]->setDuration(10.0); // set this animation duration to 10 seconds
_scratchNose = new osgAnimation::ActionStripAnimation(map["Idle_Nose_Scratch.01"].get(),0.2,0.3);
_scratchNose->setLoop(1); // one time
// add the main loop at priority 0 at time 0.
osgAnimation::Timeline* tml = _manager->getTimeline();
tml->play();
tml->addActionAt(0.0, _mainLoop.get(), 0);
// add a scratch head priority 1 at 3.0 second.
tml->addActionAt(5.0, _scratchHead.get(), 1);
// populate time with scratch head
for (int i = 1; i < 20; i++)
{
// we add a scratch head priority 1 each 10 second
// note:
// it's possible to add the same instance more then once on the timeline
// the only things you need to take care is if you remove it. It will remove
// all instance that exist on the timeline. If you need to differtiate
// it's better to create a new instance
tml->addActionAt(5.0 + 10.0 * i, _scratchHead.get(), 1);
}
// we will add the scratch nose action only when the player hit a key
// in the operator()
// now we will add callback at end and begin of animation of Idle_Nose_Scratch.02
_scratchNose->setCallback(0.0, new NoseBegin);
_scratchNose->setCallback(_scratchNose->getNumFrames()-1, new NoseEnd);
将它们添加到Timeline之中,设置在弹起键盘上按键的时候添加一个Action
if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
{
if (_releaseKey) // we hit a key and release it execute an action
{
osgAnimation::Timeline* tml = _manager->getTimeline();
// dont play if already playing
if (!tml->isActive(_scratchNose.get()))
{
// add this animation on top of two other
// we add one to evaluate the animation at the next frame, else we
// will miss the current frame
tml->addActionAt(tml->getCurrentFrame() + 1, _scratchNose.get(), 1);
}
_releaseKey = false;
}
}
- 添加管理器
和其他管理器一样的使用方式,在场景中添加管理器到节点中
osg::ref_ptr<osgAnimation::TimelineAnimationManager> tl = new osgAnimation::TimelineAnimationManager(*animationManager);
root->setUpdateCallback(tl.get());
ExampleTimelineUsage* callback = new ExampleTimelineUsage(tl.get());
root->setEventCallback(callback);
root->getUpdateCallback()->addNestedCallback(callback);
附:这个示例中存在一个Bug,Idle_Head_Scratch.02并不会被调用,需要我们添加该动作的时间,使用setDuration设置一个Action运行的时候就好了。