CoreGraphic框架解析 (十)—— 一个简单小游戏 (二)

版本记录

版本号 时间
V1.0 2019.02.01 星期五

前言

quartz是一个通用的术语,用于描述在iOSMAC OS X 中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D 是一组二维绘图和渲染APICore Graphic会使用到这组APIQuartz Core专指Core Animation用到的动画相关的库、API和类。CoreGraphicsUIKit下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics数据结构和函数可以通过前缀CG来识别。在app中很多时候绘图等操作我们要利用CoreGraphic框架,它能绘制字符串、图形、渐变色等等,是一个很强大的工具。感兴趣的可以看我另外几篇。
1. CoreGraphic框架解析(一)—— 基本概览
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 类波浪线的实现
4. CoreGraphic框架解析(四)—— 基本架构补充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一个简单绘制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一个简单绘制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一个简单绘制示例 (三)
8. CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)
9. CoreGraphic框架解析 (九)—— 一个简单小游戏 (一)

Masking Patterns

掩模图案(Masking patterns)在图案单元格绘制方法之外定义其颜色信息。 这允许您更改图案颜色以满足您的需要。

这是一个没有颜色关联的遮罩图案的例子:

有了模式,您现在可以应用颜色。 下面的第一个示例显示应用于蒙版的蓝色,第二个示例显示橙色:

现在,您将把您使用过的模式更改为蒙版模式(masking pattern)

drawPattern替换以下代码:

context.setFillColor(UIColor.yellow.cgColor)
context.setStrokeColor(UIColor.darkGray.cgColor)
context.drawPath(using: .fillStroke)

使用下面的代码

context.fillPath()

这会将代码恢复为填充路径。

用以下内容替换pattern分配:

let pattern = CGPattern(
      info: nil,
      bounds: CGRect(x: 0, y: 0, width: 20, height: 20),
      matrix: transform,
      xStep: 25,
      yStep: 25,
      tiling: .constantSpacing,
      isColored: false,
      callbacks: &callbacks)

这会将isColored参数设置为false,从而将模式更改为屏蔽模式。 您还将垂直和水平间距增加到25。现在,您需要为模式提供颜色空间信息。

用以下内容替换patternSpace赋值:

let baseSpace = CGColorSpaceCreateDeviceRGB()
let patternSpace = CGColorSpace(patternBaseSpace: baseSpace)!

在这里,您将获得对标准设备相关RGB颜色空间的引用。 然后,您将图案颜色空间更改为此值,而不是之前的nil值。

在下面,替换这些行:

var alpha : CGFloat = 1.0
context.setFillPattern(pattern!, colorComponents: &alpha)

用下面的代码

let fillColor: [CGFloat] = [0.0, 1.0, 1.0, 1.0]
context.setFillPattern(pattern!, colorComponents: fillColor)

填充图案时,这会在蒙版下方创建一种颜色。

运行playground。 您模式的颜色更新以反映在draw方法之外配置的cyan颜色设置:

是时候看看如何描边和填充掩模图案了。 这就像描边彩色图案。

使用以下内容替换drawPattern定义中的context.fillPath()行:

context.setStrokeColor(UIColor.darkGray.cgColor)
context.drawPath(using: .fillStroke)

虽然您在draw(_:)中设置了stroke颜色,但您的图案颜色仍然在方法之外设置。

运行playground以查看stroked模式:

您现在已经积累了不同模式配置和masking patterns的经验。 您可以开始构建Recall所需的模式。


Creating the Game Pattern

在视图控制器在playground中显示之前添加以下代码:

extension UIBezierPath {
  // 1
  convenience init(triangleIn rect: CGRect) {
    self.init()
    // 2
    let topOfTriangle = CGPoint(x: rect.width / 2, y: 0)
    let bottomLeftOfTriangle = CGPoint(x: 0, y: rect.height)
    let bottomRightOfTriangle = CGPoint(x: rect.width, y: rect.height)
    // 3
    self.move(to: topOfTriangle)
    self.addLine(to: bottomLeftOfTriangle)
    self.addLine(to: bottomRightOfTriangle)
    // 4
    self.close()
  }
}

下面进行详细说明:

  • 1) 扩展UIBezierPath以创建三角形路径。
  • 2) 指定构成三角形的三个点。
  • 3) 从顶部开始绘制三角形。 move(to :)开始路径,addLine(to :)line添加到路径中。
  • 4) 关闭路径。

将以下结构添加到PatternView的顶部:

public struct Constants {
  static let patternSize: CGFloat = 30.0
  static let patternRepeatCount = 2
}

这些代表您在设置模式时将使用的常量。 patternSize定义模式单元格大小。 patternRepeatCount定义模式视图中的模式单元格数。

Constants定义后添加以下内容:

let drawTriangle: CGPatternDrawPatternCallback = { _, context in
  let trianglePath = UIBezierPath(triangleIn:
    CGRect(x: 0, y: 0,
           width: Constants.patternSize,
           height: Constants.patternSize))
  context.addPath(trianglePath.cgPath)
  context.fillPath()
}

这定义了一个用于绘制三角形图案的新函数。 调用UIBezierPath(triangleIn :)返回表示三角形的路径。 然后,在绘制之前将此路径添加到上下文中。

请注意,该函数未指定填充颜色,因此它可以是masking pattern

draw(_ :)中,将callbacks更改为以下内容:

var callbacks = CGPatternCallbacks(
  version: 0, drawPattern: drawTriangle, releaseInfo: nil)

您现在正在使用三角绘制函数。

删除drawPattern,因为它不再需要。 人们只能在圈子里走了这么长时间。

同样在draw(_ :)中,用以下代码替换分配transformpattern的代码:

// 1
let patternStepX: CGFloat =
  rect.width / CGFloat(Constants.patternRepeatCount)
let patternStepY: CGFloat =
  rect.height / CGFloat(Constants.patternRepeatCount)
// 2
let patternOffsetX: CGFloat = (patternStepX - Constants.patternSize) / 2.0
let patternOffsetY: CGFloat = (patternStepY - Constants.patternSize) / 2.0
// 3
let transform = CGAffineTransform(translationX: patternOffsetX, y: patternOffsetY)
// 4
let pattern = CGPattern(
  info: nil,
  bounds: CGRect(
    x: 0, 
    y: 0, 
    width: Constants.patternSize, 
    height: Constants.patternSize),
  matrix: transform,
  xStep: patternStepX,
  yStep: patternStepY,
  tiling: .constantSpacing,
  isColored: false,
  callbacks: &callbacks)

这是代码的作用,一步一步:

  • 1) 使用视图的宽度和高度以及视图中的图案单元格数计算水平和垂直步长。
  • 2) 计算出尺寸,使图案单元在其边界内水平和垂直居中。
  • 3) 根据您定义的居中变量设置CGAffineTransform转换。
  • 4) 根据计算出的参数创建模式对象。

运行playground,您应该在每个方向上看到两个三角形,在其边界内垂直和水平居中:

现在,您将获得更接近Recall应用程序的背景颜色。

MyViewController中,更改loadView()中的背景颜色设置,如下所示:

view.backgroundColor = .lightGray

接下来转到PatternView并在draw(_ :)中更改上下文填充设置,如下所示:

UIColor.white.setFill()

运行playground,您的主视图背景现在应该是灰色的,图案视图为白色背景:

1. Customizing the Pattern View

现在您已正确显示基本模式,您可以进行更改以控制模式方向。

Constants定义之后,在PatternView顶部附近添加以下枚举:

enum PatternDirection: CaseIterable {
  case left
  case top
  case right
  case bottom
}

这表示三角形可以指向的不同方向。

PatternDirection定义之后添加以下类属性:

var fillColor: [CGFloat] = [1.0, 0.0, 0.0, 1.0]
var direction: PatternDirection = .top

这表示您将应用于masking pattern和图案pattern方向的颜色。 该类设置默认颜色为红色,默认方向为top

删除在draw(_ :)底部附近的本地fillColor声明。 这将确保您使用class属性。

用以下内容替换transform赋值:

// 1
var transform: CGAffineTransform
// 2
switch direction {
case .top:
  transform = .identity
case .right:
  transform = CGAffineTransform(rotationAngle: CGFloat(0.5 * .pi))
case .bottom:
  transform = CGAffineTransform(rotationAngle: CGFloat(1.0 * .pi))
case .left:
  transform = CGAffineTransform(rotationAngle: CGFloat(1.5 * .pi))
}
// 3
transform = transform.translatedBy(x: patternOffsetX, y: patternOffsetY)

这就是上面代码的作用:

  • 1) 为模式转换声明一个CGAffineTransform变量。
  • 2) 如果模式方向为top,则将变换分配给单位矩阵。 否则,变换是基于方向的旋转。 例如,如果图案指向右侧,则旋转为π/ 2弧度或顺时针90º。
  • 3) 应用CGAffineTransform转换以使模式单元在其边界内居中。

运行playground,根据您的默认图案填充颜色,您的三角形为红色:

现在是设置代码来控制和测试图案颜色和方向的好时机。

在类属性定义之后在PatternView中添加以下方法:

init(fillColor: [CGFloat], direction: PatternDirection = .top) {
  self.fillColor = fillColor
  self.direction = direction
  super.init(frame: CGRect.zero)
}
  
required init?(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
}

这将设置一个初始化器,它接收填充颜色和图案方向。 direction参数具有默认值。

您还在sb初始化视图时添加了所需的初始化程序。 将代码传输到应用程序后,您将需要此功能。

MyViewController中,更改了patternView赋值,因为您已经更改了初始化程序:

let patternView = PatternView(
  fillColor: [0.0, 1.0, 0.0, 1.0],
  direction: .right)

在这里,您使用非默认值实例化模式视图。

运行playground,你的三角形现在是绿色的并指向右边:

恭喜! 您已经使用Playgrounds对您的模式进行了原型设计。 是时候在Recall中使用这种模式了。

2. Updating the App Pattern View

打开Recall启动项目。 转到PatternView.swift并将UIBezierPath扩展从playground复制到文件末尾。

接下来,将PatternView.swift中的PatternView类替换为playground中的类。

注意:通过使用Xcode的代码折叠功能,您可以大大简化此过程。 在playground中,将光标放在class PatternView: UIView {的左大括号后面并从菜单中选择Editor ▸ Code Folding ▸ Fold。 双击生成的折叠线以选择整个类,然后按Command-C。 在项目中,重复该过程以折叠并选择该类。 按Command-V替换它。

构建并运行应用程序。 你应该看到这样的东西:

有点不对劲。 您的图案似乎卡在默认模式下。 看起来新游戏视图没有刷新模式视图。

转到GameViewController.swift并将以下内容添加到setupPatternView(_:towards:havingColor:)的末尾:

patternView.setNeedsDisplay()

这会提示系统重绘图案,以便它获取新的图案信息。

构建并运行应用程序。 您现在应该看到混合的颜色和方向:

点击其中一个答案按钮并玩游戏以检查一切是否按预期工作。

恭喜您完成Recall


Performance

Core Graphics模式非常快。 以下是可用于绘制模式的几个选项:

  • 1) 使用您在本教程中学习的Core Graphics模式API。
  • 2) 使用UIKit包装方法,如UIColor(patternImage :)
  • 3) 在具有许多Core Graphics调用的循环中绘制所需的模式。

如果您的模式只绘制一次,则UIKit包装方法最简单。 它的性能也应该与较低级别的Core Graphics调用相当。 一个例子是背景图案。

Core Graphics可以在后台线程中工作,而不像UIKit,它在主线程上运行。 Core Graphics模式在复杂的绘图或动态模式下性能更好。

在循环中绘制图案将是最慢的。 Core Graphics模式使绘制调用一次并缓存结果,使其更有效。

后记

本篇主要讲述了一个简单小游戏,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,671评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,442评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,524评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,623评论 1 275
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,642评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,584评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,953评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,621评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,865评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,608评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,698评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,378评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,958评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,940评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,173评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,419评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,425评论 2 342

推荐阅读更多精彩内容