Box2D C++教程15-用户数据
本文出自https://shuwoom.com博客,欢迎访问!
备注:由于本人最近在学习box2d引擎,而中文资料中好的文章比较少,我就在google上找了一些英文资料。于是,发现了网上的Box2Dtutorial系列文章,觉得写得挺好的,于是做了一些翻译和大家分享分享。由于这是我第一次翻译技术型文章,翻译不当的地方还请各位多理解。
下面我给出原文网址:http://www.iforce2d.net/b2dtut/user-data,本系列翻译文章仅用于学习交流之用,请勿用于商业用途。欢迎各位转载!
用户数据
在之前的话题中,我们可以看到从游戏的实体类(entity class)中引用物理对象(physics object)是多么的有用。相反的话,也是挺有用的---------在游戏中一个physics object的指针指向一个entity。在Box2D中这叫做user data,这是一个你用来保存一些对你有用信息的指针。下面的类就有这样的功能:
.b2Body
.b2Fixture
.b2Joint
Box2D不关心这信息是什么,因为Box2D和这信息没有任何关系。Box2D只是持有它,并在你需要的时候告诉你。上面的类都有下面的函数来实现这个功能:
//in b2Body, b2Fixture, b2Joint
void SetUserData(void* data);
void* GetUserData();
在bodies和fixtures的user data上做一些设置会对接下来的话题非常有用,那么久让我们尝试用一个简单的例子来掌握它吧。在这个实例中,我们将完全实现在之前的话题中的功能(获取渲染的位置和游戏实体的速度),但我们不会通过在Ball类中存储一个指向physic body的指针。相反,我们会在physics body中存储一个指向Ball对象的指针,并且在每一个时间步后,我们会随时用新的信息更新ball类成员变量。注意,这不是一个实用的方法来完成这个任务,它只是用来演示而已。
为了从每一个physics body中获取user data,我们也会尝试在场景中遍历所有的bodies。它是通过bodies链接列表实现的,而不是在一个恰当的场景中循环实现。我们可以使用b2World::GetBodyList()来获得链表中的第一个元素来开始迭代。
我们就从之前话题中的代码开始并做一些小的修改。首先,取出所有m_body成员变量中引用的Ball类,并修改Ball的构造函数,把Ball类自己作为创建的physic body的user tata:
Ball(b2World* world, float radius) {
m_radius = radius;
//set up dynamic body
b2BodyDef myBodyDef;
myBodyDef.type = b2_dynamicBody;
myBodyDef.position.Set(0, 20);
b2Body* body = world->CreateBody(&myBodyDef);
//set this Ball object in the body's user data
body->SetUserData( this );
//add circle fixture
b2CircleShape circleShape;
circleShape.m_p.Set(0, 0);
circleShape.m_radius = m_radius;
b2FixtureDef myFixtureDef;
myFixtureDef.shape = &circleShape;
myFixtureDef.density = 1;
body->CreateFixture(&myFixtureDef);
}
接下来,想Ball中添加一些成员变量来保存我们需要的物理信息:
b2Vec2 m_position;
float m_angle;
b2Vec2 m_linearVelocity;
….再用上面这些成员变量取代之前使用的m_body->GetPosition(),m_body->GetAngle()和m_body->GetLinearVelocity()。例如在renderAtBodyPosition()函数中确定笑脸的位置部分的代码是这样:
glTranslatef( m_position.x, m_position.y, 0 );
glRotatef( m_angle * RADTODEG, 0, 0, 1 );
编译运行程序,你会看到我们正在回到初始阶段的最后一个主题,所有的ball都是渲染在(0,0)处并没有旋转。
我们可以在Step()函数中做一些修改,在Test::Step()函数调用之后,我们在渲染物体之前,需要从他们的physics bodies中更新ball对象的位置。要做到这一点,要在世界中遍历所有的bodies,获取需要的position/angle/velocity值并且设置包含在user data中的Ball对象:
b2Body* b = m_world->GetBodyList();//get start of list
while ( b != NULL ) {
//obtain Ball pointer from user data
Ball* ball = static_cast<Ball*>( b->GetUserData() );
if ( ball != NULL ) {
ball->m_position = b->GetPosition();
ball->m_angle = b->GetAngle();
ball->m_linearVelocity = b->GetLinearVelocity();
}
//continue to next body
b = b->GetNext();
}
这样的行为可以追溯到之前。再一次说明,不推荐你为了渲染而使用这个方法来链接游戏entities到physics body,这只是演示说明user data的函数功能。要想渲染,之前的方法会更容易实现和管理。从physics body的更新中获得信息对我们帮助不大,因为我们是要持续地渲染图形而不过去关系在物理模拟世界中正在发生的事。
那么在什么样的情况下我们会使用到user data这一特征呢?这一点在我们想要从物理引擎中获取我们无法驱策的正在发生的信息时会有用,例如当fixtures碰撞,它们和什么碰撞以及相应的反应等等。其他有用的地方包括ray-casting或者AABB查询相交的fixtures(在fixture中的user data获取相关联的游戏entity)。
设置复杂的user data
由于user data接受 void类型的指针,任何可以转换为void指针的都可以作为user data的参数。这个可以是单独的数字,现有对象指针(如上面的)或者和physics对象相关联的用来保存一些复杂信息的指针。这里有一些例子:
//setting and retrieving an integer
int myInt = 123;
body->SetUserData( (void*)myInt );
...later...
int udInt = (int)body->GetUserData();
//setting and retrieving a complex structure
struct bodyUserData {
int entityType;
float health;
float stunnedTimeout;
};
bodyUserData* myStruct = new bodyUserData;
myStruct->health = 4;//set structure contents as necessary
body->SetUserData(myStruct);
...later...
bodyUserData* udStruct = (bodyUserData*)body->GetUserData();
在每一种情况,你应该为user data设置相同类型的值。例如,如果你在第一个例子中给fixture传递一个integer的user data,那么所有的fixtures都应该被赋予integer型的user data。如果你的一些fixture被赋予integer的user data而其他的fixtures被赋予structure结构的user data,当要从fixture中调用GetUserData( )取回user data时,就说不清你是在处理integer还是structure(“碰撞callbacks”这一节的话题将会解释为甚在碰撞检测时你不能总是知道你在处理fixture还是body)。
在大多数应用中,使用包含多个成员变量的struecture结构传递给user data是很方便的。Box2D在你摧毁body/fixure/joint时没有删除任何的user data objects,因此你要记得在不许要使用它们是要自己清理。
在上面使用的structure结构中的成员变量,提供了一系列有限的属性使用,而且如果你的游戏中有不同的实体类型,那么它就不在灵活。看看'real scenarios' 这一节内容的碰撞callbacks,你会发现使用类继承的例子是一个更为实际的方法,特别是在你有许多类型的实体时。