《Cocos2D权威指南》——2.2 构建游戏场景-阿里云开发者社区

开发者社区> 华章出版社> 正文

《Cocos2D权威指南》——2.2 构建游戏场景

简介: 本节书摘来自华章计算机《Cocos2D权威指南》一书中的第2章,第2.2节,作者:王寒,屈光辉,周雪彬著, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

2.2 构建游戏场景

在构建游戏场景之前,我们需要将制作该游戏所需要资源文件添加进来。
2.2.1 添加资源目录
找到本书的随书源代码,打开chapter2/resource/arts目录,把需要的图片和声音资源都添加到项目中。右键单击Resource,选择“Add Files to"VerticalShootingGame"…”,如图2-3所示。
浏览chapter2/resource/arts目录,确保选中“Copy items into destination group抯 folder(if needed)”,然后单击“Add”,如图2-4所示。

    ![image](https://yqfile.alicdn.com/57e5a9fb36f81851618ffc1929c9c0d8c0998108.png)
      

2.2.2 添加游戏背景
在一个漆黑的游戏背景上玩游戏,肯定会让人觉得乏味。接下来,我们给游戏添加一个静态背景图片。iPhone屏幕大小是480*320,这里设置竖直放置,图片长度是480,宽度为320。打开HelloWorldLayer.m,找到init方法,在if判断里添加代码清单2-5所示代码。
代码清单2-5 在if判断里添加代码

if( (self=[super init]) ) {
        //1.get screen's size
    CGSizewinSize = [[CCDirector sharedDirector] winSize];
        //2.add background
        CCSprite *bgSprite = [CCSprite spriteWithFile:@"background_1.jpg"];
bgSprite.position = ccp(winSize.width / 2,winSize.height/2);
        [selfaddChild:bgSprite z:0];
    }

按照代码注释中的标号解释这段代码的作用。
1)通过CCDirector对象获得当前设备的屏幕大小。
屏幕大小以point为单位,支持Retina显示的iPhone4以及不支持Retina显示的iPhone 3GS屏幕都为(320,480)。想获得实际像素大小,也可以调用与之相应的像素级API winSizeInPixels。
2)通过sprite的spriteWithFile方法,从background_1.jpg实例化一个精灵,然后通过之前获得的屏幕大小计算出背景精灵图片放置的坐标位置。
因为精灵的坐标定位是相对于它的锚点(anchorPoint)而来,而sprite的anchorPoint为图片的中心点。所以,这里我们取屏幕的中点作为精灵的坐标点。最后,把该背景精灵图片作为当前层的子节点添加进去,Cocos2D框架会自动处理背景图片的渲染。
注意 这里背景图片格式为JPG,而不是大家常见的PNG图片,因为背景图片一般不需要有透明像素。JPG图片和PNG图片最大的区别就是JPG图片没有Alpha通道,因而能够缩小背景图片的大小,并且渲染的速度也会更快一些。
编译并运行,结果如图2-5所示。

image


2.2.3 添加玩家飞机
好了,是时候添加玩家操控的飞机了。
打开HelloWorldLayer.m文件,在@implementation HelloWorldLayer的上面添加下列枚举定义,如代码清单2-6所示。
代码清单2-6 在@implementation HelloWorldLayer的上面加上枚举定义
enum  {
   kTagPalyer = 1,
};

在init方法中添加背景图片的代码后面添加代码清单2-7所示代码。
代码清单2-7 在init方法中添加背景图片的代码后面添加代码

//3.add player's plane
    CCSprite *playerSprite = [CCSprite spriteWithFile:@"hero_1.png"];
    playerSprite.position = CGPointMake(winSize.width / 2, playerSprite.contentSize.height/2 + 20);
    [selfaddChild:playerSprite z:4 tag:kTagPalyer];

接下来,我们为大家解释代码清单2-7中的这段代码。
首先,从hero_1.png文件初始化一个player精灵,通过屏幕大小与精灵纹理大小的运算,计算放置player精灵的坐标。
注意 这里我们使用的是相对坐标,而不是绝对坐标,这样写有助于代码的可适应性,比如移植到Android平台后,有多种不同分辨率的设备,如果使用绝对坐标定位就会导致界面布局混乱。这里的CGPointMake等价于ccp宏。
然后,调用CCNode的addChild方法,把player精灵加到当前layer中。这里我们使用带tag的addChild版本,方便后面使用getChildByTag提取层中的精灵。
编译并运行结果如图2-6所示。

image


2.2.4 添加敌机
现在,我们需要添加一些敌机,希望敌机会从屏幕上方随机出现,然后向下俯冲。为了简单起见,这些敌机暂时不会发射子弹,随着学习的深入,玩家可以自行添加这个特性。
步骤1 添加一个CCArray *_enemySprites实例变量。
打开HelloWorldLayer.m文件,在init里代码清单2-7后面初始化此数组,如代码清单2-8所示。
代码清单2-8 在init里初始化数组
//4.init enemy sprites array
_enemySprites = [[CCArray alloc] init];

注意 这里的成员变量命名加了下划线作为前缀,它是一种编码风格,主要用于区别成员变量与局部变量。
步骤2 为了防止忘记释放内存,立刻在dealloc方法里释放掉该数组,如代码清单2-9所示。
代码清单2-9 在dealloc方法里释放掉数组

[_enemySprites release];
_enemySprites = nil;

步骤3 初始化一系列的敌机精灵,并把这些精灵都添加到数组中。
找到init方法,在代码清单2-8所示代码后添加代码清单2-10所示代码。
代码清单2-10 找到init方法添加代码

//5.initialize 10 enemy sprites & add them to _enemySprites array for future useage
const int NUM_OF_ENEMIES = 10;
for (int i=0; i < NUM_OF_ENEMIES; ++i) {
            CCSprite *enemySprite = [CCSprite spriteWithFile:@"enemy1.png"];
enemySprite.position = ccp(0,winSize.height + enemySprite.contentSize.height + 10);
enemySprite.visible = NO;
            [selfaddChild:enemySprite z:4];

            [_enemySpritesaddObject:enemySprite];
        }

这段代码和前面的类似,唯一区别就是在一个循环里初始10个sprite,并且把sprite初始可见性设置为NO,最后把这些sprite都添加到_enemySprites数组中。
如果现在运行项目,结果与上一次编译的没有区别,因为新初始化的敌机精灵是不可见的,同时它们的位置是在设备屏幕上方之外。现在,我们需要一些代码,希望能够隔一段时间有一架敌机随机从上方俯冲下来。
步骤4 打开init方法,在代码清单2-10后添加代码清单2-11所示代码。
代码清单2-11 init方法继续添加代码

//6.spawn enemy after 1.0 sec
        [self performSelector:@selector(spawnEnemy) 
                 withObject:nil
                 afterDelay:1.0f];

该方法的作用是:调用完init方法之后,隔1.0秒调用spawnEnemy方法。这种特性非常有用,因为我们有时候希望在触发某个事件之后,不要求马上响应,而是隔一段时间再做处理。
步骤5 定义一些私有方法。
其实在Objective-C世界里不存在真正意义的私有方法,有时为了不让API暴露给客户端程序员,一些类的内部使用的方法和变量需要放在私有扩展里,而不是直接声明在头文件中。
找到HelloWorldLayer.m的@implementation HelloWorldLayer部分,紧跟上面代码清单2-11添加代码清单2-12所示代码。
代码清单2-12 @implementation HelloWorldLayer部分后添加代码

@interface HelloWorldLayer()
-(void) spawnEnemy;
-(CCSprite*) getAvailableEnemySprite;
@end

这里定义一个匿名的Category,把一些私有方法直接放到这里就可以。
步骤6 实现这两个方法。
在代码清单2-12中找到@end,在该行之前添加代码清单2-13所示代码。
代码清单2-13 @end之前添加代码

#pragma mark - private methods
-(void) spawnEnemy{
    //1.
    CGSizewinSize = [CCDirector sharedDirector].winSize;
    CCSprite *enemySprite = [self getAvailableEnemySprite];

    //2.
floatdurationTime = arc4random() % 4 + 1;
idmoveBy = [CCMoveBy actionWithDuration:durationTime
position:ccp(0,-enemySprite.position.y-enemySprite.contentSize.height)];
id callback = [CCCallBlockN actionWithBlock:^(id sender)
    {
        CCSprite *sp = (CCSprite*)sender;
        sp.visible = NO;
        sp.position = ccp(0,winSize.height + sp.contentSize.height + 10);
        CCLOG(@"reset enemy plane!");
    }];
id action = [CCSequence actions:moveBy,callback, nil];

enemySprite.visible = YES;
enemySprite.position = ccp( arc4random() % (int)(winSize.width - enemySprite.contentSize.width) + enemySprite.contentSize.width/2 , enemySprite.position.y);
CCLOG(@"enemySprite x = %f, y = %f",enemySprite.position.x, enemySprite.position.y);
    [enemySpriterunAction:action];

    //3.
    [selfperformSelector:_cmdwithObject:nil afterDelay:arc4random()%3 + 1];

}

-(CCSprite*) getAvailableEnemySprite{
    CCSprite *result = nil;
CCARRAY_FOREACH(_enemySprites, result)
    {
if (!result.visible) {
    break;
        }
    }
return result;
}

我们按照代码注释的序号逐步讲解spawnEnemy方法。
1)通过CCDirector的winSize获得设备的大小(以point为单位);通过调用自定义的getAvailableEnemySprite方法从_enemySprites数组中获取一个还没有飞行的飞机。
2)通过arc4random方法获取一个随机时间,以此时间建立一个CCMoveBy的action,同时计算出此action要移动的偏移量;接着,使用Block建立了一个回调action,最后通过CCSequence action把这两个action串在一起,把需要俯冲的敌机的可见性设置为YES;同时,为了视觉效果,在runAction之前,先设置敌机出现的坐标,改为随机位置出现;在moveBy的动作结束以后,精灵的可见性又变成NO,同时坐标也要发回初始化时的坐标位置。这一切都通过一种神奇函数式编程方式block做到。
3)隔1~4秒调用spawnEnemy方法,此处_cmd参数就是spawnEnemy方法本身。这样,随机地每隔一段时间,就会有一架敌机以不同速度向下俯冲。
编译并运行,我们应该会得到如图2-7所示的效果!

image

版权声明:如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developerteam@list.alibaba-inc.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接