懒骨头(http://blog.csdn.net/iamlazybone QQ:124774397 )
两点:
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();
}
}
还有用指定字体生成label的方法:
CCLabelBMFont *backLb = CCLabelBMFont::create("Go Back", s_font);
CCMenuItemLabel *goBack = CCMenuItemLabel::create(backLb, this, menu_selector(About::goBack));
Goback 一直在闪,是通过 一个CCRepeatForever的action来实现的,即一直重复模式
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);
1 文本中 \n 是有效的
2 长文本区域 CCSizeMake(winSize.width * 0.85, 320)
3 对其方式 kCCTextAlignmentLeft
————————————————————————————————————————————————
游戏开始:
————————————————————————————————————————————————
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));
然后用两帧图片生成 动画,然后播放这个动画,来达到主角飞机一直在闪的效果。
下面这个段代码是幽灵飞机初始化:
// 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));
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));
}
}
销毁主角飞船方法:也很简单,更新生命值,播放动画特效,然后把自己从parent删除,最后播放音效。
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);
每0.16秒调用一下shoot方法:也就是发射子弹的频率。
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,一左一右。
骨头以后会在这里大作文章的,比如改成四排子弹,八排子弹,霰弹等等。哈哈哈哈,这是一件相当过瘾的事情!
下面紧接着看看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
敌机类和主角飞机类应该是大同小异。
射击相关:每隔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);
}
}
初始化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);
加上游戏状态:分数和剩余生命 // 每秒调一次 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;
}
void GameLayer::checkIsCollide()
{
CCObject *units;
CCObject *bullets;
CCObject *enemybs;
//这里是相对于每个敌人
CCARRAY_FOREACH(enemy_items, units)
{
UnitSprite *enemy = dynamic_cast(units);
//这里是相对于主角的子弹
CCARRAY_FOREACH(play_bullet, bullets)
{
UnitSprite *bullet = dynamic_cast(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(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);
}
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;
}
}
void GameOver::playAgain(CCObject* pSender)
{
CCScene *scene = CCScene::create();
scene->addChild(GameLayer::create());
CCDirector::sharedDirector()->replaceScene(CCTransitionFade::create(1.2, scene));
}
就是一个大的全屏按钮,点击暂停按钮后,游戏暂停,此按钮开始监听,任意触摸屏幕后,游戏继续,无他。
注意,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;
}
全局配置,比如各种不同的敌机的参数初始化。
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游戏开发系列笔记13:一个横版拳击游戏Demo-中
Cocos2dx游戏开发系列笔记12:一个横版拳击游戏Demo-上
Cocos2dx游戏开发系列笔记11:解刨《战神传说》完结篇
Cocos2dx游戏开发系列笔记10:解刨《战神传说》
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游戏开发环境搭建
-------------------- 飞船降落--------------------
最后,骨头介绍一下陪在身边的哲哲(右边就是低调的哲哲)
哲哲,小名 YIYI ,手工爱好者,文艺范,手艺人,《YiYiの妙舍》创始人,很有自己想法。