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

Cocos2dx 3.2 横版过关游戏Brave学习笔记(六)

2014-09-04 17:58 381 查看
使用物理引擎

在cocos2dx中自带了一个物理引擎,可以在初始化场景时启用。

把MainScene的createScene函数修改一下:
Scene* MainScene::createScene()
{
// init with physics
auto scene = Scene::createWithPhysics();
auto layer = MainScene::create();
//set physics world
layer->setPhysicsWorld(scene->getPhysicsWorld());
scene->addChild(layer);
return scene;
}


MainScene增加setPhysicsWorld函数,用于设置一个PhysicsWorld*类型的私有变量。

然后角色类在初始化的时候设置一下body以及碰撞和接触的条件,不应发生碰撞,但需要检测到接触事件。原文里用到了Sensor,可能是版本不一样我没有发现怎么设置sensor,所以用监听接触事件代替碰撞。
auto size = this->getContentSize();
auto body = PhysicsBody::createBox(Size(size.width/2, size.height));
body->setCollisionBitmask(0);
body->setContactTestBitmask(1);
this->setPhysicsBody(body);


这个时候运行一下,发现角色们直接开始往下掉了……原来默认重力不为0. 

可以在MainScene::onEnter里将其设置为0:
void MainScene::onEnter()
{
Layer::onEnter();
// set gravity to zero
_world->setGravity(Vec2(0, 0));
}


另外再额外增加按钮来切换是否显示调试用的框框。
auto debugItem = MenuItemImage::create(
"CloseNormal.png",
"CloseSelected.png",
CC_CALLBACK_1(MainScene::toggleDebug, this));
debugItem->setScale(2.0);
debugItem->setPosition(Vec2(VisibleRect::right().x - debugItem->getContentSize().width - pauseItem->getContentSize().width ,
VisibleRect::top().y - debugItem->getContentSize().height));

_menu = Menu::create(pauseItem, debugItem, NULL);
_menu->setPosition(0,0);
this->addChild(_menu);


然后在MainScene中增加对物理接触的监听。
_listener_contact = EventListenerPhysicsContact::create();
_listener_contact->onContactBegin = CC_CALLBACK_1(MainScene::onContactBegin,this);
_listener_contact->onContactSeperate = CC_CALLBACK_1(MainScene::onContactSeperate,this);
_eventDispatcher->addEventListenerWithFixedPriority(_listener_contact, 10);


对应的监听函数为:
bool MainScene::onContactBegin(const PhysicsContact& contact)
{
auto playerA = (Player*)contact.getShapeA()->getBody()->getNode();
auto playerB = (Player*)contact.getShapeB()->getBody()->getNode();
auto typeA = playerA->getPlayerType();
auto typeB = playerB->getPlayerType();
if(typeA == Player::PlayerType::PLAYER)
{
// only one player so ShapeB must belong to an enemy
log("contact enemy!");
playerB->setCanAttack(true);
}
if(typeB == Player::PlayerType::PLAYER)
{
// only one player so ShapeA must belong to an enemy
log("contact enemy!");
playerA->setCanAttack(true);
}
return true;
}

void MainScene::onContactSeperate(const PhysicsContact& contact)
{
auto playerA = (Player*)contact.getShapeA()->getBody()->getNode();
auto playerB = (Player*)contact.getShapeB()->getBody()->getNode();
auto typeA = playerA->getPlayerType();
auto typeB = playerB->getPlayerType();
if(typeA == Player::PlayerType::PLAYER)
{
// only one player so ShapeB must belong to an enemy
log("leave enemy!");
playerB->setCanAttack(false);
}

if(typeB == Player::PlayerType::PLAYER)
{
// only one player so ShapeA must belong to an enemy
log("leave enemy!");
playerA->setCanAttack(false);
}
}


函数的作用主要是将接触到的敌人设置为可被攻击,在可被攻击状态如果点击这个敌人,玩家会进行攻击。

这时我发现教程好像省略了些东西。现在应该补充一下了。

敌人应该会响应触摸,然后发出一个clickEnemy消息,MainScene收到消息后,会判断敌人是否可被攻击,如果可被攻击,玩家执行attack Event,敌人执行beHit Event。

玩家会播放攻击帧动画,敌人会播放被击中帧动画。

这就需要把状态机的状态完善一下,在状态中加入"beingHit"状态, 并添加相应的用于转换的Event。在FSM::init()中
bool FSM::init()
{
this->addState("walking",[](){cocos2d::log("Enter walking");})
->addState("attacking",[](){cocos2d::log("Enter attacking");})
->addState("dead",[](){cocos2d::log("Enter dead");})
->addState("beingHit",[](){cocos2d::log("Enter beingHit");});

this->addEvent("walk","idle","walking")
->addEvent("walk","attacking","walking")
->addEvent("attack","idle","attacking")
->addEvent("attack","walking", "attacking")
->addEvent("die","idle","dead")
->addEvent("die","walking","dead")
->addEvent("die","attacking","dead")
->addEvent("stop","walking","idle")
->addEvent("stop","attacking","idle")
->addEvent("walk","walking","walking")
->addEvent("beHit","idle","beingHit")
->addEvent("beHit","walking","beingHit")
//		->addEvent("beHit","attacking","beingHit") can attacking be stoped by beHit?
->addEvent("die","beingHit","dead")
->addEvent("stop","beingHit","idle")
->addEvent("stop","idle","idle");

return true;
}


另外每个态的回调函数也要写好,在Player::initFSM()中
void Player::initFSM()
{
_fsm = FSM::create("idle");
_fsm->retain();
auto onIdle =[&]()
{
log("onIdle: Enter idle");
this->stopActionByTag(WALKING);
auto sfName = String::createWithFormat("%s-1-1.png", _name.c_str());
auto spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName(sfName->getCString());
this->setSpriteFrame(spriteFrame);
};
_fsm->setOnEnter("idle",onIdle);

auto onAttacking =[&]()
{
log("onAttacking: Enter Attacking");
auto animate = getAnimateByType(ATTACKING);
auto func = [&]()
{
this->_fsm->doEvent("stop");
};
auto callback = CallFunc::create(func);
auto seq = Sequence::create(animate, callback, nullptr);
this->runAction(seq);
};
_fsm->setOnEnter("attacking",onAttacking);

auto onBeingHit = [&]()
{
log("onBeingHit: Enter BeingHit");
auto animate = getAnimateByType(BEINGHIT);
auto func = [&]()
{
this->_fsm->doEvent("stop");
};
auto wait = DelayTime::create(0.6f);
auto callback = CallFunc::create(func);
auto seq = Sequence::create(wait,animate, callback, nullptr);
this->runAction(seq);
};
_fsm->setOnEnter("beingHit",onBeingHit);

auto onDead = [&]()
{
log("onDead: Enter Dead");
auto animate = getAnimateByType(DEAD);
auto func = [&]()
{
log("A charactor died!");
NotificationCenter::getInstance()->postNotification("ENEMY_DEAD",nullptr);
this->removeFromParentAndCleanup(true);
};
auto blink = Blink::create(3,5);
auto callback = CallFunc::create(func);
auto seq = Sequence::create(animate, blink, callback, nullptr);
this->runAction(seq);
_progress->setVisible(false);
};
_fsm->setOnEnter("dead",onDead);
}


beingHit里可以延迟一点时间,等到刀砍下来在播放帧动画。

死亡之后尸体可以闪烁几下在消失不迟。

然后我们让敌人响应触摸事件并发送消息,Player中:
_listener = EventListenerTouchOneByOne::create();
_listener->setSwallowTouches(true);
_listener->onTouchBegan = CC_CALLBACK_2(Player::onTouch,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(_listener,this);


bool Player::onTouch(Touch* touch, Event* event)
{
if(_type == PLAYER)
return false;

l
4000
og("Player: touch detected!");
auto pos = this->convertToNodeSpace(touch->getLocation());
auto size = this->getContentSize();
auto rect = Rect(size.width/2, 0, size.width, size.height);
if(rect.containsPoint(pos))
{
NotificationCenter::getInstance()->postNotification("clickEnemy",this);
log("enemy touched!");
return true;
}
log("enemy not touched!");
return false;
}


在MainScene中,接收这个消息:
NotificationCenter::getInstance()->addObserver(this, callfuncO_selector(MainScene::clickEnemy),"clickEnemy",nullptr);


接受消息的回调函数:
void MainScene::clickEnemy(Ref* obj)
{
log("click enemy message received!");
auto enemy = (Player*)obj;
if(enemy == nullptr)
{
log("enemy null");
return;
}
if(enemy->isCanAttack())
{
_player->attack();
enemy->beHit(_player->getAttack());
}
else
{
_player->walkTo(enemy->getPosition());
}
}


给Player增加生命值,最大生命值,攻击力属性,并可以用函数获取/设定。
然后上面的函数:

void Player::attack()
{
_fsm->doEvent("attack");
}

void Player::beHit(int attack)
{
_health -= attack;
if(_health <= 0)
{
_health = 0;
this->_progress->setProgress((float)_health/_maxHealth*100);
_fsm->doEvent("die");
return;
}
else
{
this->_progress->setProgress((float)_health/_maxHealth*100);
_fsm->doEvent("beHit");
}
}


可以看出把状态机设置好,然后在适当时候触发事件即可。

这一次主要是关于物理引擎碰撞检测,但同时也涉及了触摸事件捕捉,事件的发放与接收,状态机的使用,等等。

现在终于可以把怪杀死了。

我想我会提交一个“Note 6” 的版本更新。https://github.com/douxt/Brave_cpp

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