您的位置:首页 > 移动开发 > Cocos引擎

【cocos3.x+box2d+tileMap】制作马里奥游戏(三)创世纪

2016-01-25 11:49 507 查看
     转载请注明来源:http://blog.csdn.net/pur_e/article/details/50578741
    嘿嘿,标题开得很牛叉,不过事实上也确实如此,我们将在这里创造一个游戏世界,虽然世界很简单,马里奥只能简单感受重力、接受碰撞、左右移动、跳跃,但确实已经是一个小小的世界了!
一、载入地图,初始化box2d等
       代码中我会加入比较详细的注释,主要还是看代码。

bool MarioScene::init(){
if(!Scene::init()){
return false;
}

//初始化box2d世界
initBox2d();

//创建地图
_map = TMXTiledMap::create("tmx/test.tmx");
//地图分辨率低,放大两倍
_map->setScale(2.0f);
this->addChild(_map,-129);

//initMap();
//创建静态刚体墙
createPhysical(2);

//创建马里奥
_mario = MarioPlayer::create();
_mario->setPosition(100, 600);
_mario->setName("player");
_mario->setTag(CONTACT_PROCESSER);
this->addChild(_mario);
//将马里奥添加到box2d世界
this->addBodyToWorld(_mario, b2_dynamicBody);

this->scheduleUpdate();
//添加触碰事件处理
auto listener = EventListenerTouchAllAtOnce::create();
listener->onTouchesBegan = CC_CALLBACK_2(MarioScene::onTouchesBegan, this);
listener->onTouchesEnded = CC_CALLBACK_2(MarioScene::onTouchesEnded, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

return true;
}

///////////////////////////////////////////////////////////////
//初始化Box2d世界
void MarioScene::initBox2d(){
//重力为10的世界
b2Vec2 gravity(0,-10);
_b2World = new b2World(gravity);
//设置允许进入休眠状态,休眠Body不需要任何模拟
_b2World->SetAllowSleeping(true);
//[ 开启连续物理测试,这是因为计算机只能把一段连续的时间分成许多离散的时间点,再对每个时间点之间的行为进行演算,如果时间点的分割不够细致,速度较快的两个物体碰撞时就可能会产生“穿透”现象,开启连续物理将启用特殊的算法来避免该现象。]
_b2World->SetContinuousPhysics(true);
//设置碰撞检测处理回调
_b2World->SetContactListener(this);

auto debugDraw = new GLESDebugDraw(PTM_RATIO);   //这里新建一个 debug渲染模块
uint32 flags = 0;
flags += b2Draw::e_shapeBit;
//    flags += b2Draw::e_jointBit;
//    flags += b2Draw::e_aabbBit;
//    flags += b2Draw::e_pairBit;
//    flags += b2Draw::e_centerOfMassBit;

debugDraw->SetFlags(flags);   //需要显示那些东西
_b2World->SetDebugDraw(debugDraw);    //设置

}

///////////////////////////////////////////////////////////////
//添加cocos对象到box2d世界中
void MarioScene::addBodyToWorld(Node* node, b2BodyType type,float scale){
b2BodyDef bodydef;
bodydef.type = type;
//scale可以外部手工传入
if (scale == -1) {
scale = node->getScale();
}
auto size = node->getContentSize();
//转换为世界坐标
auto pos = node->convertToWorldSpaceAR(Vec2(0,0));

bodydef.position.Set((pos.x)  / PTM_RATIO, (pos.y) / PTM_RATIO);
auto body = _b2World->CreateBody(&bodydef);
body->SetUserData(node);
node->setUserData(body);

b2PolygonShape box;
box.SetAsBox(size.width * scale / PTM_RATIO / 2 , size.height * scale / PTM_RATIO / 2);
node->boundingBox();
b2FixtureDef fixtureDef;
fixtureDef.shape = &box;
fixtureDef.density = 1;
fixtureDef.friction = 0;
//fixtureDef.restitution = 1.0f;
body->CreateFixture(&fixtureDef);
}


添加静态刚体墙相关信息,参见:【cocos3.x+box2d+tileMap】制作马里奥游戏(二) 制作地图
二、让世界运转
       box2d的世界运转需要update中定时调用step函数,而cocos也需要在update中处理与box2d世界的同步及马里奥移动。

void MarioScene::update(float dt){
float timeStep = 0.03f;
int32 velocityIterations = 8;
int32 positionIterations = 8;

//驱动box2d世界运转
_b2World->Step(timeStep, velocityIterations, positionIterations);
_b2World->DrawDebugData();

//处理对Player触摸操作,进行左右移动及停止
_mario->dealPlayerStatus(dt);

//处理地图,马里奥移动到屏幕指定位置开始,由地图移动替代之
dealMap(dt);

//将box2d世界与cocos对象关联起来,主要是位置,角度等
for(b2Body* b = _b2World->GetBodyList();b;b=b->GetNext()){
if(b->GetType() == b2_dynamicBody){
auto node = static_cast<Node*>(b->GetUserData());
if(node){
node->setPosition(b->GetPosition().x * PTM_RATIO,b->GetPosition().y * PTM_RATIO);
//角度也跟着变化
//node->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()));
}
}
}
}


       其中,马里奥移动、地图处理在下一节。
三、马里奥移动
       这个稍微有点麻烦,不过比起自己实现一个物理引擎,Box2d已经帮我们做了很多。
思路:
跳跃很简单,只要给马里奥一个向上的冲量就可以了,在box2d世界中自身的重力下,会很真实的有起跳-减速-下落-加速过程
左右移动稍微麻烦,需要我们自己来实现慢慢加速,比较快的减速,这里我们给马里奥一个状态属性,在每一帧的处理中,根据状态给他慢慢加速或减速
1.处理触摸事件
       在第一节初始化时,我们已经为场景添加了多重触摸事件的监听,提醒一下,要开启多重触摸,需要修改:

AppController.mm中:
// Enable or disable multiple touches
[eaglView setMultipleTouchEnabled:YES];


       然后我们来实现触摸事件处理 代码实现:

bool MarioScene::onTouchesBegan(const std::vector<Touch*>& touches,Event *event){
for(auto touch:touches){
auto location = touch->getLocation();
auto visibleRect = VisibleRect::getVisibleRect();
auto body = static_cast<b2Body*>(_mario->getUserData());

if(location.x > visibleRect.size.width / 2 && location.y < visibleRect.size.height / 2){
//触碰右下屏,添加状态
_mario->addStatus(MarioPlayer::STATUS_RIGHT_MOVING);
}else if(location.x < visibleRect.size.width / 2 && location.y < visibleRect.size.height / 2){
//触碰左下屏,添加状态
_mario->addStatus(MarioPlayer::STATUS_LEFT_MOVING);
}else if(location.y >= visibleRect.size.height / 2){
//触碰上屏,如果没有在跳跃,才进行跳跃
if(!_mario->haveStatus(MarioPlayer::STATUS_JUMP)){
//给马里奥一个向上20的冲量
body->ApplyLinearImpulse(b2Vec2(0,20), body->GetWorldCenter(), true);
_mario->addStatus(MarioPlayer::STATUS_JUMP);
}
}
}
return true;
}

void MarioScene::onTouchesEnded(const std::vector<Touch*>& touches,Event *event){
for(auto touch:touches){
auto location = touch->getLocation();
auto visibleRect = VisibleRect::getVisibleRect();
if(location.x > visibleRect.size.width / 2 && location.y < visibleRect.size.height / 2){
//删除触碰右下屏状态
_mario->delStatus(MarioPlayer::STATUS_RIGHT_MOVING);
}else if(location.x < visibleRect.size.width / 2 && location.y < visibleRect.size.height / 2){
//删除触碰左下屏状态
_mario->delStatus(MarioPlayer::STATUS_LEFT_MOVING);
}
}
}


2.在update中处理状态:

//处理对Player触摸操作,进行左右移动及停止
_mario->dealPlayerStatus(dt);


具体实现:

void MarioPlayer::dealPlayerStatus(float dt){
auto body = static_cast<b2Body*>(getUserData());
auto speed = body->GetLinearVelocity();
b2Vec2 impluse(0,0);
if(haveStatus(STATUS_RIGHT_MOVING)){
//当前是向右移动
//将马里奥方向调往右
_anim->setScaleX(1);
_normal->setScaleX(1);
//让马里奥动起来
_anim->setVisible(true);
_normal->setVisible(false);
//如果没有到达最大速度,则根据加速度慢慢增大
if(speed.x < MAX_SPEED){
impluse.x = std::min(MAX_SPEED / ACCELERATE_TIME * dt,MAX_SPEED - speed.x);
}
}else if(haveStatus(STATUS_LEFT_MOVING)){
//当前是向左移动
//将马里奥方向调往左
_anim->setScaleX(-1);
_normal->setScaleX(-1);
//让马里奥动起来
_anim->setVisible(true);
_normal->setVisible(false);
//如果没有到达最大速度,则根据加速度慢慢增大
if(speed.x > -MAX_SPEED){
impluse.x = -std::min(MAX_SPEED / ACCELERATE_TIME * dt,MAX_SPEED + speed.x);
}
}else{
//让马里奥不要动了
_anim->setVisible(false);;
_normal->setVisible(true);
if(std::abs(speed.x) <= MIN_SPEED){
//小于最小速度,直接停止
impluse.x = -speed.x;
}else if(speed.x > 0){
//如果当前是往右移动,按减速加速度快速停止
impluse.x = -MAX_SPEED / DECELERATE_TIME * dt;
}else if(speed.x < 0){
//如果当前是往左移动,按减速加速度快速停止
impluse.x = MAX_SPEED / DECELERATE_TIME * dt;
}
}
body->ApplyLinearImpulse(impluse, body->GetWorldCenter(), true);
}


3.地图移动
       现在马里奥已经可以移动了,不过真实游戏中,他移动到一定程度,就是地图移动而不是马里奥移动了,我们也需要实现这个效果。
       需要注意的是,移动时,box2d的世界也需要跟着移动,否则两者就不同步了。

void MarioScene::dealMap(float dt){
auto pos = _mario->convertToWorldSpaceAR(Vec2(0,0));
auto maPos = _map->convertToWorldSpaceAR(Vec2(0,0));
auto body = static_cast<b2Body*>(_mario->getUserData());
auto bodyPos = body->GetPosition();
//从box2d获取角色新的位置
auto newPos = Vec2(bodyPos.x * PTM_RATIO,bodyPos.y * PTM_RATIO);
auto visibleRect = VisibleRect::getVisibleRect();

//如果角色移动到达规定位置,由地图移动替换之
if(newPos.x / visibleRect.size.width  > MARIO_PLAYER_POSITION_PERCENT){
//计算新的地图位置
auto mapX = visibleRect.size.width * MARIO_PLAYER_POSITION_PERCENT  - newPos.x + maPos.x;
//移动地图
_map->setPosition(Vec2(mapX,0));
//移动box2d静态刚体墙
_pyhsicalBody->SetTransform(b2Vec2(mapX / PTM_RATIO,0), 0);

//移动马里奥在box2d世界中对应的刚体,马里奥就不用移动了,因为接下来就会根据这个刚体调整位置
body->SetTransform(b2Vec2(visibleRect.size.width * MARIO_PLAYER_POSITION_PERCENT / PTM_RATIO,bodyPos.y), 0);
//_mario->setPosition(Vec2(visibleRect.size.width * MARIO_PLAYER_POSITION_PERCENT,pos.y));
}


至此,我们的马里奥就已经可以很好的模拟左右移动、跳跃、碰撞墙了。如下图:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息