Cocos2dx学习之SimpleGame
关于如何打开并成功运行SimpleGame项目我就不讨论了。
项目位置: D:\cocos2d-x-2.2.1\cocos2d-x-2.2.1\samples\Cpp\SimpleGame
这个游戏是忍者射击飞镖击中敌人的模式。
首先从程序的入口地址开始:
bool AppDelegate::applicationDidFinishLaunching() { // 初始化导演类 CCDirector *pDirector = CCDirector::sharedDirector(); //设置OpenGLView渲染方式 pDirector->setOpenGLView(CCEGLView::sharedOpenGLView()); //获取屏幕尺寸CCSize CCSize screenSize = CCEGLView::sharedOpenGLView()->getFrameSize(); CCSize designSize = CCSizeMake(480, 320); std::vector<std::string> searchPaths; //如果屏幕的高度大于320的话,采用的是hd,sd两个文件夹中的两套图片资源可以在Resources文件夹中查看 if (screenSize.height > 320) { searchPaths.push_back("hd"); searchPaths.push_back("sd"); pDirector->setContentScaleFactor(640.0f/designSize.height); } else { searchPaths.push_back("sd"); pDirector->setContentScaleFactor(320.0f/designSize.height); } //设置文件工具类的搜索路径 CCFileUtils::sharedFileUtils()->setSearchPaths(searchPaths); #if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) || (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionShowAll); #else CCEGLView::sharedOpenGLView()->setDesignResolutionSize(designSize.width, designSize.height, kResolutionNoBorder);//游戏显示的适应屏幕的方式设定 #endif // turn on display FPS pDirector->setDisplayStats(true); // set FPS. the default value is 1.0/60 if you don't call this pDirector->setAnimationInterval(1.0 / 60); // 创建游戏主场景 CCScene *pScene = HelloWorld::scene(); // 通过Director运行游戏主场景 pDirector->runWithScene(pScene); return true; }
CCSprite *player = CCSprite::create("Player.png", CCRectMake(0, 0, 27, 40) ); //创建主角玩家 //设置玩家的坐标在屏幕的左中部 player->setPosition( ccp(origin.x + player->getContentSize().width/2, origin.y + visibleSize.height/2) ); this->addChild(player); //添加schedule任务,每过一秒调用一次gameLogic函数(需要注意schedule_selector的使用) this->schedule( schedule_selector(HelloWorld::gameLogic), 1.0 ); //设置触摸允许 this->setTouchEnabled(true); //创建敌人的存储CCArray、创建飞镖CCArray来存储飞镖 _targets = new CCArray; _projectiles = new CCArray; //schedule没有interval时间的情况下,默认每帧都会调用updateGame函数 this->schedule( schedule_selector(HelloWorld::updateGame) ); //CocosDenshion下面的SimpleAudioEngine初始化,并playBackgroundMusic. CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic("background-music-aac.wav", true);
上面代码主要需要注意如何正确的去使用schedule这个重要的函数。
CCSprite *target = CCSprite::create("Target.png", CCRectMake(0,0,27,40) );//创建敌人Sprite //下面代码主要是用来控制敌人出现的Y轴的位置,需要有一个范围 CCSize winSize = CCDirector::sharedDirector()->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;//生成本次敌人精灵的位置actualY // 将敌人精灵刚好设定在右侧的屏幕边缘处 //而精灵的y值是由函数计算出来的actualY,具有随机性 target->setPosition( ccp(winSize.width + (target->getContentSize().width/2), CCDirector::sharedDirector()->getVisibleOrigin().y + actualY) ); this->addChild(target); // 计算将敌人精灵从屏幕右侧移动到屏幕左侧外的时间,对时间的随机性选择actualDuration会使敌人精灵的移动速度有差异 int minDuration = (int)2.0; int maxDuration = (int)4.0; int rangeDuration = maxDuration - minDuration; // srand( TimGetTicks() ); int actualDuration = ( rand() % rangeDuration ) + minDuration; // 创建限时动作 //需要注意的是CCCallFuncN类的使用以及callfuncN_selector()的使用当精灵移动结束后需要执行的函数 CCFiniteTimeAction* actionMove = CCMoveTo::create( (float)actualDuration, ccp(0 - target->getContentSize().width/2, actualY) );CCFiniteTimeAction* actionMoveDone = CCCallFuncN::create( this, callfuncN_selector(HelloWorld::spriteMoveFinished));target->runAction( CCSequence::create(actionMove, actionMoveDone, NULL) ); //合并成一个动作序列,依次执行 target->setTag(1);//设置敌人精灵的标签为1 _targets->addObject(target);// 将产生的敌人假如敌人精灵的数组中,统一管理
上面需要注意的是关于动作执行完以后的回调函数的使用,也就是CCCallFuncN:create(this,callfuncN_selector(回调函数名))
其次,就是CCSequence的使用了,需要注意的是CCSequence::create(动作1,动作2,动作3.....,后面必须有NULL)
否则会报错
CCArray中的方法,addObject()添加一个obj对象到数组中。
void HelloWorld::spriteMoveFinished(CCNode* sender) //精灵移动结束的回调函数 { CCSprite *sprite = (CCSprite *)sender;//将CCNode强转 this->removeChild(sprite, true);//精灵移动结束需要从Layer中移除 if (sprite->getTag() == 1) // 根据精灵的Tag标签来区分当前的精灵是敌人 { _targets->removeObject(sprite); //使用CCArray中的removeObject方法,来将已经移动结束的敌人从数组中移除 //由于敌人已经移动结束了,说明敌人精灵已经移动到屏幕的左侧,按游戏规则,游戏结束 GameOverScene *gameOverScene = GameOverScene::create(); gameOverScene->getLayer()->getLabel()->setString("You Lose :["); CCDirector::sharedDirector()->replaceScene(gameOverScene); //跳转到游戏结束场景 } else if (sprite->getTag() == 2) // 当前的精灵是飞镖的话,飞镖移动结束后的回调,需要处理的是将飞镖从CCArray中移除 { _projectiles->removeObject(sprite); } }
void HelloWorld::registerWithTouchDispatcher() { //注册添加屏幕监听,并且优先级设置为0 CCDirector::sharedDirector()->getTouchDispatcher()->addStandardDelegate(this,0); }
void HelloWorld::updateGame(float dt) { CCArray *projectilesToDelete = new CCArray; CCObject* it = NULL; CCObject* jt = NULL; // for (it = _projectiles->begin(); it != _projectiles->end(); it++) CCARRAY_FOREACH(_projectiles, it) //通过it迭代访问_projectiles数组 { CCSprite *projectile = dynamic_cast<CCSprite*>(it);//强转成精灵 CCRect projectileRect = CCRectMake( //获取CCRect包围 projectile->getPosition().x - (projectile->getContentSize().width/2), projectile->getPosition().y - (projectile->getContentSize().height/2), projectile->getContentSize().width, projectile->getContentSize().height); CCArray* targetsToDelete =new CCArray; // for (jt = _targets->begin(); jt != _targets->end(); jt++) CCARRAY_FOREACH(_targets, jt) //使用jt迭代访问_targets敌人精灵数组 { CCSprite *target = dynamic_cast<CCSprite*>(jt); CCRect targetRect = CCRectMake( //获取CCRect包围 target->getPosition().x - (target->getContentSize().width/2), target->getPosition().y - (target->getContentSize().height/2), target->getContentSize().width, target->getContentSize().height); // if (CCRect::CCRectIntersectsRect(projectileRect, targetRect)) if (projectileRect.intersectsRect(targetRect)) //通过CCRect包围来检测是否发生了碰撞,注意函数intersectsRect的使用 { targetsToDelete->addObject(target);//发生了碰撞,即飞镖击中了敌人,将敌人精灵放入待删除的数组中 } } // for (jt = targetsToDelete->begin(); jt != targetsToDelete->end(); jt++) CCARRAY_FOREACH(targetsToDelete, jt) //使用jt迭代访问targetsToDelete敌人精灵数组 { CCSprite *target = dynamic_cast<CCSprite*>(jt); _targets->removeObject(target); //从_targets中移除敌人精灵 this->removeChild(target, true); //并从layer中移除敌人精灵 //击中的敌人数+1 _projectilesDestroyed++; if (_projectilesDestroyed >= 5)//如果击中的敌人数目大于等于5个,玩家胜利 { GameOverScene *gameOverScene = GameOverScene::create(); gameOverScene->getLayer()->getLabel()->setString("You Win!"); CCDirector::sharedDirector()->replaceScene(gameOverScene); } } if (targetsToDelete->count() > 0) //如果击中了的敌人,就将飞镖精灵放入待删除的数组中 { projectilesToDelete->addObject(projectile); } targetsToDelete->release();//释放targetsToDelete数组资源 } // for (it = projectilesToDelete->begin(); it != projectilesToDelete->end(); it++) CCARRAY_FOREACH(projectilesToDelete, it) //通过迭代的方式移除projectile并清理资源 { CCSprite* projectile = dynamic_cast<CCSprite*>(it); _projectiles->removeObject(projectile); this->removeChild(projectile, true); } projectilesToDelete->release(); }
在上面的代码中,我们需要知道这样一个事实,那就是: 对于飞镖来说,移除分为2种情况,第一种是没有击中敌人精灵的移除方式,第二种是击中敌人精灵的移除方式
但是他们都做了同样的操作,具体是从飞镖数组中移除,从Layer层中移除。对于敌人精灵同样如此。
void HelloWorld::ccTouchesEnded(CCSet* touches, CCEvent* event) //触摸屏幕弹起时调用 { // 获取触摸点(需要注意如何从CCSet中获取object,以及如何使用CCTouch对象来获取触摸点坐标) CCTouch* touch = (CCTouch*)( touches->anyObject() ); //anyObject方法会返回第一个obj,如果是空的话,返回null CCPoint location = touch->getLocation(); CCLog("++++++++after x:%f, y:%f", location.x, location.y); // Set up initial location of projectile CCSize winSize = CCDirector::sharedDirector()->getVisibleSize(); CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin(); CCSprite *projectile = CCSprite::create("Projectile.png", CCRectMake(0, 0, 20, 20));//创建飞镖精灵 projectile->setPosition( ccp(origin.x+20, origin.y+winSize.height/2) ); //设置飞镖的位置 // 计算x,y坐标差值 float offX = location.x - projectile->getPosition().x; float offY = location.y - projectile->getPosition().y; // 禁止飞镖向后射击以及向上下设计 if (offX <= 0) return; // Ok to add now - we've double checked position this->addChild(projectile); // Determine where we wish to shoot the projectile to float realX = origin.x+winSize.width + (projectile->getContentSize().width/2);//计算飞镖飞出的终点x值 float ratio = offY / offX; //其实就是三角函数tan值 float realY = (realX * ratio) + projectile->getPosition().y;//根据realX的值计算出对应的realY值 CCPoint realDest = ccp(realX, realY);//飞镖即将飞出屏幕的真实坐标 // 计算飞镖在屏幕中飞行的距离 float offRealX = realX - projectile->getPosition().x; float offRealY = realY - projectile->getPosition().y; float length = sqrtf((offRealX * offRealX) + (offRealY*offRealY));//飞行距离 float velocity = 480/1; // 480pixels/1sec float realMoveDuration = length/velocity;//计算飞行的时间 //上面计算关于飞行距离,飞行时间主要是让飞镖可以匀速飞行,看起来不会很奇怪,同样注意CCSequence和CCCallFuncN及callfuncN_selector的使用 projectile->runAction( CCSequence::create( CCMoveTo::create(realMoveDuration, realDest), CCCallFuncN::create(this, callfuncN_selector(HelloWorld::spriteMoveFinished)), NULL) ); // 设置精灵标签,将精灵加入飞镖精灵数组中,同意管理,访问 projectile->setTag(2); _projectiles->addObject(projectile); //声音的控制 CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect("pew-pew-lei.wav"); }
这里我模糊的地方在于
gameOverScene->getLayer()->getLabel()->setString("You Win!");
参考他人的作答:
上面给我一些启示,继续查看源代码中的CC_SYNTHESIZE_READONLY这个宏定义
如下:
/** CC_SYNTHESIZE_READONLY is used to declare a protected variable. We can use getter to read the variable. @param varType : the type of variable. @param varName : variable name. @param funName : "get + funName" is the name of the getter. @warning : The getter is a public inline function. The variables and methods declared after CC_SYNTHESIZE_READONLY are all public. If you need protected or private, please declare. */ #define CC_SYNTHESIZE_READONLY(varType, varName, funName)\ protected: varType varName;\ //创建protected属性的varType类型的varName变量 public: virtual varType get##funName(void) const { return varName; } //创建public virtual varType类型的get方法,返回varName变量
再回过头来看看GameOverScene.h中的这2句代码:
CC_SYNTHESIZE_READONLY(GameOverLayer*, _layer, Layer);
CC_SYNTHESIZE_READONLY(cocos2d::CCLabelTTF*, _label, Label);
感觉终于算是搞懂了。
这个游戏也就此分析完毕。~~~~~~~~~~~~~~~~~~~~~~~