上一篇我们以这个游戏的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()方法中的第二个定时器处理中弹判定,以及触摸发射子弹的机制