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);
感觉终于算是搞懂了。
这个游戏也就此分析完毕。~~~~~~~~~~~~~~~~~~~~~~~

浙公网安备 33010602011771号