用OgreOde创建一个会走动的角色
原文http://www.game798.com/html/2007-12/5156.htm
第一版 by SuperMegaMau
这个教程包括的代码和算法是作者自己的经验,也许不正确或不怎么准确,如果发现问题请纠正。
内容
- 1 介绍
- 2 创建物理模型
- 2.1 创建角色
- 2.2 获取 AABB
- 2.3 创建新空间
- 2.4 创建球
- 2.5 创建椭球
- 2.6 创建关节
- 3 移动角色
- 4 让角色爬起来
- 5 问题
|
介绍
我相信我不是第一人自问如何用OgreOde创建一个运动角色。搜索论坛和wiki后,我意识到这是一个很有用的信息。这个教程解释了如何创建一个可以在地形上行走的运动角色(包括其它meshes,如树和房屋)。
创建物理模型
我按照在Monster的方法用下图代表一个角色:
下面,我假设你对Ogre的SceneNodes, meshes 和AlignedBoxes都有所了解,并且会用SceneManager创建地形。
创建角色
首先创建一个SceneNode来放角色的mesh,在这个例子中我用了Ogre例子中的忍者模型。创建两个SceneNode并把它们连在一起。后面我会解释为什么是两个Node。
Entity* ninja = mSceneMgr->createEntity("ninja","ninja.mesh");
SceneNode* ninjaNode = mSceneMgr->getRootSceneNode()->createChildSceneNode("ninja");
SceneNode* modelNode = ninjaNode->createChildSceneNode("ninja_model");
modelNode->attachObject(ninja);
ninjaNode->setScale(0.05,0.05,0.05);
也许你已经注意到,ninjaNode被缩放得很小,这是因为如果mesh很大的话渲染的速度就变很慢(不知道为什么)。
获取 AABB(AxisAlignedBox, 轴对齐包围盒)
现在用AxisAlignedBox获取mesh的大小。
AxisAlignedBox aab = modelNode->getAttachedObject("ninja")->getBoundingBox();
Ogre::Vector3 min = aab.getMinimum()*ninjaNode->getScale();
Ogre::Vector3 max = aab.getMaximum()*ninjaNode->getScale();
Ogre::Vector3 center = aab.getCenter()*ninjaNode->getScale();
Ogre::Vector3 size(fabs(max.x-min.x),fabs(max.y-min.y),fabs(max.z-min.z));
float radius = (size.x>size.z)?size.z/2.0f:size.x/2.0f;
创建一个新空间
我们需要创建一个新空间把角色放在其中,并且取消内部碰撞检测。
OgreOde::SimpleSpace* dollSpace = new OgreOde::SimpleSpace(_world->getDefaultSpace());
dollSpace->setInternalCollisions(false);
创建球体(腿部)
现在有了两个SceneNodes, "ninjaNode" 和 "modelNode"。ninjaNode是代表你角色的节点,modelNode是你真正贴mesh的地方。这么做是因为mesh的中心总是在OgreOde::Body的中心,所以我们用ninjaNode来创建碰撞体的位置,然后根据ninjaNode和OgreOde::Body来获得mesh的正确位置。
左边图是用一个SceneNode所得到的效果,右边是用两个SceneNode。你可以注意到,左边的角色悬浮在半空中。下面代码创建了一个球体代表角色的脚部。我们需要一个SphereGeometry和一个TransformGeometry 将球体放到正确位置。
译注:注释为我的猜想,具体不知道步骤这么复杂,欢迎纠正。
(1) 创建一个碰撞体,命名为feet
(2) 设置碰撞体为球形物体,半径为AABB获得的半径
(3) SphereGeometry,半径为AABB获得的半径
(4) TransformGeometry,空间为刚才创建的空间, TransformGeometry似乎是为了包含某特定形状的几何体
(5) 改变modelNode相对于ninjiaNode的位置,以便让脚占到地上
(6) 让TransformGeometry包含一个OgreOde::Body和一个几何体
(7) 将Ogre::Body粘到ninjaNode上
怀疑创建SphereGeometry是否只是让Ogre::Body具象化
OgreOde::Body* dollFeetBody = new OgreOde::Body("feet");
dollFeetBody->setMass(OgreOde::SphereMass(70*2.5,radius));
OgreOde::SphereGeometry* feetGeom = new OgreOde::SphereGeometry(radius);
OgreOde::TransformGeometry* feetTrans = new OgreOde::TransformGeometry(dollSpace);
modelNode->translate(Vector3(0,-radius/ninjaNode->getScale().y,0));
feetTrans->setBody(dollFeetBody);
feetTrans->setEncapsulatedGeometry(feetGeom);
ninjaNode->attachObject(dollFeetBody);
创建椭球体
对于角色的上半身用一个椭球体来表示。
译注:和上面一样。
(1) 创建Ogre::Body
(2) 设置Ogre::Body形状,另外设置不被重力影响,
(3) 创建TransformGeometry,空间为刚才创建的空间
(4) 创建CapsuleGeometry,半径为AABB获得半径
(5) 设置CapsuleGeometry位置和方向和阻尼
(6) 让TransformGeometry包含Ogre::body
(7) 让TransformGeometry包含CapsuleGeometry
(8) 将Ogre::Body粘到ninjiaNode上
OgreOde::Body* dollTorsoBody = new OgreOde::Body("torso");
dollTorsoBody->setMass(OgreOde::CapsuleMass(70*2.5,radius,Vector3::UNIT_Y,radius));
dollTorsoBody->setAffectedByGravity(false);
dollTorsoBody->setDamping(0,50000);
OgreOde::TransformGeometry* torsoTrans = new OgreOde::TransformGeometry(dollSpace);
OgreOde::CapsuleGeometry* torsoGeom = new OgreOde::CapsuleGeometry(radius,size.y-4*radius,dollSpace);
torsoGeom->setPosition(Ogre::Vector3(0,size.y-((size.y-4*radius)/2+2*radius),0)); //can't find a good way to explain this
torsoGeom->setOrientation(Quaternion(Degree(90),Vector3::UNIT_X));
torsoTrans->setBody(dollTorsoBody);
torsoTrans->setEncapsulatedGeometry(torsoGeom);
ninjaNode->attachObject(dollTorsoBody);
这个几何体和脚的几何体在同一个空间,所以我们要取消内部碰撞检测。讲阻尼设置高些,并且取消重力,不然它会从那个球体上掉下来。
创建关节
剩下的事情就是将两个碰撞体连在一起了。一个绞连连接的代表是自行车前轮。
OgreOde::HingeJoint* joint = new OgreOde::HingeJoint();
joint->attach(dollTorsoBody,dollFeetBody);
joint->setAxis(Ogre::Vector3::UNIT_X); //set the rotation axis
注意:不要忘记记录所有碰撞体和连接的位置以便你之后能得到它们。你可以用ogre堆栈或者创建你自己的。
移动角色
你可以通过不同方法移动或者旋转你的角色,我决定通过改变碰撞体方向而不是施加力或者力矩。
前后移动
下面代码可以放在按键响应里执行,象KC_UP。现在你需要获取碰撞体,从堆栈或者hashTable中获得,然后获取它的方向。我用:
译注:猜想堆栈就是你为屏幕上所有物体所创建的Ogre::Body的一个列表。
OgreOde::Body* torso = torsoBodies->getObject("ninja");
Quaternion q = torso->getOrientation();
然后赋予脚一个角速度。
OgreOde::Body* feet = feetBodies->getObject("ninja");
feet->wake();
feet->setAngularVelocity(q*Ogre::Vector3(10*cos(1),0,10*sin(1)));
10是我们用的角速度,必须乘以三角函数以便让角色向正确的方向前进。
左右转动
下面代码同样放在按键响应中执行,比如KC_RIGHT。
OgreOde::Body* torso = torsoBodies->getObject("ninja");
Quaternion q1 = torso->getOrientation();
Quaternion q2(Degree(-4),Ogre::Vector3::UNIT_Y);
torso->setOrientation(q1*q2);
用Degree(-4)让角色向右转动,用正数向左转动。也许你已经注意到,我总是从椭球体获取或者设置方向。我没有太多想,我想如果你从脚的球体来获取和设置也应该没有什么问题。
注意:如果你松开按键,你必须把速度设置为0来停止运动。
feetbody->setAngularVelocity(Vector3(0,0,0));
feetBody->setLinearVelocity(Vector3(0,feetBody->getLinearVelocity().y,0));
让角色爬起来
最后,我们要确定你的角色不摔倒,所以我们需要不时重新设定他的垂直方向。
OgreOde::Body* torso = torsoBodies->getObject("ninja");
Quaternion q = torso->getOrientation();
Vector3 x = q.xAxis();
Vector3 y = q.yAxis();
Vector3 z = q.zAxis();
torso->wake();
torso->setOrientation(Quaternion(x,Vector3::UNIT_Y,z));
问题
部分代码没有我想象的那么好。我重新设定垂直方向会让角色有奇怪的行为。虽然我设置了速度为0,但是角色在一些不规则的表面上仍然无法停下来。