Peeking Inside the iPhone Toolbox
These technologies include Objective-C or C/C++, Xcode, UIKit, Quartz 2D, Core
Animation, OpenGL, audio APIs, networking, and GameKit.
UIKit provides one of the simplest ways to draw images and other useful UI elements, still remains relatively fast due to underlying hardware acceleration.
UIKit is a great choice for games that don’t have a large number of graphical elements or animations and don’t need to run at the maximum of 60 fps.
Quartz 2D provides developers with a more advanced, low-level drawing engine.
While it may be too slow to use for rendering the main elements of a game, Quartz 2D is another valuable tool in the iPhone developer’s toolbox.
OpenGL ES gives you powerful graphics rendering using a tried-and-true interface, may be the single most important tool in your toolbox.
Audio APIs
You can use more advanced APIs such as OpenAL or simpler built-in services.
Networking
Real-time multiplayer activities can be implemented through sockets and streams with servers and clients, or through GameKit’s Bluetooth matchmaking.
UIKit Controls
The Cocoa Touch app environment
Two frameworks are central to this environment:
Foundation framework: Defines common classes such as NSObject and NSString that are used throughout Cocoa Touch.
UIKit framework: Provides classes for the creation of iPhone screens with buttons, labels, text fields, sliders, tables of data, and other UI components. Besides UI components, UIKit defines Objective-C classes that encapsulate app object properties and methods, and prototypes that define various app, view, and input events.
Memory Management in Objective-C
In Objective-C, release is not the same as free in C. release 只是把object的引用计数-1, 当该对象的引用计数为0时, 会被自动释放.
alloc, clone, retain操作都会增加引用计数, 所以需要对应一个release.
由autorelease pool管理的的object不需要手动释放, 例如
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// other code here
[pool release];
Cocoa Touch and the UIKit Framework
UIView
A UIView is a container object, contain buttons, icons, labels, text fields, and any sort of UI components.
The Frame Property: 定义了view的position和size. Origion是左上角点. Size是width和height.
CGRect newframe;
newFrame.origin = CGPointMake(100,100);
newFrame.size = CGSizeMake(100,40);
UIView *myView = [[UIView alloc] initWithFrame:newFrame];
颜色:
myView.backgroundColor = [UIColor redColor];
myView.backgroundColor = [UIColor colorWithRed:1.0 green:0.5 blue:1.0 alpha:1.0];
myView.backgroundColor = [UIColor clearColor]; // 设置为透明色! 当然也可以通过设置alpha值为0来实现.
The Center Property: view的中心点.
myView.center = CGPointMake(15,25);
Loading and Displaying Images Programmatically
UIImage* myImageObj;
NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"myImage" ofType:@"png"];
// load it like this
myImageObj = [[UIImage alloc] initWithContentsOfFile:imagePath];
// or like this
myImageObj = [UIImage imageNamed:imagePath];
be aware that [UIImage imageNamed:] tends to retain images longer than necessary sometimes.
UIImage是immutable, 意味着你不能改变image的属性, 要想draw image, 必须依靠ImageView:
UIImageView* myImgView = [[UIImageView alloc] initWithImage:myImageObj];
[mainView addSubview:myImgView];
Check Collision:
CGRectIntersectsRect: detect whether one view’s frame overlaps another view’s frame.
CADisplayLink: NSTimer的又一种选择
相对于NSTimer, CADisplayLink更精确, 且它是和iphone的screen logic同步的, 每秒刷新60次.
CADisplayLink * theTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(gameLogic)];
theTimer.frameInterval = 2;
[theTimer addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
CADisplayLink默认就是repeat的, 直到invalidate为止.
applicationWillResignActive
applicationWillResignActive is called when the user is interrupted, such as when the user receives a phone call or text, or when the device is locked.
applicationDidBecomeActive
iPhone在中断(applicationWillResignActive将被调用)后回到程序将调用的函数.
applicationDidReceiveMemoryWarning
内存低时会被调用. You should use the applicationDidReceiveMemoryWarning method to release any cached data your application may hold in the app delegate. 例如清楚web cache
viewDidUnload
在该view退出的时候调用到,相当于虚构函数.
Saving and Loading Game State
NSUserDefaults
NSString *kLivesKey = @"IVBrickerLives";
NSString *kScoreKey = @"IVBrickerScore";
- (void)saveGameState {
[[NSUserDefaults standardUserDefaults] setInteger:lives forKey:kLivesKey];
[[NSUserDefaults standardUserDefaults] setInteger:score forKey:kScoreKey];
}
- (void)loadGameState {
lives = [[NSUserDefaults standardUserDefaults] integerForKey:kLivesKey];
livesLabel.text = [NSString stringWithFormat:@"%d", lives];
score = [[NSUserDefaults standardUserDefaults] integerForKey:kScoreKey];
scoreLabel.text = [NSString stringWithFormat:@"%d", score];
}
XCode 一直停在Attaching to…的解决方法:
进入Orgnization->project->删除当前启动的项目
Handling Accelerometer Input
- (void) accelerometer:(UIAccelerometer *)accelerometer
didAccelerate:(UIAcceleration *)accel
{
//从accel.x, accel.y, accel.z可以得到x, y, z值;
}
- (void)viewDidLoad {
[super viewDidLoad];
UIAccelerometer *theAccel = [UIAccelerometer sharedAccelerometer];
theAccel.updateInterval = 1.0f / 30.0f; //设置更新间隔
theAccel.delegate = self; // 把当前view controller作为accelerometer的delegate.
//那么,这个加速器就会自动调用当前view controller的didAccelerate函数.
// 当然,还必须首先声明UIAccelerometerDelegate:
// @interface IVBrickerViewController : UIViewController<UIAccelerometerDelegate>
}
Handling Touchscreen Input
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
[touch locationInView:touch.view].x; // the x of the touch point.
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
[touch locationInView:touch.view].x //当前touch point的x分量
}
Timer
float theInterval = 1.0/30.0;
[NSTimer scheduledTimerWithTimeInterval:theInterval
target:self //指明目标是当前view controller
selector:@selector(animteFun:) //每隔一个interval就会调用这个回调函数
userInfo:nil
repeats:YES];
CADisplayLink (QuartzCore.framework下有效)
通过CADisplayLink, 也可以周期性的调用某一个方法, 比NSTimer更精确.
CADisplayLink * theTimer = [CADisplayLink displayLinkWithTarget:self
selector:@selector(animateFun)];
// CADisplayLink defaults to running at 60 frames per second, 这里我们设置为2, 表明每2帧调//用一次回调函数.
// CADisplayLink is set to repeat until it is invalidated.
theTimer.frameInterval = 2;
[theTimer addToRunLoop: [NSRunLoop currentRunLoop]
forMode: NSDefaultRunLoopMode];
Application Interruptions
- (void)applicationWillResignActive:(UIApplication *)application {
[viewController pauseGame];
[viewController saveGameState];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
[viewController loadGameState];
[viewController startPlaying];
}
Low Memory Warnings
applicationDidReceiveMemoryWarning:.
当内存比较低时,会调用这个方法, 你可以在这里释放不用的内存.
Saving and Loading Game State
NSString *kLivesKey = @"IVBrickerLives";
NSString *kScoreKey = @"IVBrickerScore";
- (void)saveGameState {
[[NSUserDefaults standardUserDefaults] setInteger:lives forKey:kLivesKey];
[[NSUserDefaults standardUserDefaults] setInteger:score forKey:kScoreKey];
}
- (void)loadGameState {
lives = [[NSUserDefaults standardUserDefaults] integerForKey:kLivesKey];
livesLabel.text = [NSString stringWithFormat:@"%d", lives];
score = [[NSUserDefaults standardUserDefaults] integerForKey:kScoreKey];
scoreLabel.text = [NSString stringWithFormat:@"%d", score];
}
Managing Memory with a Custom Image Loader
static NSMutableDictionary *dict;
+ (UIImage*)loadImage:(NSString*)imageName
{
if (!dict) dict = [[NSMutableDictionary dictionary] retain];
UIImage* image = [dict objectForKey:imageName];
if (!image)
{
NSString* imagePath = [[NSBundle mainBundle]
pathForResource:imageName
ofType:nil];
image = [UIImage imageWithContentsOfFile:imagePath];
if (image)
{
[dict setObject:image forKey:imageName];
}
}
return image;
}
+ (void)releaseCache {
if (dict) {
[dict removeAllObjects];
}
}
Animation
- 使用UIImageView Animation Properties
NSMutableArray *images = [NSMutableArray alloc] initWithCapacity: 30];
// load the images into the array
for (int i = 1; i <= 30; i++) {
NSString *imageName = [NSString stringWithFormat: @”animation1_f%0d.png”, i ];
UIImage *image = [ImageCache loadImage: imageName ];
[images addObject: image];
}
// set the animations property on the image view
imageView.animationImages = images; // 加入image array
[images release];
imageView.animationDuration = 1; //间隔时间
imageView.animationRepeatCount = 1; // 重复次数
[imageView startAnimating];
- 使用NSTimer
- 使用CADisplayLink
Quartz 2D Game
A Quart2D game loop: MVC model
Model: persist, records shared information on a common “blackboard,” while capturing external
changes to the UI (such as a finger touch or accelerometer movement).
Controller: update
View: render
Quartz 2D plays a heavy role in the views, and is only a bit player in the controller and model.
Quart 2D中的坐标系统,
左下角是(0, 0), 同OpenGL
Saving and Restoring the Context
// Get a graphics context, with no transformations
CGContextRef context = UIGraphicsGetCurrentContext();
得到当前view的context, context就像是a physical canvas, 可以被moved, rotated, shrunk, stretched. 想象一张很大的canvas, 你可以move, rotate它, 使得能更方便的绘画.
在绘画前,你要save context, 然后对canvas做一定的matrix变换(其实就是move,rotate等操作), 绘画完之后再restore context, 使得canvas回到原来的位置!
// Save context, This remembers the current state of the canvas for us
CGContextSaveGState(context)
// restore context
CGContextRestoreGState(context)
Quartz 2D describes the shifts of the canvas with matrix transformations. In 2D games, we’re
typically interested in affine transformations.
It’s important to follow one simple algorithm whenever drawing with Quartz 2D:
1. Save the context state.
2. Perform a single, combined matrix transformation.
3. Draw your graphics that need this transformation.
4. Restore the context.
例子:
- (void)drawRect:(CGRect)rect {
// Get a graphics context, saving its state
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Reset the transformation
// CGContextGetCTM得到CTM(the graphic context’s transformation matrix)
CGAffineTransform t0 = CGContextGetCTM(context);
t0 = CGAffineTransformInvert(t0); // 创建t0的逆矩阵
CGContextConcatCTM(context,t0); // 将已存在的矩阵与t0连接起来(乘法操作)
// Draw a green rectangle
CGContextBeginPath(context);
CGContextSetRGBFillColor(context, 0,1,0,1); // 指定我们要绘制的path的颜色 rgba
// 增加一个rectangle, CGRectMake是一个macro
CGContextAddRect(context, CGRectMake(0,0,100,200)); // (x, y, width, height)
CGContextClosePath(context); // 与beginPath 对应
CGContextDrawPath(context,kCGPathFill); //绘画
CGContextRestoreGState(context);
}
Changing to Landscape Orientation
[application setStatusBarOrientation: UIInterfaceOrientationLandscapeRight
animated:NO];
// Get the orientation of the device.
UIDeviceOrientation orient = [[UIDevice currentDevice] orientation];
若定义CGFloat scale;
当运行到Self.scale = 1.0f时, 如果你自已定义了setScale函数, 会调用到你自函数.
如果你只是写scale = 1.0f, 则不会调用setScale函数.
OpenGL Basics
Matrix Types
In OpenGL there are four main matrixes, called
the model, scene objects, 屏幕中的所有对象
the view, camera position, 我们的相机的位置和朝向
the viewport, recorder, 相当于照片的大小和aspect ratio(宽高比).
the projection lens, 相当于相机上的镜头类型, 是透视投影还是水平投影, 我们通过projection 矩阵来设置我们的视野区域.
Project矩阵又分为: perspective mode and orthographic mode.
perspective mode是透视投影, 远处的物体小, 近处的物体大.
这里, 我们需要定义一个view frustrum
例如: glFrustrum(left, right, bottom, top, near, far);
更方便的做法是使用gluPerspective(fovY, aspect, zNear, zFar). It allows you to specify the view frustrum as an aspect ratio and a viewing angle // 它不包含在openGL ES中.
fovY: is the vertical angle of the field of view.
aspect ratio: is the width of the view over the height. (width / height)
zNear and zFar are the z positions of the near and far clipping planes.
orthographic mode是水平投影, 远近的物体一样大小.
例如: glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);// (left, right, bottom, top, near, far);
Rendering Basics
在EAGLView中有一个CAEAGLLayer对象, CAEAGLLayer对象是真正在底层context上render opengl scene.
+ (Class)layerClass {
return [CAEAGLLayer class];
} // This tells the UIView that its main layer is going to be a CAEAGLLayer.
//初始化函数
- (id)initWithFrame:(CGRect)rect {
if ((self = [super initWithFrame:rect])) {
// get the layer
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
//不要把最底层设置成transparency, 因为opengl在iPhone上会对最底层实施优化,如
//果设成transparency会影响效率.
eaglLayer.opaque = YES;
// set the drawing properties on our layer
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO],
//refers to how the memory is handled after it has been displayed. Generally, we do not //want to retain the backing buffer. Opengl不停在绘制frame, 保持当前frame的内存叫做//backing buffer. 通常情况下, 我们都将进入新的frame,而不用保持当前frame, 即不用//retain the backing buffer.
kEAGLDrawablePropertyRetainedBacking,
//指定图片格式.RGBA8表示每一个pixel需要1 byte来保存r,g,b和a的每个分量
//RGB565表示5 bits for red, 6 bits for green, 5 bits for blue.
//显然RGBA8表示的颜色更丰富.
kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyColorFormat, nil];
//我们将使用OpenGL ES 1.1
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
if (!context || ![EAGLContext setCurrentContext:context]) {
[self release];
return nil;
}
}
return self;
}
Frame Buffers, Render Buffers, and Depth Buffers
Frame buffer: basically the big chunk of memory that will be used to hold the various bits of data needed to render a single frame.
Render buffer: is where the frame will be rendered before it is copied into the CAEAGLLayer backing buffer and ultimately ends up on the display.
Depth buffer: 使得屏幕中的物体有深度(哪个在前,哪个在后), 即在z方向上是有顺序的.
glEnable(GL_DEPTH_TEST) //2D中用不到
如果使用了USE_DEPTH_BUFFER, 将会block createFramebuffer方法.
- (BOOL)createFramebuffer {
glGenFramebuffersOES(1, &viewFramebuffer);
glGenRenderbuffersOES(1, &viewRenderbuffer);
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context renderbufferStorage:GL_RENDERBUFFER_OES
fromDrawable:(CAEAGLLayer*)self.layer];
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
GL_COLOR_ATTACHMENT0_OES,
GL_RENDERBUFFER_OES, viewRenderbuffer);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
if (USE_DEPTH_BUFFER) {
glGenRenderbuffersOES(1, &depthRenderbuffer);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
glRenderbufferStorageOES(GL_RENDERBUFFER_OES,
GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES,
GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES,
depthRenderbuffer);
}
if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) !=
GL_FRAMEBUFFER_COMPLETE_OES) {
NSLog(@"failed to make complete framebuffer object %x",
glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
return NO;
}
return YES;
}
Render:
-(void)beginDraw
{
// make sure that you are drawing to the current context
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
// make sure we are in model matrix mode and clear the frame
glMatrixMode(GL_MODELVIEW);
glClear(GL_COLOR_BUFFER_BIT);
// set a clean transform
glLoadIdentity();
}
-(void)finishDraw
{
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
How to Draw Stuff with OpenGL
绘制一个矩形 步骤:
1 定义顶点和颜色:
static CGFloat spinnySquareVertices[8] = {
-0.5f, -0.5f,
0.5f, -0.5f,
-0.5f, 0.5f,
0.5f, 0.5f,
};
static CGFloat spinnySquareColors[16] = {
1.0, 1.0, 0, 1.0,
0, 1.0, 1.0, 1.0,
0, 0, 0, 0,
1.0, 0, 1.0, 1.0,
};
2 绘制函数
-(void)render
{
// clear the matrix 保存当前矩阵
// glPushMatrix() takes the current state of the model matrix and pushes a copy // of it onto a stack, effectively saving it for later.
glPushMatrix();
glLoadIdentity();//so our objects start at 0,0,0
// move to my position
// glTranslate() is always relative to the current matrix position.因此我们要先
//load一个单位阵
glTranslatef(x, y, z);
// rotate
glRotatef(xRotation, 1.0f, 0.0f, 0.0f); // 绕x轴旋转xRotation度
glRotatef(yRotation, 0.0f, 1.0f, 0.0f);
glRotatef(zRotation, 0.0f, 0.0f, 1.0f);
//scale
glScalef(xScale, yScale, zScale);
// load arrays into the engine
//对于二维object来说, vertexSize = 2, 分别是x, y
// colorSize一直是4, 分别是r, g, b, a
glVertexPointer(vertexSize, GL_FLOAT, 0, spinnySquareVertices);
glEnableClientState(GL_VERTEX_ARRAY);//告诉引擎vertex被定义了
glColorPointer(colorSize, GL_FLOAT, 0, spinnySquareColors);
glEnableClientState(GL_COLOR_ARRAY);
//render, 这里vertexCount =4, renderStyle =GL_TRIANGLE_STRIP
glDrawArrays(renderStyle, 0, vertexCount);
//restore the matrix 恢复到我们pushMatrix时保存的矩阵
// This means that whatever is happening to the engine outside our object, we won't //change it.
glPopMatrix();
}
在iPhone上, 你最高只能得到60fps.
如何设计gameLoop:
一种方法是
The simplest is to simply use an NSTimer that calls your game loop every 1/60 second. The upside of this method is that it is very easy. The downside is that if you take too long to render, you can get out of sync with the display refresh rate, and your frames per second will suffer.
更好的方法是:
A more complicated approach is to simply run the game loop as fast as possible. As soon as you are finished rendering a frame, you start rendering the next—you don't wait for any timers to fire or anything. This is generally how high-end 3D games work.
对于iPhone来说, Apple引进了display link.
A display link is a hardware-triggered callback that will call a specified method on your object every time the screen refreshes. This object is called the CADisplayLink.
Eg
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(gameLoop)];
[displayLink setFrameInterval:animationFrameInterval];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
这将会每1/60秒调用gameloop一次. 只对IOS3.1以后的版本适用.
Adding Buttons
有两种方式添加控件:
1 use UIKit and simply lay them over your OpenGL view
这种方式虽然简单, 但是效率较低. 所以在游戏中的控件尽量用opengl来render, 如果是设置或其他界面的控件, 就用UIKit好了!
2 use OpenGL draw your own buttons
We will use GL_LINE_LOOP to draw the unfilled button and GL_TRIANGLE_STRIP to draw the filled one.
坐标的选取:
在定义object的时候,如何选取坐标是一个问题,
是以中心为(0, 0)点,还是以左下角为中心点.
通常情况下,以中心为(0,0)点比较好!