用 SpriteKit 做一个逃逸游戏 (4)

在代码中怎样使用?

首先,为不同的物体类别创建常量。在 MyScene.m 中的常量声明语句后加入下列语句:

static const uint32_t ballCategory  = 0x1 << 0

// 00000000000000000000000000000001

static const uint32_t bottomCategory = 0x1 << 1;

// 00000000000000000000000000000010

static const uint32_t blockCategory = 0x1 << 2

// 00000000000000000000000000000100

static const uint32_t paddleCategory = 0x1 << 3;

// 00000000000000000000000000001000

上述代码定义了 4 种物体类型。首先将字节(32位)的末位设置为1,其他位设置为0。然后通过左移操作符<< 将 1 不停往左移。这样,每个类型常量都只有一个位被置为1,对于每一个常量来说,它的 1 的位置都是不同的。

目前你只用到两个类别,地(屏幕底部)和小球。不久后你会用到其他类别,如果必要,你还可以扩展更多的类别常量。

定义好类别常量后,你可以创建一个物体,让它围在屏幕的底部。尝试独立完成这个任务,这和我们曾经创建过的围住屏幕四周的笼子是一样的。(将该物体的节点命名为 bottom,最终我们再来配置这个节点)

隐含内容:创建一个edge-based 状物体盖在屏幕底部

 

在 MyScene.m 的 initWithSize: 方法中加入:

 

    CGRect bottomRect = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, 1);

    SKNode* bottom = [SKNode node];

    bottom.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:bottomRect];

    [self addChild:bottom];

万事俱备,只欠东风。让我们开始第一次亲密接触。首先,在 initWithSize 方法中设置地面、球和球拍的 categoryBitMasks。

 

bottom.physicsBody.categoryBitMask = bottomCategory;     ball.physicsBody.categoryBitMask = ballCategory;     paddle.physicsBody.categoryBitMask = paddleCategory;

代码很简单。将你早先创建的常量赋给相应物体的categoryBitMask 掩码。

接着,用以下代码( initWithSize: 方法中)设置contactTestBitMask 掩码:

ball.physicsBody.contactTestBitMask = bottomCategory;

这里,我们仅仅关心什么时候球和地面发生接触。因此将 contactTestBitMask设置为 bottomCategory。

然后创建一个 SKPhysicsContactDelegate。由于这只是一个简单游戏,可以用 MyScene 作为所有接触的委托。

MyScene.h 中找到:

@interface MyScene : SKScene

一句,修改为:

@interface MyScene : SKScene

采用比较正式的描述:MyScene 是一个SKPhysicsContactDelegate ,它将接受所有(配置好的)物体的碰撞通知!

现在将 physicsWorld 的 delegate 设置为 MyScene。在MyScene.m 的 initWithSize: 方法中加入:

self.physicsWorld.contactDelegate = self;

用 SpriteKit 做一个逃逸游戏 (4)

设置SKPhysicsContactDelegate

最后,实现 didBeginContact: 方法处理碰撞。在 MyScene.m中增加方法:

-(void)didBeginContact:(SKPhysicsContact*)contact {

     // 1 创建两个物体的局部变量

     SKPhysicsBody* firstBody;

     SKPhysicsBody* secondBody;

     // 2 始终把类别代码较小的物体赋给firstBody变量

     if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {

         firstBody = contact.bodyA;

         secondBody = contact.bodyB;

     } else {

         firstBody = contact.bodyB;

         secondBody = contact.bodyA;

     }

     // 3 对球和地接触的碰撞进行处理

     if (firstBody.categoryBitMask == ballCategory && secondBody.categoryBitMask == bottomCategory) {

         //TODO: 将打印语句替换为游戏结束界面

         NSLog(@"Hit bottom. First contact has been made.");

     }

}

这段代码可能有点绕,让我们来过一遍代码。

  1. 创建两个局部变量,用于指向碰撞发生时所涉及到的两个物体。
  2. 判断两个物体其中哪一个的 categoryBitmask 值更小,并将它们赋给两个局部变量,其中 categoryBitmask 较小的一个始终要赋给 firstBody 变量(实际上是对二者进行排序)。当两个指定物体间的碰撞进行处理时,这样做(排序)会节省一些工作。
  3. 由于前面排序过的缘故,现在你只需判断 firstBody 是否为球,secondBody 是否为地即可知道二者间是否发生碰撞。因为很显然,这种情况是不存在的: firstBody 是地而 secondBody 是球(我们在定义常量时,ballCategory>bottomCategory)。然后简单输出一个打印语句。

让我们来看看效果。编译运行程序,当球拍没有接住小球而让球落到了地上,则控制台中会输出消息:

用 SpriteKit 做一个逃逸游戏 (4)

第一次亲密接触:)

创建游戏结束界面

很不幸,当玩家输掉游戏时不可能看到控制台消息。因此,你需要用一个图形化的界面来告诉玩家。这样,我们需要专门为游戏结束创建一个场景。

点击菜单 File\New\File…,选择iOS\CocoaTouch\Objective-C class 模板,点击Next。类命名为GameOverScene, 继承自 SKScene,一次点击 Next, Create

打开 GameOverScene. h 在@end  语句前加入:

-(id)initWithSize:(CGSize)size playerWon:(BOOL)isWon;

这个初始化方法多了一个参数,用于表示玩家是输还是赢。这个场景可以既用于游戏胜利也用可用于游戏失败。非常省事 :)

 

GameOverScene.m 修改为如下代码:

#import "GameOverScene.h" #import "MyScene.h"  

@implementation GameOverScene  

-(id)initWithSize:(CGSize)size playerWon:(BOOL)isWon {

     self = [super initWithSize:size];

     if (self) {

         SKSpriteNode* background = [SKSpriteNode spriteNodeWithImageNamed:@"bg.png"];

         background.position = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);

         [self addChild:background];  

         // 1

         SKLabelNode* gameOverLabel = [SKLabelNode labelNodeWithFontNamed:@"Arial"];

         gameOverLabel.fontSize = 42;

         gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));

         if (isWon) {

             gameOverLabel.text = @"Game Won";

         } else {

             gameOverLabel.text = @"Game Over";

         }

         [self addChild:gameOverLabel];

     }

     return self;

}  

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

     MyScene* breakoutGameScene = [[MyScene alloc] initWithSize:self.size];

     // 2

     [self.view presentScene:breakoutGameScene];

}  

@end

这些代码太常见了,所以我只对有注释的部分进行介绍:

  1. 创建一个 Label 用于显示胜利或失败信息。SKLabelNode 通常用于以设备字体显示文本信息。

注意:要想知道设备上安装了哪些字体,可以使用这段代码:

NSArray*familyNames = [UIFont familyNames];

 

for(NSString *familyName in familyNames ){

    printf( "Family: %s \n",[familyName UTF8String] );

 

    NSArray *fontNames =[UIFont fontNamesForFamilyName:familyName];

    for( NSString *fontName infontNames ){

       printf( "\tFont: %s \n", [fontName UTF8String] );

 

    }

}

 

  1. 当用户触摸 game over 窗口,我们再次显示游戏开始界面,以便玩家可以重新开始游戏。

来试一下新场景的使用。在 MyScene.m 顶部加入导入语句:

#import "GameOverScene.h"

然后在 didBeginContact:方法中将 NSLog 语句替换为:

GameOverScene* gameOverScene = [[GameOverScene alloc] initWithSize:self.frame.size playerWon:NO];

[self.view presentScene:gameOverScene];

编译运行程序,进行游戏,当球拍没接住球时:

用 SpriteKit 做一个逃逸游戏 (4)

不错,很有成就感吧!但搞笑的是,这个游戏怎样才能获胜?

 

加入砖块——并将它们击飞

Adding Some Blocks – and They Are Gone…

在 MyScene.m 的 initWithSize 方法中加入一些砖块:

// 1 局部变量声明     

int numberOfBlocks = 3;     

int blockWidth = [SKSpriteNode spriteNodeWithImageNamed:@"block.png"].size.width;    

float padding = 20.0f;    

// 2 计算 xOffset    

float xOffset = (self.frame.size.width - (blockWidth * numberOfBlocks + padding * (numberOfBlocks-1))) / 2;    

// 3 创建砖块并加入游戏场景     

for (int i = 1; i &lt;= numberOfBlocks; i++) {    

    SKSpriteNode* block = [SKSpriteNode spriteNodeWithImageNamed:@&quot;block.png&quot;];    

    block.position = CGPointMake((i-0.5f)*block.frame.size.width + (i-1)*padding + xOffset, self.frame.size.height * 0.8f);     

    block.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:block.frame.size];    

    block.physicsBody.allowsRotation = NO;         block.physicsBody.friction = 0.0f;    

    block.name = blockCategoryName;         block.physicsBody.categoryBitMask = blockCategory;    

    [self addChild:block];    

}

这段代码创建了3 块砖块,以固定距离间隔并居于屏幕中轴。

  1. 局部变量声明,例如砖块数量以及砖块的宽度。
  2. 计算 x 偏移。即屏幕左边与第一块砖之间的间隔。用屏幕宽度减去三块砖的宽度和砖块间的空格,再除以2来得到。
  3. 创建砖块,用 blockWidth、padding 和 xOffset 设置每块砖的物理属性和位置。

加入砖块后的效果。编译运行程序。

用 SpriteKit 做一个逃逸游戏 (4)

游戏一开始,砖块井然有序。



用 SpriteKit 做一个逃逸游戏 (4)

上一篇:通过开源文档学LESS(草稿)


下一篇:uva - 10057 - A mid-summer night's dream. (数学、中位数)