两点:
1 感谢 net19880504 同学,在上篇提到:想让骨头继续写《战神传说》的解刨篇,因为有人在关注而开心。
2 感谢 kanhai 同学,骨头加的哲哲链接虽然是AD性质,但不像一般的AD那样影响阅读,而且骨头也很喜欢这些可爱的介绍,谢谢理解
今晚继续:解刨《战神传说》完结篇
上篇骨头学习了开始菜单和动画,接下来看看其他的类:
————————————————————————————————————————————————
设置类 Options.cpp:
先贴背景图
- CCSprite *sp = CCSprite::create(s_loading);
- sp->setAnchorPoint(CCPointZero);
- addChild(sp, 0, 1);
出现了一个新的控件,开关控件 CCMenuItemToggle:
- CCMenuItemToggle *toggle = CCMenuItemToggle::createWithTarget(this, menu_selector(Options::setOptions), CCMenuItemFont::create("On"),CCMenuItemFont::create("Off"), NULL);
- int selectId = Config::sharedConfig()->getAudioState()? 0 : 1;
- toggle->setSelectedIndex(selectId);
在setOptions方法里处理声音开关,全局的背景音乐和音效控制方法:
- void Options::setOptions(CCObject* pSender)
- {
- bool tmpSound = Config::sharedConfig()->getAudioState();
- Config::sharedConfig()->updateAudioState(!tmpSound);
- if (Config::sharedConfig()->getAudioState()) {
- SimpleAudioEngine::sharedEngine()->resumeAllEffects();
- SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
- }else{
- SimpleAudioEngine::sharedEngine()->pauseAllEffects();
- SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
- }
- }
- CCLabelBMFont *backLb = CCLabelBMFont::create("Go Back", s_font);
- CCMenuItemLabel *goBack = CCMenuItemLabel::create(backLb, this, menu_selector(About::goBack));
- goBack->runAction(CCRepeatForever::create((CCActionInterval*)seq));
————————————————————————————————————————————————
关于页面 About.cpp
这个页面很简单,只有一个长文本需要注意一下:
- CCLabelTTF *about = CCLabelTTF::create(" I recode ....ginal. \n ... ", "Arial", 18, CCSizeMake(winSize.width * 0.85, 320), kCCTextAlignmentLeft);
- about = CCLabelTTF::create(" I recode thistion", "Arial", 18, CCSizeMake(winSize.width * 0.85, 320), kCCTextAlignmentLeft);
- about->setPosition(ccp(winSize.width / 2, winSize.height / 2 - 20));
- about->setAnchorPoint(ccp(0.5, 0.5));
- addChild(about);
2 长文本区域 CCSizeMake(winSize.width * 0.85, 320)
3 对其方式 kCCTextAlignmentLeft
————————————————————————————————————————————————
游戏开始:
————————————————————————————————————————————————
![](http://static.oschina.net/uploads/img/201312/09233251_Z9Bz.jpg)
Ship.cpp的父类是UnitSprite.cpp,这个父类里只在h文件里声明了一个返回键代理,和四个需要子类实现虚方法。
所有的主角飞机,敌机,子弹,的父类都是它。
这四个虚方法分别是:
- virtual void destroy() = 0;
- virtual void hurt() = 0 ;
- virtual CCRect collideRect() = 0;
- virtual bool isActive() = 0;
下面是主角飞机的初始化和动画:
- // init life
- CCTexture2D * shipTextureCache = CCTextureCache::sharedTextureCache()->addImage(s_ship01);
- CCRect rec = CCRectMake(0, 0, 60, 38);
- this->initWithTexture(shipTextureCache, rec);
- this->setPosition(m_appearPosition);
- // set frame
- CCSpriteFrame *frame0 = CCSpriteFrame::createWithTexture(shipTextureCache, CCRectMake(0, 0, 60, 38));
- CCSpriteFrame *frame1 = CCSpriteFrame::createWithTexture(shipTextureCache, CCRectMake(60, 0, 60, 38));
- CCArray *animFrames = CCArray::create();
- animFrames->addObject(frame0);
- animFrames->addObject(frame1);
- // ship animate
- // 这个方法有差异
- CCAnimation *animation = CCAnimation::createWithSpriteFrames(animFrames, 0.1);
- CCAnimate *animate = CCAnimate::create(animation);
- this->runAction(CCRepeatForever::create(animate));
根据 shipTextureCache 来创建两帧 CCSpriteFrame
然后用两帧图片生成 动画,然后播放这个动画,来达到主角飞机一直在闪的效果。
下面这个段代码是幽灵飞机初始化:
- // revive effect
- this->m_canBeAttack = false;
- CCSprite *ghostSprite = CCSprite::createWithTexture(shipTextureCache, CCRectMake(0, 45, 60, 38));
- ccBlendFunc cbl = {GL_SRC_ALPHA, GL_ONE};
- ghostSprite->setBlendFunc(cbl);
- ghostSprite->setScale(8);
- ghostSprite->setPosition(ccp(this->getContentSize().width / 2, 12));
- this->addChild(ghostSprite, 3000, 99999);
- ghostSprite->runAction(CCScaleTo::create(0.5, 1, 1));
何为幽灵飞机呢,就是主角飞机死掉后,一个由大变小的动画,由代码可看出,首先设置为不可攻击,且放大8倍,然后在半秒内回复到正常大小,且可以被攻击。
ccBlendFunc cbl = {GL_SRC_ALPHA, GL_ONE};
这句代码骨头还不太理解,大意就是用来设置描绘时的颜色混合方案。ccBlendFunc包含了一个src和一个dst,分别表示目标和源的运算因子。
比如 ghostSprite->setBlendFunc(cbl);,这句代码,就是以这个Sprite作为源,Sprite所在位置的其它像素作为目标,进行混合运算.
至于这样做的效果和目的,骨头先mark一下。
受伤方法:很简单,hp减少,颜色改变
- void Ship::hurt()
- {
- if (m_canBeAttack) {
- CCLog("under fire!");
- m_HP--;
- this->setColor(ccc3(255, 0, 0));
- }
- }
- void Ship::destroy()
- {
- CCLOG("destroy one ship");
- Config::sharedConfig()->updateLifeCount();
- CCLOG("life count is %d",Config::sharedConfig()->getLifeCount());
- Effect *effect = Effect::create();
- effect->explode(this->getParent(), this->getPosition());
- this->removeFromParent();
- if (Config::sharedConfig()->getAudioState()){
- CocosDenshion::SimpleAudioEngine::sharedEngine()->playEffect(s_shipDestroyEffect);
- }
- }
还有个返回矩形方法:碰撞判断好用的
- CCRect Ship::collideRect()
- {
- CCPoint pos = getPosition();
- CCSize cs = getContentSize();
- return CCRectMake(pos.x - cs.width / 2 , pos.y - cs.height / 2, cs.width, cs.height / 2);
- }
- // 子弹发射
- this->schedule(schedule_selector(Ship::shoot), 0.16);
- void Ship::shoot(float dt)
- {
- int offset = 13;
- CCPoint position = this->getPosition();
- CCSize contentSize = this->getContentSize();
- Bullet *bullet_a = new Bullet(m_bulletSpeed, "W1.png", 1);
- if (bullet_a) {
- bullet_a->autorelease();
- play_bullet->addObject(bullet_a);
- this->getParent()->addChild(bullet_a, bullet_a->m_zorder, 901);
- bullet_a->setPosition(ccp(position.x + offset, position.y + 3 + contentSize.height * 0.3));
- }else{
- delete bullet_a;
- bullet_a = 0;
- }
- Bullet *bullet_b = new Bullet(m_bulletSpeed, "W1.png", 1);
- if (bullet_b) {
- bullet_b->autorelease();
- play_bullet->addObject(bullet_b);
- this->getParent()->addChild(bullet_b, bullet_b->m_zorder, 901);
- bullet_b->setPosition(ccp(position.x - offset, position.y + 3 + contentSize.height * 0.3));
- }else{
- delete bullet_b;
- bullet_a = 0;
- }
- }
骨头以后会在这里大作文章的,比如改成四排子弹,八排子弹,霰弹等等。哈哈哈哈,这是一件相当过瘾的事情!
下面紧接着看看Bullet.cpp 对象:
————————————————————————————————————————————————
子弹类:Bullet.cpp
- Bullet::Bullet(int speed, const char *weapon, int attactMode)
更新方法:
这里面主要是更新位置,比如dt是0.5秒,即一秒钟更新两次,所以每次位移变化量就应该是速度×dt,并且判断没hp了就设置为不可用。
- void Bullet::update(float dt)
- {
- CCPoint position = this->getPosition();
- position.x -= m_velocityx * dt;
- position.y -= m_velocityy * dt;
- setPosition(position);
- if (m_Hp <= 0) {
- m_active = false;
- }
- }
下面是子弹的销毁方法:
播放特效,然后把当前子弹从子弹列表中删除,然后从父控件删除,最后播放一个放大2倍的动画,和一个渐渐消失的动画。
- void Bullet::destroy()
- {
- // 子弹爆炸特效
- CCSprite *explode = CCSprite::create(s_hit);
- ccBlendFunc cb = {GL_SRC_ALPHA, GL_ONE };
- explode->setBlendFunc(cb);
- explode->setPosition(this->getPosition());
- explode->setRotation(CCRANDOM_0_1() * 360);
- explode->setScale(0.75);
- getParent()->addChild(explode, 9999);
- play_bullet->removeObject(this);
- enemy_bullet->removeObject(this);
- this->removeFromParent();
- CCCallFuncN *removeExplode = CCCallFuncN::create(explode, callfuncN_selector(Bullet::removeExplode));
- explode->runAction(CCScaleBy::create(0.3, 2, 2));
- explode->runAction(CCSequence::create(CCFadeOut::create(0.3), removeExplode, NULL));
- }
————————————————————————————————————————————————
敌机类:Enemy.cpp
![](http://static.oschina.net/uploads/img/201312/09233251_drP2.jpg)
敌机类和主角飞机类应该是大同小异。
射击相关:每隔m_delayTime秒发射一次子弹
- this->schedule(schedule_selector(Enemy::shoot),this->m_delayTime);
- void Enemy::shoot(float dt)
- {
- CCPoint pos = this->getPosition();
- Bullet *bullet = new Bullet(m_bulletSpeed, "W2.png", m_attackMode);
- bullet->autorelease();
- enemy_bullet->addObject(bullet);
- getParent()->addChild(bullet, m_zOrder, 900);
- bullet->setPosition(ccp(pos.x, pos.y - getContentSize().height * 0.2));
- }
销毁方法,见每行注释,跟主角飞机是一样的。
- void Enemy::destroy()
- {
- // 更新分数
- Config::sharedConfig()->setScoreValue(m_scoreValue );
- // 爆炸特效和闪光特效
- Effect *effect = Effect::create();
- effect->explode(this->getParent(), getPosition());
- effect->spark(this->getPosition(),this->getParent(), 1.2, 0.7);
- // 敌机爆炸,从敌机数组删除
- enemy_items->removeObject(this);
- // 删除精灵
- this->removeFromParent();
- // 声音
- if (Config::sharedConfig()->getAudioState()) {
- SimpleAudioEngine::sharedEngine()->playEffect(s_explodeEffect);
- }
- }
————————————————————————————————————————————————
主要逻辑类:GameLayer.cpp
初始化init方法里:
启动触摸 this->setTouchEnabled(true);
初始化各种数组:play_bullet = CCArray::create(); play_bullet->retain();
游戏状态: m_state = statePlaying;//statePlaying=0 绝大部分游戏都使用这种状态机机制。
在屏幕顶端加上游戏状态:分数和剩余生命
- // ship life
- CCTexture2D *shipTexture = CCTextureCache::sharedTextureCache()->addImage(s_ship01);
- CCSprite *life = CCSprite::createWithTexture(shipTexture, CCRectMake(0, 0, 60, 38));
- life->setScale(0.6);
- life->setPosition(ccp(30,winSize.height-23));
- addChild(life, 1, 5);
- <span style="white-space:pre"> </span>加上游戏状态:分数和剩余生命<span style="white-space: pre;"> </span> // 每秒调一次 scoreCounter函数
- schedule(schedule_selector(GameLayer::scoreCounter), 1);
- if (Config::sharedConfig()->getAudioState()) {
- SimpleAudioEngine::sharedEngine()->playBackgroundMusic(s_bgMusic, true);
- }
碰撞检测方法:判断两个矩形是否相交
- bool GameLayer::collide(UnitSprite *a, UnitSprite *b)
- {
- if(!a || !b)
- {
- return false;
- }
- CCRect aRect = a->collideRect();
- CCRect bRect = b->collideRect();
- if (aRect.intersectsRect(bRect)) {
- return true;
- }
- return false;
- }
然后使用上面的碰撞检测方法,看看有没有飞机受伤:注释很详细,就是遍历套遍历。碰撞上了就调用hurt(),越界了就destroy掉。
- void GameLayer::checkIsCollide()
- {
- CCObject *units;
- CCObject *bullets;
- CCObject *enemybs;
- //这里是相对于每个敌人
- CCARRAY_FOREACH(enemy_items, units)
- {
- UnitSprite *enemy = dynamic_cast<UnitSprite*>(units);
- //这里是相对于主角的子弹
- CCARRAY_FOREACH(play_bullet, bullets)
- {
- UnitSprite *bullet = dynamic_cast<UnitSprite*>(bullets);
- if (this->collide(enemy, bullet)) {//判断敌人和子弹
- enemy->hurt();
- bullet->hurt();
- }
- //越界删除
- if (!(m_screenRec.intersectsRect(bullet->boundingBox()))) {
- bullet->destroy();
- }
- }
- if (collide(enemy, m_ship)) {//判断敌人和主角
- if (m_ship->isActive()) {
- enemy->hurt();
- m_ship->hurt();
- }
- }
- if (!(m_screenRec.intersectsRect(enemy->boundingBox()))) {
- enemy->destroy();
- }
- }
- //相对于每个敌人的子弹
- CCARRAY_FOREACH(enemy_bullet, enemybs)
- {
- UnitSprite *enemyb = dynamic_cast<UnitSprite*>(enemybs);
- if (enemyb) {
- if (collide(enemyb, m_ship)) {//判断叠人子弹和主角
- if (m_ship->isActive()) {
- enemyb->hurt();
- m_ship->hurt();
- }
- }
- if (!m_screenRec.intersectsRect(enemyb->boundingBox())) {
- enemyb->destroy();
- }
- }
- }
- }
- void GameLayer::updateUI()
- {
- if (m_tempScore < Config::sharedConfig()->getScoreValue()) {
- m_tempScore += 5;
- }
- // char score[20];
- // char s[] = "Score:";
- // sprintf(score, "%d", m_tempScore);
- // m_lbScore->setString(strcat(s, score));
- char lifecount[2];
- sprintf(lifecount, "%d",Config::sharedConfig()->getLifeCount());
- m_lifeCount->setString(lifecount);
- }
关于触摸事件:在CCScene进入和退出的两个方法里,分别注册和注销系统的触摸事件。
- void GameLayer::onEnter()
- {
- CCDirector* pDirector = CCDirector::sharedDirector();
- pDirector->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
- CCLayer::onEnter();
- }
- void GameLayer::onExit()
- {
- CCDirector* pDirector = CCDirector::sharedDirector();
- pDirector->getTouchDispatcher()->removeDelegate(this);
- CCLayer::onExit();
- }
- void GameLayer::ccTouchMoved(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
- {
- if ((m_state == statePlaying) && m_ship) {
- CCPoint pos = touch->getDelta();
- CCPoint currentPos = m_ship->getPosition();
- currentPos = ccpAdd(currentPos, pos);
- currentPos = ccpClamp(currentPos, CCPointZero, ccp(winSize.width, winSize.height));
- m_ship->setPosition(currentPos);
- }
- }
发现两个很有用的方法:
ccpAdd:两点相加
ccpCliamp:保证pos点落在两点确定的矩形之间。
暂停游戏方法:框架自带的pause方法,然后手动停止音效,停止特效。
- void GameLayer::doPause(CCObject *pSender)
- {
- CCDirector::sharedDirector()->pause();
- SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
- SimpleAudioEngine::sharedEngine()->pauseAllEffects();
- PauseLayer *pauseLayer = PauseLayer::create();
- addChild(pauseLayer,9999);
- }
接下来是这个类里最复杂的方法了:背景滚动
感觉这个demo里,背景滚动的方法弄的有些复杂了。
其实前背景后背景,两个背景使用不同的速度来滚动,一个3秒滚动48像素,一个3秒滚动200像素。然后两个背景对象交替显示。
- // 无限滚动地图,采用两张图循环加载滚动
- void GameLayer::initBackground()
- {
- m_backSky = CCSprite::create(s_bg01);
- m_backSky->setAnchorPoint(ccp(0, 0));
- m_backSkyHeight = m_backSky->getContentSize().height;
- addChild(m_backSky, -10);
- // Tile map
- m_backTileMap = CCTMXTiledMap::create(s_level01);
- addChild(m_backTileMap, -9);
- m_backTileMapHeight = m_backTileMap->getMapSize().height * m_backTileMap->getTileSize().height;
- m_backSkyHeight -= 48;
- m_backTileMapHeight -= 200;
- m_backSky->runAction(CCMoveBy::create(3, ccp(0, -48)));
- m_backTileMap->runAction(CCMoveBy::create(3, ccp(0, -200)));
- schedule(schedule_selector(GameLayer:: movingBackground),3);
- }
- // 这里就是视差背景了
- void GameLayer::movingBackground(float dt)
- {
- m_backSky->runAction(CCMoveBy::create(3, ccp(0, -48)));
- m_backTileMap->runAction(CCMoveBy::create(3, ccp(0, -200)));
- // 每次移动48
- m_backSkyHeight -= 48;
- // 每次移动200
- m_backTileMapHeight -= 200;
- // 图的顶部到达屏幕顶部时
- if (m_backSkyHeight <= winSize.height) {
- if (!m_isBackSkyReload) {
- // 如果另一张图还没加载则create一个
- m_backSkyRe = CCSprite::create(s_bg01);
- m_backSkyRe->setAnchorPoint(ccp(0, 0));
- addChild(m_backSkyRe, -10);
- m_backSkyRe->setPosition(ccp(0, winSize.height));
- // 反转标志位
- m_isBackSkyReload = true;
- }
- // 第二张图紧接着第一张图滚动
- m_backSkyRe->runAction(CCMoveBy::create(3, ccp(0, -48)));
- }
- // 第一张图完全经过屏幕
- if (m_backSkyHeight <= 0) {
- m_backSkyHeight = m_backSky->getContentSize().height;
- // 移除第一张的精灵
- this->removeChild(m_backSky, true);
- // 指向第二张图的精灵
- m_backSky = m_backSkyRe;
- // 第二张的精灵指针置空
- m_backSkyRe = NULL;
- // 反转标志位
- m_isBackSkyReload = false;
- }
- if (m_backTileMapHeight <= winSize.height) {
- if (!m_isBackTileReload) {
- m_backTileMapRe = CCTMXTiledMap::create(s_level01);
- this->addChild(m_backTileMapRe, -9);
- m_backTileMapRe->setPosition(0, winSize.height);
- m_isBackTileReload = true;
- }
- m_backTileMapRe->runAction(CCMoveBy::create(3, ccp(0, -200)));
- }
- if (m_backTileMapHeight <= 0) {
- m_backTileMapHeight = m_backTileMap->getMapSize().height * m_backTileMap->getTileSize().height;
- this->removeChild(m_backTileMap, true);
- m_backTileMap = m_backTileMapRe;
- m_backTileMapRe = NULL;
- m_isBackTileReload = false;
- }
- }
游戏结束类:GameOver.cpp
很简单,init里贴点文本、图片,然后一个重新开始按钮
- void GameOver::playAgain(CCObject* pSender)
- {
- CCScene *scene = CCScene::create();
- scene->addChild(GameLayer::create());
- CCDirector::sharedDirector()->replaceScene(CCTransitionFade::create(1.2, scene));
- }
游戏暂停类:PauseLayer.cpp
就是一个大的全屏按钮,点击暂停按钮后,游戏暂停,此按钮开始监听,任意触摸屏幕后,游戏继续,无他。
注意,demo里的代码有问题,点击不能重开游戏,只需要注释掉 if 即可。
- bool PauseLayer::ccTouchBegan(cocos2d::CCTouch *touch, cocos2d::CCEvent *event)
- {
- // 因为回调调不到了,所以resume写在了这里
- CCRect rect = menu->getChildByTag(10)->boundingBox();
- // if (rect.containsPoint(touch->getLocation())) {
- CCLog("touch play");
- CCDirector::sharedDirector()->resume();
- SimpleAudioEngine::sharedEngine()->resumeAllEffects();
- SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
- removeFromParent();
- // }
- return true;
- }
配置类:Config.cpp
全局配置,比如各种不同的敌机的参数初始化。
- EnemyType enemyType;
- enemyType.type = 0;
- enemyType.textureName = "E0.png";
- enemyType.bulletType = "W2.png";
- enemyType.hp = 1;
- enemyType.moveType = 0;
- enemyType.scoreValue = 15;
- m_enemyTypes.push_back(enemyType);
- enemyType.type = 1;
- enemyType.textureName = "E1.png";
- enemyType.bulletType = "W2.png";
- enemyType.hp = 2;
- enemyType.moveType = 0;
- enemyType.scoreValue = 40;
- m_enemyTypes.push_back(enemyType);
————————————————————————————————————————————————
最后,来点过瘾的。
把Ship.cpp里的设计方法void Ship::shoot(float dt) 加个for循环:
- void Ship::shoot(float dt)
- {
- int offset = 13;
- CCPoint position = this->getPosition();
- CCSize contentSize = this->getContentSize();
- for(int i=1;i<10;i++){//--------------新增
- Bullet *bullet_a = new Bullet(m_bulletSpeed, "W1.png", 1);
- if (bullet_a) {
- bullet_a->autorelease();
- play_bullet->addObject(bullet_a);
- this->getParent()->addChild(bullet_a, bullet_a->m_zorder, 901);
- //-------------修改offset*i
- bullet_a->setPosition(ccp(position.x + offset*i, position.y + 3 + contentSize.height * 0.3));
- }else{
- delete bullet_a;
- bullet_a = 0;
- }
- Bullet *bullet_b = new Bullet(m_bulletSpeed, "W1.png", 1);
- if (bullet_b) {
- bullet_b->autorelease();
- play_bullet->addObject(bullet_b);
- this->getParent()->addChild(bullet_b, bullet_b->m_zorder, 901);
- //-------------修改offset*i
- bullet_b->setPosition(ccp(position.x - offset*i, position.y + 3 + contentSize.height * 0.3));
- }else{
- delete bullet_b;
- bullet_a = 0;
- }
- }//-------------新增
- }
看效果
爽吧 哈哈哈
————————————————————————————————————————————————
大体就这么多,真正消化掉这些东西的话,还要自己动手敲。
身体要紧,得休息了。
晚安:)
------------------- 飞船起飞--------------------
Cocos2dx游戏开发系列笔记11:解刨《战神传说》完结篇
Cocos2dx游戏开发系列笔记9:android手机上运行《战神传说》,并解决横竖屏即分辨率自适应问题
Cocos2dx游戏开发系列笔记8:开搞一个射击游戏《战神传说》//就个打飞机的
Cocos2dx游戏开发系列笔记7:一个简单的跑酷游戏《萝莉快跑》的消化(附下载)
Cocos2dx游戏开发系列笔记6:怎样让《萝莉快跑》的例子运行在vs和手机上
Cocos2dx游戏开发系列笔记5:继续润色《忍者飞镖射幽灵》
Cocos2dx游戏开发系列笔记4:怎样新加一个Scene类?
Cocos2dx游戏开发系列笔记3:牛刀小试->忍者飞镖射幽灵的Demo
Cocos2dx游戏开发系列笔记2:一个刚创建的cocos2dx中的demo里都有什么
Cocos2dx游戏开发系列笔记1:一个崭新的开始,cocos2dx2.2+ndkr9+Cygwin+vs2012游戏开发环境搭建
-------------------- 飞船降落--------------------
最后,骨头介绍一下陪在身边的哲哲(右边就是低调的哲哲)