【cocos2d-x 3.0】入门样例《SimpleGame》源码解读【2】

上一篇我们以这个游戏的win32\main.cpp为出发点,分析了引擎的启动入口和基本运作机制,回顾最后的一个重点语句

auto scene = HelloWorld::scene();
从这里开始,后面的工作真正交给了我们,不用太关心引擎底层运行机制的问题了!

让我们进入HelloWorldScene类吧,先来看一下上面调用的这个方法

Scene* HelloWorld::scene()
{
	Scene * scene = NULL;
	do 
	{
		// 'scene' is an autorelease object
		// [RN] 创建一个场景实例(一个空白的场景)
		scene = Scene::create();
		CC_BREAK_IF(! scene);

		// 'layer' is an autorelease object
		// [RN] 创建一个HelloWorld场景层(即本类所表现的层)
		HelloWorld *layer = HelloWorld::create(); // [RN] *注意* 正是这句调用了下面的init()方法来初始化!
		CC_BREAK_IF(! layer);

		// [RN] 把上面的层放入场景
		// add layer as a child to scene
		scene->addChild(layer);
	} while (0);

	// return the scene
	// [RN] 返回这个场景指针(返回到AppDelegate::applicationDidFinishLaunching(),然后由导演director来执行这个场景)
	return scene;
}

这里涉及到了scene和layer的概念

根据UP主个人的理解,scene就像是一个空白的舞台,无论你想表现什么东西首先需要一个舞台

然后layer就像是舞台中的一层布景(因为布景可能有前景、背景或者更多层)

我们的HelloWorldScene类正是继承自cocos2d::LayerColor

所以这个游戏中的HelloWorldScene场景类对象,也就是一个自身包含了舞台scene和layer的东西

接下来注意到HelloWorld::create()实际上就是通过一个宏调用了HelloWorld::init()方法,我们看看它又做了什么

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
	bool bRet = false;
	do 
	{
		//
		// super init first
		//

		// [RN] 预处理白色背景
		CC_BREAK_IF(! LayerColor::initWithColor( Color4B(255,255,255,255) ) );

		//
		// add your codes below...
		//

		// 1. Add a menu item with "X" image, which is clicked to quit the program.

		// Create a "close" menu item with close icon, it's an auto release object.
		// [RN] 创建关闭按钮的图片对象,并传入关闭功能的回调函数
		auto closeItem = MenuItemImage::create(
			"CloseNormal.png",
			"CloseSelected.png",
                CC_CALLBACK_1(HelloWorld::menuCloseCallback,this));
		CC_BREAK_IF(! closeItem);
        
		// Place the menu item bottom-right conner.
		// [RN] 将关闭按钮置位右下角
                auto visibleSize = Director::getInstance()->getVisibleSize();
                auto origin = Director::getInstance()->getVisibleOrigin();
        
		closeItem->setPosition(Point(origin.x + visibleSize.width - closeItem->getContentSize().width/2,
                                    origin.y + closeItem->getContentSize().height/2));

		// Create a menu with the "close" menu item, it's an auto release object.
		// [RN] 创建菜单对象,并将关闭按钮放置其中
		auto menu = Menu::create(closeItem, NULL);
		menu->setPosition(Point::ZERO);
		CC_BREAK_IF(! menu);

		// Add the menu to HelloWorld layer as a child layer.
		// [RN] 将菜单对象置入画面,并且定位于更高的层
		this->addChild(menu, 1);

		/
		// 2. add your codes below...
		// [RN] 创建玩家的精灵
		auto player = Sprite::create("Player.png", Rect(0, 0, 27, 40) );
        
		player->setPosition( Point(origin.x + player->getContentSize().width/2,
                                 origin.y + visibleSize.height/2) );
		this->addChild(player);

		// [RN] 定时器,每隔1.0秒执行gameLogic添加一个敌人
 		this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 );

		// [RN] 获得事件Dispatcher
                auto dispatcher = Director::getInstance()->getEventDispatcher();
		// [RN] 创建多点触控事件Listener
                auto listener = EventListenerTouchAllAtOnce::create();
		// [RN] 指定触摸之后回调的函数
                listener->onTouchesEnded = CC_CALLBACK_2(HelloWorld::onTouchesEnded, this);
		// [RN] 注册(启用)触控事件Listener
                 dispatcher->addEventListenerWithSceneGraphPriority(listener, this);
        
		// [RN] 创建敌人数组、子弹数组并初始化
		_targets = new Array();
                _targets->init();
        
		_projectiles = new Array();
                _projectiles->init();

		// use updateGame instead of update, otherwise it will conflit with SelectorProtocol::update
		// see http://www.cocos2d-x.org/boards/6/topics/1478
		// [RN] 未指定间隔的定时器,表示每帧都会执行updateGame(在本例就是做中弹判定用)
		this->schedule( schedule_selector(HelloWorld::updateGame) );

		// [RN] 播放背景音乐的简单方法
		CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("background-music-aac.wav", true);

		bRet = true;
	} while (0);

	return bRet;
}

上面的一系列工作,就好比演出之前,我们需要准备好即将登台的演员和道具

值得一提的是两个 定时器,还有触摸事件的回调函数,它们是使这个游戏运作起来的关键

第一个定时器调用了HelloWorld::gameLogic方法,就是每隔1秒增加一个敌人

void HelloWorld::gameLogic(float dt)
{
	this->addTarget();
}
具体是这样增加的

// cpp with cocos2d-x
void HelloWorld::addTarget()
{
	// [RN] 创建敌人精灵
	Sprite *target = Sprite::create("Target.png", Rect(0,0,27,40) );
    
	// Determine where to spawn the target along the Y axis
	// [RN] 获取可视区域尺寸(理论创建尺寸,并非放缩后的显示尺寸)
	Size winSize = Director::getInstance()->getVisibleSize();
	float minY = target->getContentSize().height/2;
	float maxY = winSize.height -  target->getContentSize().height/2;
	int rangeY = (int)(maxY - minY);
	// srand( TimGetTicks() );
	int actualY = ( rand() % rangeY ) + (int)minY;

	// Create the target slightly off-screen along the right edge,
	// and along a random position along the Y axis as calculated
	// [RN] 设置敌人初始位置从屏幕最右侧开始,随机一个高度
	target->setPosition( 
		Point(winSize.width + (target->getContentSize().width/2), 
            Director::getInstance()->getVisibleOrigin().y + actualY) );
	this->addChild(target);

	// Determine speed of the target
	// [RN] 随机一个敌人速度
	int minDuration = (int)2.0;
	int maxDuration = (int)4.0;
	int rangeDuration = maxDuration - minDuration;
	// srand( TimGetTicks() );
	int actualDuration = ( rand() % rangeDuration ) + minDuration;

	// Create the actions
	// [RN] 创建一个动作:移动到屏幕最左方
	FiniteTimeAction* actionMove = MoveTo::create( (float)actualDuration,
                                            Point(0 - target->getContentSize().width/2, actualY) );
	// [RN] 指定动作结束后回调的函数
	FiniteTimeAction* actionMoveDone = CallFuncN::create( CC_CALLBACK_1(HelloWorld::spriteMoveFinished, this));
	// [RN] 在target身上实施上面定义的动作与回调
	target->runAction( Sequence::create(actionMove, actionMoveDone, NULL) );

	// Add to targets array
	// [RN] 设定敌人的TAG为1
	target->setTag(1);
	// [RN] 最后将本次生成的敌人target加入敌人列表
	_targets->addObject(target);
}
基本上都是很容易懂的,不过其中有个关键之处,就是在敌人动作结束之后(飞到屏幕最左方之后)回调了HelloWorld::spriteMoveFinished方法

void HelloWorld::spriteMoveFinished(Node* sender)
{
	Sprite *sprite = (Sprite *)sender;
	// [RN] 从场景上移除这个对象
	this->removeChild(sprite, true);

	// [RN] 如果这个对象是敌人对象,则在敌人数组中移除这个敌人
	if (sprite->getTag() == 1)  // target
	{
		_targets->removeObject(sprite);
        
		// [RN] 敌人逃脱,你输了!创建GameOver场景并切换过去
		auto gameOverScene = GameOverScene::create();
		gameOverScene->getLayer()->getLabel()->setString("You Lose :[");
		Director::getInstance()->replaceScene(gameOverScene);

	}
	else if (sprite->getTag() == 2) // projectile
	{
		// [RN] 如果是一个子弹对象,则在子弹数组中移除这个子弹,不触发别的事件
		_projectiles->removeObject(sprite);
	}
}
这个方法处理了所有sprite精灵对象运动结束后的事件,其中包含了游戏结束的逻辑,建立GameOver场景并显示

细心的同学可能发现了,GameOverScene创建场景实例的方式好像和HelloWorldScene并不太一样


没错,通过对比HelloWorldScene.h和GameOverScene.h可以发现:

前者本身继承于Layer,类中包含了一个static的scene()方法里create了一个scene对象、一个layer层,最终通过调用scene()方法返回场景指针;

后者本身继承于Scene,类中包含了一个_layer对象,最终直接通过调用类的create方法(其中创建了_layer层)并返回场景指针


两种途径都可以实现场景类的管理,总之最后交给导演director的都是scene,然而scene里包含了layer。


至此关于第一个定时器:增加敌人、以及判定GameOver的过程我们已经有所了解。

下一篇将会讨论init()方法中的第二个定时器处理中弹判定,以及触摸发射子弹的机制

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值