您的位置:首页 > 移动开发 > IOS开发

手把手教你制作一款Box2D小游戏(二)

2017-01-03 21:33 337 查看

我们继续来制作这款Box2D小游戏。
前一篇中我们介绍了StepBlock类的制作,本篇中我们首先来定义好游戏的场景(CCScene)和层(CCLayer)。
我们定义一个GameLayer类,继承自CCLayer,声明如下:

#import "CCLayer.h"

#import "cocos2d.h"

#import "Box2D.h"

#import "ParallaxBackground.h"

#import "Ball.h"

#import "TagDefinitions.h"

#import "ContactListener.h"

 

@interface GameLayer : CCLayer <CCTouchOneByOneDelegate>{

    float screenWidth;

    float screenHeight;

    b2World* world;

    ParallaxBackground* background;

    NSMutableArray* steps;

    Ball* ball;

    float lowestStepY;

    float stepDistance;

    BOOL gameBegin;

    ForceType appliedForceType;

    CCLabelAtlas* scoreLb;

    CCLabelAtlas* highScoreLb;

    CCSprite* scoreIcon;

    CCSprite* highScoreIcon;

    int score;

    int highScore;

    ContactListener* myListener;

    BOOL isTouchable;

}

 

@property int lastScoredStep;

 

+ (GameLayer*) gameLayer;

+ (id)scene;

- (b2World*) sharedWorld;

- (void)addScore;

 

@end
screenHeight和screenWidth为屏幕高宽,world为这款游戏的世界对象,background是游戏的滚动背景(需要自己制作),steps是游戏中需要用到的StepBlock的数组,主要是出于复用的考虑。ball是游戏中的小球对象,我们接下来会实现。lowestStepY前面介绍过了,是位置最低的Block的y值。stepDistance是相邻的上下两个StepBlock之间的间隔。gameBegin用来标记游戏是否已经开始,如果游戏没有开始,所有的物体都应静止不动。appliedForceType用来记录小球的受力情况(小球受点击屏幕控制)。下面的跟score相关的CCLabelAtlas,CCSprite以及int值都是跟当前分数和最高分有关的,myListener是重载b2ContactListener的类的实例,用来侦听碰撞事件并做相应的处理,我们后面来介绍。isTouchable用来控制屏幕点击,后面会用到。
GameLayer.mm的定义:
首先定义属性的实现:

@synthesize lastScoredStep;
然后是一个静态变量:

static GameLayer* instance;
静态方法,用来返回静态成员instance(instance在初始化的时候会被赋值为self):

+ (GameLayer*)gameLayer{

    return instance;

}
静态的scene方法,返回一个包含GameLayer的场景对象供AppDelegate引用:

+ (id)scene{

    CCScene* scene = [CCScene node];

    GameLayer* gameLayer = [GameLayer node];

    [scene addChild:gameLayer];

   

    return scene;

}
下面列出了需要用到的图片素材:
highscore.png:



numbers.png:



score.png:



接着我们在GameLayer中添加下面的方法,初始化Score和HighScore(效果可以看上一篇最开始的那个截图):

- (void)initLabels{

    int labelPos = 25;

    scoreIcon = [CCSpritespriteWithFile:@"score.png"];

    scoreIcon.anchorPoint = ccp(0, 1);

    scoreIcon.position = ccp(17, screenHeight -labelPos);

    [self addChild:scoreIcon];

    scoreLb = [CCLabelAtlaslabelWithString:@"0" charMapFile:@"numbers.png"itemWidth:16 itemHeight:20 startCharMap:'0'];

    scoreLb.position = ccp(100, screenHeight -labelPos);

    scoreLb.anchorPoint = ccp(0, 1);

    [self addChild:scoreLb];

   

    highScoreIcon = [CCSpritespriteWithFile:@"highscore.png"];

    highScoreIcon.anchorPoint = ccp(0, 1);

    highScoreIcon.position = ccp(screenWidth * 0.5 +25, screenHeight - labelPos);

    [self addChild:highScoreIcon];

    highScoreLb = [CCLabelAtlaslabelWithString:[NSString stringWithFormat:@"%d", highScore]charMapFile:@"numbers.png" itemWidth:16 itemHeight:20startCharMap:'0'];

    highScoreLb.position = ccp(screenWidth * 0.5 +59, screenHeight - labelPos);

    highScoreLb.anchorPoint = ccp(0, 1);

    [self addChild:highScoreLb];

}
然后添加下面的方法,都是跟score和highscore相关的方法,比较容易理解,不多做解释:

- (void)initScores{

    score = 0;

    [scoreLb setString:@"0"];

}

 

- (void)saveHighScore{

    [[NSUserDefaults standardUserDefaults]setObject:[NSNumber numberWithInt:highScore] forKey:@"highScore"];

    highScore = [[[NSUserDefaultsstandardUserDefaults] objectForKey:@"highScore"] intValue];

}

 

- (void)addScore{

    score++;

    [scoreLb setString:[NSStringstringWithFormat:@"%d", score]];

    if (score > highScore) {

        highScore = score;

        [highScoreLbsetString:[NSString stringWithFormat:@"%d", highScore]];

    }

}
接下来我们添加初始化StepBlock的方法:

- (void)initSteps{

    steps = [[NSMutableArray array] retain];

   

    lowestStepY = screenHeight * 0.5f;

    [steps addObject:[[StepBlock alloc]initWithPos:ccp(screenWidth * 0.5f, lowestStepY) andType:StepBlockNormal]];

    lowestStepY -= stepDistance;

    [steps addObject:[[StepBlock alloc]initWithPos:ccp(0, lowestStepY) andType:StepBlockNormal]];

    lowestStepY -= stepDistance;

    [steps addObject:[[StepBlock alloc]initWithPos:ccp(1000, lowestStepY) andType:StepBlockNormal]];

   

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

        lowestStepY -=stepDistance;

        [stepsaddObject:[[StepBlock alloc] initWithY:lowestStepY]];

    }

   

    int tagBase = 1;

    for (StepBlock* step in steps) {

        step.tag = tagBase++;

        [self addChild:step];

    }

}
需要在StepBlock类中重载下面的方法:

- (void)setTag:(NSInteger)tag{

    body->SetUserData([NSNumbernumberWithInteger:tag]);

    if (tagSave < 1) {

        tagSave = tag;

    }

}
tagSave用来区分每一个Block。
接着我们定义GameLayer的初始化方法:

- (id)init{

    if (self = [super init]) {

        instance = self;

       

        //screen dimensions

        CGSize screenSize =[[CCDirector sharedDirector] winSize];

        screenHeight =screenSize.height;

        screenWidth =screenSize.width;

       

        //init cache

        [[GB2ShapeCachesharedShapeCache] addShapesWithFile:@"steps-elements.plist"];

        highScore =[[[NSUserDefaults standardUserDefaults] objectForKey:@"highScore"]intValue];

       

        //background

        background =[ParallaxBackground background];

        [selfaddChild:background];

       

        //init physics

        [self initPhysics];

       

        //init random seed

        srand(time(nil));

 

        stepDistance = 90;

        gameBegin = false;

        isTouchable = true;

        lastScoredStep = -1;

        appliedForceType =ForceNone;

        [self initSteps];

        [self initLabels];

        [self initScores];

    }

   

    return self;

}
初始化方法中的initPhysics用来初始化Box2D相关的对象:

- (void)initPhysics{

    b2Vec2 gravity;

    gravity.Set(0.0f, -8.0f);

    world = new b2World(gravity);

    world->SetAllowSleeping(true);

    world->SetContinuousPhysics(false);

   

    myListener = new ContactListener();

    world->SetContactListener(myListener);

   

    ball = [Ball ball];

    [self addChild:ball];

   

    // Define the ground body.

      b2BodyDef groundBodyDef;

      groundBodyDef.position.Set(0, 0); //bottom-left corner

     

      // The body is also added to theworld.

      b2Body* groundBody =world->CreateBody(&groundBodyDef);

      b2EdgeShape groundBox;

       

    b2Filter filter;

    filter.maskBits = 3;

    filter.categoryBits = 2;

     

    float leftX = 15.0/PTM_RATIO;

    float rightX = (screenWidth-15)/PTM_RATIO;

    float Y = 2*screenHeight/PTM_RATIO;

     

      // left

      groundBox.Set(b2Vec2(leftX, Y),b2Vec2(leftX,0));

      b2Fixture* fixture2 =groundBody->CreateFixture(&groundBox,0);

    fixture2->SetFilterData(filter);

    fixture2->SetFriction(0);

     

      // right

      groundBox.Set(b2Vec2(rightX, Y),b2Vec2(rightX, 0));

      b2Fixture* fixture3 =groundBody->CreateFixture(&groundBox,0);

    fixture3->SetFilterData(filter);

    fixture3->SetFriction(0);

}
我们在这个方法中初始化了世界对象,listener对象,ball对象以及左右的墙体,注意一下我们定义的墙体碰撞过滤标记。
接着我们定义reset方法,用来在一局游戏结束(小球掉下去了或者升到屏幕外边去了)的时候重置游戏:

- (void)reset{

    [self initScores];

    [ball reset];

    lastScoredStep = -1;

    lowestStepY = screenHeight * 0.5f;

    [[steps objectAtIndex:0]resetWithPos:ccp(screenWidth * 0.5f, lowestStepY) andType:StepBlockNormal];

    lowestStepY -= stepDistance;

    [[steps objectAtIndex:1] resetWithPos:ccp(0,lowestStepY) andType:StepBlockNormal];

    lowestStepY -= stepDistance;

    [[steps objectAtIndex:2] resetWithPos:ccp(1000,lowestStepY) andType:StepBlockNormal];

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

        lowestStepY -=stepDistance;

        [[stepsobjectAtIndex:(3+i)] resetWithY:lowestStepY];

    }

    [self scheduleUpdate];

}
最后定义update方法:

- (void)update:(ccTime)delta{

    int speed = min(80 + score/10, 150);

    if (ball.position.y < -20 || ball.position.y> (screenHeight + 20)) {

        [self showGameOver];

        return;

    }

   

    if (appliedForceType == ForceLeft) {

        [ball pushLeft];

    } else if (appliedForceType == ForceRight) {

        [ball pushRight];

    }

    float length = delta * speed;

    lowestStepY += length;

    for (StepBlock* step in steps) {

        if ([step moveUp:lengthlowestStepY:(lowestStepY - stepDistance)] == NO) {

           lowestStepY -= stepDistance;

        }

    }

   

    int32 velocityIterations = 8;

    int32 positionIterations = 4;

   

    world->Step(delta, velocityIterations,positionIterations);

}
update方法中用world的Step方法来执行物理计算,同时也更新StepBlock的位置,并且根据小球的受力来使其移动,根据小球的y坐标来判断是否GameOver。
showGameOver方法定义如下:

- (void)showGameOver{

    gameBegin = false;

    [self unscheduleUpdate];

    [background stopRolling];

    [self saveHighScore];

    isTouchable = false;

    [self schedule:@selector(enableTouch)interval:1.0f repeat:1 delay:1.0f];

}
enableTouch方法:

- (void)enableTouch{

    isTouchable = true;

    [self unschedule:_cmd];

}
我们需要通过点击屏幕左右来控制小球的受力(移动),因此让GameLayer实现CCTouchOneByOneDelegate委托,重载下面的三个方法:

- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{

    if (!isTouchable) {

        return NO;

    }

    if (gameBegin) {

        CGPoint touchLocation =[touch locationInView:[[CCDirector sharedDirector] view]];

        touchLocation =[[CCDirector sharedDirector] convertToGL:touchLocation];

        if (touchLocation.x <0.5 * screenWidth) {

           appliedForceType = ForceLeft;

        } else {

           appliedForceType = ForceRight;

        }

    } else {

        gameBegin = true;

        [backgroundstartRolling];

        [self reset];

    }

   

    return YES;

}

 

- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event{

    CGPoint touchLocation = [touchlocationInView:[[CCDirector sharedDirector] view]];

    touchLocation = [[CCDirector sharedDirector]convertToGL:touchLocation];

    if (touchLocation.x < 0.5 * screenWidth) {

        appliedForceType =ForceLeft;

    } else {

        appliedForceType =ForceRight;

    }

}

 

- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event{

    appliedForceType = ForceNone;

}
别忘了注册代理:

- (void)onEnterTransitionDidFinish{

    [[[CCDirector sharedDirector] touchDispatcher]addTargetedDelegate:self priority:1 swallowsTouches:YES];

}

 

- (void)onExit{

    [[[CCDirector sharedDirector] touchDispatcher]removeDelegate:self];

}
这样GameLayer就只做完成了。
由于篇幅限制,我们在下一篇中完成ContactListener和Ball类的编写。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  IOS box2d 游戏