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

     转载请注明来源: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));
}

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


你可能感兴趣的:(cocos2dx,box2D,TileMap,马里奥)