上一篇讲了如何编译一个支持动画的fbx插件,以及读取显示。这里讲一下如何把两个文件的动画放在一起显示,效果如下:
|
|
|
问题的产生是这样子的,做fbx动画的人不熟练,明明是一个模型的两个动画,却偏偏要输出成两个单独的文件(如果是一个文件,直接切换AnimationList的动画就好了,就不需要用到Swtich了,当然重载动画节点的回调依然是有必要的)。要做到上面的效果基本思路是这样子的,用一个Swtich节点作为两个动画节点的父节点,在一段时间内显示一个节点,一段时间显示另一个节点,Swtich部分代码参考。区别在于我将开关条件换成了时间。部分代码如下:
class SwitchCallback :public osg::NodeCallback
{
public:
osg::Timer_t _startTick;
SwitchCallback(double time_run, double time_action) {
_startTick = osg::Timer::instance()->tick();
t1 = time_run;
t2 = t1 + time_action;
}
double t1, t2;
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::Switch* animationSwitch = dynamic_cast<osg::Switch*>(node);
if (animationSwitch && nv)
{
double pass = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());
while (pass > t2) pass -= t2;
if (pass < t1) {
animationSwitch->setValue(1, false);
animationSwitch->setValue(0, true);
}
else {
animationSwitch->setValue(0, false);
animationSwitch->setValue(1, true);
}
}
traverse(node, nv);
}
};
osg::ref_ptr<osg::Switch> root=new osg::Switch;
//添加模型到子节点,并设置为当前要遍历到的节点,为true
root->addChild(readAnimation("run.fbx", 0.5, osgAnimation::Animation::LOOP), true);
root->addChild(readAnimation("attack.fbx",3.0,osgAnimation::Animation::ONCE), false);
root->setUpdateCallback(new SwitchCallback(runtime, actionTime));
但是仅仅这样做是不是就足够了呢?会遇到这样的问题:第二段动画不是从时间原点开始播放的,准确的说,第二段动画一直在播放,只是在某个时间节点之后才显示,而这个时间节点如果不是第二段动画播放时长的倍数,就会出现这样的问题。另外细心的同学可能也发现了,上面的动画拼接效果图,人是有停顿几秒的,这是怎么实现的呢?
还得从动画的参数说起,下面这个读取动画的函数,我设置了三个参数,动画文件名,动画播放时间(一段),播放模式。
osg::Node* readAnimation(const char* s,double time,osgAnimation::Animation::PlayMode playmode) {
//读取带动画的节点
osg::ref_ptr<osg::Node> animationNode = osgDB::readNodeFile(s);
AnimationManagerFinder* m_cFinder = new AnimationManagerFinder();
m_cFinder->apply(animationNode);
animationNode->accept(*m_cFinder);
if (m_cFinder->_am.valid())
{
animationNode->setUpdateCallback(m_cFinder->_am.get());
}
osgAnimation::Animation* an= m_cFinder->_am->getAnimationList()[0];
an->setPlayMode(playmode);//设置播放模式
an->setDuration(time);//设置播放时间
m_cFinder->_am->playAnimation(an);
return animationNode.release();
}
播放模式有以下四种,要解决上面的问题就要用到ONCE(只播放一次),一般都会设置为LOOP(循环播放)。
ONCE, STAY, LOOP, PPONG |
将第二段动画的播放模式设置为ONCE,在每次开启第二个节点时都重新playanimation,为了控制动画需要用强制转换获取osgAnimation::BasicAnimationManager 这个对象。另外需要设置一个flag避免在Swtich回调中反复play。
if (!flag) {
osg::ref_ptr<osgAnimation::BasicAnimationManager> bam =dynamic_cast<osgAnimation::BasicAnimationManager*>(animationSwitch->getChild(1)->getUpdateCallback());
bam->playAnimation(bam->getAnimationList()[0]);
flag = true;
}
完整代码如下:
#include <osg/Switch>
#include <osg/ShapeDrawable>
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgGA/TrackballManipulator>
#include <osgAnimation/BasicAnimationManager>
struct AnimationManagerFinder : public osg::NodeVisitor
{
osg::ref_ptr<osgAnimation::BasicAnimationManager> _am;
AnimationManagerFinder() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) {}
void apply(osg::Node* node) {
if (_am.valid())
return;
if (node->getUpdateCallback()) {
osgAnimation::AnimationManagerBase* bam = dynamic_cast<osgAnimation::AnimationManagerBase*>(node->getUpdateCallback());
if (bam) {
_am = new osgAnimation::BasicAnimationManager(*bam);
return;
}
}
traverse(*node);
}
};
osg::Node* readAnimation(const char* s, double time, osgAnimation::Animation::PlayMode playmode) {
//读取带动画的节点
osg::ref_ptr<osg::Node> animationNode = osgDB::readNodeFile(s);
AnimationManagerFinder* m_cFinder = new AnimationManagerFinder();
m_cFinder->apply(animationNode);
animationNode->accept(*m_cFinder);
if (m_cFinder->_am.valid())
{
animationNode->setUpdateCallback(m_cFinder->_am.get());
}
osgAnimation::Animation* an = m_cFinder->_am->getAnimationList()[0];
an->setPlayMode(playmode);//设置播放模式
an->setDuration(time);//设置播放时间
m_cFinder->_am->playAnimation(an);
return animationNode.release();
}
class SwitchCallback :public osg::NodeCallback
{
public:
osg::Timer_t _startTick;
SwitchCallback(double time_run, double time_action) {
_startTick = osg::Timer::instance()->tick();
t1 = time_run;
t2 = t1 + time_action;
flag = false;
}
double t1, t2;
bool flag;
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::Switch* animationSwitch = dynamic_cast<osg::Switch*>(node);
if (animationSwitch && nv)
{
double pass = osg::Timer::instance()->delta_s(_startTick, osg::Timer::instance()->tick());
while (pass > t2) pass -= t2;
if (pass < t1) {
animationSwitch->setValue(1, false);
animationSwitch->setValue(0, true);
flag = false;
}
else {
animationSwitch->setValue(0, false);
animationSwitch->setValue(1, true);
if (!flag) {
osg::ref_ptr<osgAnimation::BasicAnimationManager> bam = dynamic_cast<osgAnimation::BasicAnimationManager*>(animationSwitch->getChild(1)->getUpdateCallback());
bam->playAnimation(bam->getAnimationList()[0]);
flag = true;
}
}
}
traverse(node, nv);
}
};
int main(int argc, char** argv)
{
osgViewer::Viewer viewer;
osg::ref_ptr<osg::Group> gr = new osg::Group;
osg::ref_ptr<osg::Switch> root = new osg::Switch;
root->addChild(readAnimation("run.fbx", 0.5, osgAnimation::Animation::LOOP), true);//添加模型到子节点,并设置为当前要遍历到的节点,为true
root->addChild(readAnimation("attack.fbx", 3.0, osgAnimation::Animation::ONCE), false);
root->setUpdateCallback(new SwitchCallback(2, 5));
osg::ref_ptr<osg::Geode> ge = new osg::Geode;
//添加一个长方体作为背景
ge->addDrawable(new osg::ShapeDrawable(new osg::Box(osg::Vec3d(0, 0, 0), 1000, 1, 1000)));
gr->addChild(ge);
gr->addChild(root);
///让osgviewer在一个便于观察的视点初始化,不用每次启动程序都调整位置
viewer.setCameraManipulator(new osgGA::TrackballManipulator);
viewer.getCameraManipulator()->setHomePosition(osg::Vec3d(1000, 100, 0), osg::Vec3d(0, 100, 0), osg::Vec3d(1, 0, 0));
viewer.setSceneData(gr.get());
viewer.setUpViewInWindow(200, 200, 1280, 720, 0);//设置窗口而不是全屏
return viewer.run();
}
第一段动画每次播放耗时0.5秒,设置为循环播放,Swtich控制每7秒,前2秒播放跑步动画,播放四遍
第二段动画每次播放耗时3秒,设置为一次播放,Swtich控制每7秒,后5秒播放挥砍动作,由于动画只有3秒,不足的2秒模型为静止状态。