转载请注明来源: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已经帮我们做了很多。
思路:
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)); }
至此,我们的马里奥就已经可以很好的模拟左右移动、跳跃、碰撞墙了。如下图: