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所示。
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所示。
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所示的效果!