免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!
原文链接地址:http://www.raywenderlich.com/3997/introduction-to-augmented-reality-on-the-iphone
教程截图:
Create a simple augmented reality game where you can blast some aliens!
在这篇教程中,你将学习到如何为你的iphone和ipod touch制作一个简单的增强现实游戏。
在这个游戏中,你将使用到摄像头,陀螺仪和cocos2d框架。听起来很振奋人心吧?
在写作这篇教程的时候,探索上面提到的一些技术的过程真的是非常有趣。这里有一些数学和转换,不过不要担心---没有什么事情是很难的!
学习这篇教程的时候,你需要一个iPhone4,因为这个教程使用陀螺仪来移动游戏世界视图。
你也需要一些基本的cocos2d方面的知识,当然要安装好cocos2d。如果你对于cocos2d完全陌生的话,你可以先看看本博客上的其它cocos2d教程。
你准备好爆头一些虚拟外星人了吗?跟我来吧!
著作权声明:本文由http://www.cnblogs.com/andyque翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!
Getting Started
打开Xcode,然后从File菜单中选择New\New Project。然后选择 iOS\cocos2d\cocos2d template,接着点击Next。把工程命名为ARSpaceShips,并点击Next,同时选择一个文件夹位置来保存本项目,最后点Create。
我们将重用Space Shooter游戏的一些资源文件,所以,先下载它们并解压缩。
下载完后,把Fonts,Sounds和Spritesheet文件夹拖到Resources分组下面。同时确保 Copy items into destination group’s folder被复选中,并且ARSpaceships target也要被选中。然后点击Finish。这时,你的工程看起来应该如下图所示:
我们将在后面使用到这些资源。
玩一玩摄像头!
如果你现在运行项目,那简单太无聊了,就是一个黑屏的HelloWorld,这玩意儿谁没看过!让我们往里面添加一些非常好玩的内容吧。选择AppDelegate.h,然后在interface里面添加一个UIView成员变量。
现在,打开AppDelegate.m文件,找到EAGLView *glView所在的代码行,把像素格式改成kEAGLColorFormatRGBA8。改完后如下图所示:
EAGLView
*
glView
=
[EAGLView viewWithFrame:[window bounds]
pixelFormat:kEAGLColorFormatRGBA8 depthFormat:
0
];
如果你不改变像素格式的话,那么摄像头里拍摄出来的图像就显示不出来。因为我们现在正在做一个增强现实的游戏,所以必须这样做!
在 [window addSubview: viewController.view];下面,我们添加了以下代码:
//
set the background color of the view
[CCDirector sharedDirector].openGLView.backgroundColor
=
[UIColor clearColor];
[CCDirector sharedDirector].openGLView.opaque
=
NO;
//
set value for glClearColor
glClearColor(
0.0
,
0.0
,
0.0
,
0.0
);
//
prepare the overlay view and add it to the window
overlay
=
[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
overlay.opaque
=
NO;
overlay.backgroundColor
=
[UIColor clearColor];
[window addSubview:overlay];
这里,我们把openGLView的背景颜色清除了,把视图设置为透明的,同时指定了glClearColor,最后,我们创建并添加了一个新的视图,叫做overlay。这个视图后面用来显示camera里的内容。
接下来,在你刚刚添加的代码后面增加以下代码行:
#define
CAMERA_TRANSFORM 1.24299
UIImagePickerController
*
uip;
@try
{
uip
=
[[[UIImagePickerController alloc] init] autorelease];
uip.sourceType
=
UIImagePickerControllerSourceTypeCamera;
uip.showsCameraControls
=
NO;
uip.toolbarHidden
=
YES;
uip.navigationBarHidden
=
YES;
uip.wantsFullScreenLayout
=
YES;
uip.cameraViewTransform
=
CGAffineTransformScale(uip.cameraViewTransform,
CAMERA_TRANSFORM, CAMERA_TRANSFORM);
}
@catch
(NSException
*
e) {
[uip release];
uip
=
nil;
}
@finally
{
if
(uip) {
[overlay addSubview:[uip view]];
[overlay release];
}
}
[window bringSubviewToFront:viewController.view];
首先,我们了一个常量来缩放摄像头。摄像头的比率是4:3,而iphone屏幕的比例是3:4,所以,我们需要缩放一下摄像头,使之与屏幕匹配。
其次,我们创建了一个UIImagePickerController,设置了它的一些属性,具体效果就是没有控件并且缩放了的摄像头。然后,我们把它添加到了overlay视图中。
最后,我们需要把 viewController.view显示到前面来(因为它包含了cocos2d的显示内容)。这样子摄像头拍摄的内容也就会显示到前台来了。
现在,运行一下应用程序。现在,你将看到摄像头里面捕捉的画面是你的Hello World的背景了。
Shake, Rattle, and Roll…Well at Least Yaw!
因为我们现在的程序已经可以把现实捕捉并显示出来了,接下来,我们将集中精力来解决本教程中比较难的部分。
首先,我们需要把CoreMotion framework添加到项目中去。点击project文件,然后选择ARSpaceships target,再选择Build Phases标签页,展开 Link Binary With Libraries.。
点击上图中的+号,选择 CoreMotion.framework,然后点击Add按钮。现在,我们就可以在项目中使用陀螺仪啦。
打开HelloWorldLayer.h,然后导入一些头文件:
#include
<
CoreMotion
/
CoreMotion.h
>
#import
<
CoreFoundation
/
CoreFoundation.h
>
然后在interface里面添加一些成员变量:
CMMotionManager
*
motionManager;
CCLabelTTF
*
yawLabel;
CCLabelTTF
*
posIn360Label;
同时添加属性声明语句:
@property (nonatomic, retain) CMMotionManager
*
motionManager;
现在,重点要来了!打开HelloWorldLayer.m文件,在if ((self=[super init]))语句内部,删除原来的“Hello World”标签语句,同时添加下面的代码来设置一些新的标签.
//
add and position the labels
yawLabel
=
[CCLabelTTF labelWithString:
@"
Yaw:
"
fontName:
@"
Marker Felt
"
fontSize:
12
];
posIn360Label
=
[CCLabelTTF labelWithString:
@"
360Pos:
"
fontName:
@"
Marker Felt
"
fontSize:
12
];
yawLabel.position
=
ccp(
50
,
240
);
posIn360Label.position
=
ccp(
50
,
300
);
[self addChild: yawLabel];
[self addChild:posIn360Label];
目前为止,并没有什么特别的。只是添加了一些标签,指明了字体和一些文字。标签的位置都是在屏幕的左边。
接下来,你需要设置motion manager,它会启动陀螺仪。
self.motionManager
=
[[[CMMotionManager alloc] init] autorelease];
motionManager.deviceMotionUpdateInterval
=
1.0
/
60.0
;
if
(motionManager.isDeviceMotionAvailable) {
[motionManager startDeviceMotionUpdates];
}
[self scheduleUpdate];
这里,我们分配并初始化了 motion manager对象。同时,我们还设置了更新间隔为每秒60次。如果设置支持陀螺仪的话,那么就启动更新。最后,我们触发一个update定时器。
不要忘了在.m文件中添加synthesize语句,如下所示:
@synthesize
motionManager;
因为我们触发了update定时器,所以我们需要添加一个update方法。同时,在init方法的下面增加下面一个方法:
-
(
void
)update:(ccTime)delta {
CMDeviceMotion
*
currentDeviceMotion
=
motionManager.deviceMotion;
CMAttitude
*
currentAttitude
=
currentDeviceMotion.attitude;
//
1: Convert the radians yaw value to degrees then round up/down
float
yaw
=
roundf((
float
)(CC_RADIANS_TO_DEGREES(currentAttitude.yaw)));
//
2: Convert the degrees value to float and use Math function to round the value
[yawLabel setString:[NSString stringWithFormat:
@"
Yaw: %.0f
"
, yaw]];
//
3: Convert the yaw value to a value in the range of 0 to 360
int
positionIn360
=
yaw;
if
(positionIn360
<
0
) {
positionIn360
=
360
+
positionIn360;
}
[posIn360Label setString:[NSString stringWithFormat:
@"
360Pos: %d
"
, positionIn360]];
}
现在,你可以运行应用程序了。你将会看到Yaw和positionIn360的对应标签值在改变。
那玩意儿究竟如何工作?
尽管可以跑起来了,但是你可能会问,它究竟是如何工作的呢?让我们花几分钟时间来一步步解释上面的代码是如何工作的。
首先,下载iTunes app store上面的免费程序 Gyrosocope app。安装并运行它,当你移动iphone的时候,就可以看到每个值究竟是怎么变化的。
我们关心的值是Yaw。这个值代表往左或往右移动。在Gyroscope程序中,它的单位是度,然而 motion manager获取的值却是弧度。这就是为什么我们需要使用内置的函数CC_RADIANS_TO_DEGREES来把弧度转换成角度的原因啦。
因此,在第一部分中,我们得到了yaw的弧度值,并且把它转换成角度,最后赋值给yaw变量。第二部分,我们只是把yaw的值显示到屏幕上去。如果你运行工程,你会看到yaw值的变化范围是从0~180,然后又从-180回到0.
现在看看第三部分,你可能会奇怪positionIn360的值倒底是什么啊?好吧,这里只是一个手段,目的是使得放置飞碟的过程变得容易。
这里的逻辑其实非常简单。如果yaw值是正的,那么我们什么也不做。如果是负的,那么就减去360.(加上一个负值和减去一个正值是一样的)。最后一行代码只是在屏幕上显示那个值。
如果你还没完全理解,没关系,运行一下程序,看看具体值是怎么变化的吧。
灯光,摄像机,Action!
现在,我们为陀螺仪的使用奠定基础了,让我们往里面添加一些太空飞船吧!我们将创建一个新的文件。首先,左键点ARSpaceships工程文件,然后选择New File。接着选 iOS\Cocoa Touch\Objective-C class,然后点击Next。确保NSObject被选中基类,然后点Next。把文件命名为EnemyShip.m,最后点Save。
把 EnemyShip.h里的内容换成下面的代码:
#import
"
cocos2d.h
"
@interface
EnemyShip : CCSprite {
int
yawPosition;
int
timeToLive;
}
@property (readwrite)
int
yawPosition;
@property (readwrite)
int
timeToLive;
@end
同时修改EnemyShip.m:
#import
"
EnemyShip.h
"
@implementation
EnemyShip
@synthesize
yawPosition, timeToLive;
-
(
id
)init {
self
=
[super init];
if
(self){
yawPosition
=
0
;
timeToLive
=
0
;
}
return
self;
}
@end
现在,回到HelloWorldLayer.h。在顶部导入EnemyShip类的头文件,如下所示:
在interface里面声明以下成员变量:
NSMutableArray
*
enemySprites;
int
enemyCount;
CCSpriteBatchNode
*
batchNode;
最后,在interface声明下面,添加enemyCount属性,同时定义一些方法,具体如下图所示:
@property (readwrite)
int
enemyCount;
-
(EnemyShip
*
)addEnemyShip:(
int
)shipTag;
-
(
void
)checkEnemyShipPosition:(EnemyShip
*
)enemyShip withYaw:(
float
)yawPosition;
-
(
void
)updateEnemyShipPosition:(
int
)positionIn360 withEnemy:(EnemyShip
*
)enemyShip;
-
(
void
)runStandardPositionCheck:(
int
)positionIn360 withDiff:(
int
)difference withEnemy:(EnemyShip
*
)enemyShip;
打开 HelloWorldLayer.m文件,同时作以下修改:
//
Place after the #import statement
#include
<
stdlib.h
>
//
Place after the other @synthesize statement
@synthesize
enemyCount;
#define
kXPositionMultiplier 15
#define
kTimeToLive 100
//
Add to the bottom of init
batchNode
=
[CCSpriteBatchNode batchNodeWithFile:
@"
Sprites.pvr.ccz
"
];
[self addChild:batchNode];
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:
@"
Sprites.plist
"
];
这里,我们加载了spritesheet,它的资源文件在一开始的时候,我们就把它拖到项目中来了。
接下来,我们将添加一个新的方法来创建新的敌人太空飞船。在dealloc方法的上面添加下列方法:
-
(EnemyShip
*
)addEnemyShip:(
int
)shipTag {
EnemyShip
*
enemyShip
=
[EnemyShip spriteWithSpriteFrameName:
@"
enemy_spaceship.png
"
];
//
Set position of the space ship randomly
int
x
=
arc4random()
%
360
;
enemyShip.yawPosition
=
x;
//
Set the position of the space ship off the screen, but in the center of the y axis
//
we will update it in another method
[enemyShip setPosition:ccp(
5000
,
160
)];
//
Set time to live on the space ship
enemyShip.timeToLive
=
kTimeToLive;
enemyShip.visible
=
true
;
[batchNode addChild:enemyShip z:
3
tag:shipTag];
return
enemyShip;
}
这个方法接收一个整数值作为tag,并且返回一个EnemyShip CCSprite。下面一行代码,我们从精灵表单中创建一个EnemyShip精灵。接着,我们使用arc4random方法来得到一个0~360的随机数。最后,我们设置了飞船的位置,并把timeToLive的值设置为100,把飞船添加到batchNode里面,最后返回飞船精灵对象。
在addEnemyShip下面,我们添加一个checkEnemyShipPosition方法:
-
(
void
)checkEnemyShipPosition:(EnemyShip
*
)enemyShip withYaw:(
float
)yawPosition {
//
Convert the yaw value to a value in the range of 0 to 360
int
positionIn360
=
yawPosition;
if
(positionIn360
<
0
) {
positionIn360
=
360
+
positionIn360;
}
BOOL checkAlternateRange
=
false
;
//
Determine the minimum position for enemy ship
int
rangeMin
=
positionIn360
-
23
;
if
(rangeMin
<
0
) {
rangeMin
=
360
+
rangeMin;
checkAlternateRange
=
true
;
}
//
Determine the maximum position for the enemy ship
int
rangeMax
=
positionIn360
+
23
;
if
(rangeMax
>
360
) {
rangeMax
=
rangeMax
-
360
;
checkAlternateRange
=
true
;
}
if
(checkAlternateRange) {
if
((enemyShip.yawPosition
<
rangeMax
||
enemyShip.yawPosition
>
rangeMin )
||
(enemyShip.yawPosition
>
rangeMin
||
enemyShip.yawPosition
<
rangeMax)) {
[self updateEnemyShipPosition:positionIn360 withEnemy:enemyShip];
}
}
else
{
if
(enemyShip.yawPosition
>
rangeMin
&&
enemyShip.yawPosition
<
rangeMax) {
[self updateEnemyShipPosition:positionIn360 withEnemy:enemyShip];
}
}
}
这个方法看起来似乎有点让人摸不着头脑,一会最大值,一会儿最小值,一会候选值。不过,不要担心,其实非常简单。首先,我们检查设置的yaw坐标址(positionIn360),然后把此值限制在0~360之间。
因为,我们有两端范围都是0~360,所以需要检查一下设置的positionIn360具体属于哪一端。我们使用一个任意数23来代表将在屏幕一半处显示的度数。
因为,我们只需要关心变化范围是0~23和337~360的空间了。因为,另一端的线将会包过来。(这里相像成3维空间的一个圆)
最后,如果敌人飞船在屏幕46度的范围之内的话,那么就要更新敌人飞船。checkAlternateRange判断语句只是用来决定什么时候来更新敌人飞船。
如果checkAlternateRange为真的话,那么我们就检查敌船的位置是否落在min和max的范围之内。
positionIn360
=
20
rangeMin
=
357
rangeMax
=
20
enemyShip.yawPosition
=
359
因为我们要考虑线段的两端,我们的min范围比max范围要大一些。现在,我们做一个判断,看敌船的位置是不是大于rangeMin,如果是,则显示在屏幕上。
if语句中的else就更加明了了。他检查敌船的位置是不是大于min且小于max。
多么复杂的一个update方法啊!我们在checkEnemyShipPosition方法下面添加以下代码:
-
(
void
)updateEnemyShipPosition:(
int
)positionIn360 withEnemy:(EnemyShip
*
)enemyShip {
int
difference
=
0
;
if
(positionIn360
<
23
) {
//
Run 1
if
(enemyShip.yawPosition
>
337
) {
difference
=
(
360
-
enemyShip.yawPosition)
+
positionIn360;
int
xPosition
=
240
+
(difference
*
kXPositionMultiplier);
[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
}
else
{
//
Run Standard Position Check
[self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];
}
}
else
if
(positionIn360
>
337
) {
//
Run 2
if
(enemyShip.yawPosition
<
23
) {
difference
=
enemyShip.yawPosition
+
(
360
-
positionIn360);
int
xPosition
=
240
-
(difference
*
kXPositionMultiplier);
[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
}
else
{
//
Run Standard Position Check
[self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];
}
}
else
{
//
Run Standard Position Check
[self runStandardPositionCheck:positionIn360 withDiff:difference withEnemy:enemyShip];
}
}
在这个方法中,我们测试,看是否设备的positionIn360是不是在讨论的3个范围内。在第一个测试中,我们检测positionIn360是不理小于23,如果是,我们就看看是不是有一些敌船在线的另一端(大于337)。
第二部分测试,看是否positionIn360大于337.如果是的话,就再检测它是否小于23.
第二部分测试,看敌船是否在23和337之间。如果是,则调用runStandardPositionCheck方法。这个方法的定义如下所示:
-
(
void
)runStandardPositionCheck:(
int
)positionIn360 withDiff:(
int
)difference withEnemy:(EnemyShip
*
)enemyShip {
if
(enemyShip.yawPosition
>
positionIn360) {
difference
=
enemyShip.yawPosition
-
positionIn360;
int
xPosition
=
240
-
(difference
*
kXPositionMultiplier);
[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
}
else
{
difference
=
positionIn360
-
enemyShip.yawPosition;
int
xPosition
=
240
+
(difference
*
kXPositionMultiplier);
[enemyShip setPosition:ccp(xPosition, enemyShip.position.y)];
}
}
在这个方法中,我们检查看是否enemyShip是否在设备的positionIn360的左边还是右边。当enemyShip的位置小于positionIn360时,它将出现在屏幕的左边。当enemyShip的位置大于positionIn360,那么它将出现在屏幕的右边。
现在,你会说,请等一下!你忘了描述这些变量的作用了!好吧,接下来就解释一下。
如果敌船的yaw坐标值在屏幕的范围之内(从 positionIn360 – 23到 positionIn360 + 23),然后,首先我们计算它位于屏幕的哪一边。如果大于positionIn360,那么就在屏幕的右边,否则就在屏幕的左边。
difference变量的作用是保存设备的positionIn360和敌船的 yaw位置的角度差值。一旦计算出来后,我们就把这个差值乘以一个任意的倍数。这个倍数代表每一度的像素个数。这个里,我们选择15.
基于它位于于当前屏幕的位置,我们将把这个计算出来的值增加240或者减去240。
现在,所有需要的方法已经全部准备就绪啦。
在init方法的底部,添加下面的代码,在屏幕中增加5个飞船:
//
Loop through 1 - 5 and add space ships
enemySprites
=
[[NSMutableArray alloc] init];
for
(
int
i
=
0
; i
<
5
;
++
i) {
EnemyShip
*
enemyShip
=
[self addEnemyShip:i];
[enemySprites addObject:enemyShip];
enemyCount
+=
1
;
}
因为,我们添加了敌船到屏幕中了,我们确保它们的位置被更新。在udpate方法的底部添加下面方法:
//
Loop through array of Space Ships and check the position
for
(EnemyShip
*
enemyShip
in
enemySprites) {
[self checkEnemyShipPosition:enemyShip withYaw:yaw];
}
在我们忘记之前,在dealloc里面添加清理代码来清理我们之前创建的enemySpritesArray数组:
现在,编译并运行工程吧!当你旋转设备的时候,你将会看到5个飞船在不同的地方。
免费的激光和爆炸
目前为止,这个现实增加的游戏完成的差不多了。不过,还有一个很严重的问题:这里飞船打中后没什么感觉。
很明显,我们并不想这样,所以,让我们添加一些很酷的激光和爆炸效果吧。
在开始之前,让我们移除屏幕上的label--他们只是作为调试时用的。因此,找开 HelloWorldLayer.m中关于labels的代码,并把它们注释掉。完成之后,编译并运行,保证没有错误。
现在,看看有趣的部分---让我们往游戏中添加一些火力吧!首先,我们将添加一个方法用来判断玩家的开火区域是否击中了飞船。在HelloWorldLayer.h文件中,在@end之前声明下列方法:
-
(BOOL) circle:(CGPoint) circlePoint withRadius:(
float
) radius collisionWithCircle:(CGPoint) circlePointTwo collisionCircleRadius:(
float
) radiusTwo;
打开HelloWorldLayer.m,然后在dealloc方法上面实现上述方法:
-
(BOOL) circle:(CGPoint) circlePoint withRadius:(
float
) radius collisionWithCircle:(CGPoint) circlePointTwo collisionCircleRadius:(
float
) radiusTwo {
float
xdif
=
circlePoint.x
-
circlePointTwo.x;
float
ydif
=
circlePoint.y
-
circlePointTwo.y;
float
distance
=
sqrt(xdif
*
xdif
+
ydif
*
ydif);
if
(distance
<=
radius
+
radiusTwo)
return
YES;
return
NO;
}
这个方法用来检测是否两个点的半径有交集。输入的参数是敌方飞船位置和屏幕的中心点位置。两个点的半径都设置为50.
首先,我们计算两个点X和Y值的差。接下来计算两点的距离。这个在高中就学过的,叫勾股定理。你可以从这里得到更多的信息。
接下来,我们往屏幕中添加一个区域,用作火力瞄准器。下载这些资源文件,解压缩,然后把scope.png拖到Resouces文件夹下去。确保“Copy items into destination group’s folder”被复选中,然后点Finish。
打开HelloWorldLayer.m的init方法,然后在 [self scheduleUpdate]方法之前,添加以下代码:
//
Add the scope crosshairs
CCSprite
*
scope
=
[CCSprite spriteWithFile:
@"
scope.png
"
];
scope.position
=
ccp(
240
,
160
);
[self addChild:scope z:
15
];
//
Allow touches with the layer
[self registerWithTouchDispatcher];
如果你现在运行程序,你将看到一个瞄准器出现在屏幕的中间。
非常好,现在让我们添加一些爆炸效果,在玩家点击屏幕的时候就触发。我们将按照添加scope.png的方法一样,来添加Explision.plist。先找到之前下载的本项目资源文件。把Explosion.plist拖到资源文件夹中,确保“Copy items into destination group’s folder”被复选上,然后点击Finish。
你可能会奇怪这个文件到底是什么?我使用一个很酷的软件制作的,你可能之前也听说过了,叫做 Particle Designer,它是由71 Squared的工程师所开发的。我不会在这里讲解如何使用此软件来制作这样的粒子效果文件,但是,实际上这个过程是非常简单的。选择一种粒子效果,然后调节一些参数,最后导出plist文件就可以了。
现在,在dealloc方法之前,添加下列代码:
-
(
void
)ccTouchesBegan:(NSSet
*
)touches withEvent:(UIEvent
*
)
event
{
CGPoint location
=
CGPointMake(
240
,
160
);
//
1
for
(EnemyShip
*
enemyShip
in
enemySprites) {
if
(enemyShip.timeToLive
>
0
) {
//
Check to see if yaw position is in range
BOOL wasTouched
=
[self circle:location withRadius:
50
collisionWithCircle:enemyShip.position collisionCircleRadius:
50
];
if
(wasTouched) {
enemyShip.timeToLive
=
0
;
enemyShip.visible
=
false
;
enemyCount
-=
1
;
}
}
}
//
2
CCParticleSystemQuad
*
particle
=
[CCParticleSystemQuad particleWithFile:
@"
Explosion.plist
"
];
particle.position
=
ccp(
240
,
160
);
[self addChild:particle z:
20
];
particle.autoRemoveOnFinish
=
YES;
//
3
if
(enemyCount
==
0
) {
//
Show end game
CGSize winSize
=
[CCDirector sharedDirector].winSize;
CCLabelBMFont
*
label
=
[CCLabelBMFont labelWithString:
@"
You win!
"
fntFile:
@"
Arial.fnt
"
];
label.scale
=
2.0
;
label.position
=
ccp(winSize.width
/
2
, winSize.height
/
2
);
[self addChild:label z:
30
];
}
}
这段代码的第一部分用来做碰撞检测,用来测试是否飞船在瞄准器范围之内。如果其中一个飞船被击中了,我们就飞船的属性来隐藏它,同时把enemyCount计数减1.第二部分代码,往屏幕中心添加了一个爆炸粒子系统。第二部分代码,也是最后一部分代码,它用来检查enemyCount是否为0,如果是的话,就显示一个label,告知玩家游戏结束了。
这个游戏如果就这样的话,有点无聊。所以,让我们往游戏中添加一些基本的AI吧。其实也很简单的,就是随着时间的推移,我们会改变一下飞船的位置。所以,在update方法底部添加下列代码:
//
Loop through array of Space Ships and if the timeToLive is zero
//
change the yawPosition of the sprite
for
(EnemyShip
*
enemyShip
in
enemySprites) {
enemyShip.timeToLive
--
;
if
(enemyShip.timeToLive
==
0
) {
int
x
=
arc4random()
%
360
;
[enemyShip setPosition:ccp(
5000
,
160
)];
enemyShip.yawPosition
=
x;
enemyShip.timeToLive
=
kTimeToLive;
}
}
这段代码将会遍历所有的enemySprites,然后更新timeToLive属性。然后,检查这个timeToLive属性是否等于0,如果是的话,那么就给飞船的yawPositon设置一个随机值,同时重置timeToLive属性。编译并运行工程吧,现在你想要打中飞船的话就有一些难度了,开火!
Pump up the Volume!
游戏如果没有声音的话,那就太没意思了!所以,让我们添加一些音乐吧!
在HellowWorldLayer.m文件顶部包含Simple Audio Engine所需的头文件,具体如下所示:
#import
"
SimpleAudioEngine.h
"
然后在init方法的最后添加下列代码,记得添加在 if ((self=[super init]))语句内部:
[[SimpleAudioEngine sharedEngine] playBackgroundMusic:
@"
SpaceGame.caf
"
loop:YES];
[[SimpleAudioEngine sharedEngine] preloadEffect:
@"
explosion_large.caf
"
];
[[SimpleAudioEngine sharedEngine] preloadEffect:
@"
laser_ship.caf
"
];
这里会加载背景音乐,同时预加载音效。
现在,找到ccTouchesBegan,然后在这个方法的开头添加下列代码:
[[SimpleAudioEngine sharedEngine] playEffect:
@"
laser_ship.caf
"
];
这里会在你点击屏幕的时候播放一个发射激光的音效。
还是在ccTouchesBegan方法里面,打开遍历enemySprites那个循环,然后在 (wasTouched)的if判断句内添加下列代码:
[[SimpleAudioEngine sharedEngine] playEffect:
@"
explosion_large.caf
"
];
当飞船被打中的时候,将会播放爆炸的音效!
编译并运行代码,尽情玩吧!
何去何从?
这里有本教程的完整源代码。
如果你想学习更多有关制作增强现实的游戏的话,下面有一些比较好的资源:
- 更多关于摄像头的纵横比的:1 2 3
- Apple’s documentation on UIImagePickerController Class
- Apple’s documentation on Core Motion
我希望你在学习这个教程的时候会得到许多快乐!如果有什么建议或意见,请留言,谢谢!