接《基于Cocos2d-x-1.0.1的飞机大战游戏开发实例(上)》
三、代码分析
1.界面初始化
1 bool PlaneWarGame::init() 2 { 3 bool bRet = false; 4 do 5 { 6 CC_BREAK_IF(! CCLayer::init()); 7 8 _size = CCDirector::sharedDirector()->getWinSize(); 9 10 // 设置触摸可用 11 this->setIsTouchEnabled(true); 12 // 从窗口中取消息 13 CCDirector::sharedDirector()->getOpenGLView()->SetWin32KeyLayer( this ); 14 15 // 初始化游戏背景 16 CC_BREAK_IF(!initBackground()); 17 // 菜单1(暂停/声音/炸弹) 18 CC_BREAK_IF(!initMenu1()); 19 // 菜单2(继续/重新开始/返回) 20 CC_BREAK_IF(!initMenu2()); 21 // 菜单3(重新开始/返回) 22 CC_BREAK_IF(!initMenu3()); 23 24 // 创建玩家飞机 25 _player = new PlaySprite; 26 _player->setPosition(CCPointZero); 27 addChild(_player); 28 29 // 调度器:定时调用自定义的回扫函数 30 this->schedule( schedule_selector(PlaneWarGame::gameLoop)); 31 this->schedule( schedule_selector(PlaneWarGame::shoot), 0.1 ); 32 this->schedule( schedule_selector(PlaneWarGame::addEnemy), 0.3 ); 33 this->schedule( schedule_selector(PlaneWarGame::addProp), 5 ); 34 35 // 初始化两个数组 36 _enemys = CCArray::array(); 37 _enemys->retain(); 38 _bullets = CCArray::array(); 39 _bullets->retain(); 40 41 // 背景音乐 42 CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic( 43 "planewar/game_music.wav", true); 44 45 bRet = true; 46 } while (0); 47 48 return bRet; 49 }
初始化的工作包括:
- 设置触摸可用
- 设置可自定义处理消息
- 初始化游戏背景
- 初始化三个菜单(界面菜单/游戏暂停时的菜单/游戏结束时的菜单)
- 初始化两个CCArray类型的数组,分别用来存储有效的敌机精灵对象和子弹对象
- 设置需要定时调用函数的调度器
- 设置背景音乐
- 添加玩家角色
2.初始化游戏背景
1 bool PlaneWarGame::initBackground() 2 { 3 // 显示分数 4 _label = CCLabelBMFont::labelWithString("0123456789","planewar/font.fnt"); 5 _label->setPosition(ccp(220,_size.height-20)); 6 _label->setColor(ccc3(0,0,0)); 7 this->addChild(_label, 1); 8 9 // 显示炸弹数量 10 CCLabelBMFont* bombcount = CCLabelBMFont::labelWithString("X09","planewar/font.fnt"); 11 bombcount->setAnchorPoint(ccp(0,0)); 12 bombcount->setPosition(ccp(73,10)); 13 bombcount->setColor(ccc3(0,0,0)); 14 addChild(bombcount, 10,111); 15 16 // 添加背景 17 CCSprite* bg1 = CCSprite::spriteWithFile("planewar/bg1.png"); 18 if (!bg1)return false; 19 bg1->setAnchorPoint(ccp(0,1)); 20 bg1->setPosition( ccp(0, _size.height) ); 21 this->addChild(bg1, 0, 11); 22 23 bg1->runAction( 24 CCSequence::actions( 25 CCMoveBy::actionWithDuration(10,ccp(0,-720)), 26 CCCallFunc::actionWithTarget(this, callfunc_selector(PlaneWarGame::bg1roll)),NULL)); 27 28 CCSprite* bg2 = CCSprite::spriteWithFile("planewar/bg2.png"); 29 if (!bg1)return false; 30 bg2->setAnchorPoint(ccp(0,1)); 31 bg2->setPosition( ccp(0, _size.height*2) ); 32 this->addChild(bg2, 0, 12); 33 34 bg2->runAction( 35 CCSequence::actions( 36 CCMoveBy::actionWithDuration(20,ccp(0,-1440)), 37 CCCallFunc::actionWithTarget(this, callfunc_selector(PlaneWarGame::bg2roll)),NULL)); 38 return true; 39 } 40 41 void PlaneWarGame::bg1roll(){ 42 //运动出屏幕重设位置,运动 43 CCSprite * bg = (CCSprite *)getChildByTag(11); 44 bg->setPosition(ccp(0,1440)); 45 CCAction* seq = CCSequence::actions( 46 CCMoveBy::actionWithDuration(20,ccp(0,-1440)), 47 CCCallFunc::actionWithTarget(this, callfunc_selector(PlaneWarGame::bg1roll)),NULL); 48 bg->runAction(seq); 49 } 50 51 void PlaneWarGame::bg2roll(){ 52 //运动出屏幕重设位置,运动 53 CCSprite * bg = (CCSprite *)getChildByTag(12); 54 bg->setPosition(ccp(0,1440)); 55 CCAction* seq = CCSequence::actions( 56 CCMoveBy::actionWithDuration(20,ccp(0,-1440)), 57 CCCallFunc::actionWithTarget(this, callfunc_selector(PlaneWarGame::bg2roll)),NULL); 58 bg->runAction(seq); 59 }
初始化游戏背景时,需要注意的是:要做出飞机向上走的视觉效果,就要把背景向下滚动,这里使用两个背景bg1和bg2,大小分别为屏幕大小。初始bg1在屏幕内,bg2在屏幕上方,两个同事向下运动;当bg1刚刚运动到屏幕外,bg2刚好盖住屏幕,bg1挪到屏幕上方向下运动...循环操作,就能做出循环滚动的背景效果了。
3.子弹的处理
1 // 确定子弹类型 2 void PlaneWarGame::shoot(float dt) 3 { 4 if (_isOver)return; 5 if(_player==NULL) return; 6 7 if(_player->_bulletKind == BK_SINGLE) 8 { 9 CCSprite *bullet = CCSprite::spriteWithFile( 10 "planewar/plane.png", CCRectMake(112,2,9,17)); 11 addBullet(bullet,_player->getPlayerPt()); 12 }else if (_player->_bulletKind == BK_DOUBLE) 13 { 14 CCSprite *bullet = CCSprite::spriteWithFile( 15 "planewar/plane.png", CCRectMake(66,238,8,15)); 16 addBullet(bullet,ccp(_player->getPlayerPt().x-20,_player->getPlayerPt().y)); 17 CCSprite *bullet1 = CCSprite::spriteWithFile( 18 "planewar/plane.png", CCRectMake(66,238,8,15)); 19 addBullet(bullet1,ccp(_player->getPlayerPt().x+20,_player->getPlayerPt().y)); 20 } 21 } 22 // 添加子弹 23 void PlaneWarGame::addBullet(CCSprite* bullet, CCPoint pt) 24 { 25 bullet->setPosition(pt); 26 this->addChild(bullet); 27 28 bullet->runAction( CCSequence::actions( 29 CCMoveTo::actionWithDuration(0.5, ccp(pt.x,_size.height+bullet->getContentSize().height/2)), 30 CCCallFuncN::actionWithTarget(this,callfuncN_selector(PlaneWarGame::spriteMoveFinished)), 31 NULL) ); 32 33 bullet->setTag(1); 34 _bullets->addObject(bullet); 35 } 36 // 回收 37 void PlaneWarGame::spriteMoveFinished(CCNode* sender) 38 { 39 CCSprite *sprite = (CCSprite *)sender; 40 this->removeChild(sprite, true); 41 if (sprite->getTag()==1)//子弹 42 _bullets->removeObject(sprite); 43 }
吃道具可改变子弹类型。子弹的回收方式:从飞机坐标处出发,运动到屏幕外被回收。道具的处理类似。
4.设置触摸/鼠标点击控制玩家角色的移动
1 bool PlaneWarGame::ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent) 2 { 3 if(_player==NULL) return false; 4 5 CCPoint pt = CCDirector::sharedDirector()->convertToGL( 6 pTouch->locationInView(pTouch->view())); 7 8 CCRect rt = _player->getRect(); 9 if (CCRect::CCRectContainsPoint(rt,pt)) 10 _player->_isDragEnabled = true; 11 return true; 12 } 13 14 void PlaneWarGame::ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent) 15 { 16 if(_player==NULL) return; 17 18 CCSize size = CCDirector::sharedDirector()->getWinSize(); 19 CCPoint pt = CCDirector::sharedDirector()->convertToGL( 20 pTouch->locationInView(pTouch->view())); 21 22 CCRect rt = CCRect(0,0,size.width,size.height); 23 24 if (_player->_isDragEnabled && CCRect::CCRectContainsPoint(rt,pt)) 25 _player->setPlayerPt(pt); 26 } 27 28 void PlaneWarGame::ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent) 29 { 30 _player->_isDragEnabled = false; 31 }
这里重写3个虚函数。移动角色的方式:触摸/鼠标按下时,如果点击位置在角色上,将角色可被拖拽状态置为true;触摸/鼠标移动时,根据角色的可被拖拽状态和屏幕范围确定是否移动;触摸/鼠标弹起时,将角色的可被拖拽状态恢复为false。
5.碰撞检测
1 void PlaneWarGame::updateGame(float dt) 2 { 3 CCArray *bulletsToDelete = CCArray::array(); 4 CCObject* it = NULL; 5 CCObject* jt = NULL; 6 7 if(_player==NULL) return; 8 9 CCRect playerRect = _player->getRect(); 10 // 己方飞机和敌方飞机的碰撞 11 CCARRAY_FOREACH(_enemys, jt) 12 { 13 EnemySprite *enemy = dynamic_cast<EnemySprite*>(jt); 14 if (!enemy->isNull() && CCRect::CCRectIntersectsRect(playerRect, enemy->getRect())) 15 { 16 _player->die(); 17 gameover(false); 18 return; 19 } 20 } 21 22 ////////////////////////////////////////////////////////////////////////// 23 // 道具和角色的碰撞检测 24 CCSprite* prop = (CCSprite*)getChildByTag(8);//子弹 25 if (NULL != prop) 26 { 27 CCRect propRect = CCRectMake( 28 prop->getPosition().x - (prop->getContentSize().width/2), 29 prop->getPosition().y - (prop->getContentSize().height/2), 30 prop->getContentSize().width, 31 prop->getContentSize().height); 32 if (CCRect::CCRectIntersectsRect(playerRect, propRect)) 33 { 34 _player->_bulletKind = BK_DOUBLE; 35 removeChild(prop,true); 36 CocosDenshion::SimpleAudioEngine::sharedEngine()-> 37 playEffect("planewar/get_double_laser.wav",false); 38 } 39 } 40 CCSprite* prop1 = (CCSprite*)getChildByTag(9);//炸弹 41 if (NULL != prop1) 42 { 43 CCRect prop1Rect = CCRectMake( 44 prop1->getPosition().x - (prop1->getContentSize().width/2), 45 prop1->getPosition().y - (prop1->getContentSize().height/2), 46 prop1->getContentSize().width, 47 prop1->getContentSize().height); 48 if (CCRect::CCRectIntersectsRect(playerRect, prop1Rect)) 49 { 50 _player->_bombCount++; 51 removeChild(prop1,true); 52 CocosDenshion::SimpleAudioEngine::sharedEngine()-> 53 playEffect("planewar/get_bomb.wav",false); 54 } 55 } 56 57 ////////////////////////////////////////////////////////////////////////// 58 // 子弹和敌机的碰撞检测 59 CCARRAY_FOREACH(_bullets, it) 60 for (int i=0; i<_bullets->count(); i++) 61 { 62 CCSprite *bullet = dynamic_cast<CCSprite*>(_bullets->objectAtIndex(i)); 63 CCRect bulletRect = CCRectMake( 64 bullet->getPosition().x - (bullet->getContentSize().width/2), 65 bullet->getPosition().y - (bullet->getContentSize().height/2), 66 bullet->getContentSize().width, 67 bullet->getContentSize().height); 68 69 CCArray* enemysToDelete =CCArray::array(); 70 71 CCARRAY_FOREACH(_enemys, jt) 72 { 73 EnemySprite *enemy = dynamic_cast<EnemySprite*>(jt); 74 if (!enemy->_die && CCRect::CCRectIntersectsRect(bulletRect, enemy->getRect())) 75 { 76 if (enemy->_hp>0) 77 { 78 enemy->_hp--; 79 //bulletsToDelete->addObject(bullet); 80 _bullets->removeObject(bullet); 81 removeChild(bullet,true); 82 } 83 else if (enemy->_hp<=0) 84 { 85 //bulletsToDelete->addObject(bullet); 86 _bullets->removeObject(bullet); 87 removeChild(bullet,true); 88 enemysToDelete->addObject(enemy); 89 } 90 } 91 } 92 93 CCARRAY_FOREACH(enemysToDelete, jt) 94 { 95 EnemySprite *enemy = dynamic_cast<EnemySprite*>(jt); 96 enemy->_die = true; 97 enemy->die(); 98 _bulletsDestroyed++; 99 } 100 101 enemysToDelete->release(); 102 } 103 104 // 释放已死亡的子弹 105 CCARRAY_FOREACH(bulletsToDelete, it) 106 { 107 CCSprite* projectile = dynamic_cast<CCSprite*>(it); 108 _bullets->removeObject(projectile); 109 this->removeChild(projectile, true); 110 } 111 bulletsToDelete->release(); 112 }
碰撞检测使用最简单的矩形检测。
6.玩家角色的添加和操作
1 void PlaySprite::onEnter() 2 { 3 CCNode::onEnter(); 4 5 CCSize size = CCDirector::sharedDirector()->getWinSize(); 6 _sprite = CCSprite::spriteWithFile("planewar/hero1.png"); 7 _sprite->setContentSize(CCSize(62,68)); 8 _sprite->setPosition( ccp(size.width/2,_sprite->getContentSize().height/2) ); 9 addChild(_sprite,10); 10 11 CCAnimation * animation = CCAnimation::animation(); 12 animation->addFrameWithFileName("planewar/hero1.png"); 13 animation->addFrameWithFileName("planewar/hero2.png"); 14 CCAction* action = CCRepeatForever::actionWithAction( 15 CCAnimate::actionWithDuration(0.1f, animation, false)); 16 action->setTag(11); 17 _sprite->runAction(action); 18 19 // 调度器 20 schedule( schedule_selector(PlaySprite::move), 0.002 ); 21 } 22 23 CCRect PlaySprite::getRect() 24 { 25 if (_sprite!=NULL) 26 return CCRect( 27 _sprite->getPosition().x - (_sprite->getContentSize().width/2), 28 _sprite->getPosition().y - (_sprite->getContentSize().height/2), 29 _sprite->getContentSize().width, 30 _sprite->getContentSize().height); 31 } 32 33 CCPoint PlaySprite::getPlayerPt() 34 { 35 if (_sprite!=NULL) 36 return _sprite->getPosition(); 37 } 38 39 void PlaySprite::setPlayerPt(CCPoint pt) 40 { 41 if (_sprite!=NULL) 42 return _sprite->setPosition(pt); 43 } 44 45 void PlaySprite::setMoveMode( UINT message, WPARAM wParam) 46 { 47 if (message == WM_KEYDOWN) 48 {// 控制飞机移动 49 if (wParam == VK_UP) 50 _mode = MM_UP; 51 else if (wParam == VK_DOWN) 52 _mode = MM_DOWN; 53 else if (wParam == VK_LEFT) 54 _mode = MM_LEFT; 55 else if (wParam == VK_RIGHT) 56 _mode = MM_RIGHT; 57 }else if (message == WM_KEYUP) 58 { 59 if (wParam == VK_UP || wParam == VK_DOWN ||wParam == VK_LEFT||wParam == VK_RIGHT) 60 _mode = MM_NONE; 61 } 62 } 63 64 void PlaySprite::move(float dt) 65 { 66 CCSize winSize = CCDirector::sharedDirector()->getWinSize(); 67 68 switch(_mode) 69 { 70 case MM_NONE: 71 break; 72 case MM_UP: 73 if (getPlayerPt().y<winSize.height) 74 setPlayerPt(ccp(getPlayerPt().x,getPlayerPt().y+1)); 75 break; 76 case MM_DOWN: 77 if (getPlayerPt().y>0) 78 setPlayerPt(ccp(getPlayerPt().x,getPlayerPt().y-1)); 79 break; 80 case MM_LEFT: 81 if (getPlayerPt().x>0) 82 setPlayerPt(ccp(getPlayerPt().x-1,getPlayerPt().y)); 83 break; 84 case MM_RIGHT: 85 if (getPlayerPt().x<winSize.width) 86 setPlayerPt(ccp(getPlayerPt().x+1,getPlayerPt().y)); 87 break; 88 } 89 } 90 91 void PlaySprite::die() 92 { 93 if (_sprite==NULL)return; 94 95 _sprite->stopActionByTag(11); 96 CCAnimation * animation = CCAnimation::animation(); 97 animation->addFrameWithFileName("planewar/hero_blowup_n1.png"); 98 animation->addFrameWithFileName("planewar/hero_blowup_n2.png"); 99 animation->addFrameWithFileName("planewar/hero_blowup_n3.png"); 100 animation->addFrameWithFileName("planewar/hero_blowup_n4.png"); 101 _sprite->runAction(CCSequence::actions( 102 CCAnimate::actionWithDuration(0.1f, animation, false), 103 CCCallFunc::actionWithTarget(this, callfunc_selector(PlaySprite::destroy)),NULL)); 104 } 105 106 void PlaySprite::destroy() 107 { 108 if (_sprite==NULL)return; 109 110 removeChild(_sprite,true); 111 _sprite = NULL; 112 }
- 玩家角色类是派生自CCNode类,用组合的方式包含一个CCSprite类的成员对象,在玩家角色类内部封装对精灵的操作。
- 重写onEnter函数初始化_sprite成员,设置位置、大小、图片和帧动画等。
- 玩家的方向键控制移动和鼠标拖拽移动方式类似,在操作时设置移动状态,在schedule定时调用的函数move中根据移动状态来移动玩家角色。
- 关于碰撞之后的动画和销毁,参照上面的die和destroy函数,对象被碰撞时调用die函数运行爆炸的帧动画,动画结束后自动调用destroy函数销毁对象。
- 敌机对象的操作和玩家角色类似。
四、完成效果图
经过不到一周的时间,从什么都没有,到游戏正常运行、功能完整,体会了cocos2dx的调用机制。
下面是运行效果图:
如果实现中有什么问题,欢迎大家在评论中指教!