GameplayKit框架詳細解析(三) —— GameplayKit的實用狀態(tài)機(二)

版本記錄

版本號 時間
V1.0 2019.08.21 星期三

前言

GameplayKit框架井赌,構建和組織你的游戲邏輯谤逼。 整合常見的游戲行為,如隨機數(shù)生成仇穗,人工智能流部,尋路和代理行為。接下來幾篇我們就一起看一下這個框架纹坐。感興趣的看下面幾篇文章枝冀。
1. GameplayKit框架詳細解析(一) —— 基本概覽(一)
2. GameplayKit框架詳細解析(二) —— GameplayKit的實用狀態(tài)機(一)

開始

1. Swift

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

下面就是看代碼了

1. Coordinator.swift
protocol Coordinator {
  func start()
}
2. ApplicationCoordinator.swift
import UIKit

class ApplicationCoordinator: Coordinator {
  let kanjiStorage: KanjiStorage
  let window: UIWindow
  let rootViewController: UINavigationController
  let stateMachine: KanjiStateMachine

  init(window: UIWindow) {
    self.window = window
    kanjiStorage = KanjiStorage()
    rootViewController = UINavigationController()
    rootViewController.navigationBar.prefersLargeTitles = true
    
    stateMachine = KanjiStateMachine(presenter: rootViewController,
                                     kanjiStorage: kanjiStorage,
                                     states: [AllState(), DetailState(), ListState()])
  }
  
  func start() {
    stateMachine.enter(AllState.self)
    window.rootViewController = rootViewController
    window.makeKeyAndVisible()
    subscribeToNotifications()
  }
  
  func subscribeToNotifications() {
    NotificationCenter.default.addObserver(
      self, selector: #selector(receivedAllKanjiNotification),
      name: Notifications.AllKanji, object: nil)
    
    NotificationCenter.default.addObserver(
      self, selector: #selector(receivedKanjiDetailNotification),
      name: Notifications.KanjiDetail, object: nil)
    
    NotificationCenter.default.addObserver(
      self, selector: #selector(receivedKanjiListNotification),
      name: Notifications.KanjiList, object: nil)
  }
  
  @objc func receivedAllKanjiNotification() {
    stateMachine.enter(AllState.self)

  }
  
  @objc func receivedKanjiDetailNotification(notification: NSNotification) {
    // 1
    guard
      let kanji = notification.object as? Kanji,
      // 2
      let detailState = stateMachine.state(forClass: DetailState.self)
      else {
        return
    }
    
    // 3
    detailState.kanji = kanji
    
    // 4
    stateMachine.enter(DetailState.self)
  }
  
  @objc func receivedKanjiListNotification(notification: NSNotification) {
    // 1
    guard
      let word = notification.object as? String,
      let listState = stateMachine.state(forClass: ListState.self)
      else {
        return
    }
    
    // 2
    listState.word = word
    
    // 3
    stateMachine.enter(ListState.self)
  }
}
3. KanjiListCoordinator.swift
import UIKit

class KanjiListCoordinator: Coordinator {
  private let presenter: UINavigationController
  private let kanjiList: [Kanji]
  private let title: String
  private var kanjiDetailCoordinator: KanjiDetailCoordinator?
  private let kanjiStorage: KanjiStorage
  
  init(presenter: UINavigationController,
       kanjiStorage: KanjiStorage,
       list: [Kanji],
       title: String) {
    self.presenter = presenter
    self.kanjiStorage = kanjiStorage
    self.kanjiList = list
    self.title = title
  }
  
  func start() {
    let listViewController = KanjiListViewController(kanjiList: kanjiList, title: title)
    listViewController.delegate = self
    presenter.pushViewController(listViewController, animated: true)
  }
}

// MARK: - KanjiListViewControllerDelegate
extension KanjiListCoordinator: KanjiListViewControllerDelegate {
  func kanjiListViewController(_ kanjiListViewController: KanjiListViewController,
                               didSelectKanji selectedKanji: Kanji) {
    NotificationCenter.default.post(name: Notifications.KanjiDetail, object: selectedKanji)
  }
}
4. KanjiDetailCoordinator.swift
import UIKit

class KanjiDetailCoordinator: Coordinator {
  private let presenter: UINavigationController
  private var kanjiListCoordinator: KanjiListCoordinator?
  private let kanjiStorage: KanjiStorage
  private let kanji: Kanji
  
  init(presenter: UINavigationController, kanji: Kanji, kanjiStorage: KanjiStorage) {
    self.kanji = kanji
    self.presenter = presenter
    self.kanjiStorage = kanjiStorage
  }
  
  func start() {
    let kanjiDetailViewController = KanjiDetailViewController(kanji: kanji)
    kanjiDetailViewController.delegate = self
    
    presenter.pushViewController(kanjiDetailViewController, animated: true)
  }
}

// MARK: - KanjiDetailViewControllerDelegate
extension KanjiDetailCoordinator: KanjiDetailViewControllerDelegate {
  func kanjiDetailViewController(_ kanjiDetailViewController: KanjiDetailViewController,
                                 didSelectWord word: String) {
    NotificationCenter.default.post(name: Notifications.KanjiList, object: word)
  }
}
5. AppDelegate.swift
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?
  private var applicationCoordinator: ApplicationCoordinator?
  
  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    let window = UIWindow(frame: UIScreen.main.bounds)
    let applicationCoordinator = ApplicationCoordinator(window: window)
    
    self.window = window
    self.applicationCoordinator = applicationCoordinator
    
    applicationCoordinator.start()
    return true
  }
}
6. Notifications.swift
import Foundation

enum Notifications {
  static let AllKanji = NSNotification.Name(rawValue: "AllKanjiNotification")
  static let KanjiDetail = NSNotification.Name(rawValue: "KanjiDetailNotification")
  static let KanjiList = NSNotification.Name(rawValue: "KanjiListNotification")
}
7. KanjiStorage.swift
import Foundation

struct KanjiCache {
  let kanjiArray: [Kanji]
  let kanjiDictionary: [String: Kanji]
}

// Provides kanji data from JSON
class KanjiStorage {
  static let kanjiURL = Bundle.main.url(forResource: "kanji", withExtension: "json")
  private let allKanjiFromJSON: KanjiCache
  
  init() {
    // Parse json and store its data
    guard
      let kanjiURL = KanjiStorage.kanjiURL,
      let data = try? Data(contentsOf: kanjiURL),
      let allKanjis = try? JSONDecoder().decode([Kanji].self, from: data)
      else {
        preconditionFailure("Could not read kanji from kanji.json")
    }
    
    let kanjiDictionary = allKanjis.reduce([:]) { (dictionary, kanji) -> [String: Kanji] in
      var dictionary = dictionary
      dictionary[kanji.character] = kanji
      return dictionary
    }
    
    // Save new cache
    allKanjiFromJSON = KanjiCache(kanjiArray: allKanjis,
                                  kanjiDictionary: kanjiDictionary)
  }
  
  func allKanji() -> [Kanji] {
    return allKanjiFromJSON.kanjiArray
  }
  
  func kanjiForWord(_ word: String) -> [Kanji] {
    let kanjiInWord: [Kanji] = word.compactMap { (character) -> Kanji? in
      let kanjiForCharacter = allKanjiFromJSON.kanjiDictionary["\(character)"]
      return kanjiForCharacter
    }
    return kanjiInWord
  }
}
8. Kanji.swift
import Foundation

struct WordExample: Codable {
  let word: String
  let meaning: String
}

struct Kanji: Codable {
  let character: String
  let meaning: String
  let examples: [WordExample]
}
9. KanjiViewController.swift
import UIKit

class KanjiViewController: UIViewController {
  override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    let allButton = UIBarButtonItem(title: "All",
                                    style: .plain,
                                    target: self,
                                    action: #selector(allTapped))
    navigationItem.rightBarButtonItem = allButton
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  @objc private func allTapped() {
    NotificationCenter.default.post(name: Notifications.AllKanji, object: nil)
  }
}
10. KanjiDetailViewController.swift
import Foundation
import UIKit

protocol KanjiDetailViewControllerDelegate: class {
  func kanjiDetailViewController(_ kanjiDetailViewController: KanjiDetailViewController,
                                 didSelectWord word: String)
}

class KanjiDetailViewController: KanjiViewController {
  let selectedKanji: Kanji
  var delegate: KanjiDetailViewControllerDelegate?
  
  @IBOutlet weak var detailTableView: UITableView? {
    didSet {
      guard let detailTableView = detailTableView else { return }
      detailTableView.dataSource = self
      detailTableView.delegate = self
      
      // Word cell
      let wordCellNib = UINib(nibName: "WordExampleTableViewCell", bundle: nil)
      detailTableView.register(wordCellNib, forCellReuseIdentifier: "WordExampleTableViewCell")
      
      // Detail cell
      let detailCellNib = UINib(nibName: "KanjiDataTableViewCell", bundle: nil)
      detailTableView.register(detailCellNib, forCellReuseIdentifier: "KanjiDataTableViewCell")
    }
  }
  
  init(kanji: Kanji) {
    selectedKanji = kanji
    super.init(nibName: nil, bundle: nil)
    title = "Kanji details"
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

// MARK: - UITableViewDataSource
extension KanjiDetailViewController: UITableViewDataSource {
  func numberOfSections(in tableView: UITableView) -> Int {
    return 2
  }
  
  func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    switch section {
    case 1: return "Words"
    default: return nil
    }
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
    case 0: return 1
    case 1: return selectedKanji.examples.count
    default: return 0
    }
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    switch indexPath.section {
    case 0:
      let cell = tableView.dequeueReusableCell(withIdentifier: "KanjiDataTableViewCell", for: indexPath)
      (cell as? KanjiDataTableViewCell)?.setupCell(data: selectedKanji)
      return cell
    case 1:
      let cell = tableView.dequeueReusableCell(withIdentifier: "WordExampleTableViewCell", for: indexPath)
      let word = selectedKanji.examples[indexPath.row]
      (cell as? WordExampleTableViewCell)?.setupCell(data: word)
      return cell
    default:
      fatalError()
    }
  }
}

// MARK: - UITableViewDelegate
extension KanjiDetailViewController: UITableViewDelegate {
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    defer {
      tableView.deselectRow(at: indexPath, animated: true)
    }
    
    guard indexPath.section == 1 else {
      return
    }
    let word = selectedKanji.examples[indexPath.row].word
    delegate?.kanjiDetailViewController(self, didSelectWord: word)
  }
}
11. KanjiListViewController.swift
import UIKit

protocol KanjiListViewControllerDelegate: class {
  func kanjiListViewController(_ kanjiListViewController: KanjiListViewController,
                               didSelectKanji selectedKanji: Kanji)
}

class KanjiListViewController: KanjiViewController {
  let kanjiList: [Kanji]
  var delegate: KanjiListViewControllerDelegate?
  
  @IBOutlet weak var kanjiListTableView: UITableView! {
    didSet {
      kanjiListTableView?.dataSource = self
      kanjiListTableView?.delegate = self
    }
  }
  
  init(kanjiList: [Kanji], title: String) {
    self.kanjiList = kanjiList
    super.init(nibName: nil, bundle: nil)
    self.title = title
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
}

extension KanjiListViewController: UITableViewDataSource, UITableViewDelegate {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return kanjiList.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell
    if let dequeuedCell = tableView.dequeueReusableCell(withIdentifier: "ListItem") {
      cell = dequeuedCell
    } else {
      cell = UITableViewCell(style: .subtitle, reuseIdentifier: "ListItem")
    }
    let kanji = kanjiList[indexPath.row]
    cell.textLabel?.text = kanji.character
    cell.detailTextLabel?.text = kanji.meaning
    cell.accessoryType = .disclosureIndicator
    return cell
  }
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let kanji = kanjiList[indexPath.row]
    delegate?.kanjiListViewController(self, didSelectKanji: kanji)
    tableView.deselectRow(at: indexPath, animated: true)
  }
}
12. KanjiStateMachine.swift
import UIKit
import GameplayKit.GKStateMachine

class KanjiStateMachine: GKStateMachine {
  let presenter: UINavigationController
  let kanjiStorage: KanjiStorage
  
  init(presenter: UINavigationController,
       kanjiStorage: KanjiStorage,
       states: [GKState]) {
    // 1
    self.presenter = presenter
    
    // 2
    self.kanjiStorage = kanjiStorage
    
    // 3
    super.init(states: states)
  }
}
13. AllState.swift
import GameplayKit.GKState

class AllState: GKState {
  // 1
  lazy var allKanjiListCoordinator = makeAllKanjiCoordinator()
  
  // 2
  override func didEnter(from previousState: GKState?) {
    if previousState == nil {
      allKanjiListCoordinator?.start()
    } else {
      (stateMachine as? KanjiStateMachine)?.presenter.popToRootViewController(animated: true)
    }
  }
  
  override func isValidNextState(_ stateClass: AnyClass) -> Bool {
    return stateClass == DetailState.self
  }
  
  private func makeAllKanjiCoordinator() -> KanjiListCoordinator? {
    // 3
    guard let kanjiStateMachine = stateMachine as? KanjiStateMachine else {
      return nil
    }
    
    let kanjiStorage = kanjiStateMachine.kanjiStorage
    
    // 4
    return KanjiListCoordinator(presenter: kanjiStateMachine.presenter,
                                kanjiStorage: kanjiStorage,
                                list: kanjiStorage.allKanji(),
                                title: "Kanji List")
  }
}
14. DetailState.swift
import GameplayKit.GKState

class DetailState: GKState {
  // 1
  var kanji: Kanji?
  var kanjiDetailCoordinator: KanjiDetailCoordinator?
  
  override func didEnter(from previousState: GKState?) {
    guard
      let kanji = kanji,
      let kanjiStateMachine = (stateMachine as? KanjiStateMachine)
      else {
        return
    }
    
    // 2
    let kanjiDetailCoordinator = KanjiDetailCoordinator(
      presenter: kanjiStateMachine.presenter,
      kanji: kanji,
      kanjiStorage: kanjiStateMachine.kanjiStorage)
    
    self.kanjiDetailCoordinator = kanjiDetailCoordinator
    kanjiDetailCoordinator.start()
  }

  override func isValidNextState(_ stateClass: AnyClass) -> Bool {
    return stateClass == AllState.self || stateClass == ListState.self
  }
}
15. ListState.swift
import GameplayKit.GKState

class ListState: GKState {
  // 1
  var word: String?
  var kanjiListCoordinator: KanjiListCoordinator?
  
  override func didEnter(from previousState: GKState?) {
    guard
      let word = word,
      let kanjiStateMachine = (stateMachine as? KanjiStateMachine)
      else {
        return
    }
    
    let kanjiStorage = kanjiStateMachine.kanjiStorage
    
    // 2
    let kanjiForWord = kanjiStorage.kanjiForWord(word)
    
    // 3
    let kanjiListCoordinator = KanjiListCoordinator(
      presenter: kanjiStateMachine.presenter, kanjiStorage: kanjiStorage,
      list: kanjiForWord, title: word)
    
    self.kanjiListCoordinator = kanjiListCoordinator
    kanjiListCoordinator.start()
  }
  
  override func isValidNextState(_ stateClass: AnyClass) -> Bool {
    return stateClass == DetailState.self || stateClass == AllState.self
  }
}

后記

本篇主要講述了GameplayKit的實用狀態(tài)機,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耘子,一起剝皮案震驚了整個濱河市果漾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谷誓,老刑警劉巖绒障,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捍歪,居然都是意外死亡户辱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門糙臼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庐镐,“玉大人,你說我怎么就攤上這事弓摘》偃担” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵韧献,是天一觀的道長末患。 經(jīng)常有香客問我,道長锤窑,這世上最難降的妖魔是什么璧针? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮渊啰,結(jié)果婚禮上探橱,老公的妹妹穿的比我還像新娘申屹。我一直安慰自己,他們只是感情好隧膏,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布哗讥。 她就那樣靜靜地躺著,像睡著了一般胞枕。 火紅的嫁衣襯著肌膚如雪杆煞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天腐泻,我揣著相機與錄音决乎,去河邊找鬼。 笑死派桩,一個胖子當著我的面吹牛构诚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播铆惑,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼范嘱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了员魏?” 一聲冷哼從身側(cè)響起彤侍,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逆趋,沒想到半個月后盏阶,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡闻书,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年名斟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片魄眉。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡砰盐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坑律,到底是詐尸還是另有隱情岩梳,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布晃择,位于F島的核電站冀值,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宫屠。R本人自食惡果不足惜列疗,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望浪蹂。 院中可真熱鬧抵栈,春花似錦告材、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至产艾,卻和暖如春灿渴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胰舆。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蹬挤,地道東北人缚窿。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像焰扳,于是被迫代替她去往敵國和親倦零。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

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