天天看点

Ogre角色控制

最近研究了下Ogre的角色控制, 看了Ogre1.7 Character的例子, 这里对角色控制做一些总结, 写的不是很专业O(∩_∩)O~.

      一般的角色控制将全身骨骼做成一个个动画, 骨骼带动全身模型. 例如攻击的动画, 手攻击的时候身体和腿脚也会做一定的动作, 这样全身的骨骼带动整个模型来实现动画效果, 这样实现起来比较简单, 在大部分的时候效果也不错, 但是这在做角色控制的这样也带来很多问题.

      典型的问题是, 当一个动画进行的时候如果要执行另一个动画, 那么这两个动画会相互影响, 导致很别扭的现象发生, 这是因为两个动画用的同一副骨骼, 每个动画帧对骨骼影响, 两个动作相互叠加而导致.

      如上图, 1是正常的Idel动画, 2是正常的Attract动画, 3是Idel+Attract动画, 可以看到, 攻击动画明显受到影响.

      如果再不是很明显的情况下, 可以直接混用骨骼动画, 另一种是在执行一个动画的时候将上一个动画关闭, 在执行当前动画.

  1. mAnimation->setEnabled(false); 
  2. mAnimation->setTimePosition(0); 
  3. mAnimation = mEntity->getAnimationState("idle_1"); 
  4. mAnimation->setEnabled(true); 
  5. mAnimation->setLoop(true); 

mAnimation->setEnabled(false);

mAnimation->setTimePosition(0);

mAnimation = mEntity->getAnimationState("idle_1");

mAnimation->setEnabled(true);

mAnimation->setLoop(true);

骨骼融合

      查看Ogre1.7 Character的demo, 会发现他的角色控制的比较好, 但是也会发现在跳的时候不能攻击或者拔武器, 这里对他进行简要的分析.

      查看Sinbad角色骨骼动画, 会发现他有很多动画.

        其中会发现有RunBase, TopBase, IdleBase, IdleTop这些动画, Base名称的动画为下半身动画, Top名称的为动画为上半身动画, 简言之就是上半身动画只会驱动上身骨骼来影响上半身, 下半身动画只会驱动下半身骨骼来影响下身模型.

      为什么要这么做? 由前面的分析我们得知: 当两个骨骼动画同时执行时会相互影响. 那么将不同的骨骼动画分开, 在执行的时候不影响其他不属于本动作的骨骼部分, 那么就会更自然.

      Ogre 1.7的例子就是这么做的, 在做某个动作的时候分别控制上半身和下半身的骨骼动画, 效果会更好. 例如在奔跑的时候, 下半身执行奔跑的动画, 如果这时候要拿出武器, 那么上半身就调用相关的骨骼动画, 而下半身依旧执行奔跑的动画, 不同部分执行不同的动画.

      这样对分别控制不同部分的动画也对程序员提出了一些要求, 为了把做出的动作更自然, 程序员需要对各种动作的衔接很熟悉, 不然会做出很别扭的动画.

      下一部分就是关于骨骼融合, 骨骼融合使不同动画的过度产生了更好的效果, 一般来说就是前一个骨骼动画淡出, 新骨骼动画淡入, 直至动画完全执行最后又淡出……

      在Ogre 1.7中, 使用了两个数组来模拟不同部分的骨骼淡入和淡出.

  1. void Player::fadeAnimations(float deltaTime) 
  2.     for (int i = 0; i < ANIM_SIZE; i++) 
  3.     { 
  4.         if (mFadingIn[i]) 
  5.         { 
  6.             // slowly fade this animation in until it has full weight 
  7.             float newWeight = mAnims[i]->getWeight() + deltaTime / 10; 
  8.             mAnims[i]->setWeight(Math::Clamp<float>(newWeight, 0, 1)); 
  9.             if (newWeight >= 1) mFadingIn[i] = false; 
  10.         } 
  11.         else if (mFadingOut[i]) 
  12.         { 
  13.             // slowly fade this animation out until it has no weight 
  14.             // and then disable it 
  15.             float newWeight = mAnims[i]->getWeight() - deltaTime / 10; 
  16.             mAnims[i]->setWeight(Math::Clamp<float>(newWeight, 0, 1)); 
  17.             if (newWeight <= 0) 
  18.             { 
  19.                 mAnims[i]->setEnabled(false); 
  20.                 mFadingOut[i] = false; 
  21.             } 
  22.         } 
  23.     } 

void Player::fadeAnimations(float deltaTime)

{

for (int i = 0; i < ANIM_SIZE; i++)

{

if (mFadingIn[i])

{

// slowly fade this animation in until it has full weight

float newWeight = mAnims[i]->getWeight() + deltaTime / 10;

mAnims[i]->setWeight(Math::Clamp<float>(newWeight, 0, 1));

if (newWeight >= 1) mFadingIn[i] = false;

}

else if (mFadingOut[i])

{

// slowly fade this animation out until it has no weight

// and then disable it

float newWeight = mAnims[i]->getWeight() - deltaTime / 10;

mAnims[i]->setWeight(Math::Clamp<float>(newWeight, 0, 1));

if (newWeight <= 0)

{

mAnims[i]->setEnabled(false);

mFadingOut[i] = false;

}

}

}

}

      更新骨骼的时候会调用此函数, 用来模拟淡入淡出的效果, 他检查mFadingIn[i]和mFadingOut[i]全部骨骼是否是淡入,或者淡出, 如果是那么会增加对应骨骼动画的权值(weight)骨骼动画执行的比重, 其值为(0~1) 如果是1, 那么骨骼已经完成过度, 将其置为false.

      可以看到Weight是用来控制角色骨骼执行程度的.

      这里说明一下AnimationState中的几个函数.

  1. void setTimePosition(Real timePos); 
  2. Real getLength() const; 
  3. Real setWeight(Real weight); 

void setTimePosition(Real timePos);

Real getLength() const;

Real setWeight(Real weight);

TimePosition : 是指动画当前的帧数(当前动画执行的时间点), 比如0~100帧是完整的动画, 超过100可设置跳回到0执行循环.

Length : 是指动画的全部帧数(执行完毕动画所用的时间).

Weight : 如上图所示, 动画执行的程度(0~1).

      在写程序的时候,会发现调用hasEnded()来判断当前动画是否执行完毕不会执行预期的行为, 这是因为hasEnded()只在动画不执行Loop循环的时候有效,那么在Loop状态下就需要通过一个变量timer来记录当前动画执行的时间, 通过比较timer和Length来判断此次动画是否执行完毕.例如此次动画执行完毕后调用idle动画:

  1. mTimer += timeSinceLastFrame; 
  2. if ( mTimer > mAnimation->getLength()) 
  3.     mAnimation->setEnabled(false); 
  4.     mAnimation->setTimePosition(0); 
  5.     mAnimation = mEntity->getAnimationState("idle_1"); 
  6.     mAnimation->setEnabled(true); 
  7.     mAnimation->setLoop(true); 

mTimer += timeSinceLastFrame;

if ( mTimer > mAnimation->getLength())

{

mAnimation->setEnabled(false);

mAnimation->setTimePosition(0);

mAnimation = mEntity->getAnimationState("idle_1");

mAnimation->setEnabled(true);

mAnimation->setLoop(true);

}

转载自:http://blog.csdn.net/flyingpig204/article/details/5713306