瘸腿蛤蟆原创笔记,欢迎转载,转载请标明出处:
代码可以在:http://download.csdn.net/detail/notbaron/7870335 处下载
上篇回顾
本篇名言:一个客观的艺术不只是用来看的,而是活生生的。但是你必须知道如何去靠近它,因此你必须要做静心。 [OSHO]
上篇中,蛤蟆学习了Box2d物理引擎中的Box2dTestBed代码结构,接下去蛤蟆继续学习这个Box2d引擎的使用,这次我们接着37篇的Ray-cast 使用,来介绍Ray-cast的原理。
理论介绍
我们先学习这次用到的理论知识,主要是一些类和函数介绍:
b2ContactListener类,这个b2ContactListener类(有源代码提供, 在b2WorldCallbacks.h文件中)是获取碰撞信息。可以使用这些信息用来声音和游戏逻辑。我们可以通过在时间步之后遍历得到接触结果。但是因为分布执行可能会错过一些接触。此外我们在一个时间步中会得到很多回调函数(我们需要高效的回调函数)。我们不能在这个回调函数中创建或摧毁Box2D的实体。
class b2RayCastCallback这个类是 raycasts.的回调函数类。主要实现如下这个函数
virtualfloat32ReportFixture( b2Fixture* fixture, constb2Vec2& point,
constb2Vec2&normal, float32 fraction) = 0;
参数 Fixture表示被ray击中的fixture,point表示初始交点, normal法向相量。
在查询中发现FIXTURE是被调用,返回-1,0,fraction 或者1 。-1表示忽视,0 表示停止ray case,Fraction 表示从这个点截断, 1 表示不截断 ray 并继续。
class DestructionListener : publicb2DestructionListener 在删除Body,隐性删除Fixture和Joint的时候会被调用。
class GLESDebugDraw : public b2Draw这个类实现DEBUG画图调用。在每个时间步内都会被调用。
这个Draw类被继承和注册,提供在游戏中画物理实体以供调度。由于Box2D中所有对象都是不可见的,要创建可见对象我们得用刚体定义中的userData属性来创建自己的图形,如果不创建自己的图形,则用b2DebugDraw类来实现物体的可见,方便调试。
void DrawDebugData(); 这个函数用来调用绘画shapes,其他debug数据。
Draw函数,是Node类的函数。
virtual void draw |
( |
Renderer * |
renderer, |
const Mat4 & |
transform, |
||
bool |
transformUpdated |
||
) |
这个函数是Node类的虚函数,所有继承于Node的函数都可以去实现它。
官方解释如下:
重写这个方法来绘制你自己的节点。 以下的GL状态是默认开启的:
|
CustomCommand这个类继承于RenderCommand 如下图1所示:
RenderCommand是RenderCommand体系的基类.渲染器(Renderer) 指定如何渲染
RenderCommands对象.
具体步骤
1、定义一个碰撞类RayCastNew
首先我们创建一个RayCastNew.h文件中,定义一个类RayCastNew,让其继承于b2ContactListener(该类再做理论介绍了已经)。在该类中定义一些常用然后定义一个析构函数:析构函数主要作用是创建一个物理世界,最后增加一个Step函数用于每帧的世界变化。这个类如下:
class RayCastNew : public b2ContactListener
{
public:
enum
{
e_maxBodies = 256 //定义最多Body数量
};
enum Mode //定义3个模式
{
e_closest,
e_any,
e_multiple
};
RayCastNew() //析构函数,主要是创建物理世界,只调用一次,完成第一次的世界构建。
{
b2Vec2 gravity;
gravity.Set(0.0f,-10.0f); //定义一个重力向量
m_world= new b2World(gravity);//通过向量创建一个物理世界
{
b2BodyDef bd; //定义变量,用于定义Body参数。
b2Body* ground = m_world->CreateBody(&bd);//创建地面Body.
b2EdgeShape shape; //定义变量边缘形状.
shape.Set(b2Vec2(-40.0f, 0.0f),b2Vec2(40.0f, 0.0f));//设置边缘形状的参数
ground->CreateFixture(&shape,0.0f); //关联地面Body和边缘形状(其实就是一条线)
GLESDebugDraw * _debugDraw =newGLESDebugDraw(0.5);//定义GLESDebugDraw类,用于画底面
m_world->SetDebugDraw(_debugDraw);//注册GLESDebugDraw
uint32 flags = 0;
flags+= b2Draw::e_shapeBit;//设置需要画的内容
_debugDraw->SetFlags(flags);//加载需要画内容的位
m_world->DrawDebugData();//画出地面Body.
}
m_angle= 0.0f; //设置角度
m_mode= e_closest; //设置模式
}
void Step(Settings*settings ) //实现step函数
{
bool advanceRay =settings->pause == 0 ||settings->singleStep; //参看Setting变量的选项是是否为0.
float32 L = 11.0f; //设置射线长度
b2Vec2 point1(0.0f, 10.0f); //射线起点
b2Vec2 d(L * cosf(m_angle), L * sinf(m_angle)); //射线的长度向量
b2Vec2 point2 = point1 + d; //射线的终点
if (m_mode ==e_closest) //根据模式不同选择不同的分支
{
RayCastClosestCallback callback; //定义回调函数变量
m_world->RayCast(&callback,point1, point2);//定义射线的起终点,和射线的回调函数,当有射线碰撞的是偶会调用callback变量中的函数。
if (callback.m_hit) //这个变量是RayCastClosestCallback中的,标记是否有碰撞,这个变量会在碰撞后被callback中的函数ReportFixture设置。
{
m_debugDraw.DrawPoint(callback.m_point,5.0f,b2Color(0.4f, 0.9f, 0.4f)); //画点
m_debugDraw.DrawSegment(point1,callback.m_point,b2Color(0.8f,0.8f, 0.8f)); //画线
b2Vec2 head = callback.m_point + 0.5f * callback.m_normal; //得到法向量的一半长度
m_debugDraw.DrawSegment(callback.m_point,head,b2Color(0.9f, 0.9f, 0.4f)); //画线,是从碰撞点到法向量长度的一半。
}
else
{
m_debugDraw.DrawSegment(point1,point2,b2Color(0.8f, 0.8f, 0.8f));//没有碰撞就画整条直线
}
}
if (advanceRay)
{
m_angle+= 0.25f * b2_pi / 180.0f; //每帧都改变角度大小
}
}
b2EdgeShape m_edge; //以下是变量定义不一一解释了
Mode m_mode;
GLESDebugDraw m_debugDraw;
int32 m_textLine;
b2World* m_world;
b2Body* m_groundBody;
b2PolygonShape m_polygons[4];
b2CircleShape m_circle;
int32 m_bodyIndex;
b2Body* m_bodies[e_maxBodies];
float32 m_angle;
};
2、定义实现b2RayCastCallback类
在RayCastNew.h函数中,加入如下相关 结构体和类定义。
struct ContactPoint //结构体,定义碰撞相关的信息变量
{
b2Fixture* fixtureA;//碰撞物体A
b2Fixture* fixtureB; //碰撞物体B
b2Vec2 normal; //法向量
b2Vec2 position; //碰撞位置
b2PointState state; //碰撞状态
float32 normalImpulse; //法向冲量
float32 tangentImpulse; //切向冲量
float32 separation; //分离
};
const int32 k_maxContactPoints = 2048;
class RayCastClosestCallback : publicb2RayCastCallback//该类继承于b2RayCastCallback,会在射线发生碰撞的时候被调用
{
public:
RayCastClosestCallback()//析构函数,初始设置为FALSE。
{
m_hit= false;
}
float32 ReportFixture(b2Fixture*fixture,const b2Vec2& point, const b2Vec2& normal, float32 fraction)
{
b2Body* body = fixture->GetBody();//获取碰撞的Body
void* userData = body->GetUserData();//获取碰撞的Body的数据
if (userData) //如果有Body信息
{
int32 index = *(int32*)userData;//获取数据
if (index == 0)
{
// By returning -1, we instruct the calling code to ignore this fixtureand
// continue the ray-cast to the next fixture.
return -1.0f;
}
}
m_hit= true;//设置碰撞为true。
m_point= point;//设置碰撞的点
m_normal= normal;//设置碰撞的法向量。
returnfraction;//返回fraction表示,射线从碰撞的地方截断。
}
bool m_hit; //以下三个变量
b2Vec2 m_point;
b2Vec2 m_normal;
};
#define RAND_LIMIT 32767
#define DRAW_STRING_NEW_LINE 25
inline float32 RandomFloat() //这个是随机浮点函数
{
float32 r = (float32)(std::rand()& (RAND_LIMIT));
r /=RAND_LIMIT;
r =2.0f * r - 1.0f;
return r;
}
/// Random floating point number in range [lo, hi]
inline float32 RandomFloat(float32lo,float32 hi) //这个是随机浮点函数,范围在lo到hi
{
float32 r = (float32)(std::rand()& (RAND_LIMIT));
r /=RAND_LIMIT;
r =(hi -lo) * r+lo;
return r;
}
struct Settings//定义结构体,用于定义所有和绘图相关的信息。
{
Settings()
{
viewCenter.Set(0.0f,20.0f);
hz= 60.0f;
velocityIterations= 8;
positionIterations= 3;
drawShapes= 1;
drawJoints= 1;
drawAABBs= 0;
drawContactPoints= 0;
drawContactNormals= 0;
drawContactImpulse= 0;
drawFrictionImpulse= 0;
drawCOMs= 0;
drawStats= 0;
drawProfile= 0;
enableWarmStarting= 1;
enableContinuous= 1;
enableSubStepping= 0;
enableSleep= 1;
pause= 0;
singleStep= 0;
}
b2Vec2 viewCenter;
float32 hz;
int32 velocityIterations;
int32 positionIterations;
int32 drawShapes;
int32 drawJoints;
int32 drawAABBs;
int32 drawContactPoints;
int32 drawContactNormals;
int32 drawContactImpulse;
int32 drawFrictionImpulse;
int32 drawCOMs;
int32 drawStats;
int32 drawProfile;
int32 enableWarmStarting;
int32 enableContinuous;
int32 enableSubStepping;
int32 enableSleep;
int32 pause;
int32 singleStep;
};
3、定义个视图层类Box2D类
该类主要是调用RayCastNew类,我们只要理解Box2D是RayCastNew 类的容器即可。主要实现的函数式draw函数,这个函数是Node类的虚函数,实现可以实现Node的画图。
该类的头文件如下描述:
#pragma once
#include "cocos2d.h"//包含cocos2d头文件
#include "GLES-Render.h"//这个文件来自源码包里面,主要负责绘图功能的,不然咱们什么都看不到的了
#include "RayCastNew.h"//包含碰撞类头文件
USING_NS_CC;
class Box2D :
publicLayer
{
public:
staticBox2D*viewWithEntryID(); //这个是静态类,被HelloWorld.pp中的init函数调用
bool Box2D::initWithEntryID();
Box2D(void);
~Box2D(void);
RayCastNew* m_test; //定义碰撞体类
Settings settings;
virtualvoid draw(Renderer *renderer,constMat4 &transform, uint32_t flags)override;//重写Node的draw函数
CustomCommand _customCmd; //定义渲染对象类
void Box2D::onDraw(const Mat4&transform, uint32_tflags);//被draw调用
};
CPP实现如下:
#include "Box2D.h"
Box2D::Box2D(void) //构造函数
{
}
Box2D::~Box2D(void) //析构函数
{
}
Box2D* Box2D::viewWithEntryID() //静态函数是实现
{
Box2D*pView =newBox2D();
pView->initWithEntryID();
pView->autorelease();
return pView;
}
bool Box2D::initWithEntryID()
{
m_test= newRayCastNew(); //新建一个RayCastNew对象
returntrue;
}
void Box2D::draw(Renderer *renderer,constMat4 &transform,uint32_t flags)//重写Node类的draw
{
Layer::draw(renderer,transform,flags);
_customCmd.init(_globalZOrder);//命令按深度排序
_customCmd.func = CC_CALLBACK_0(Box2D::onDraw,this,transform, flags);//定义回调函数onDraw
renderer->addCommand(&_customCmd);
}
void Box2D::onDraw(const Mat4 &transform, uint32_t flags)
{
Director* director =Director::getInstance();//获取实例
CCASSERT(nullptr !=director,"Director is null when seting matrixstack");
director->pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
director->loadMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW,transform);
GL::enableVertexAttribs( cocos2d::GL::VERTEX_ATTRIB_FLAG_POSITION );
m_test->Step(&settings);//调用step
m_test->m_world->DrawDebugData();//实现画图
CHECK_GL_ERROR_DEBUG();
director->popMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
}
4、HelloWorld文件处理
首先在HelloWolrd.h头文件中,加入头文件:
#include "Box2D.h"
然后在HelloWorld.cpp文件中,找到init函数,加入如下:
Box2D* view =Box2D::viewWithEntryID();//调用Box2D的静态函数,创建一个Layer,该Layer中包含碰撞的RayCast物理世界。
addChild(view,0, 2);//将Layer添加到HelloWorld 层中。
view->setScale(15);//设置放大系数
view->setAnchorPoint( Vec2(0,0));//设置锚点
view->setPosition(Vec2(origin.x+visibleSize.width/2,origin.y+visibleSize.height/3) );//设置显示位置
这些代码实现将试图类增加到HelloWorld类中。
然后编译即可。