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

使用物理引擎


在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;

	log("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










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值