Swan's Quest, Chapter 3: The notable scroll
Developer Tools 入门 8m

天鹅任务第三章:非凡卷轴

Swan's Quest, Chapter 3: The notable scroll

2020年6月24日

在 Apple 官方观看视频

一句话判断

Swan’s Quest 第三章转向了视觉领域——你需要用 Core Graphics 在屏幕上绘制特定的图形和符号来解谜,从音频编程跳到了 2D 绘图,考验的是你对 CGPathCGContext 和贝塞尔曲线的掌握。

这场 Session 讲了什么

第三章从音频挑战转向了图形绘制挑战。玩家需要用 Core Graphics 绘制特定的几何图形(线条、曲线、形状组合),这些图形作为「卷轴」上的密码来解锁下一关。

核心编程任务是:理解 CGContext 的绘制状态机(save/restore)、用 CGPathUIBezierPath 构建复杂路径、控制线条样式(颜色、宽度、虚线模式)、以及将绘制结果渲染到屏幕上。

这一章的设计意图是让开发者在游戏过程中自然地学习 Core Graphics 的基础 API,而不是啃枯燥的文档。

值得深挖的点

贝塞尔曲线的绘制技巧

解谜过程中需要绘制精确的曲线路径。UIBezierPathaddCurve(to:controlPoint1:controlPoint2:) 使用三次贝塞尔曲线,两个控制点决定曲线的弯曲方向和程度。理解控制点的几何含义是解谜的关键——控制点就像磁铁,把曲线拉向自己的方向。

代码片段

import UIKit

// 绘制解谜图形
class ScrollView: UIView {
    
    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        // 设置绘制属性
        context.setStrokeColor(UIColor.systemBlue.cgColor)
        context.setLineWidth(3.0)
        context.setLineCap(.round)
        context.setLineJoin(.round)
        
        // 绘制一个五角星路径
        let center = CGPoint(x: rect.midX, y: rect.midY)
        let radius: CGFloat = 80
        let path = UIBezierPath()
        
        for i in 0..<5 {
            let angle = CGFloat.pi / 2 + CGFloat(i) * 2 * CGFloat.pi / 5
            let outerPoint = CGPoint(
                x: center.x + radius * cos(angle),
                y: center.y - radius * sin(angle)
            )
            
            if i == 0 {
                path.move(to: outerPoint)
            } else {
                path.addLine(to: outerPoint)
            }
            
            // 内角点
            let innerAngle = angle + CGFloat.pi / 5
            let innerRadius = radius * 0.4
            let innerPoint = CGPoint(
                x: center.x + innerRadius * cos(innerAngle),
                y: center.y - innerRadius * sin(innerAngle)
            )
            path.addLine(to: innerPoint)
        }
        path.close()
        
        context.addPath(path.cgPath)
        context.strokePath()
    }
}

最佳实践

  1. saveGState / restoreGState 管理绘制状态。每次变换颜色、线宽或做仿射变换前先 save,画完后 restore,避免状态泄漏影响后续绘制。
  2. 在 Playground 中实时预览。Swift Playgrounds 的 Live View 可以即时显示 Core Graphics 绘制结果,改一行代码就能看到效果,比在 Xcode 中编译运行快得多。

还有什么值得关注

  • 第四章是最终章,会综合前三章学到的知识。
  • Core Graphics 的坐标系原点在左下角,而 UIKit 的原点在左上角,绘制时注意坐标系的差异。
  • 如果你用 SwiftUI 做 iOS 14+ 的开发,大部分场景可以用 PathShape 替代 Core Graphics,语法更简洁。
WWDC 2020