CoreGraphic框架解析 (十一)—— 一個簡單小游戲 (三)

版本記錄

版本號 時間
V1.0 2019.02.01 星期五

前言

quartz是一個通用的術(shù)語认轨,用于描述在iOSMAC OS X 中整個媒體層用到的多種技術(shù) 包括圖形很钓、動畫纸淮、音頻袱院、適配屎慢。Quart 2D 是一組二維繪圖和渲染APICore Graphic會使用到這組API忽洛,Quartz Core專指Core Animation用到的動畫相關(guān)的庫腻惠、API和類。CoreGraphicsUIKit下的主要繪圖系統(tǒng)欲虚,頻繁的用于繪制自定義視圖集灌。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics數(shù)據(jù)結(jié)構(gòu)和函數(shù)可以通過前綴CG來識別苍在。在app中很多時候繪圖等操作我們要利用CoreGraphic框架绝页,它能繪制字符串荠商、圖形、漸變色等等续誉,是一個很強(qiáng)大的工具莱没。感興趣的可以看我另外幾篇。
1. CoreGraphic框架解析(一)—— 基本概覽
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 類波浪線的實(shí)現(xiàn)
4. CoreGraphic框架解析(四)—— 基本架構(gòu)補(bǔ)充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一個簡單繪制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一個簡單繪制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一個簡單繪制示例 (三)
8. CoreGraphic框架解析 (八)—— 基于CoreGraphic的一個簡單繪制示例 (四)
9. CoreGraphic框架解析 (九)—— 一個簡單小游戲 (一)
10. CoreGraphic框架解析 (十)—— 一個簡單小游戲 (二)

源碼

1. Swift

首先看下工程組織結(jié)構(gòu)

下面看下sb中的內(nèi)容

下面就是源碼了

1. ResultViewController.swift
import UIKit

class ResultViewController: UIViewController {
  // MARK: - Outlets
  @IBOutlet weak var scoreLabel: UILabel!

  // MARK: - Properties
  var score: Int?
  
  // MARK: - View Life Cycle
  override func viewDidLoad() {
    super.viewDidLoad()
    
    if let score = score {
      scoreLabel.text = "Your final score: \(score)"
    }
  }

  // MARK: - Actions
  @IBAction func playAgainPressed(_ sender: Any) {
    presentingViewController?.dismiss(animated: true, completion: nil)
  }
}
2. PatternView.swift
import UIKit

class PatternView: UIView {
  // MARK: - Structures
  public struct Constants {
    static let patternSize: CGFloat = 30.0
    static let patternRepeatCount = 2
  }
  
  // MARK: - Constants
  enum PatternDirection: CaseIterable {
    case left
    case top
    case right
    case bottom
  }
  
  // MARK: - Properties
  var fillColor: [CGFloat] = [1.0, 0.0, 0.0, 1.0]
  var direction: PatternDirection = .top
  
  // Callback that draws a single pattern, a triangle
  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()
  }
  
  // MARK: - Initialization
  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)
  }
  
  // MARK: - Drawing
  override func draw(_ rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()!
    
    // Background fill
    UIColor.white.setFill()
    context.fill(rect)
    
    // Set up color space
    let baseSpace = CGColorSpaceCreateDeviceRGB()
    let patternSpace = CGColorSpace(patternBaseSpace: baseSpace)!
    context.setFillColorSpace(patternSpace)
    
    // Pattern that draws the triangle
    var callbacks = CGPatternCallbacks(
      version: 0, drawPattern: drawTriangle, releaseInfo: nil)
    
    // Set up the pattern dimensions
    let patternStepX: CGFloat = rect.width / CGFloat(Constants.patternRepeatCount)
    let patternStepY: CGFloat = rect.height / CGFloat(Constants.patternRepeatCount)
    let patternOffsetX: CGFloat = (patternStepX - Constants.patternSize) / 2.0
    let patternOffsetY: CGFloat = (patternStepY - Constants.patternSize) / 2.0
    
    // Set up the transformation matrix based on the pattern direction
    var transform: CGAffineTransform
    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))
    }
    // Add an offset (margin) so the patterns line up nicely with each other
    transform = transform.translatedBy(x: patternOffsetX, y: patternOffsetY)
    
    // Create the pattern
    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)
    // Set the  pattern
    context.setFillPattern(pattern!, colorComponents: fillColor)
    // Paint the rectangle with the pattern
    context.fill(rect)
  }
}

// MARK: - UIBezierPath extension
extension UIBezierPath {
  convenience init(triangleIn rect: CGRect) {
    self.init()
    // Draw out the triangle path
    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)
    self.move(to: topOfTriangle)
    self.addLine(to: bottomLeftOfTriangle)
    self.addLine(to: bottomRightOfTriangle)
    self.close()
  }
}
3. Game.swift
import UIKit

class Game {
  // MARK: - Properties
  let maxAttemptsAllowed = 5
  let colorSelections = [UIColor.blue, UIColor.red, UIColor.magenta]
  let totalPatternCount: Int
  
  var score: Int
  var attempt: Int
  var answers: [PatternView.PatternDirection]
  
  private var majorityPatternCount: Int {
    return totalPatternCount / 2 + 1
  }
  
  // MARK: - Object Lifecycle
  init(patternCount count: Int) {
    totalPatternCount = count
    score = 0
    attempt = 0
    answers = []
  }
  
  // MARK: - Gameplay
  func play(_ guess: PatternView.PatternDirection) -> (correct: Bool, score: Int)? {
    if done() {
      return nil
    }
    if guess == answers[attempt] {
      score = score + 1
      attempt = attempt + 1
      return (true, score)
    } else {
      attempt = attempt + 1
      return (false, score)
    }
  }
  
  func setupNextPlay() -> (directions: [PatternView.PatternDirection], colors: [UIColor]) {
    var directions: [PatternView.PatternDirection] = []
    var colors: [UIColor] = []
    
    // Get a list of directions that don't belong to the correct answer
    let wrongDirections = PatternView.PatternDirection.allCases.filter{
      $0 != answers[attempt]
    }
    // Get a random number of correct answers to fill, to maintain the majority
    let numberOfCorrectPatterns = Int.random(in: majorityPatternCount ..< totalPatternCount)
    
    // Fill out the return info
    for index in 0..<totalPatternCount {
      // Front load with the correct answer
      if index < numberOfCorrectPatterns {
        directions.append(answers[attempt])
      } else {
        // Next, randomly assign wrong answers
        directions.append(wrongDirections.randomElement()!)
      }
      // Pick a random color for the pattern
      colors.append(colorSelections.randomElement()!)
    }
    // Randomly reorder the directions
    directions.shuffle()
    
    return (directions, colors)
  }
  
  func done() -> Bool {
    return attempt >= maxAttemptsAllowed
  }
  
  func reset() {
    score = 0
    attempt = 0
    answers.removeAll()
    
    generatePlays()
  }
}

// MARK: - Private methods
private extension Game {
  func generatePlays() {
    // Pick the random direction that will be the dominant one
    let allPatternDirections = PatternView.PatternDirection.allCases
    answers = (0..<maxAttemptsAllowed).map{ _ in
      allPatternDirections.randomElement()!
    }
  }
}
4. GameViewController.swift
import UIKit

class GameViewController: UIViewController {
  // MARK: - Outlets
  @IBOutlet weak var scoreLabel: UILabel!
  
  @IBOutlet weak var item1PatternView: PatternView!
  @IBOutlet weak var item2PatternView: PatternView!
  @IBOutlet weak var item3PatternView: PatternView!
  @IBOutlet weak var item4PatternView: PatternView!
  
  @IBOutlet weak var leftButton: UIButton!
  @IBOutlet weak var topButton: UIButton!
  @IBOutlet weak var bottomButton: UIButton!
  @IBOutlet weak var rightButton: UIButton!
  
  @IBOutlet weak var choiceFeedbackLabel: UILabel!
  
  // MARK: - Properties
  let numberOfPatterns = 4
  var game: Game?
  var score: Int? {
    didSet {
      if let score = score, let game = game {
        scoreLabel.text = "\(score) / \(game.maxAttemptsAllowed)"
      }
    }
  }
  
  // MARK: - View Lifecycle
  override func viewDidLoad() {
    super.viewDidLoad()
    game = Game(patternCount: numberOfPatterns)
  }
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    game?.reset()
    score = game?.score
  }
  
  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    showNextPlay()
  }
  
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "resultSegue" {
      if let destinationViewController = segue.destination as? ResultViewController {
        destinationViewController.score = score
      }
    }
  }
  
  // MARK: - Actions
  @IBAction func choiceButtonPressed(_ sender: UIButton) {
    switch sender {
    case leftButton:
      play(.left)
    case topButton:
      play(.top)
    case bottomButton:
      play(.bottom)
    case rightButton:
      play(.right)
    default:
      play(.left)
    }
  }
}

// MARK: - Private methods
private extension GameViewController {
  func showNextPlay() {
    guard let game = game else { return }
    // Check if the game is still in progress
    if !game.done() {
      // Get the next set of directions and colors for the pattern views
      let (directions, colors) = game.setupNextPlay()
      // Update the pattern views
      setupPatternView(item1PatternView, towards: directions[0], havingColor: colors[0])
      setupPatternView(item2PatternView, towards: directions[1], havingColor: colors[1])
      setupPatternView(item3PatternView, towards: directions[2], havingColor: colors[2])
      setupPatternView(item4PatternView, towards: directions[3], havingColor: colors[3])
      // Re-enable the buttons and hide the answer feedback
      controlsEnabled(true)
    }
  }
  
  func controlsEnabled(_ on: Bool) {
    // Enable or disable the buttons
    leftButton.isEnabled = on
    topButton.isEnabled = on
    bottomButton.isEnabled = on
    rightButton.isEnabled = on
    // Show or hide the feedback on the answer
    choiceFeedbackLabel.isHidden = on
  }
  
  // Sets up the pattern view given a diretion and color
  func setupPatternView(
    _ patternView: PatternView,
    towards: PatternView.PatternDirection,
    havingColor color: UIColor
  ) {
    patternView.direction = towards
    patternView.fillColor = color.rgba
    patternView.setNeedsDisplay()
  }
  
  // Displays the results of the choice
  func displayResults(_ correct: Bool) {
    if correct {
      print("You answered correctly!")
      choiceFeedbackLabel.text = "\u{2713}" // checkmark
      choiceFeedbackLabel.textColor = .green
    } else {
      print("That one got you.")
      choiceFeedbackLabel.text = "\u{2718}" // wrong (X)
      choiceFeedbackLabel.textColor = .red
    }
    // Visual indicator of correctness
    UIView.animate(withDuration: 0.5, animations: {
      self.choiceFeedbackLabel.transform = CGAffineTransform(scaleX: 1.8, y: 1.8)
    }, completion: { _ in
      UIView.animate(withDuration: 0.5) {
        self.choiceFeedbackLabel.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
      }
    })
  }
  
  // Processes the play and displays the result
  func play(_ selection: PatternView.PatternDirection) {
    // Temporarily disable the buttons and make the feedback label visible
    controlsEnabled(false)
    // Check if the answer is correct
    if let result = game?.play(selection) {
      // Update the score
      score = result.score
      // Show whether the answer is correct or not
      displayResults(result.correct)
    }
    // Wait a little before showing the next play or transition to the
    // end game view
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(2000)) {
      if (self.game?.done())! {
        self.performSegue(withIdentifier: "resultSegue", sender: nil)
      } else {
        self.showNextPlay()
      }
    }
  }
}

// MARK: - UIColor extension
extension UIColor {
  // Returns an array that splits out the RGB and Alpha values
  var rgba: [CGFloat] {
    var red: CGFloat = 0
    var green: CGFloat = 0
    var blue: CGFloat = 0
    var alpha: CGFloat = 0
    getRed(&red, green: &green, blue: &blue, alpha: &alpha)
    return [red, green, blue, alpha]
  }
}

下面就是實(shí)現(xiàn)效果

后記

本篇主要講述了一個簡單小游戲酷鸦,感興趣的給個贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饰躲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子臼隔,更是在濱河造成了極大的恐慌嘹裂,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摔握,死亡現(xiàn)場離奇詭異寄狼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)氨淌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門泊愧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盛正,你說我怎么就攤上這事删咱。” “怎么了豪筝?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵痰滋,是天一觀的道長。 經(jīng)常有香客問我续崖,道長敲街,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任严望,我火速辦了婚禮聪富,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘著蟹。我一直安慰自己墩蔓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布萧豆。 她就那樣靜靜地躺著奸披,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涮雷。 梳的紋絲不亂的頭發(fā)上阵面,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼样刷。 笑死仑扑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的置鼻。 我是一名探鬼主播镇饮,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼箕母!你這毒婦竟也來了储藐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤嘶是,失蹤者是張志新(化名)和其女友劉穎钙勃,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聂喇,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辖源,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了希太。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片同木。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖跛十,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情秕硝,我是刑警寧澤芥映,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站远豺,受9級特大地震影響奈偏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜躯护,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一惊来、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棺滞,春花似錦裁蚁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至移必,卻和暖如春室谚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工秒赤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留猪瞬,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓入篮,卻偏偏與公主長得像陈瘦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子崎弃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容