探索iOS绘图与游戏开发:Core Graphics与Sprite Kit实战
1. Core Graphics绘图基础
在iOS开发中,Core Graphics提供了强大的绘图功能。在绘图时,有时矩形的大小值可能为负数,不过这是正常的。一个具有负大小的CGRect会在其原点的相反方向渲染(负宽度向左,负高度向上)。以下是定义矩形的代码:
CGRect currentRect = CGRectMake(self.firstTouchLocation.x,
self.firstTouchLocation.y,
self.lastTouchLocation.x -
self.firstTouchLocation.x,
self.lastTouchLocation.y -
self.firstTouchLocation.y);
定义好矩形后,绘制矩形或椭圆就很简单了,只需调用两个函数,一个用于在定义的CGRect中绘制形状,另一个用于描边和填充:
case kRectShape:
CGContextAddRect(context, currentRect);
CGContextDrawPath(context, kCGPathFillStroke);
break;
case kEllipseShape:
CGContextAddEllipseInRect(context, currentRect);
CGContextDrawPath(context, kCGPathFillStroke);
break;
编译并运行应用程序,尝试使用矩形和椭圆工具,还可以更改颜色,包括使用随机颜色。
2. 绘制图像
接下来,我们要绘制图像。将
iphone.png
和
iphone@2x.png
添加到项目的
Images.xcassets
中,创建一个名为
iphone
的图像集,并将这两个图像拖入其中。然后在
drawRect:
方法中添加以下代码:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, self.currentColor.CGColor);
CGContextSetFillColorWithColor(context, self.currentColor.CGColor);
CGRect currentRect = CGRectMake(self.firstTouchLocation.x,
self.firstTouchLocation.y,
self.lastTouchLocation.x -
self.firstTouchLocation.x,
self.lastTouchLocation.y -
self.firstTouchLocation.y);
switch (self.shapeType) {
case kLineShape:
CGContextMoveToPoint(context,
self.firstTouchLocation.x,
self.firstTouchLocation.y);
CGContextAddLineToPoint(context,
self.lastTouchLocation.x,
self.lastTouchLocation.y);
CGContextStrokePath(context);
break;
case kRectShape:
CGContextAddRect(context, currentRect);
CGContextDrawPath(context, kCGPathFillStroke);
break;
case kEllipseShape:
CGContextAddEllipseInRect(context, currentRect);
CGContextDrawPath(context, kCGPathFillStroke);
break;
case kImageShape: {
CGFloat horizontalOffset = self.image.size.width / 2;
CGFloat verticalOffset = self.image.size.height / 2;
CGPoint drawPoint = CGPointMake(self.lastTouchLocation.x -
horizontalOffset,
self.lastTouchLocation.y -
verticalOffset);
[self.image drawAtPoint:drawPoint];
break;
}
default:
break;
}
}
这里需要注意,在
switch
语句中,
case kImageShape:
后面的代码添加了花括号,这是因为编译器对
case
语句后第一行声明的变量有问题,添加花括号可以避免编译器报错。
首先,我们计算图像的中心,因为我们希望图像以用户最后触摸的点为中心绘制。然后,通过减去偏移量创建一个新的
CGPoint
:
CGFloat horizontalOffset = self.image.size.width / 2;
CGFloat verticalOffset = self.image.size.height / 2;
CGPoint drawPoint = CGPointMake(self.lastTouchLocation.x -
horizontalOffset,
self.lastTouchLocation.y -
verticalOffset);
最后,调用以下代码让图像绘制自身:
[self.image drawAtPoint:drawPoint];
构建并运行应用程序,从分段控件中选择“图像”,检查是否可以在绘图画布上放置图像。
3. 优化QuartzFun应用程序
虽然应用程序可以正常工作,但我们可以进行一些优化。在
QuartzFunView.m
的
touchesMoved:withEvent:
和
touchesEnded:withEvent:
方法中,都包含
[self setNeedsDisplay];
这行代码,它会导致整个视图被擦除并重新绘制,即使只有一小部分发生了变化。
为了提高效率,我们可以使用
setNeedsDisplayInRect:
方法,它只标记视图的一个矩形部分需要重新显示。具体操作步骤如下:
1. 在
QuartzFunView.m
顶部添加以下代码:
@interface QuartzFunView ()
@property (assign, nonatomic) CGPoint firstTouchLocation;
@property (assign, nonatomic) CGPoint lastTouchLocation;
@property (strong, nonatomic) UIImage *image;
@property (readonly, nonatomic) CGRect currentRect;
@property (assign, nonatomic) CGRect redrawRect;
@end
这里声明了
redrawRect
用于跟踪需要重新绘制的区域,以及只读属性
currentRect
。
2. 在文件末尾添加
currentRect
属性的访问器方法:
- (CGRect)currentRect {
return CGRectMake (self.firstTouchLocation.x,
self.firstTouchLocation.y,
self.lastTouchLocation.x - self.firstTouchLocation.x,
self.lastTouchLocation.y - self.firstTouchLocation.y);
}
-
在
drawRect:方法中,将所有对currentRect的引用改为self.currentRect,并删除计算currentRect的代码。 -
替换
touchesEnded:withEvent:和touchesMoved:withEvent:方法:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.lastTouchLocation = [touch locationInView:self];
if (self.shapeType == kImageShape) {
CGFloat horizontalOffset = self.image.size.width / 2;
CGFloat verticalOffset = self.image.size.height / 2;
self.redrawRect = CGRectUnion(self.redrawRect,
CGRectMake(self.lastTouchLocation.x -
horizontalOffset,
self.lastTouchLocation.y -
verticalOffset,
self.image.size.width,
self.image.size.height));
} else {
self.redrawRect = CGRectUnion(self.redrawRect, self.currentRect);
}
self.redrawRect = CGRectInset(self.redrawRect, -2.0, -2.0);
[self setNeedsDisplayInRect:self.redrawRect];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
self.lastTouchLocation = [touch locationInView:self];
if (self.shapeType == kImageShape) {
CGFloat horizontalOffset = self.image.size.width / 2;
CGFloat verticalOffset = self.image.size.height / 2;
self.redrawRect = CGRectUnion(self.redrawRect,
CGRectMake(self.lastTouchLocation.x -
horizontalOffset,
self.lastTouchLocation.y -
verticalOffset,
self.image.size.width,
self.image.size.height));
} else {
self.redrawRect = CGRectUnion(_redrawRect, self.currentRect);
}
[self setNeedsDisplayInRect:self.redrawRect];
}
-
在
touchesBegan:withEvent:方法中添加以下代码:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.useRandomColor) {
self.currentColor = [UIColor randomColor];
}
UITouch *touch = [touches anyObject];
self.firstTouchLocation = [touch locationInView:self];
self.lastTouchLocation = [touch locationInView:self];
self.redrawRect = CGRectZero;
[self setNeedsDisplay];
}
重新构建并运行应用程序,虽然可能看不到明显的区别,但通过这些优化,减少了重新绘制视图所需的工作量,尤其是在更复杂的应用程序中,对性能的提升会更明显。
4. Sprite Kit框架介绍
iOS 7引入了Sprite Kit框架,用于高性能的2D图形渲染。与Core Graphics和Core Animation不同,Sprite Kit专注于视频游戏开发。它基于OpenGL,提供了OpenGL的高性能特性,而无需深入了解OpenGL编码。
Sprite Kit不实现像Core Graphics那样灵活的通用绘图系统,它没有绘制路径、渐变或填充颜色的方法。相反,它提供了场景图(类似于UIKit的视图层次结构),可以变换每个图节点的位置、比例和旋转,并且每个节点可以自行绘制。大多数绘图发生在
SKSprite
类(或其子类)的实例中,它代表一个准备好显示在屏幕上的图形图像。
5. 开始使用Sprite Kit构建游戏
我们将使用Sprite Kit构建一个简单的射击游戏
TextShooter
。具体步骤如下:
1. 在Xcode中,按
⌘N
或选择
File ➤ New ➤ File…
,从iOS部分选择“Game”模板。
2. 点击“Next”,将项目命名为
TextShooter
,将“Devices”设置为“Universal”,将“Game Technology”设置为“SpriteKit”,然后创建项目。
此时,可以简单了解一下其他可用的技术选择:
| 技术选择 | 特点 |
| ---- | ---- |
| OpenGL ES | 低级别图形API,对图形硬件有几乎完全的控制,但使用难度较大 |
| Metal(iOS 8新增) | 低级别图形API,对图形硬件有几乎完全的控制,但使用难度较大 |
| Sprite Kit | 2D API,易于使用,适合快速开发2D游戏 |
| SceneKit(iOS 8新增) | 用于构建3D图形应用程序的工具包 |
运行
TextShooter
项目,会看到默认的Sprite Kit应用程序,初始时只显示“Hello, World”文本,触摸屏幕会添加一些旋转的宇宙飞船。
6. 项目结构与场景初始化
Xcode创建的项目包含一个标准的
AppDelegate
类和一个小的视图控制器类
GameViewController
,它对
SKView
对象进行一些初始配置。以下是
GameViewController
的
viewDidLoad
方法代码:
- (void)viewDidLoad
{
[super viewDidLoad];
// Configure the view.
SKView * skView = (SKView *)self.view;
skView.showsFPS = YES;
skView.showsNodeCount = YES;
skView.ignoresSiblingOrder = YES;
// Create and configure the scene.
GameScene *scene = [GameScene nodeWithFileNamed:@"GameScene"];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
这个方法的流程如下:
graph TD;
A[开始] --> B[获取SKView实例并配置];
B --> C[创建并配置场景];
C --> D[设置场景的缩放模式];
D --> E[显示场景];
E --> F[结束];
首先,从故事板中获取
SKView
实例并配置它,使其在游戏运行时显示一些性能特征。Sprite Kit应用程序由一组场景组成,用
SKScene
类表示。开发时,通常会为应用程序的每个视觉不同的部分创建一个新的
SKScene
子类。场景可以表示快节奏的游戏显示,也可以是简单的开始菜单。
创建场景有两种方式:
- 手动编程分配和初始化实例。
- 从Sprite Kit场景文件加载。
Xcode模板采用后一种方式,生成一个名为
GameScene.sks
的Sprite Kit场景文件,其中包含
SKScene
对象的存档副本。通常使用
SKScene nodeWithFileNamed:
方法从存档中加载
SKScene
并初始化为具体子类的实例。
选择
GameScene.sks
文件,Xcode会在Level Designer中打开它。在Level Designer中,可以设计场景,就像在Interface Builder中构建用户界面一样。设计好场景后,将其保存到场景文件并重新运行应用程序,就可以看到设计的场景。
7. 场景缩放模式
场景的
scaleMode
属性用于调整场景在
SKView
中显示时的大小,有四种不同的缩放模式,对应
SKSceneScaleMode
枚举的四个值:
| 缩放模式 | 描述 |
| ---- | ---- |
| SKSceneScaleModeAspectFill | 调整场景大小以填充屏幕,同时保持其宽高比,但可能会裁剪部分场景 |
| SKSceneScaleModeAspectFit | 保持场景的宽高比,确保整个场景可见,可能会在视图中显示黑边 |
| SKSceneScaleModeFill | 沿两个轴缩放场景,使其完全适合视图,但可能会扭曲场景内容 |
| SKSceneScaleModeResizeFill | 将场景的左下角放置在视图的左下角,并保持其原始大小 |
为了了解每种缩放模式的效果,我们可以创建另一个Sprite Kit项目
ResizeModes
,具体操作步骤如下:
1. 按照之前的步骤创建一个Sprite Kit项目,命名为
ResizeModes
,选择
GameScene.sks
文件。
2. 在对象库中,找到一个标签节点并拖到场景中心,在
SKNode Inspector
中,将标签的文本改为“Center”。
3. 拖动另一个标签到场景的左下角,将其文本属性改为“Bottom Left”。
4. 拖动第三个标签到场景的右上角,将其文本改为“Top Right”。
5. 再拖动几个标签到场景的顶部和底部,分别命名为“Top”和“Bottom”。
6. 必要时,可以更改标签的颜色和字体,使文本更清晰。
7. 在项目导航器中选择
GameScene.m
,删除
didMoveToView:
方法。
8. 选择
GameViewController.m
,找到
viewDidLoad
方法中设置
SKScene
对象
scaleMode
的代码,初始值为
SKSceneScaleModeAspectFill
。
9. 在iPhone模拟器(或设备)上运行应用程序,然后编辑代码,分别使用
SKSceneScaleModeAspectFit
、
SKSceneScaleModeFill
和
SKSceneScaleModeResizeFill
运行应用程序,观察不同缩放模式的效果。
通过以上步骤,我们可以深入了解Core Graphics绘图和Sprite Kit游戏开发的基础知识,并通过实际操作掌握相关技能。
探索iOS绘图与游戏开发:Core Graphics与Sprite Kit实战
8. 深入理解Sprite Kit场景与节点
在Sprite Kit中,场景和节点是构建游戏的核心元素。场景(
SKScene
)是一个容器,用于容纳各种节点(
SKNode
),而节点则代表游戏中的各种元素,如角色、道具等。
场景和节点之间的关系类似于UIKit中的视图控制器和视图。
SKView
类似于
UINavigationController
,它管理着场景的显示,而场景则管理着节点的显示和行为。
在Level Designer中,我们可以直观地设计场景。场景元素都是节点,
SKScene
本身也是
SKNode
的子类。当我们在Level Designer中选择一个节点时,可以通过
SKNode Inspector
设置其属性。同时,在底部的Xcode Object Library中,会自动过滤出可以添加到Sprite Kit场景中的对象类型,我们可以通过拖动这些对象到编辑器来设计场景。
9. 自定义Sprite Kit节点用于游戏开发
在
TextShooter
游戏中,我们将使用一种特殊的方式来构建游戏对象,即使用文本节点。我们可以创建一个
SKSprite
的子类,专门用于处理文本显示。
以下是一个简单的示例,展示如何创建一个文本节点:
// 假设我们有一个自定义的文本节点类
@interface TextSpriteNode : SKSpriteNode
- (instancetype)initWithText:(NSString *)text;
@end
@implementation TextSpriteNode
- (instancetype)initWithText:(NSString *)text {
SKLabelNode *label = [SKLabelNode labelNodeWithText:text];
// 设置标签的属性,如字体、颜色等
label.fontName = @"Arial";
label.fontSize = 20;
label.fontColor = [SKColor whiteColor];
// 根据标签的大小创建一个SKSpriteNode
self = [super initWithColor:[SKColor clearColor] size:label.frame.size];
if (self) {
[self addChild:label];
}
return self;
}
@end
使用这个自定义的文本节点类,我们可以在游戏中轻松创建文本对象,而无需使用复杂的图形资源。
10. 游戏逻辑的初步实现
在
TextShooter
游戏中,我们需要实现一些基本的游戏逻辑,如射击、移动等。以下是一个简单的示例,展示如何在场景中实现射击逻辑:
// 在GameScene.m中
@interface GameScene ()
@property (nonatomic, strong) NSMutableArray *bullets;
@end
@implementation GameScene
- (void)didMoveToView:(SKView *)view {
self.bullets = [NSMutableArray array];
// 其他初始化代码
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
location = [self convertPointFromView:location];
// 创建一个子弹节点
TextSpriteNode *bullet = [[TextSpriteNode alloc] initWithText:@"*"];
bullet.position = location;
[self addChild:bullet];
[self.bullets addObject:bullet];
// 让子弹移动
SKAction *moveAction = [SKAction moveToY:self.size.height duration:1.0];
SKAction *removeAction = [SKAction removeFromParent];
SKAction *sequence = [SKAction sequence:@[moveAction, removeAction]];
[bullet runAction:sequence];
}
@end
在这个示例中,当用户触摸屏幕时,会创建一个子弹节点,并让它向上移动,当子弹移出屏幕后,将其从场景中移除。
11. 性能优化与注意事项
在使用Sprite Kit开发游戏时,性能优化是非常重要的。以下是一些性能优化的建议:
-
减少节点数量
:尽量减少场景中的节点数量,避免创建过多不必要的节点。
-
合理使用纹理
:如果使用纹理,确保纹理的大小和格式合适,避免使用过大的纹理。
-
优化动画
:使用
SKAction
来创建动画,避免手动更新节点的位置和状态,以提高性能。
-
使用批处理
:Sprite Kit支持批处理,可以将多个节点合并为一个批次进行渲染,减少渲染调用次数。
12. 总结与展望
通过前面的学习,我们掌握了Core Graphics的绘图基础,包括矩形、椭圆和图像的绘制,以及如何优化绘图性能。同时,我们也了解了Sprite Kit框架,并使用它构建了一个简单的射击游戏
TextShooter
。
在未来的开发中,我们可以进一步扩展
TextShooter
游戏,添加更多的游戏元素,如敌人、关卡等。也可以尝试使用Sprite Kit的其他功能,如物理引擎、粒子系统等,来创建更加丰富和有趣的游戏。
以下是一个简单的流程图,展示了
TextShooter
游戏的基本流程:
graph LR;
A[游戏开始] --> B[初始化场景和节点];
B --> C[等待用户触摸屏幕];
C --> D{是否触摸屏幕};
D -- 是 --> E[创建子弹节点并移动];
E --> C;
D -- 否 --> C;
通过不断的实践和学习,我们可以在iOS开发中更好地利用Core Graphics和Sprite Kit,开发出高质量的绘图应用和游戏。
超级会员免费看
68

被折叠的 条评论
为什么被折叠?



