zoukankan      html  css  js  c++  java
  • Sprite Kit教程:制作一个通用程序 2

    注1:本文译自Sprite Kit Tutorial: Making a Universal App: Part 2

    目录

    上一篇文章中,创建了一个基本的游戏程序:一些可爱的鼹鼠从洞里面跳出来。并且为了能够让程序很好的运行在iPhone 3.5英寸,iPhone 4英寸,iPad和iPad Retina上,还花费了大量的篇幅介绍UI设计和坐标系相关知识。

    本文,将给鼹鼠添加一些可爱的动画:笑和被打时的表情,并添加一种玩法:可以通过敲打鼹鼠来赚取点数,另外还会添加一些音效。

    动画的定义:可行性

    为了让游戏更加有趣,我们将在游戏中给鼹鼠添加两个动画。首先,当鼹鼠从洞里面跳出来时是笑的动画,然后,当你敲打它们的时候,是一个被敲打的表情。

    在开始之前,我们先来看看在代码里面定义动画的可行性。

    鼹鼠笑的动画需要用到的图片和相关顺序是这样的: mole_laugh1.png, mole_laugh2.png mole_laugh3.png, mole_laugh2.png, mole_laugh3.png, mole_laugh1.png。

    我们可以通过硬编码的方式来配置我们的动画,如下代码所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    [animFrames addObject:
        [SKTexture textureWithImageNamed:@"mole_laugh1.png"]];
    [animFrames addObject:
        [SKTexture textureWithImageNamed:@"mole_laugh2.png"]];
    [animFrames addObject:
        [SKTexture textureWithImageNamed:@"mole_laugh3.png"]];
    [animFrames addObject:
        [SKTexture textureWithImageNamed:@"mole_laugh2.png"]];
    // And so on...
    

    不过,这很容易让我们的代码剧增。为了简洁一点,此处我们不用上面的代码来定义动画,而是使用属性列表来代替。

    属性列表

    如果之前你没有用过属性列表,也没关系。属性列表就是一个特殊的文件,可以用Xcode创建,文件按照一定的格式包含数组、字典、字符串和数字,所以非常容易创建,并且在代码中叶能够方便的读取到这些值。

    下面我们在Xcode中试试吧。右键单击ShackAMole,选择”New File…”,接着选择 “iOSResourceProperty List”,然后单击”Next”。将文件命名为”laughAnim.plist”,最后单击创建。现在可以在Xcode中看到laughAnim.plist的可视化编辑界面,如下图所示:

    南

    每个属性列表都有一个root element。一般这是一个数组或者字典。在我们创建的这个文件中,将包含让鼹鼠笑起来所需动画的所有图片名称,是一个数组,所以点击root element的第二列(Type,当前为Dictionary),将其修改为Array。

    接着,单击Root单词右边的加号按钮,以在数组中添加一个新的entry。默认情况下,entry的类型是String(刚好是我们想要的).将这个entry的值修改为”mole_laugh1.png”。

    继续点击加号按钮添加新的一条记录,直到所有的图片名称都添加进来了,如下图所示:

    接着添加一个鼹鼠被打击所需要图片的属性列表文件,跟上面的步骤一样,不过记得将文件命名为hitAnim.plist文件,如下所示:

    下面,我们就在代码中加载这些图片吧。打开MyScene.h文件,并为每个动画动作添加对应的属性,如下代码所示:

    1
    2
    3
    
    // Inside @interface MyScene
    @property (strong, nonatomic) SKAction *laughAnimation;
    @property (strong, nonatomic) SKAction *hitAnimation;
    

    我们用上面这两个属性记录每个SKAction,这样可以在代码中方便的查找和重用。

    接着在MyScene.m中添加一个方法,方法中的代码根据传入的属性列表创建并返回SKAction,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    - (SKAction *)animationFromPlist:(NSString *)animPlist
    {
        NSString *plistPath = [[NSBundle mainBundle] pathForResource:animPlist ofType:@"plist"]; // 1
        NSArray *animImages = [NSArray arrayWithContentsOfFile:plistPath]; // 2
        NSMutableArray *animFrames = [NSMutableArray array]; // 3
        for (NSString *imageName in animImages) { // 4
            [animFrames addObject:[SKTexture textureWithImageNamed:imageName]]; // 5
        }
        float framesOverOneSecond = 1.0f/(float)[animFrames count];
        return [SKAction animateWithTextures:animFrames timePerFrame:framesOverOneSecond resize:NO restore:YES]; // 6
    }
    

    理解上面的代码很重要,我们一行一行的来看看吧:

    1. 由于属性列表是包含在工程里面的,所以它应该在程序的”main bundle”中。这个helper方法计算出了属性列表文件在main bundle中的全路径。
    2. 这行代码是读取属性列表文件中的内容。NSArray中有一个名为arrayWithContentsOfFile的方法,此处将文件名传递进去,就可以将属性列表中的内容读取到数组中了。(注意,之所以可以这样,是因为属性列表中的root element设置为NSArray),如果是一个字典的话,可以使用[NSDictionary dictionaryWithContentsOfFile…]。
    3. 创建一个空的数组,用来存储动画的每一帧。
    4. 循环遍历数组获得每个图片名字。
    5. 获得每个图片的纹理,然后将其添加到数组中。
    6. 根据纹理数组返回一个SKAction。

    接下来,在init方法的尾部为每个动画调用上面这个helper方法,如下代码所示:

    1
    2
    
    self.laughAnimation = [self animationFromPlist:@"laughAnim"];
    self.hitAnimation = [self animationFromPlist:@"hitAnim"];
    

    最后一步:使用动画(让鼹鼠笑起来)。修改popMole方法,如下代码所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    - (void)popMole:(SKSpriteNode *)mole
    {
        SKAction *easeMoveUp = [SKAction moveToY:mole.position.y + mole.size.height duration:0.2f];
      easeMoveUp.timingMode = SKActionTimingEaseInEaseOut;
      SKAction *easeMoveDown = [SKAction moveToY:mole.position.y duration:0.2f];
      easeMoveDown.timingMode = SKActionTimingEaseInEaseOut;
        SKAction *sequence = [SKAction sequence:@[easeMoveUp, self.laughAnimation, easeMoveDown]];
        [mole runAction:sequence];
    }
    

    上面的代码与之前唯一不同的就是用laughAnimation action替代了pop down之前的延迟一秒。laughAnimation action会使用laughAnim.plist中的纹理,注意之前已经把restore设置为YES了,所以当动画播放完之后,鼹鼠会回到正常的表情。

    现在编译并运行程序,可以看到鼹鼠跳出来,并笑了!如下图所示:

    下面我们来看看如何停止鼹鼠的微笑动画,并开始敲打它们。

    添加游戏逻辑

    现在我们准备给游戏添加玩法,也就是游戏逻辑。基本想法就是会有一定数量的鼹鼠出现,当玩家打击到这些出现的鼹鼠时,就会获得相应的点数,玩家将尽力获得最多的点数。

    因此,我们需要记录分数,并将分数显示在屏幕中。当鼹鼠显示完毕时候,我们将对用户做出提示。

    先打开MyScene.h文件,并将下面这些实例变量添加到之前写的action后面:

    1
    2
    3
    4
    
    @property (strong, nonatomic) SKLabelNode *scoreLabel;
    @property (nonatomic) NSInteger score;
    @property (nonatomic) NSInteger totalSpawns;
    @property (nonatomic) BOOL gameOver;
    

    有一个用于显示分数的label,一个记录当前分数的变量,一个记录已经弹出了多少个鼹鼠,以及游戏是否结束。

    接着,将下面的代码添加到文件MyScene.m文件中initWithSize:方法尾部:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // Add score label
    float margin = 10;
    self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
    self.scoreLabel.text = @"Score: 0";
    self.scoreLabel.fontSize = [self convertFontSize:14];
    self.scoreLabel.zPosition = 4;
    self.scoreLabel.horizontalAlignmentMode = SKLabelHorizontalAlignmentModeLeft;
    self.scoreLabel.position = CGPointMake(margin, margin);
    [self addChild:self.scoreLabel];
    

    上面的代码创建了一个用于分数显示的label。label位于屏幕的左下角,并且距离左下角的边距为10 point。并将label的属性horizontalAlignmentMode设置为SKLabelHorizontalAlignmentModeLeft,这样可以让label的文字从左侧对齐。

    另外,此处并没有直接给label设置字体大小,而是先通过一个helper函数将字体大小做转换。这是因为在iPad和iPad retina上字体的尺寸要大一点。下面是convertFontSize方法的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    
    - (float)convertFontSize:(float)fontSize
    {
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
            return fontSize * 2;
        } else {
            return fontSize;
        }
    }
    

    如上代码所示,如果是iPad和iPad retina,那么就将字体尺寸变为原来的两倍,否则保持原样。

    接着,我们需要添加触摸检测的代码,用来判断是否打击了某个鼹鼠。不过在开始之前,我们需要给鼹鼠添加一个flag,以此知道鼹鼠当前是否可以点击(tappable)。只有当鼹鼠笑的时候才可以点击,而当它移动或者在底下时是不可以点击的,也就是“安全的”。

    我们可以创建一个SKSpriteNode的子类来记录这个flag,不过在此我们只需要存储一个信息,因此我们可以使用SKSpriteNode中的userData属性来代替。如下,再次将popMole做修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    - (void)popMole:(SKSpriteNode *)mole
    {
        if (self.totalSpawns > 50) return;
        self.totalSpawns++;
        // Reset texture of mole sprite
        mole.texture = self.moleTexture;
      SKAction *easeMoveUp = [SKAction moveToY:mole.position.y + mole.size.height duration:0.2f];
        easeMoveUp.timingMode = SKActionTimingEaseInEaseOut;
        SKAction *easeMoveDown = [SKAction moveToY:mole.position.y duration:0.2f];
        easeMoveDown.timingMode = SKActionTimingEaseInEaseOut;
        SKAction *setTappable = [SKAction runBlock:^{
            [mole.userData setObject:@1 forKey:@"tappable"];
        }];
        SKAction *unsetTappable = [SKAction runBlock:^{
            [mole.userData setObject:@0 forKey:@"tappable"];
        }];
        SKAction *sequence = [SKAction sequence:@[easeMoveUp, setTappable, self.laughAnimation, unsetTappable, easeMoveDown]];
        [mole runAction:sequence completion:^{
            [mole removeAllActions];
        }];
    }
    

    主要做了如下修改:

    • 如果显示的鼹鼠数量有50个,那么立即返回,也就是说,在游戏中,50是最大的上限。
    • 在函数开头,重置一下鼹鼠的图片(“mole_1.png”)。这样做是因为如果鼹鼠在上一次显示的时候被击打了,它仍然显示被击打的图片,所以在这里显示之前,需要重置一下。
    • 在鼹鼠笑之前,先运行一个action,该action会在block中运行一段代码。该block将userData字典中名为tappable的key值设置为1,这样就可以表示鼹鼠可以被击打了。
    • 类似的,当鼹鼠笑过之后,同样运行一个action:将tappable的值设置为0.

    现在,鼹鼠有一个flag可以表示它是否可以被击中了。接着我们可以添加touchesBegan:方法了。将如下代码添加到文件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        CGPoint touchLocation = [touch locationInNode:self];
        SKNode *node = [self nodeAtPoint:touchLocation];
        if ([node.name isEqualToString:@"Mole"]) {
            SKSpriteNode *mole = (SKSpriteNode *)node;
            if (![[mole.userData objectForKey:@"tappable"] boolValue]) return;
            self.score += 10;
            [mole.userData setObject:@0 forKey:@"tappable"];
            [mole removeAllActions];
            SKAction *easeMoveDown = [SKAction moveToY:(mole.position.y - mole.size.height) duration:0.2f];
            easeMoveDown.timingMode = SKActionTimingEaseInEaseOut;
            // Slow down the animation by half
            easeMoveDown.speed = 0.5;
            SKAction *sequence = [SKAction sequence:@[self.hitAnimation, easeMoveDown]];
            [mole runAction:sequence];
        }
    }
    

    上面的touchesBegan:方法首先获得触摸的位置,然后找到触摸位置对于的SKNode,如果node的名字是Mole,那么会进一步判断这个鼹鼠的tappable。

    如果鼹鼠被击中,会将该鼹鼠设置为不可再被击中,并把分数增加。然后停止所有运行的action,并播放被击中的动画,动画播放完毕之后,就立即把鼹鼠放回洞中。

    最后一步:添加一些代码对分数进行更新,并且做一个级别完成条件的检查,如下代码所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    if (self.gameOver) return;
    if (self.totalSpawns >= 50) {
        SKLabelNode *gameOverLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
        gameOverLabel.text = @"Level Complete!";
        gameOverLabel.fontSize = 48;
        gameOverLabel.zPosition = 4;
        gameOverLabel.position = CGPointMake(CGRectGetMidX(self.frame),
                                             CGRectGetMidY(self.frame));
        [gameOverLabel setScale:0.1];
        [self addChild:gameOverLabel];
        [gameOverLabel runAction:[SKAction scaleTo:1.0 duration:0.5]];
        self.gameOver = YES;
        return;
    }
    [self.scoreLabel setText:[NSString stringWithFormat:@"Score: %d", self.score]];
    

    搞定!编译并运行程序,应该可以击打鼹鼠,并看到分数在增加!如下图所示:

    添加音效

    为了让程序更加有趣,下面我们给这游戏添加音效。先来这里下载音效吧。加压出文件,并把声音资源拖拽到WhackAMole文件件中。确保勾选上Copy items into destination group’s folder,然后单击Finish。

    将下面声明语句添加到MyScene.h文件顶部:

    1
    
    #import <AVFoundation/AVFoundation.h>
    

    接着将如下属性添加到@end前面:

    1
    2
    3
    
    @property (strong, nonatomic) AVAudioPlayer *audioPlayer;
    @property (strong, nonatomic) SKAction *laughSound;
    @property (strong, nonatomic) SKAction *owSound;
    

    然后在MyScene.m文件中做如下修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    // Add at the bottom of your initWithSize: method
    // Preload whack sound effect
    self.laughSound = [SKAction playSoundFileNamed:@"laugh.caf" waitForCompletion:NO];
    self.owSound = [SKAction playSoundFileNamed:@"ow.caf" waitForCompletion:NO];
    NSURL *url = [[NSBundle mainBundle] URLForResource:@"whack" withExtension:@"caf"];
    NSError *error = nil;
    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
    if (!self.audioPlayer) {
        NSLog(@"Error creating player: %@", error);
    }
    [self.audioPlayer play];
    // Add at bottom of popMole method, change the sequence action to:
    SKAction *sequence = [SKAction sequence:@[easeMoveUp, setTappable, self.laughSound, self.laughAnimation, unsetTappable, easeMoveDown]];
    // Add inside touchesBegan: method, change the sequence action to:
    SKAction *sequence = [SKAction sequence:@[self.owSound, self.hitAnimation, easeMoveDown]];
    

    搞定!编译并运行程序试试吧!

    何去何从

    本文的代码工程在这里

    至此,关于如何制作通用程序的介绍到此结束!

    如果你希望学习更多相关Sprite Kit内容,可以看看这本书:iOS Games by Tutorials。本书会告诉你需要知道的内容——从物理特性,到磁贴地图,以及粒子系统,甚至是制作自己的关卡编辑器。


  • 相关阅读:
    吴裕雄 Bootstrap 前端框架开发——Bootstrap 辅助类:关闭图标
    吴裕雄 Bootstrap 前端框架开发——Bootstrap 辅助类:显示下拉式功能
    吴裕雄 Bootstrap 前端框架开发——Bootstrap 辅助类:显示关闭按钮
    【安卓手机通用】android adb shell 命令大全
    Exclusive access control to a processing resource
    Jquery一些常见性能的问题
    Jquery一些常见性能的问题
    Memcached的基础梳理
    Memcached的基础梳理
    Memcached的基础梳理
  • 原文地址:https://www.cnblogs.com/riasky/p/3459276.html
Copyright © 2011-2022 走看看