GameplayKit框架详细解析(三) —— GameplayKit的实用状态机(二)

版本记录

版本号 时间
V1.0 2019.08.21 星期三

前言

GameplayKit框架,构建和组织你的游戏逻辑。 整合常见的游戏行为,如随机数生成,人工智能,寻路和代理行为。接下来几篇我们就一起看一下这个框架。感兴趣的看下面几篇文章。
1. GameplayKit框架详细解析(一) —— 基本概览(一)
2. GameplayKit框架详细解析(二) —— GameplayKit的实用状态机(一)

开始

1. Swift

首先看下工程组织结构

下面就是看代码了

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的实用状态机,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容