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

版本記錄

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

前言

GameplayKit框架,構建和組織你的游戲邏輯痴晦。 整合常見的游戲行為,如隨機數(shù)生成琳彩,人工智能誊酌,尋路和代理行為。接下來幾篇我們就一起看一下這個框架露乏。感興趣的看下面幾篇文章碧浊。
1. GameplayKit框架詳細解析(一) —— 基本概覽(一)

開始

首先看下主要內(nèi)容

在本教程中,您將使用GameplayKitGKStateMachine將iOS應用程序轉(zhuǎn)換為使用狀態(tài)機進行導航邏輯瘟仿。

下面看下寫作環(huán)境

Swift 5, iOS 13, Xcode 11

管理狀態(tài)很難辉词,但是您可以使用許多技術來管理常規(guī)iOS應用中的狀態(tài)。 GameplayKit框架隱藏了一種有用的技術:GKStateMachine猾骡。

狀態(tài)機通常用于游戲編程瑞躺。但是,它們的實用性并不止于此兴想。程序員已經(jīng)解決了狀態(tài)機的問題幢哨。 GKStateMachine存在一個游戲開發(fā)框架內(nèi),但不要讓這一點阻止你嫂便。沒有理由你不能在任何其他有狀態(tài)管理的iOS應用程序中使用它捞镰。

狀態(tài)機的一些用途可能是理解復雜的業(yè)務邏輯加袋,控制視圖控制器的狀態(tài)或管理導航惊楼。在本教程中,您將重構應用程序以使用狀態(tài)機來控制導航翰灾。

當您完成本教程時厂画,您將更深入地了解狀態(tài)機并學習如何:

  • 使用狀態(tài)機簡化應用程序中的邏輯凸丸。
  • 使用GKStateMachine配置狀態(tài)機。
  • 通過定義有效轉(zhuǎn)換來控制狀態(tài)機流袱院。

首先屎慢,打開示例項目。Kanji List是一個學習日語Kanji字符的應用程序忽洛,這對你下次去東京旅行肯定是不可或缺的腻惠。

該應用程序顯示了Kanji列表。單擊Kanji將顯示該Kanji的含義以及使用Kanji的單詞列表欲虚。單擊任何一個單詞將帶您進入單詞中的Kanji列表集灌,您可以在其中重復該過程到您的內(nèi)容。

在本教程中复哆,您將重構Kanji List的導航邏輯以使用狀態(tài)機欣喧。 目前,Kanji List使用協(xié)調(diào)器模式(coordinator pattern)進行導航寂恬。 這很棒续誉;這意味著導航代碼已經(jīng)從視圖控制器中提取出來。 您只需要添加狀態(tài)機來組織coordinators初肉。

如果您不熟悉協(xié)調(diào)器模式酷鸦,請不要擔心。 這是一個簡單的模式牙咏,用于處理視圖控制器之間的應用程序流臼隔。


Understanding State Machines

狀態(tài)機是用于表示一次只能處于一個狀態(tài)的系統(tǒng)的數(shù)學抽象。 聽起來很復雜吧妄壶? 它實際上非常簡單摔握,也是一種非常有用的方法來查看一些問題。 例如丁寄,想想iPhone中的電池氨淌。 您可以將電池看作狀態(tài)機:

這個狀態(tài)機有四種狀態(tài):

  • 1)Charging:手機已插入并處于充電狀態(tài)泊愧。
  • 2)Fully Charged:電池已滿,手機正在通過充電器供電盛正。
  • 3)Discharging:手機已拔下删咱,依靠電池供電。
  • 4)Flat:電池電量不足豪筝。

將手機插入充電器會使其進入Charging狀態(tài)痰滋。 從那里,它可以在拔出時轉(zhuǎn)換到Discharging狀態(tài)续崖。 如果您將電話插入電池直到電池達到100%敲街,它將進入Fully Charged狀態(tài)。 拔下電話時严望,如果讓電池完全放電多艇,它將進入Flat狀態(tài)。

狀態(tài)機表示哪些狀態(tài)轉(zhuǎn)換有效著蟹。 電池在充電時不會flat(除非您需要新電池6章),因此狀態(tài)機中沒有從ChargingFlat的轉(zhuǎn)換萧豆。


Different Between States

首先奸披,您需要定義不同的狀態(tài),以將Kanji列表中的導航表示為狀態(tài)機涮雷。 在Xcode中打開入門項目后阵面,構建并運行應用程序。

該應用程序以所有支持的Kanji列表開始洪鸭。 這將是All狀態(tài)样刷。 接下來,點擊Kanji以顯示詳細信息屏幕览爵。 這將是Detail狀態(tài)置鼻。 最后,點擊使用Kanji的一個單詞轉(zhuǎn)到Kanji中的單詞列表蜓竹,您將其稱為List狀態(tài)箕母。

很好!由于Kanji List是一個簡單的應用程序俱济,這三個狀態(tài)足以代表應用程序的功能嘶是。隨著應用程序功能的增長,狀態(tài)機將成為控制事物的有用工具蛛碌。

1. Transitioning Between States

在應用程序中定義不同的狀態(tài)有助于您更好地理解應用程序的工作方式聂喇。但是,如果沒有定義狀態(tài)之間的有效轉(zhuǎn)換蔚携,狀態(tài)機就不完整了希太。

每個屏幕都包含導航欄中的All按鈕克饶,以轉(zhuǎn)換到All屏幕。這意味著從Detail信息屏幕或List屏幕到All屏幕的轉(zhuǎn)換有效誊辉。

點擊一個單詞可將應用程序帶到List屏幕彤路,該屏幕顯示單詞中的所有kanji。因此芥映,您只能通過Detail屏幕點擊單詞轉(zhuǎn)換到List屏幕。

此外远豺,點擊kanji導航到Detail屏幕奈偏。這意味著從All屏幕或List屏幕到Detail屏幕的轉(zhuǎn)換有效。

將所有這些結合起來呈現(xiàn)狀態(tài)機的這個圖:


Creating the State Machine

現(xiàn)在躯护,關于狀態(tài)已經(jīng)討論很多了惊来。 是時候?qū)懸恍┐a了! 首先棺滞,在State Machine組下創(chuàng)建一個名為KanjiStateMachine.swift的文件裁蚁。 用以下內(nèi)容替換import語句:

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)
  }
}

雖然不需要繼承GKStateMachine,但是這個子類允許您存儲狀態(tài)稍后需要的一些重要數(shù)據(jù)继准。 初始化程序很簡單枉证,下面是正在發(fā)生的事情:

  • 1) 應用程序的協(xié)調(diào)器模式使用UINavigationController作為視圖控制器的演示者(presenter)。 狀態(tài)機將擁有演示者移必。
  • 2) KanjiStorage類本質(zhì)上是應用程序的字典室谚。 它存儲所有kanji和包含它們的單詞。 KanjiStateMachine管理要使用的每個狀態(tài)的KanjiStorage對象崔泵。
  • 3) GKStateMachine的初始化程序需要您正在使用的每個狀態(tài)的實例秒赤,因此將其傳遞給GKStateMachine.init(states:)

接下來憎瘸,打開ApplicationCoordinator.swift入篮。 這是應用程序的根協(xié)調(diào)器,它創(chuàng)建根視圖控制器并將其添加到應用程序的UIWindow幌甘。 在類的頂部為狀態(tài)機添加新屬性:

let stateMachine: KanjiStateMachine

init(window :)結束時潮售,添加以下內(nèi)容以創(chuàng)建狀態(tài)機:

stateMachine = KanjiStateMachine(
  presenter: rootViewController,
  kanjiStorage: kanjiStorage,
  states: [])

因為您還沒有創(chuàng)建任何GKState類,所以您只需暫時為狀態(tài)傳遞一個空數(shù)組含潘。


Creating Each State

現(xiàn)在您已經(jīng)創(chuàng)建了狀態(tài)機饲做,現(xiàn)在是時候添加一些狀態(tài)了。 現(xiàn)在遏弱,應用程序中的每個協(xié)調(diào)員都會根據(jù)需要創(chuàng)建其他協(xié)調(diào)器以導航到不同的屏幕盆均。 因此,因為您想要移動決定導航到哪個屏幕漱逸,所以您需要從協(xié)調(diào)器中刪除該邏輯泪姨。

ApplicationCoordinator開始游沿。 此類將保留狀態(tài)機,但所有其他協(xié)調(diào)器將由狀態(tài)機中的某個狀態(tài)創(chuàng)建肮砾。 因此诀黍,刪除ApplicationCoordinator上的allKanjiListCoordinator屬性。 稍后您將在AllState類中重新創(chuàng)建它仗处。 刪除創(chuàng)建協(xié)調(diào)器的init(window :)中的這一行:

allKanjiListCoordinator = KanjiListCoordinator(
  presenter: rootViewController, 
  kanjiStorage: kanjiStorage,
  list: kanjiStorage.allKanji(), 
  title: "Kanji List")

還有啟動協(xié)調(diào)器的start()內(nèi)部的這個方法:

allKanjiListCoordinator.start()

構建并運行應用程序眯勾。 好像它失去了一些功能:

當你開始創(chuàng)建狀態(tài)時,你將獲得它婆誓。

1. All State

State Machine組下添加名為AllState.swift的新文件吃环。 用以下內(nèi)容替換其import語句:

import GameplayKit.GKState

class AllState: GKState {
  // 1
  lazy var allKanjiListCoordinator = makeAllKanjiCoordinator()

  // 2
  override func didEnter(from previousState: GKState?) {
    allKanjiListCoordinator?.start()
  }

  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")
  }
}

下面進行細分:

  • 1) 在這里,您重新創(chuàng)建從ApplicationCoordinator.swift中刪除的協(xié)調(diào)器洋幻。
  • 2) 只要狀態(tài)機進入新狀態(tài)郁轻,didEnter(from :)就會觸發(fā)。 它是觸發(fā)allKanjiListCoordinator導航到All屏幕的理想場所文留。
  • 3) 您可以使用GKState上的stateMachine屬性來獲取其狀態(tài)機好唯。 在這里,您將其強制轉(zhuǎn)換為KanjiStateMachine以訪問您之前添加的屬性燥翅。
  • 4) 要構建KanjiListCoordinator骑篙,請為其提供顯示All屏幕所需的所有數(shù)據(jù)。

接下來权旷,打開ApplicationCoordinator.swift并找到在init(window :)中創(chuàng)建狀態(tài)機的行替蛉。 創(chuàng)建一個AllState實例并將其傳遞給states數(shù)組,如下所示:

stateMachine = KanjiStateMachine(
  presenter: rootViewController,
  kanjiStorage: kanjiStorage,
  states: [AllState()])

start()中拄氯,將以下行添加到方法的開頭:

stateMachine.enter(AllState.self)

這會導致狀態(tài)機進入AllState并觸發(fā)allKanjiListCoordinator導航到All屏幕躲查。 構建并運行應用程序。 一切都在順利進行译柏!

2. Detail State

打開KanjiListCoordinator.swift并在底部的擴展中找到kanjiListViewController(_:didSelectKanji :)镣煮。 此方法創(chuàng)建并啟動KanjiDetailCoordinator,使應用程序?qū)Ш降?code>Detail屏幕鄙麦。 刪除方法的內(nèi)容典唇,將其留空。

構建并運行應用程序胯府。 它應該仍然顯示All屏幕介衔。 但是,因為您從kanjiListViewController(_:didSelectKanji :)中刪除了導航邏輯骂因,所以KanjiListCoordinator不會創(chuàng)建下一個協(xié)調(diào)器來移動到不同的屏幕炎咖。 點擊一個kanji什么也沒做。

要解決此問題,您需要將剛剛刪除的代碼添加到新的狀態(tài)對象中乘盼。 在State Machine組下添加名為DetailState.swift的新文件升熊。 用以下內(nèi)容替換其import語句:

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()
  }
}

下面進行細分

  • 1) KanjiDetailCoordinator需要一個Kanji來顯示Detail屏幕。 你需要在這里設置它绸栅。
  • 2) 創(chuàng)建并啟動KanjiDetailCoordinator级野,類似于之前在kanjiListViewController(_:didSelectKanji :)中的操作。

3. Communicating to the State Machine

您需要一種與進入DetailState所需的狀態(tài)機進行通信的方法粹胯。 因此蓖柔,您將使用NotificationCenter提交通知,然后在ApplicationCoordinator中監(jiān)聽它风纠。 回到KanjiListCoordinator.swift渊抽,將此行添加到kanjiListViewController(_:didSelectKanji :)

NotificationCenter.default
  .post(name: Notifications.KanjiDetail, object: selectedKanji)

Notifications.KanjiDetail只是提前為您創(chuàng)建的NSNotification.Name對象。 這會發(fā)布通知议忽,傳遞顯示Detail屏幕所需的selectedKanji

再次打開ApplicationCoordinator.swift十减。 轉(zhuǎn)到在init(window :)中創(chuàng)建狀態(tài)機的行栈幸。 創(chuàng)建一個DetailState實例并將其傳遞給states數(shù)組,就像之前為AllState所做的那樣:

stateMachine = KanjiStateMachine(
  presenter: rootViewController,
  kanjiStorage: kanjiStorage,
  states: [AllState(), DetailState()])

下面帮辟,添加這一行

@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)
}

下面進行細分:

  • 1) 獲取隨通知notification一起傳遞的Kanji對象
  • 2) GKStateMachine.state(forClass :)返回傳遞給狀態(tài)機初始值設定項的狀態(tài)實例速址。 在這里獲取該實例。
  • 3) 存儲在創(chuàng)建其KanjiDetailCoordinator時要使用的DetailStatekanji由驹。
  • 4) 最后芍锚,輸入DetailState,它將創(chuàng)建并啟動KanjiDetailCoordinator蔓榄。

您仍然需要訂閱KanjiDetail通知并炮,因此將其添加到subscribeToNotifications()

NotificationCenter.default.addObserver(
  self, selector: #selector(receivedKanjiDetailNotification),
  name: Notifications.KanjiDetail, object: nil)

構建并運行應用程序。 您應該可以點擊一個Kanji并再次到達詳細信息Detail屏幕甥郑。

4. List State

實現(xiàn)ListState的過程與您之前看到的類似逃魄。 您將從協(xié)調(diào)器中刪除導航邏輯,將其移動到新的GKState類并與stateMachine通信它應該進入新狀態(tài)澜搅。

首先伍俘,打開KanjiDetailCoordinator.swift。 當用戶點擊詳細信息屏幕上的單詞時勉躺,會觸發(fā)kanjiDetailViewController(_:didSelectWord :)癌瘾。 然后,它創(chuàng)建并啟動一個KanjiListCoordinator饵溅,以顯示該單詞中所有漢字的列表屏幕妨退。

刪除kanjiDetailViewController(_:didSelectWord :)的內(nèi)容并將其替換為:

NotificationCenter.default.post(name: Notifications.KanjiList, object: word)

回到ApplicationCoordinator.swift,創(chuàng)建一個新的空方法來接收通知:

@objc func receivedKanjiListNotification(notification: NSNotification) {
}

然后,添加以下代碼以訂閱subscribeToNotifications()中的通知碧注。

NotificationCenter.default.addObserver(
  self, selector: #selector(receivedKanjiListNotification),
  name: Notifications.KanjiList, object: nil)

State Machine組下嚣伐,創(chuàng)建一個名為ListState.swift的新文件。 用以下內(nèi)容替換其import語句:

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()
  }
}

它與您用于DetailState的模式相同萍丐,但這是正在發(fā)生的事情:

  • 1) 列表屏幕顯示單詞中的所有kanji轩端。 所以,在這里存儲這個詞逝变,以便從中獲取kanji基茵。
  • 2) 使用KanjiStorage對象從單詞中獲取kanji列表。
  • 3) 將所有必要的數(shù)據(jù)傳遞到KanjiListCoordinator的初始化程序中壳影,并調(diào)用start()導航到List屏幕拱层。

現(xiàn)在您已經(jīng)擁有了ListState,您可以將其傳遞到狀態(tài)機并在需要時進入狀態(tài)宴咧。 回到ApplicationCoordinator.swift根灯,在init(window :)中將ListState的實例傳遞給KanjiStateMachine的初始化器:

stateMachine = KanjiStateMachine(
  presenter: rootViewController,
  kanjiStorage: kanjiStorage,
  states: [AllState(), DetailState(), ListState()])

將以下內(nèi)容添加到receivedKanjiListNotification(notification :)以配置并輸入ListState

// 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)

這是細分:

  • 1) 從通知和狀態(tài)機中的ListState實例獲取單詞。
  • 2) 在ListState上設置狀態(tài)以配置KanjiListCoordinator掺栅。
  • 3) 輸入ListState烙肺,使KanjiListCoordinator開始導航到列表屏幕。

構建并運行應用程序氧卧。 一切都應該順利進行桃笙,全部由GKStateMachine管理。


Using Other Abilities of GKStateMachine

還記得狀態(tài)機狀態(tài)之間的不同轉(zhuǎn)換嗎沙绝?

好吧搏明,您可以將這些轉(zhuǎn)換添加到GKState類,以防止任何無效轉(zhuǎn)換發(fā)生闪檬。 打開AllState.swift并添加以下方法:

override func isValidNextState(_ stateClass: AnyClass) -> Bool {
  return false
}

isValidNextState(_ :)允許您定義此GKState可以達到的狀態(tài)星著。 因為它返回false,狀態(tài)機將無法從此狀態(tài)轉(zhuǎn)換到任何其他狀態(tài)粗悯。 構建并運行應用程序强饮。 點擊kanji什么也不做:

因為只有將AllState移動到特定kanji的詳細信息屏幕才有意義,唯一有效的下一個狀態(tài)是DetailState为黎。 用以下內(nèi)容替換isValidNextState(_ :)的內(nèi)容:

return stateClass == DetailState.self

構建并運行應用程序邮丰,您應該能夠再次訪問詳細信息屏幕。 接下來铭乾,將其添加到DetailState.swift

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

DetailState可以移動到其余狀態(tài)中的任何一個剪廉,因此對于任一狀態(tài)都返回true

DetailState類似炕檩,將以下內(nèi)容添加到ListState.swift

override func isValidNextState(_ stateClass: AnyClass) -> Bool {
  return stateClass == DetailState.self || stateClass == AllState.self
}

構建并運行應用程序斗蒋。 一切都應該仍然有用捌斧。

現(xiàn)在,對AllState進行最后一次更改泉沾。 打開ApplicationCoordinator.swift并查看receivedAllKanjiNotification()捞蚂。

當點擊導航欄中的All按鈕時,它會觸發(fā)通知跷究,ApplicationCoordinator會彈出到根視圖控制器姓迅。 協(xié)調(diào)器不應該具有關于導航層次結構的這種知識。 它應該知道的是該應用程序是為了進入AllState俊马。 因此丁存,刪除receivedAllKanjiNotification()的內(nèi)容并將其替換為:

stateMachine.enter(AllState.self)

現(xiàn)在,不是直接彈出到根視圖控制器柴我,receiveAllKanjiNotification()將只轉(zhuǎn)換到AllState解寝。 構建并運行應用程序。 點擊All按鈕時艘儒,它會將新的視圖控制器推入堆棧聋伦。 您仍然希望它pop到根視圖控制器,而不是push到新的視圖控制器界睁。 打開AllState.swift并用以下內(nèi)容替換didEnter(from :)的內(nèi)容:

if previousState == nil {
  allKanjiListCoordinator?.start()
} else {
  (stateMachine as? KanjiStateMachine)?.presenter
    .popToRootViewController(animated: true)
}

當您調(diào)用GKStateMachine.enter(_ :)時嘉抓,先前的狀態(tài)將傳遞到didEnter(from :)到當前狀態(tài)。 如果這是狀態(tài)機的第一個狀態(tài)晕窑,則沒有先前的狀態(tài),因此previousState將為nil卵佛。 在這種情況下杨赤,您可以在allKanjiListCoordinator上調(diào)用start()。 但是如果存在先前的狀態(tài)截汪,則意味著您應該pop到根視圖控制器以返回到All屏幕疾牲。

構建并運行應用程序。 在List screenDetail screen上衙解,All按鈕應該會返回到All屏幕阳柔。

都完成了,您重構了Kanji列表以使用GKStateMachine來管理應用中的導航蚓峦。 做得好舌剂!

Apple關于GKStateGKStateMachine的文檔非常寶貴。 您可能也有興趣了解有關state machines的更多信息暑椰。

后記

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

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市一汽,隨后出現(xiàn)的幾起案子避消,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岩喷,死亡現(xiàn)場離奇詭異恕沫,居然都是意外死亡,警方通過查閱死者的電腦和手機纱意,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門婶溯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妇穴,你說我怎么就攤上這事爬虱。” “怎么了腾它?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵跑筝,是天一觀的道長。 經(jīng)常有香客問我瞒滴,道長曲梗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任妓忍,我火速辦了婚禮虏两,結果婚禮上,老公的妹妹穿的比我還像新娘世剖。我一直安慰自己定罢,他們只是感情好,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布旁瘫。 她就那樣靜靜地躺著祖凫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酬凳。 梳的紋絲不亂的頭發(fā)上惠况,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機與錄音宁仔,去河邊找鬼稠屠。 笑死,一個胖子當著我的面吹牛翎苫,可吹牛的內(nèi)容都是我干的权埠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼煎谍,長吁一口氣:“原來是場噩夢啊……” “哼弊知!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起粱快,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤秩彤,失蹤者是張志新(化名)和其女友劉穎叔扼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體漫雷,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瓜富,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了降盹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片与柑。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蓄坏,靈堂內(nèi)的尸體忽然破棺而出价捧,到底是詐尸還是另有隱情,我是刑警寧澤涡戳,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布结蟋,位于F島的核電站,受9級特大地震影響渔彰,放射性物質(zhì)發(fā)生泄漏嵌屎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一恍涂、第九天 我趴在偏房一處隱蔽的房頂上張望宝惰。 院中可真熱鬧,春花似錦再沧、人聲如沸尼夺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淤堵。三九已至,卻和暖如春什燕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竞端。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工屎即, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人事富。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓技俐,卻偏偏與公主長得像,于是被迫代替她去往敵國和親统台。 傳聞我的和親對象是個殘疾皇子雕擂,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348

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