[cocos2d-x]躲蜘蛛游戏设计[进阶]

简介: 实现一个躲蜘蛛的游戏,具备以下效果: 1.玩家精灵在游戏的最下方。 2.蜘蛛精灵在游戏的最上方,并且从上往下掉落。 3.手动控制玩家精灵。 4.碰撞检测,如果玩家精灵碰到蜘蛛则重玩。

实现一个躲蜘蛛的游戏,具备以下效果:

1.玩家精灵在游戏的最下方。

2.蜘蛛精灵在游戏的最上方,并且从上往下掉落。

3.手动控制玩家精灵。

4.碰撞检测,如果玩家精灵碰到蜘蛛则重玩。

5.音效的设置。

6.Loading界面

7.暂停和开始的设计


效果图:


代码实现:

1.创建默认的HelloWorld项目,屏幕默认的是横屏的,如何设置屏幕为竖屏?

找到RootViewController.mm文件,修改相应的代码即可:

// For ios6, use supportedInterfaceOrientations & shouldAutorotate instead
- (NSUInteger) supportedInterfaceOrientations{
#ifdef __IPHONE_6_0
    return UIInterfaceOrientationMaskPortrait;
#endif
}

2.HelloWorldScene.h:

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"
using namespace cocos2d;
class HelloWorld : public cocos2d::CCLayer
{
public:
    // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer)
    virtual bool init();

    // there's no 'id' in cpp, so we recommend to return the class instance pointer
    static cocos2d::CCScene* scene();
    
    // a selector callback
    //void menuCloseCallback(CCObject* pSender);

    // preprocessor macro for "static create()" constructor ( node() deprecated )
    CREATE_FUNC(HelloWorld);
    
    ~HelloWorld();
    
    //计分
    int count;
    CCLabelBMFont * scoreLabel;
    
    //玩家自己的精灵
    CCSprite*player;
    //位置
    CCPoint playerVelocity;
    //蜘蛛精灵
    CCArray *spiders;
    //时间间隔
    float spiderMoveDuration;
    //蜘蛛移动的数量
    int numSpidersMoved;
    
    //初始化蜘蛛
    void initSpiders();
    //重新布局蜘蛛精灵
    void resetSpiders();
    //蜘蛛更新
    void spidersUpdate(CCTime delta);
    //蜘蛛移动
    void runSpiderMoveSequence(CCSprite * spider);
    //往下移动
    void spiderBelowSceen(void * sender);
    
    //碰撞检测
    void update(float delta);
    
    //触摸控制主角精灵
    virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
    virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);
    virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);
    
    
    //触摸注册
    void registerWithTouchDispatcher();
    
    //showGameOver
    void showGameOver(const char *str);
    
    //重新开始
    void resetGame();
    //开始按钮
    void menuCloseCallback(CCObject* pSender);
    //返回上一级选择菜单
    void returnView();

    bool flag = true;
    void pauseOrStart();
};

#endif // __HELLOWORLD_SCENE_H__

3.HelloWorldScene.cpp:

#include "HelloWorldScene.h"
//音乐的类
#include "SimpleAudioEngine.h"
#include "Loading.h"
using namespace cocos2d;
using namespace CocosDenshion;

CCScene* HelloWorld::scene()
{
    // 'scene' is an autorelease object
    CCScene *scene = CCScene::create();
    
    // 'layer' is an autorelease object
    HelloWorld *layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }
    
    
    CCSize screenSize = CCDirector::sharedDirector()->getWinSize();
    
    CCSprite *background = CCSprite::create("bg.png");
    background->setPosition(ccp(screenSize.width/2, screenSize.height/2));
    this->addChild(background);
    
    count =0;
    //字体文理贴图
    scoreLabel = CCLabelBMFont::create("0", "testBMFont.fnt");
    scoreLabel->setPosition(ccp(screenSize.width * 0.5,screenSize.height * 0.5));
    this->addChild(scoreLabel);
    //放大两倍
    //scoreLabel->getChildByTag(0)->setScale(2.0);
    
    //背景音效
    SimpleAudioEngine::sharedEngine()->playBackgroundMusic("blues.mp3", true);
    
    //打开加速计
    this->setAccelerometerEnabled(true);
    this->setTouchEnabled(true);
    //获取自身的精灵
    player = CCSprite::create("alien.png");
    this->addChild(player, 0,1);
    
    float imageHeight = player->getTexture()->getContentSize().height;
    //将玩家精灵添加到屏幕的下方正中央
    player->setPosition(CCPointMake(screenSize.width/2, imageHeight/2));
    
    //调用加载蜘蛛精灵的方法
    this->initSpiders();
    //this->scheduleUpdate();
    return true;
}

//初始化蜘蛛
void HelloWorld::initSpiders()
{
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    CCSprite *tempSpider = CCSprite::create("spider.png");
    //获取蜘蛛贴图的宽度
    float imageWidth = tempSpider->getContentSize().width;
    //计算一行能显示几个蜘蛛
    int numSpiders = size.width / imageWidth;
    
    spiders = new CCArray();
    //设置一个断言
    CCAssert(spiders -> init(), "spiders array is already initialized!");
//    spiders = CCArray::createWithCapacity(numSpiders); //数组重复初始化了
    
    for (int i = 0 ; i < numSpiders; i++)
    {
        CCSprite *spider = CCSprite::create("spider.png");
        this->addChild(spider, 0, 2);
        spiders->addObject(spider);
    }
    //暂停按钮
    CCMenuItemImage *pPauseItem = CCMenuItemImage::create(
                                                          "CloseNormal.png",
                                                          "CloseSelected.png",
                                                          this,
                                                          menu_selector(HelloWorld::pauseOrStart));
    pPauseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );

    CCMenu* pMenu = CCMenu::create(pPauseItem, NULL);
    pMenu->setPosition( CCPointZero );
    //    pMenu->alignItemsHorizontally();
    this->addChild(pMenu, 1,105);
    //写一个方法,将蜘蛛移动到其他位置,不然默认的创建的几个蜘蛛精灵就是创建在原点上
    this->resetSpiders();
    this->scheduleUpdate();
}

//重新布局蜘蛛代码
void HelloWorld::resetSpiders()
{
    //分数清0
    count = 0;
    
    //获取屏幕尺寸
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    //获取蜘蛛尺寸
    CCSprite *tempSpider = (CCSprite *)spiders->lastObject();
    CCSize imageSize = tempSpider->getContentSize();
    
    int numSpiders = spiders->count();
    
    for (int i=0; i<numSpiders; i++) {
        CCSprite *spider = (CCSprite *)spiders->objectAtIndex(i);
        spider->setPosition(CCPointMake(imageSize.width * i + imageSize.width *0.5f,size.height + imageSize.height));
        //将所有的蜘蛛的动作都停止
        spider->stopAllActions();
    }
    
    this->unschedule(schedule_selector(HelloWorld::spidersUpdate));
    this->schedule(schedule_selector(HelloWorld::spidersUpdate), 0.6f);
    //this->schedule(schedule_selector(HelloWorld::spidersUpdate), 0.6f);
    numSpidersMoved = 0;
    spiderMoveDuration = 3.0f; //从上到下多长时间运行结束
    //this->scheduleUpdate();
}

//更新蜘蛛
void HelloWorld::spidersUpdate(cocos2d::CCTime delta)
{
    for (int i=0; i < spiders->count(); i++)
    {
        int randomSpiderIndex = CCRANDOM_0_1()*spiders->count();
        CCSprite * spider = (CCSprite *)spiders->objectAtIndex(randomSpiderIndex);
        
        //注意:这个方法表示当前对象是否有动作正在执行
        if (spider->numberOfRunningActions() == 0)
        {
            if (i > 0)
            {
                CCLog("Dropping a Spider after %d retries.",i);
            }
            this->runSpiderMoveSequence(spider);
            
            break;
        }
    }
}
//蜘蛛移动
void HelloWorld::runSpiderMoveSequence(cocos2d::CCSprite *spider)
{
    numSpidersMoved++;
    //控制难度
    if (numSpidersMoved %8 == 0 && spiderMoveDuration > 2.0f) {
        spiderMoveDuration -= 0.1f;
    }
    CCPoint belowScreenPosition = CCPointMake(spider->getPosition().x, -(spider->getTexture()->getContentSize().height));
    CCMoveTo *move = CCMoveTo::create(spiderMoveDuration, belowScreenPosition);
    CCCallFuncN *call = CCCallFuncN::create(this,callfuncN_selector(HelloWorld::spiderBelowSceen));
    CCSequence *sequence = CCSequence::create(move,call,NULL);
    spider->runAction(sequence);
    
}
//作为回调方法,当蜘蛛运动到屏幕下方的时候,从新返回到顶部
void HelloWorld::spiderBelowSceen(void *sender)
{
    //断言
    //CCAssert(dynamic_cast<CCSprite *>(this->getChildByTag(3))!=NULL, "非法类型");
    CCSprite * spider = (CCSprite*)sender;
    
    CCPoint pos = spider->getPosition();
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    pos.y = size.height+spider->getTexture()->getContentSize().height;
    spider->setPosition(pos);
}


HelloWorld::~HelloWorld()
{
    spiders->release();
    CCLog("资源已经回收");
}



//参数是固定的是每隔一桢间隔的时间的意思,也就是update函数每针都会调用
void HelloWorld::update(float delta)
{
    
    float playerImageSize = player->getContentSize().width;
    CCSprite *sp = (CCSprite *)spiders->lastObject();
    float spiderImageSize = sp->getContentSize().width;
    
    float playerCollisionRadius = playerImageSize * 0.4f;
    float spiderCollisionRadius = spiderImageSize * 0.4f;
    
    float maxCollisionDistance = playerCollisionRadius + spiderCollisionRadius;
    
    int numSpiders = spiders->count();
    //显示分数
    scoreLabel->setString(CCString::createWithFormat("%d",count)->getCString());

    for (int i = 0; i< numSpiders; i++)
    {
        CCSprite * spider = (CCSprite *)spiders->objectAtIndex(i);
        if (spider->numberOfRunningActions()==0)
        {
            continue;
        }
        //ccpDistance计算两点之间的距离函数
        float actualDistance = ccpDistance(player->getPosition(), spider->getPosition());
        if (actualDistance< maxCollisionDistance) {
            //主角精灵死亡音效
            SimpleAudioEngine::sharedEngine()->playEffect("alien-sfx.caf");
            
            //重新加载
            this->resetSpiders();
            const char *str = "GameOver!";
            this->showGameOver(str);
            //CCDirector::sharedDirector()->stopAnimation();
            
            CCSprite *sprider;
    
            //让屏幕停止 除了GameOver字体
            this->unschedule(schedule_selector(HelloWorld::spidersUpdate));
            this->unscheduleUpdate();
            
            break;
        }
    }
    
    count++;
}

//单点触摸
bool HelloWorld:: ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)
{
    return true;
}
//触摸移动
void HelloWorld:: ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent)
{
    player->setPosition(pTouch->getLocation());
}

 void HelloWorld:: ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent)
{
    //this->resetGame();
    //CCDirector::sharedDirector()->startAnimation();
}
//触摸注册事件
void HelloWorld:: registerWithTouchDispatcher()
{
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

//显示游戏结束
void HelloWorld::showGameOver(const char *str)
{
    //清除暂停按钮
    this->removeChildByTag(105, true);
    
    CCSize screenSize = CCDirector::sharedDirector()->getWinSize();
    CCLabelTTF * gameOver = CCLabelTTF::create(str, "Marker Felt", 60);
    gameOver->setPosition(ccp(screenSize.width / 2, screenSize.height / 3));
    this->addChild(gameOver,100,100);
	
    // game over label runs 3 different actions at the same time to create the combined effect
	// 1) 颜色渐变
    CCTintTo * tint1 = CCTintTo::create(2, 255, 0, 0);
    CCTintTo * tint2 = CCTintTo::create(2, 255, 255, 0);
    CCTintTo * tint3 = CCTintTo::create(2, 0, 255, 255);
    CCTintTo * tint4 = CCTintTo::create(2, 0, 0, 255);
    CCTintTo * tint5 = CCTintTo::create(2, 255, 0, 255);
    CCTintTo * tint6 = CCTintTo::create(2, 0, 255, 0);

	CCSequence* tintSequence = CCSequence::create(tint1,tint2,tint3,tint4,tint5,tint6,NULL);
	CCRepeatForever* repeatTint = CCRepeatForever::create(tintSequence);
	gameOver->runAction(repeatTint);
	
	// 2) 缓缓旋转
	CCRotateTo* rotate1 = CCRotateTo::create(2, 3);
    CCEaseBounceInOut*bounce1 = CCEaseBounceInOut::create(rotate1);
    CCRotateTo* rotate2 = CCRotateTo::create(2, -3);
    CCEaseBounceInOut*bounce2 = CCEaseBounceInOut::create(rotate2);
    CCSequence* rotateSequence = CCSequence::create(bounce1,bounce2,NULL);
    CCRepeatForever* repeateBounce = CCRepeatForever::create(rotateSequence);
    gameOver->runAction(repeateBounce);
    
	// 3) jumping
    CCJumpBy* jump = CCJumpBy::create(3, CCPointZero, screenSize.height/3, 1);
    CCRepeatForever* repeatJump = CCRepeatForever::create(jump);
    gameOver->runAction(repeatJump);
    
    
    //提醒继续游戏
    CCLabelTTF*touchLabel = CCLabelTTF::create("请按下面按钮(继续挑战/返回上级)", "Arial", 20 );
    touchLabel->setPosition(CCPointMake(screenSize.width/2,screenSize.height/2 + 180));
    touchLabel->setColor(ccRED);
    this->addChild(touchLabel,100,101);
    
    CCMenuItemImage *pStartItem = CCMenuItemImage::create(
                                                          "CloseNormal.png",
                                                          "CloseSelected.png",
                                                          this,
                                                          menu_selector(HelloWorld::menuCloseCallback) );
    pStartItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width/2 - 70, CCDirector::sharedDirector()->getWinSize().height / 2 +120) );

    CCMenuItemImage *pReturnItem = CCMenuItemImage::create(
                                                          "CloseNormal.png",
                                                          "CloseSelected.png",
                                                          this,
                                                          menu_selector(HelloWorld::returnView) );
    pReturnItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width/2 + 70, CCDirector::sharedDirector()->getWinSize().height / 2 + 120) );
    
    // create menu, it's an autorelease object
    CCMenu* pMenu = CCMenu::create(pStartItem,pReturnItem, NULL);
    pMenu->setPosition( CCPointZero );
//    pMenu->alignItemsHorizontally();
    this->addChild(pMenu, 1,102);
}

//重新开始
void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    //添加暂停按钮
    CCMenuItemImage *pPauseItem = CCMenuItemImage::create(
                                                          "CloseNormal.png",
                                                          "CloseSelected.png",
                                                          this,
                                                          menu_selector(HelloWorld::pauseOrStart));
    pPauseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );
    
    CCMenu* pMenu = CCMenu::create(pPauseItem, NULL);
    pMenu->setPosition( CCPointZero );
    //    pMenu->alignItemsHorizontally();
    this->addChild(pMenu, 1,105);
    this->HelloWorld::resetGame();
    this->scheduleUpdate();
    this->schedule(schedule_selector(HelloWorld::spidersUpdate),0.6f);
}

void HelloWorld::resetGame()
{
    this->removeChildByTag(100, true);
    this->removeChildByTag(101, true);
    this->removeChildByTag(102, true);
    //重新开始排列蜘蛛精灵
    this->resetSpiders();
//    this->scheduleUpdate();
}
//返回上级
void HelloWorld::returnView()
{
    CCTransitionFade * secondScene = CCTransitionFade::create(0.5f, Loading::scene(), ccBLACK);
    CCDirector::sharedDirector()->replaceScene(secondScene);
}

//暂停或者开始
void HelloWorld::pauseOrStart()
{
    if (flag) {
        CCDirector::sharedDirector()->stopAnimation();
        flag = false;
    }
    else
    {
        CCDirector::sharedDirector()->startAnimation();
        flag = true;
    }
}

4.导航页面的设计

4.1 Loading.h:

//
//  Loading.h
//  GameDemo
//
//  Created by 丁小未 on 13-9-6.
//
//

#ifndef __GameDemo__Loading__
#define __GameDemo__Loading__

#include <iostream>
#include "cocos2d.h"
using namespace cocos2d;
class Loading : public cocos2d::CCLayer
{
public:
    virtual bool init();
    static cocos2d::CCScene* scene();
    
    virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);
    
    //void makeTransition();
    void convertView();
    
    virtual void registerWithTouchDispatcher(void);
    
    virtual void onEnter();
    virtual void onExit();
    //游戏介绍
    void gameIntroduce();
    //游戏设置
    void gameSetting();
    //游戏开始
    void gameBegging();
    CREATE_FUNC(Loading);
};
#endif /* defined(__GameDemo__Loading__) */


4.2Loading.m:

#include "Loading.h"
#include "SimpleAudioEngine.h"
#include "HelloWorldScene.h"
using namespace cocos2d;
using namespace CocosDenshion;

CCScene* Loading::scene()
{
    CCScene *scene = CCScene::create();
    
    Loading *layer = Loading::create();
    
    scene->addChild(layer);
    
    return scene;
}
bool Loading::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }
    
    this->setTouchEnabled(true);
    
    return true;
}

void Loading::gameIntroduce()
{
    CCLog("introduce");
}
void Loading::gameSetting()
{
    CCLog("gamesetting");
}
void Loading::gameBegging()
{
    this->Loading::convertView();
}

void Loading::onEnter()
{
    CCLayer::onEnter();
    
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    
    CCSprite *background = CCSprite::create("bg.png");
    background->setPosition(ccp(size.width/2, size.height/2));
    this->addChild(background);
    
    CCLabelTTF *label = CCLabelTTF::create("躲避蜘蛛游戏", "Marker Felt", 48);
    label->setPosition(ccp(size.width/2, size.height - 100));
    label->setColor(ccGREEN);
    this->addChild(label);
    
    CCMenuItemImage * button = CCMenuItemImage::create("play.png", "play.png", this, menu_selector(Loading::gameBegging));
    button->setPosition(size.width/2,size.height/2-80);
    
    CCLabelTTF *labell = CCLabelTTF::create("游戏概况", "Marker Felt", 34);
    CCMenuItemLabel *mlabel = CCMenuItemLabel::create(labell,this,menu_selector(Loading::gameIntroduce));
    mlabel->setPosition(CCPointMake(size.width/2, size.height/2 + 75));
    mlabel->setColor(ccBLUE);
    
    CCLabelTTF *labell1 = CCLabelTTF::create("游戏设置", "Marker Felt", 34);
    CCMenuItemLabel *mlabel1 = CCMenuItemLabel::create(labell1,this,menu_selector(Loading::gameSetting));
    mlabel1->setPosition(CCPointMake(size.width/2, size.height/2 + 15));
    mlabel1->setColor(ccBLUE);
    
    
    CCMenu *menu = CCMenu::create(button,mlabel,mlabel1,NULL);
    menu->setPosition(CCPointZero);
    this->addChild(menu);
}

void  Loading::onExit()
{
    CCLayer::onExit();
    CCLog("结束");
}

void Loading::registerWithTouchDispatcher()
{
    CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, 0, true);
}

bool Loading::ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent)
{
    CCPoint point = pTouch->getLocation();
    CCLog("%f,%f",point.x,point.y);
    return true;
}

void Loading::convertView()
{
    CCTransitionFade * secondScene = CCTransitionFade::create(0.5f, HelloWorld::scene(), ccBLACK);
    CCDirector::sharedDirector()->replaceScene(secondScene);
}





收获:

1.获取图片的高度

float imageHeight =player->getTexture()->getContentSize().height;


2.断言的设置

CCAssert(dynamic_cast<CCSprite *>(this->getChildByTag(3))!=NULL, "非法类型");

3.设置屏幕刷新的频率

找到AppController.mm文件,找到- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions这个方法

然后添加:

//设置刷新频率

cocos2d::CCApplication::sharedApplication()->setAnimationInterval(0.3);

就能使得屏幕刷新变慢,然后方便调试

源码下载(猛戳)




cocos2d介绍[转]:

一、游戏介绍

这个例子是一个叫做Doodle Drop的游戏,是一个重力感应类游戏。玩家操纵角色来躲避从空中坠落的障碍物。游戏界面如下:

 

二、设置主场景

1、新建Cocos2d Application,工程名DoodleDrop。

2、游戏主场景。选File -> new file,选择User Templates -> Cocos2d.0.99.x -> CCNode.class。Subclass Of选择CCLayer。文件名选GameScene。

3、在头文件中声明静态方法 +(id) scene;

4、.m文件

#import "GameScene.h"

@implementation GameScene

+(id) scene {

CCScene *scene = [CCScene node];

CCLayer* layer = [GameScene node];

[scene addChild:layer];

return scene;

}

-(id) init {

if ((self = [super init])) {

CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self); return self;

}

-(void) dealloc {

// never forget to call [super dealloc] [super dealloc];

CCLOG(@"%@: %@", NSStringFromSelector(_cmd), self);

[super dealloc];

}

@end

5、删除HelloWorldScene.h和HelloWorldScene.m文件。

6、修改DoodleDropAppDelegate.m,将其中的主场景启动代码修改为GameScene:

[[CCDirector sharedDirector] runWithScene: [GameScene scene]];

三、游戏角色

1 、把玩家角色图片 alien.png 添加到工程。添加时,选中“ Copy items ”,同时勾选“ add to targets”中的“ DoodleDrop ”选项。

2 、在游戏主场景 (GameScene.h) 增加变量声明:

CCSprite* player;

3、在游戏主场景(GameScene.m)的init方法中加入下列代码:

self.isAccelerometerEnabled = YES;

player = [CCSprite spriteWithFile:@"alien.png"];

[self addChild:player z:0 tag:1];

CGSize screenSize = [[CCDirector sharedDirector] winSize];

  float imageHeight = [player texture].contentSize.height;

player.position = CGPointMake(screenSize.width / 2, imageHeight / 2);

这样,玩家角色就被放到屏幕底部正中的位置上。注意,player变量未retain。因为addChild会自动retain。

[player texture].contentSize.height返回的是渲染图的content size。渲染对象(玩家角色图片alient.png)有两个尺寸:content size和texture size。前者是图片的实际尺寸,后者是渲染尺寸——iPhone规定渲染尺寸只能是2的n次方。比如图片实际尺寸100*100,那么渲染尺寸则是128*128,因为最接近100的2的n次方为128。

四、使用加速器

1、为了响应加速器事件,你必须在init方法中加上:

self.isAccelerometerEnabled = YES;

同时实现accelerometer方法:

-(void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration{

CGPoint pos = player.position;

  pos.x += acceleration.x * 10;

player.position = pos;

}

跟java和c不同。你不能对 player.position.x进行赋值。这种赋值在

c语言中是可以的,但oc中不行。因为player.position实际上是调用[player position],这个方法返回一个临时的CGPoint变量。当你想对这个临时的CGPoint的x进行赋值后,这个变量会被被抛弃,所以你的赋值没有任何作用。所以你需要用一个新的CGPoint变量,修改其x值,然后再把这个CGPoint赋值给player.position(即调用[player setPosition:])。如果你是来自java和c++的程序员,在oc中需要留心这个“不幸的”问题并尽可能的修改编程习惯。

2、运行测试

模拟器不支持重力感应,请在物理设备上运行代码。

五、玩家控制

现住发现用加速器控制有些不灵?反应迟钝,移动也不流畅?为此,我们需要增加一些代码。

首先需要增加变量声明:

CGPoint playerVelocity;

为了便于今后的扩展(假设有一天我们会想上下移动角色),这是一个CGPoint类型,而不是一个float。

然后修改加速器方法:

-(void) accelerometer:(UIAccelerometer *)accelerometer

didAccelerate:(UIAcceleration *)acceleration

{

// 减速度系数(值越小=转向越快)

float deceleration = 0.4f;

// 加速度系数 (值越大 = 越敏感)

float sensitivity = 6.0f;

// 最大速度

float maxVelocity = 100;

// 根据加速度计算当前速度

  playerVelocity.x = playerVelocity.x * deceleration + acceleration.x * sensitivity;

// 限制最大速度为 ±maxVelocity之间

  directions if (playerVelocity.x > maxVelocity) {

playerVelocity.x = maxVelocity;

} else if (playerVelocity.x < - maxVelocity) {

playerVelocity.x = - maxVelocity;

}

}

现在,玩家速度由一个一次线性方程决定:

V= V ₀ * β + V ∍ * ε

其中,

V 为终速

V ₀ 为初速

β     为减速系数

V ∍ 为加速度

ε 为加速系数

其中, β 和 ε两个系数(即减速度系数和加速度系数:deceleration和sensitivity变量)是两个经验值,你可以自己调整它以达到理想效果。

然后,需要通过以下方法来改变游戏角色的位置:

-(void) update:(ccTime)delta {

// 不断改变角色x坐标

  CGPoint pos = player.position;

pos.x += playerVelocity.x;

// 防止角色移到屏幕以外

  CGSize screenSize = [[CCDirector sharedDirector] winSize];

float imageWidthHalved = [player texture].contentSize.width * 0.5f; float leftBorderLimit = imageWidthHalved;

float rightBorderLimit = screenSize.width - imageWidthHalved;

if (pos.x < leftBorderLimit) {

pos.x = leftBorderLimit;

playerVelocity = CGPointZero;

} else if(pos.x > rightBorderLimit) {

pos.x = rightBorderLimit;

playerVelocity = CGPointZero;

}

 

player.position = pos;

  }

然后,在init方法中加入:

[self scheduleUpdate];

这样,每隔一段时间cocos2d会自动调用update方法。

六、添加障碍物

导入spider.png图片到工程。这是一张蜘蛛的图片,在游戏中我们需要躲避的东西。

首先,增加如下变量声明:

CCArray* spiders;

float spiderMoveDuration;

  int numSpidersMoved;

在init方法中,加上一句方法调用语句:

[self initSpiders];

下面是initSpiders方法:

-(void) {

CGSize screenSize = [[CCDirector sharedDirector] winSize];

// 用一个临时的CCSprider取得图片宽度

CCSprite* tempSpider = [CCSprite spriteWithFile:@"spider.png"]; float imageWidth = [tempSpider texture].contentSize.width;

// 计算出要多少蜘蛛图片可以布满屏幕的宽度

int numSpiders = screenSize.width / imageWidth;

// 初始化数组并指定数组大小

spiders = [[CCArray alloc] initWithCapacity:numSpiders];

for (int i = 0; i < numSpiders; i++) {

CCSprite* spider = [CCSprite spriteWithFile:@"spider.png"]; [self addChild:spider z:0 tag:2];

[spiders addObject:spider];

}

[self resetSpiders];

}

tempSpider是一个临时变量,我们仅用于取得图片宽度。我们没有retain他,也不需要release他——他会自动被release。

与此相反,spiders是由我们init的,我们也没有retain(实际上init会自动retain),但我们必须自己release(OC规定,init/copy/new出来的对象,必须手动release,OC的内存管理不会自动release)。因此在dealloc方法中有这么一句:

[spiders release],spiders=nil;

同时,我们使用了coco2d提供的一个类似NSMutableArray的CCArray类,该类对数组的操作更快。以下是CCArray提供的一些方法:

+ (id) array;

+ (id) arrayWithCapacity:(NSUInteger)capacity;

+ (id) arrayWithArray:(CCArray*)otherArray;

+ (id) arrayWithNSArray:(NSArray*)otherArray;

- (id) initWithCapacity:(NSUInteger)capacity;

  - (id) initWithArray:(CCArray*)otherArray;

- (id) initWithNSArray:(NSArray*)otherArray;

- (NSUInteger) count;

- (NSUInteger) capacity;

- (NSUInteger) indexOfObject:(id)object;

  - (id) objectAtIndex:(NSUInteger)index;

- (id) lastObject;

- (BOOL) containsObject:(id)object;

#pragma mark Adding Objects

- (void) addObject:(id)object;

 

- (void) addObjectsFromArray:(CCArray*)otherArray;

  - (void) addObjectsFromNSArray:(NSArray*)otherArray;

- (void) insertObject:(id)object atIndex:(NSUInteger)index;

#pragma mark Removing Objects

- (void) removeLastObject;

- (void) removeObject:(id)object;

  - (void) removeObjectAtIndex:(NSUInteger)index;

- (void) removeObjectsInArray:(CCArray*)otherArray;

- (void) removeAllObjects;

- (void) fastRemoveObject:(id)object;

- (void) fastRemoveObjectAtIndex:(NSUInteger)index;

- (void) makeObjectsPerformSelector:(SEL)aSelector;

- (void) makeObjectsPerformSelector:(SEL)aSelector withObject:(id)object;

- (NSArray*) getNSArray;

resetSpiders 方法如下所示:

-(void) resetSpiders {

CGSize screenSize = [[CCDirector sharedDirector] winSize];

// 用一个临时的CCSprider取得图片宽度

CCSprite* tempSpider = [spiders lastObject];

CGSize size = [tempSpider texture].contentSize;

int numSpiders = [spiders count];

for (int i = 0; i < numSpiders; i++) {

// 放置每个蜘蛛的位置

CCSprite* spider = [spiders objectAtIndex:i];

spider.position =

CGPointMake(size.width * i + size.width * 0.5f,

screenSize.height + size.height);

[spider stopAllActions];

}

// 为保险起见,在注册之前先从schedule中反注册(未注册则不动作)

[self unschedule:@selector(spidersUpdate:)];

// 注册schedule,每0.7秒执行

[self schedule:@selector(spidersUpdate:) interval:0.7f];

}

 

-(void) {

  // 找出空闲的蜘蛛(未在移动的).

  for (int i = 0; i < 10; i++) {

    // 从数组中随机抽取一只蜘蛛

    int randomSpiderIndex = CCRANDOM_0_1() * [spiders count];

    CCSprite* spider = [spiders objectAtIndex:randomSpiderIndex];

    // 若蜘蛛未在移动,让蜘蛛往下掉

    if ([spider numberOfRunningActions] == 0) {

      // 控制蜘蛛往下掉

      [self runSpiderMoveSequence:spider];

       // 每次循环仅移动一只蜘蛛

      break;

    }

  }

}

-(void) runSpiderMoveSequence:(CCSprite*)spider

{

  // 随时间逐渐加快蜘蛛的速度

  numSpidersMoved++;

  if (numSpidersMoved % 8 == 0 && spiderMoveDuration > 2.0f) {

    spiderMoveDuration -= 0.1f;

  }

// 移动的终点

CGPoint belowScreenPosition = CGPointMake(spider.position.x,

-[spider texture].contentSize.height);

// 动作:移动

  CCMoveTo* move = [CCMoveTo actionWithDuration:spiderMoveDuration

position:belowScreenPosition];

// 瞬时动作:方法调用

CCCallFuncN* call = [CCCallFuncN actionWithTarget:self

selector:@selector(spiderBelowScreen:)];

// 组合动作:移动+方法调用

CCSequence* sequence = [CCSequence actions:move, call, nil];

// 运行组合动作

[spider runAction:sequence];

}

spiderBelowScreen方法重置蜘蛛的状态,让其回到屏幕上端等待下次坠落。

-(void) spiderBelowScreen:(id)sender {

// 断言:sender是否为CCSprite.

  NSAssert([sender isKindOfClass:[CCSprite class]], @"sender is not a CCSprite!");

CCSprite* spider = (CCSprite*)sender;

// 把蜘蛛重新放回屏幕上端

CGPoint pos = spider.position;

CGSize screenSize = [[CCDirector sharedDirector] winSize];

pos.y = screenSize.height + [spider texture].contentSize.height; spider.position = pos;

}

书中作者提到,出于一个“保守”程序员的习惯,作者使用了 NSAssert语句来测试sender是否是一个CCSprite类。虽然理论上,Sender应当是一个CCSprite,实际上它却有可能根本不是。 因为作者曾犯过一个错误:把CCCallFuncN写成了CCCallFunc(二者的区别在于,后者不能传递参数而前者带一个sender参数),导致sender未被作为参数传递到调用方法,即sender=nil。这样的错误也被NSAssert捕获到了,于是作者发现并修改了这个错误。

七、碰撞检测

很简单。在update方法中添加语句:

[self checkForCollision];

checkForCollision中包含了碰撞检测的所有逻辑:

-(void ) checkForCollision {

// 玩家和蜘蛛的尺寸

float playerImageSize = [player texture].contentSize.width;

float spiderImageSize = [[spiders lastObject] texture].contentSize.width;

//玩家和蜘蛛的碰撞半径

float playerCollisionRadius = playerImageSize * 0.4f;

float spiderCollisionRadius = spiderImageSize * 0.4f;

// 发生碰撞的最大距离,如果两个对象间的距离<=此距离可判定为有效碰撞

float maxCollisionDistance=playerCollisionRadius +spiderCollisionRadius;

int numSpiders = [spiders count];

//循环检测玩家和每一只蜘蛛间的碰撞距离

for (int i = 0; i < numSpiders; i++) {

  CCSprite* spider = [spiders objectAtIndex:i];

  // 计算每只蜘蛛和玩家间的距离. ccpDistance及其他非常有用的函数都列在 CGPointExtension中

  float actualDistance =   ccpDistance(player.position, spider.position);

  // 如二者距离小于碰撞最大距离,认为发生碰撞?

  if (actualDistance < maxCollisionDistance) {

    // 结束游戏.

    [self showGameOver];

  }

}

}

-(void) showGameOver

{

// 屏保开启

[self setScreenSaverEnabled:YES];

// 冻结所有对象的动作

CCNode* node;

CCARRAY_FOREACH([self children], node) {

[node stopAllActions];

}

// 使蜘蛛保持扭动

CCSprite* spider;

CCARRAY_FOREACH(spiders, spider) {

[self runSpiderWiggleSequence:spider];

}

// 游戏开始前,关闭加速器的输入

self.isAccelerometerEnabled = NO;

// 允许触摸

self.isTouchEnabled = YES;

// 取消所有schedule

[self unscheduleAllSelectors];

 

// 显示GameOver文本标签

CGSize screenSize = [[CCDirector sharedDirector] winSize];

CCLabel* gameOver = [CCLabel labelWithString:@"GAME OVER!" fontName:@"Marker Felt" fontSize:60];

gameOver.position = CGPointMake(screenSize.width / 2, screenSize.height / 3);

[self addChild:gameOver z:100 tag:100];

// 动作:色彩渐变

CCTintTo* tint1 = [CCTintTo actionWithDuration:2 red:255 green:0 blue:0];

CCTintTo* tint2 = [CCTintTo actionWithDuration:2 red:255 green:255 blue:0];

CCTintTo* tint3 = [CCTintTo actionWithDuration:2 red:0 green:255 blue:0];

CCTintTo* tint4 = [CCTintTo actionWithDuration:2 red:0 green:255 blue:255];

CCTintTo* tint5 = [CCTintTo actionWithDuration:2 red:0 green:0 blue:255];

CCTintTo* tint6 = [CCTintTo actionWithDuration:2 red:255 green:0 blue:255];

CCSequence* tintSequence = [CCSequence actions:tint1, tint2, tint3, tint4, tint5, tint6, nil];

CCRepeatForever* repeatTint = [CCRepeatForever actionWithAction:tintSequence];

[gameOver runAction:repeatTint];

// 动作:转动、颤动

CCRotateTo* rotate1 = [CCRotateTo actionWithDuration:2 angle:3];

CCEaseBounceInOut* bounce1 = [CCEaseBounceInOut actionWithAction:rotate1];

CCRotateTo* rotate2 = [CCRotateTo actionWithDuration:2 angle:-3];

CCEaseBounceInOut* bounce2 = [CCEaseBounceInOut actionWithAction:rotate2];

CCSequence* rotateSequence = [CCSequence actions:bounce1, bounce2, nil];

CCRepeatForever* repeatBounce = [CCRepeatForever actionWithAction:rotateSequence];

[gameOver runAction:repeatBounce];

// 动作:跳动

CCJumpBy* jump = [CCJumpBy actionWithDuration:3 position:CGPointZero height:screenSize.height / 3 jumps:1];

CCRepeatForever* repeatJump = [CCRepeatForever actionWithAction:jump];

[gameOver runAction:repeatJump];

// 标签:点击游戏开始

CCLabel* touch = [CCLabel labelWithString:@"tap screen to play again" fontName:@"Arial" fontSize:20];

touch.position = CGPointMake(screenSize.width / 2, screenSize.height / 4);

[self addChild:touch z:100 tag:101];

// 动作:闪烁

CCBlink* blink = [CCBlink actionWithDuration:10 blinks:20];

CCRepeatForever* repeatBlink = [CCRepeatForever actionWithAction:blink];

[touch runAction:repeatBlink];

}

当然,为了使游戏一开始就停顿在GameOver画面,需要在init方法中调用:

[self showGameOver];

只有当用户触摸屏幕后,游戏才会开始。这需要实现方法:

-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

{

[self resetGame];

}

resetGame方法负责重置游戏变量并启动游戏。

-(void) resetGame

{

// 关闭屏保

[self setScreenSaverEnabled:NO];

// 移除GameOver标签和启动游戏标签

[self removeChildByTag:100 cleanup:YES];

[self removeChildByTag:101 cleanup:YES];

// 启动加速器输入,关闭触摸输入

self.isAccelerometerEnabled = YES;

self.isTouchEnabled = NO;

// 重设蜘蛛数组

[self resetSpiders];

// 注册schedule

[self scheduleUpdate];

// 积分

score = 0;

totalTime = 0;

[scoreLabel setString:@"0"];

}

开启/关闭屏保的方法:

-(void) setScreenSaverEnabled:(bool)enabled

{

UIApplication *thisApp = [UIApplication sharedApplication];

thisApp.idleTimerDisabled = !enabled;

}

使蜘蛛不停扭动的方法如下(实际上是把图像不断的放大缩小):

-(void) runSpiderWiggleSequence:(CCSprite*)spider

{

//动作:放大

CCScaleTo* scaleUp = [CCScaleTo actionWithDuration:CCRANDOM_0_1() * 2 + 1 scale:1.05f];

//速度渐变动作:速度由慢至快,再由快至慢

CCEaseBackInOut* easeUp = [CCEaseBackInOut actionWithAction:scaleUp];

//动作:缩小

CCScaleTo* scaleDown = [CCScaleTo actionWithDuration:CCRANDOM_0_1() * 2 + 1 scale:0.95f];

//速度渐变动作:速度由慢至快,再由快至慢

CCEaseBackInOut* easeDown = [CCEaseBackInOut actionWithAction:scaleDown];

CCSequence* scaleSequence = [CCSequence actions:easeUp, easeDown, nil];

CCRepeatForever* repeatScale = [CCRepeatForever actionWithAction:scaleSequence];

[spider runAction:repeatScale];

}

 

八、CCLabel、 CCBitmapFontAtlas 和 Hiero

我们的计分标准很简单,以游戏时间作为游戏分数。

在init方法中加入:

scoreLabel = [CCLabel labelWithString:@"0" fontName:@"Arial" fontSize:48];

scoreLabel.position = CGPointMake(screenSize.width / 2, screenSize.height);

// 调整锚点。

scoreLabel.anchorPoint = CGPointMake(0.5f, 1.0f);

// 把label添加到scene,z坐标为-1,则位于所有layer的下方

[self addChild:scoreLabel z:-1];

为了将计分牌对其到屏幕上端中心位置,这里使用了“锚点”的概念。 锚点即参考点,和position属性配合使用,用于将物体向其他物体对齐。比如当把一个物体移动到一个位置点时,实际上是把这个物体的“锚点”移动/对齐到另外一个点。锚点由两个float表示,表示的是锚点相对于物体宽/高的比率。比如锚点(0.5f,1.0f)表示该锚点位于该物体宽1/2,高1/1的地方。

修改update方法,在其中加入:

// 每秒更新一次计分牌

totalTime += delta;

  int currentTime = (int)totalTime;

if (score < currentTime)

{

  score = currentTime;

  [scoreLabel setString:[NSString stringWithFormat:@"%i", score]];

}

这里需要说明的是,[CCLabel setString]方法的效率很低:它需要释放老的texture,分配一个新的texture,并用iOS font的rendering方法重新构造texture。你只需要注释[CCLabel  setString]方法就可以知道,那有多么的糟糕。不使用setString方法时帧率为60帧/秒,而使用该方法的帧率竟然才30帧/秒。

象CCSprite等刷新效率高(只是更费一点内存)的Label类,都是属于 CCBitmapFontAtlas类的特例。我们可以通过简单地把CCLabel变量声明从CCLabel更改为 CCBitmapFontAtlas,并修改它的构造语句:

scoreLabel = [CCBitmapFontAtlas bitmapFontAtlasWithString:@"0" fntFile:@"bitmapfont.fnt"];

在游戏中使用bitmapfont是很好的选择,因为操作更快速,同时会有一个缺点:bitmap字体都是大小固定的。如果同样的字体,大小不同,你需要对CCBitmapFontAtlas 对象进行缩放。或者为不同尺寸的字体创建单独的bitmap文件,并因此占用更多的内存。

当然需要把bitmapfont.fnt文件和对应的.png文件一起加入到工程的资源目录下。

如果你需要创建自己的bitmap字体,可以用Hiero这个小工具(java web application):

http://slick.cokeandcode.com/demos/hiero.jnlp

也可以使用 BMFont (windows应用):

  www.angelcode.com/products/bmfont/

Hiero允许你从TrueTypeFont创建一个.fnt文件,该文件可以直接用于 cocos2d的CCBitmapFontAtlas类。

安装Hiero时需要同意一个数字签名 。请放心,迄今为止没有迹象表明该签名有任何问题。

  Hiero的使用很简单,首先挑选一种TrueType字体,在Sample Text 文本框中输入你要用的字符,然后点击File->Save BMFont Files…即可保存为.fnt文件。

 

 

其他的选项是可选的。比如你可以加上渐变和阴影效果,使字体显得更3D。

选择Glyph cache后,你还可以调整生成的.png文件的大小。 当然,如果你象我一样只用到了极少的几个字符,只要把页宽/高设为最小值(比如在这里我们设成了256),然后点击Reset Cache应用。这样可以创建比较小.png文件同时减少内存占用。对于更复杂的字体,Hiero会创建多个.png文件——记住,每一个.png文件都应当加到工程中。

在这个例子里,我们的字体文件里只放了几个数字。因为png文件被创建为256*256大小,不管你是输入1个字还是再加几个其他的字,都会占用这么多的空间。

*注意,如果你使用了在.fnt文件中不存在的字符,那么该字符会被忽略掉,且不会显示在 CCBitmapFontAtlas中 。

九、加入音频

在工程目录中有一对音频文件: blues.mp3 和 alien- sfx.caf 。

在cocos2d中播放音频的最好也是最初的方法是用 SimpleAudioEngine。然而音频支持并不是cocos2d内置的一部分。它属于CocosDenshion,就像物理引擎一样。因此,你需要import额外的头文件:

#import "SimpleAudioEngine.h"

 

然后可以在init方法中象这样来播放音乐/音频:

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"blues.mp3" loop:YES];

[[SimpleAudioEngine sharedEngine] preloadEffect:@"alien-sfx.caf"];

对于背景音乐,我们设置loop参数为YES,这样就会循环播放。

对于音频声效,我们并没有立即播放,而仅仅是加载到内存。然后在条件合适时播放(比如碰撞发生时):

[[SimpleAudioEngine sharedEngine] playEffect:@"alien-sfx.caf"];

对于音乐,最好使用mp3格式。注意,同一时间内,只能播放1首背景音乐。虽然同时播放多首mp3从技术上是可行的,但物理硬件在同一时间内只能对一首mp3进行解码。在游戏中拒绝任何额外的CPU开销,因此对大部分游戏而言,都不会同时播放多首mp3.

至于声效,我喜欢用CAF格式。如果要进行音频格式的转换,可以使用 SoundConverter:

http://dekorte.com/projects/ shareware/SoundConverter/

如果文件大小在500k以内,该软件是免费的,无限制的许可仅仅需要$15。

如果你发现无法播放音频文件或者出现杂音,不要担心。有无数音频软件和音频编码拥有它们特有的文件格式。有些格式无法在iOS设备上播放,然而在其他设备上播放正常。解决办法是打开它们,然后重新保存。或者使用音频转换程序或音频软件。

十、迁移至iPad

如果所有的坐标都采用屏幕坐标,在iPad的大屏上运行游戏将会进行简单缩放而没有任何问题。相反,如果采用了固定坐标,你不得不重新编写游戏代码。

 

迁移至iPad工程很简单。在Groups&Files面板中选择Target,选择Project->Upgrade Current Target for iPad…,将打开对话框:

 

对于这个游戏,选“One Universal application”(即iPhone/iPad通用)。

这样的缺点是两个设备的特性都会被加到target,增加了程序大小。但程序既可在iPhone上运行,也可在iPad上运行。

另一个选择是“Two device-specific application”,你会得到两个独立于设备的app,你需要提交两次。如果用户有两个设备—— iPhone和iPad的,那么需要分别购买。

 

编译运行。程序会自动侦测当前所连接的设备类型并运行对应的版本。如图,选择iPad Simulator 3.2 ,可以查看在iPad模拟器运行游戏的效果:

 


相关文章
|
6月前
|
C# 图形学
【Unity】2D游戏-愤怒的小鸟教学实战(附源码和实现步骤 超详细)
【Unity】2D游戏-愤怒的小鸟教学实战(附源码和实现步骤 超详细)
362 2
|
6月前
|
图形学
【Unity 3D】3D游戏跑酷小子实战教学(附源码和步骤 超详细)
【Unity 3D】3D游戏跑酷小子实战教学(附源码和步骤 超详细)
291 0
|
6月前
|
算法
连连看游戏系列教程开篇
连连看游戏系列教程开篇
97 0
|
Java API 开发工具
Unity零基础到入门 ☀️| 基础知识入门篇章,看完就可以做游戏啦! | 寻找C站宝藏
❤️寻找C站宝藏-Unity 📢前言 🧡Unity零基础到入门(一) ☀️| 一起走进游戏引擎界大佬——Unity 的陈情往事(^_−)☆ 💛Unity零基础到入门(二) ☀️| Unity下载安装---使用UnityHub下载各个版本的Unity(收藏) 💚Unity零基础到入门(三) ☀️| 轻松学会 Unity界面布局和简单实例——入门级!(^_−)☆ 💙Unity零基础到入门(四) ☀️| 学会这些Unity实用知识点,我也可以做小游戏啦!(^_−)☆ 💜Unity零基础到入门(五) ☀️| 学会这些Unity常用组件,我离开发⭐️3A游戏大作⭐️又近了一步!(^_−)☆
Unity零基础到入门 ☀️| 基础知识入门篇章,看完就可以做游戏啦! | 寻找C站宝藏
下一篇
无影云桌面