如何使用Entitas開發(fā)游戲(FNGGames)


ECS架構(gòu)在Unity3D上使用的思路,很有參考價(jià)值奸腺。

原文鏈接:How I build games with Entitas (FNGGames)

這個(gè)帖子用來說明如何使用Entitas來構(gòu)建我的游戲工程。我會(huì)假定你對(duì)Entitas如何工作已經(jīng)有了相當(dāng)好的理解氧吐。這并不是一篇詳細(xì)介紹Entitas特性的教程末盔。相反陨舱,它旨在解釋如何使用Entitas構(gòu)建代碼游盲,并且最大程度地提高清晰度益缎、穩(wěn)定性和靈活性莺奔,尤其是在大型的工程中。我當(dāng)然不會(huì)聲稱這是權(quán)威的恼琼,這只是我學(xué)會(huì)使用Entitas開發(fā)某些項(xiàng)目后的一些方法晴竞。As always, YMMV(your mileage may vary).

我將嘗試涵蓋以下主題颓鲜。我會(huì)使用Unity作為示例來說明views和views controllers的概念甜滨,但是這種思維方式可以應(yīng)用到各種引擎衣摩。

  • 定義數(shù)據(jù)艾扮、邏輯和視圖層

  • 維護(hù)層與層之間的分離

  • 使用接口進(jìn)行抽象

  • 控制反轉(zhuǎn)

  • 視圖層抽象(Views與View controllers)

定義


Data: 游戲狀態(tài)泡嘴。數(shù)據(jù)比如血量、物品欄磺箕、經(jīng)驗(yàn)松靡、敵人類型雕欺、AI狀態(tài)屠列、移動(dòng)速度等等脸哀。在Entitas中這些數(shù)據(jù)在Compenents中撞蜂。

Logic: 數(shù)據(jù)改變的規(guī)則侥袜。PlaceInventory()枫吧、BuildItem()九杂、FireWeapon()等等例隆。在Etitas中叫做Systems镀层。

View: 負(fù)責(zé)向玩家展現(xiàn)游戲狀態(tài)、渲染屋休、動(dòng)畫劫樟、音頻毅哗、UI等的代碼。在示例中使用依賴于GameObject的MonoBehaviour闽烙。

Service: 外部的信息來源或者信息池黑竞。比如:尋路疏旨,排行榜饱溢,反作弊谁榜,社交窃植,物理巷怜,甚至是引擎本身延塑。

Input: 外部的模擬輸入页畦,通常通過有限的入口進(jìn)入部分游戲邏輯。比如:控制器端朵、鍵盤冲呢、鼠標(biāo)敬拓、網(wǎng)絡(luò)輸入等乘凸。

架構(gòu)


任何游戲的核心都只是CPU上的數(shù)值模擬营勤。每一款游戲都不過是一組數(shù)據(jù)(游戲狀態(tài))的集合葛作,這些數(shù)據(jù)會(huì)經(jīng)歷周期性的數(shù)值變換(游戲邏輯)赂蠢。這些數(shù)據(jù)包括得分虱岂、血量量瓜、經(jīng)驗(yàn)值绍傲、NPC對(duì)話烫饼、物品欄等等等等杠纵。游戲邏輯規(guī)定了這些數(shù)據(jù)可以經(jīng)歷的轉(zhuǎn)換規(guī)則(調(diào)用PlaceItemInInventory()用已定義的方式改變游戲狀態(tài))比藻。整個(gè)模擬系統(tǒng)可以不依賴于任何額外的層而存在银亲。

通常务蝠,一個(gè)游戲和純粹的數(shù)值模擬器之間的區(qū)別在于循環(huán)中有外部的用戶馏段,用戶有能力在外部使用模擬器已定義的邏輯改變游戲狀態(tài)院喜。隨著用戶的加入够坐,就有了連接模擬器與用戶的需求元咙。這就是“視圖層”。這一層代碼庫的一部分負(fù)責(zé)展現(xiàn)游戲狀態(tài)赶掖,通過將角色渲染到屏幕上奢赂、播放音頻膳灶、更新UI控件等方式轧钓。沒有這一層用戶就沒有辦法理解模擬器或者與模擬器進(jìn)行有效的交互毕箍。

大多數(shù)游戲架構(gòu)的目標(biāo)在于維護(hù)logic而柑、dataview的分層媒咳。想法就是模擬器層不應(yīng)該關(guān)心或者知道他們是如果渲染到屏幕上的伟葫。當(dāng)玩家受到攻擊后改變血量的方法筏养,不應(yīng)該包含渲染特效或者播放音頻的代碼渐溶。

我維護(hù)這種分離的方式是依照下圖來構(gòu)建我的工程茎辐。我將試著敘述圖中的每一部分拖陆,然后使用接口或helps來拓展類似于抽象這樣的概念依啰。

功能的抽象


抽象(在當(dāng)前特定的語境中)是解除what與how之間強(qiáng)耦合關(guān)系的過程叹誉。一個(gè)非常簡(jiǎn)單的例子长豁,想象有一個(gè)寫日志的功能匠襟,日志可供用戶查看宅此。

Naive的方法

最naive的方法父腕,我確定大多數(shù)讀者已經(jīng)知道如何避免了璧亮,就是在每一個(gè)需要寫日志的system中調(diào)用Debug.Log帘饶。這將立刻給代碼庫帶來很高的耦合度及刻,以后維護(hù)起來將是一場(chǎng)噩夢(mèng)缴饭。

幼稚方法的問題

如果有一天想要用更合適的方法骆莹,比如將日志寫入文件的方法颗搂,來代替Debug.Log會(huì)發(fā)生什么?如果是添加一個(gè)游戲中的調(diào)試控制臺(tái)并將信息記錄下來呢幕垦?你將不得不瀏覽整個(gè)代碼庫丢氢,然后添加或者替換這些方法調(diào)用。這是一場(chǎng)真正的噩夢(mèng)先改,即使是一個(gè)中等的項(xiàng)目中疚察。

這些代碼可讀性差盏道、易混淆稍浆。職責(zé)沒有分離。如果你已經(jīng)完成過一個(gè)Unity項(xiàng)目猜嘱,你可能覺得很OK因?yàn)檫@些事Unity都已經(jīng)做了打日志這件事。在這我告訴你嫁艇,這一點(diǎn)都不OK朗伶。你的CharacterController不應(yīng)該直接去解析用戶輸入,不應(yīng)該跟蹤物品欄步咪,不應(yīng)該播放腳步聲音頻论皆,也不應(yīng)該發(fā)布facebook消息,而應(yīng)該控制角色移動(dòng)猾漫,并且只用于控制角色移動(dòng)点晴。分離你的關(guān)注。

解耦-Entitas方法

可能你對(duì)這些問題太熟悉了悯周,所以尋求用Entitas來解決粒督。那現(xiàn)在就來談一談Entitas方式的好處:

在Entitas中我們通過創(chuàng)建LogMessageComponent(string message)然后設(shè)計(jì)一個(gè)ReactiveSystem來實(shí)現(xiàn)這個(gè)功能,這個(gè)system用來收集信息并且做具體的代碼實(shí)現(xiàn)禽翼。有了這個(gè)設(shè)置屠橄,我們可以很容易地創(chuàng)建一個(gè)Entity族跛,給它掛上組件以使它能夠向控制臺(tái)打印信息。

現(xiàn)在锐墙,不論什么時(shí)候想要?jiǎng)?chuàng)建日志信息礁哄,創(chuàng)建一個(gè)Entity然后交給System處理。具體的實(shí)現(xiàn)我們可以想改多少次就改多少次溪北,并且只在代碼的一個(gè)地方改(System中)桐绒。

(純)Entitas方法的問題

這種方法對(duì)某些用戶來說足夠了,尤其是類似于MatchOne這樣的小項(xiàng)目之拨。但是它本身并不是沒有問題茉继。我們添加了對(duì)UnityEngine的一個(gè)很強(qiáng)的依賴,因?yàn)槲覀冊(cè)赟ystem中使用了它的API敦锌。我們也在System中直接寫了功能的實(shí)現(xiàn)馒疹。

使用Debug.Log()在這種情況下似乎不是問題,畢竟只是一行代碼乙墙。但是如果里面包含解析json文件的操作或者需要發(fā)送網(wǎng)絡(luò)消息怎么辦颖变?現(xiàn)在你的系統(tǒng)里有很多具體的代碼實(shí)現(xiàn)。也有很多依賴和using 聲明(UnityEngine/JSON library/Networking Library等等)听想。代碼的可讀性很差腥刹,如果考慮到工具庫變化的話還很容易出錯(cuò)。如果有一天改變了引擎汉买,所有游戲代碼需要完全重寫衔峰。

使用接口的示例


在c#中,解決依賴性和提高代碼清晰度的方法是使用接口蛙粘。一個(gè)接口就類似于一個(gè)約定垫卤。告訴編譯器你的類實(shí)現(xiàn)了一個(gè)接口,就好比你說“這個(gè)類作為這個(gè)接口出現(xiàn)時(shí)具有相同的公共的API”出牧。

當(dāng)我聲明接口時(shí)我會(huì)想“我的游戲需要從這個(gè)接口中獲得什么樣的信息或功能”穴肘,然后我會(huì)試著為它提供一個(gè)描述性的、簡(jiǎn)單的API舔痕。對(duì)于日志功能來說我們只需要一個(gè)方法评抚,一個(gè)簡(jiǎn)單的LogMessage(string message)。用接口實(shí)現(xiàn)如下:

通過繼承ILogService 來向編譯器保證你的類實(shí)現(xiàn)了void LogMessage(string message) 方法伯复。這意味著你可以在上文提到的ReactiveSystem中調(diào)用它慨代。這個(gè)系統(tǒng)只關(guān)注接口ILogService。如果我們向系統(tǒng)中傳入一個(gè)JsonLogService 啸如,我們會(huì)得到一個(gè)包含日志信息的json文件侍匙。我們不能訪問JsonLogMessage 類中的公共字符串字段,因?yàn)榻涌谥胁]有定義组底。需要注意的是丈积,我們向這個(gè)system的構(gòu)造方法中傳入了一個(gè)ILogMessage 的實(shí)例筐骇,我會(huì)在下文中解釋。

在我的工程中一個(gè)更復(fù)雜的例子是IIputService 江滨。我又開始想了:我需要知道用戶的什么輸入呢铛纬?我是否可以定義一組簡(jiǎn)單的屬性或方法來獲取到我想得到的信息?以下是我的接口的一部分:

現(xiàn)在我可以寫一個(gè)EmitInputSystem 來向Entitas中發(fā)送輸入數(shù)據(jù)』;現(xiàn)在這些數(shù)據(jù)成為了游戲數(shù)據(jù)的一部分告唆,并且可以驅(qū)使其他模塊做其他的事。這種方法的好處是我可以將使用Unity的實(shí)現(xiàn)替換為AssetStore中的一個(gè)解決方案晶密,比如InControl擒悬,而不用修改任何游戲代碼。注意以下的系統(tǒng)中稻艰,代碼只關(guān)注這個(gè)特定的接口懂牧。

到現(xiàn)在,我希望你可以理解我說的“抽象”尊勿。我正在從具體的實(shí)現(xiàn)中抽象 邏輯僧凤,也就是從how 中抽象what 。在Input示例中我說了元扔,我關(guān)心的只是我可以查詢用戶是否按下了ButtonA躯保,我不關(guān)系這來自于鍵盤還是鼠標(biāo)還是網(wǎng)絡(luò)連接。在游戲中實(shí)際讀取輸入的地方澎语,這些都不重要途事。

對(duì)于“獲取time delta”功能來說,我不需要知道是來自Unity還是XNA還是Unreal擅羞,我只需要知道它是多少尸变,這關(guān)系到我要將角色在屏幕上移動(dòng)多少距離。

控制反轉(zhuǎn)


現(xiàn)在我們要向代碼中引入一種之前沒有遇到過的復(fù)雜情況:現(xiàn)在我們的system需要一個(gè)繼承自某個(gè)接口的實(shí)例的引用减俏。在上面的例子中我是通過構(gòu)造方法傳入的振惰,但是這將導(dǎo)致許多具有不同構(gòu)造方法的system。我們想要的是這些Service實(shí)例是全局可訪問的垄懂。我們也希望在代碼庫中只有這么一個(gè)地方,這個(gè)地方靠近應(yīng)用的初始化點(diǎn)痛垛,在這個(gè)地方我們可以決定使用接口的哪些實(shí)現(xiàn)草慧。也是在這里,我們創(chuàng)建實(shí)例匙头,并使這些實(shí)例全局可訪問漫谷,以便可以在system中查詢到,而不必把它們傳入每一個(gè)單獨(dú)的構(gòu)造方法中蹂析。幸運(yùn)的是這使用Enitas實(shí)現(xiàn)起來超級(jí)簡(jiǎn)單舔示。

我的方法是首先創(chuàng)建一個(gè)Helper類碟婆,其中包含每一個(gè)Service的引用。

Service.cs

現(xiàn)在可以在GameController里面很輕易的初始化它:

MetaContext里面有一組unique components持有這些接口的實(shí)例惕稻。比如:

最后有一個(gè)Feature竖共,它在系統(tǒng)層面上第一個(gè)運(yùn)行,叫做ServiceRegistrationSystems 俺祠。它的構(gòu)造方法里有一個(gè)額外的Services 參數(shù)公给,然后把這個(gè)services向下傳入到initialize systems。這些system簡(jiǎn)單地將Services 中的實(shí)例分配給MetaContext中的unique components蜘渣。

ServiceRegistrationSystems.cs

一個(gè)RegistrationSystems示例

最后的結(jié)果是我們可以全局訪問這些service實(shí)例淌铐,通過Contexts實(shí)例(_context.meta.timeService.instance)。而且我們只在一個(gè)地方創(chuàng)建它們蔫缸,所以回滾腿准、修改實(shí)現(xiàn)或者模擬現(xiàn)實(shí)用于測(cè)試都變得輕而易舉。你也可以輕松使用編譯指令獲得指定平臺(tái)的實(shí)現(xiàn)或者只在調(diào)試狀態(tài)下有效的實(shí)現(xiàn)拾碌。我們使用了“控制反轉(zhuǎn)”的依賴性解決方式吐葱,(依賴性)從system類的深處轉(zhuǎn)到了應(yīng)用的頂部(初始化處)。

View層抽象


目前為止倦沧,我們看到了上圖中左側(cè)的service接口唇撬,現(xiàn)在來看一看右側(cè)的View接口。工作方式很相似展融。就像之前說的窖认,View層關(guān)心的是將游戲狀態(tài)展現(xiàn)給玩家,包括動(dòng)畫告希、聲音扑浸、圖片、網(wǎng)格燕偶、渲染等等喝噪。目標(biāo)同樣是消除對(duì)游戲引擎或第三方庫的依賴性,得到純粹的指么、描述性的system代碼而沒有任何具體的實(shí)現(xiàn)酝惧。

Naive的方法是用一個(gè)ViewComponent 里面引用一個(gè)GameObject。然后可能需要一個(gè)簡(jiǎn)單的標(biāo)記component AssignViewComponent 來說明我們需要一個(gè)新的GameObject作為Entity的view伯诬。要使用的話需要寫一個(gè)reactive systerm作用于AssignView 和過濾器!entity.hasView來確保只在需要的地方添加view晚唇。在這個(gè)system,甚至是component中盗似,可能會(huì)直接使用Unity的API哩陕。這當(dāng)然不能實(shí)現(xiàn)我們?cè)O(shè)置的目標(biāo)。

在這里可以使用上文中提到的service形式,連同更深一層次的對(duì)view的抽象悍及。同樣闽瓢,思考一下在view中需要什么數(shù)據(jù)或功能,然后為它寫一個(gè)接口心赶。這將決定system代碼如何從view中g(shù)et或set數(shù)據(jù)扣讼。不妨把它叫做“ViewController”——這是直接控制View對(duì)象的代碼塊。典型的园担,里面可能包含transform信息(位置/旋轉(zhuǎn)/縮放)届谈,也可能有標(biāo)簽、層弯汰、名稱艰山、enable狀態(tài)。

View咏闪,天然地曙搬,應(yīng)該綁定到Entity,并且它可能需要處理這個(gè)Entity和其他游戲狀態(tài)的信息鸽嫂。為此纵装,需要在設(shè)置view的時(shí)候,傳入entity引用和Contexts實(shí)例据某。也要能夠在entity代碼內(nèi)部銷毀view橡娄。示例如下:

這是在Unity中對(duì)這個(gè)接口的一個(gè)實(shí)現(xiàn):


在這里需要一個(gè)service用于創(chuàng)建這些view并與entity綁定。這是我的IViewService 接口和在它Unity中的實(shí)現(xiàn)癣籽。

一個(gè)component持有這個(gè)view controller


一個(gè)接口來定義我需要能夠訪問view service的兩件事情


view service在Unity中的實(shí)現(xiàn):


一個(gè)LoadAssetSystem用于加載資源并且綁定view

以下是一個(gè)position system示例挽唉,使用了抽象的view而不直接與Unity交互。


代碼中沒有對(duì)Unity引擎的依賴筷狼,component和system只引用了接口瓶籽。代碼中也沒有具體的實(shí)現(xiàn)(不用關(guān)心訪問GameObject和Transform,只需要簡(jiǎn)單地去設(shè)置接口里的屬性)

這種方法的問題

有一個(gè)很明顯的瑕疵——我們?cè)诖a中寫了一個(gè)system用來與view層交互——這破壞了我們之前的原則埂材,也就是模擬器不應(yīng)該知道自己是否被渲染塑顺。在Entitas中有另一種強(qiáng)制完全與view解耦的方法——就是Entitas的“事件”功能。

事件


在游戲Match-One中俏险,Simon并沒有ViewComponent严拒。事實(shí)上,沒有任何游戲代碼知道他正在被渲染竖独。代替MonoBehaviour的是事件監(jiān)聽器糙俗。我將使用事件來重構(gòu)上面的示例,以此展示如何簡(jiǎn)化游戲邏輯预鬓,甚至是將模擬器層與view層完全解耦。

首先,需要一個(gè)使用[Event]屬性標(biāo)記的component格二,用來生成我們需要的監(jiān)聽器和事件系統(tǒng)劈彪。這里再次以Position功能為例。

這個(gè)新的屬性(Event(true))會(huì)生成一個(gè)PositionListenerComponent 和一個(gè)IPositionListener 接口《ゲ拢現(xiàn)在寫另外一個(gè)接口來作用于全部 的事件監(jiān)聽器沧奴,所以就可以在它們創(chuàng)建的時(shí)候安全地進(jìn)行初始化。

現(xiàn)在不在需要view component或view service中的LoadAsset方法的返回值了长窄,所以移除它們√戏停現(xiàn)在需要在view service中添加代碼來識(shí)別并且初始化asset中的事件監(jiān)聽器:


現(xiàn)在可以擺脫所有的類似于SetViewXXXSystem 這樣的類了,因?yàn)椴辉傩枰ㄖ獀iew執(zhí)行操作挠日。取而代之的是寫monobehaviour腳本來監(jiān)聽位置的改變疮绷,比如:

如果把這個(gè)腳本添加到一個(gè)預(yù)制體上然后在game controller中生成EventSystems 嚣潜,那這個(gè)GameObject的位置就會(huì)與entity中的PositionComponent完美同步冬骚,而不需要systems。那view層物體的位置就完全與模擬器層中entity中的位置完全解耦了懂算≈欢常可以輕松地向component中添加事件。重構(gòu)在之前IViewContoller接口中所有的功能计技,使用事件監(jiān)聽可以完全擺脫它喜德。

使用service來加載資源的模式,有控制view層中信息流初始化的能力垮媒∩崦酰可以隨意添加(IAudioPlayer,UAnimator涣澡,ICollider等等)然后將它們的引用傳遞給contexts或者相關(guān)的entity贱呐。你可以控制初始化的順序和時(shí)間(不再需要知道Unity中Start()Update()的調(diào)用時(shí)間,不再需要當(dāng)Start()執(zhí)行過早時(shí)檢查是否為空)入桂。

現(xiàn)在能夠做到使view層控制自身——view controller變成了簡(jiǎn)單的事件監(jiān)聽器奄薇,在關(guān)注的組件改變時(shí)觸發(fā),而完全不需要向模擬器層返回信息(除了在初始化時(shí)向entity上掛一個(gè)xxxListenerComponent的情況)抗愁∧俚伲可以在Unity中實(shí)現(xiàn)一整個(gè)動(dòng)畫系統(tǒng),通過monobehaviour事件監(jiān)聽器蜘腌,而不用在模擬器層中引用它沫屡。同樣適用于音頻、粒子撮珠、著色器等等沮脖。

總結(jié)


很完美,我們實(shí)現(xiàn)了開始時(shí)設(shè)置的所有目標(biāo)。

我們將entitas代碼與游戲引擎和第三方庫完全解耦了勺届。

我們有一個(gè)模擬器層(數(shù)據(jù)在component中驶俊,邏輯在system中),這對(duì)引擎來說是完全不可知的免姿。而在工程中也只有一個(gè)文件夾包含各種接口針對(duì)Unity的實(shí)現(xiàn)饼酿。這也是唯一一個(gè),當(dāng)我們想將引擎從Unity改為XNA時(shí)胚膊,需要改變的文件夾故俐。

在應(yīng)用的頂部,有一個(gè)地方會(huì)決定使用哪個(gè)實(shí)現(xiàn)紊婉∫┌妫可以在這里進(jìn)行測(cè)試模擬、嘗試新的第三方解決方案肩榕、或者輕松的改變游戲內(nèi)事物如何運(yùn)行的想法刚陡,而不以任何方法改變游戲邏輯。

模擬器層與view層是完全解耦的株汉,一旦事件系統(tǒng)運(yùn)行起來筐乳,我們的游戲邏輯甚至不知道正在被渲染。整個(gè)模擬器可以在服務(wù)器端運(yùn)行乔妈,而視圖層在客戶端運(yùn)行蝙云。

最后,再回頭看一下游戲邏輯路召,會(huì)發(fā)現(xiàn)它清晰易讀勃刨。復(fù)雜的實(shí)現(xiàn)被隱藏,而只有一些描述性的方法和屬性的調(diào)用股淡。設(shè)計(jì)只包含關(guān)注字段的接口身隐,再也不用看到巨大的包含無用信息的intelli-sense 下拉框。我們將只能訪問我們真正需要的東西唯灵。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贾铝,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子埠帕,更是在濱河造成了極大的恐慌垢揩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敛瓷,死亡現(xiàn)場(chǎng)離奇詭異叁巨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)呐籽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門锋勺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蚀瘸,“玉大人,你說我怎么就攤上這事宙刘〔越” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵悬包,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我馍乙,道長(zhǎng)布近,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任丝格,我火速辦了婚禮撑瞧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘显蝌。我一直安慰自己预伺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布曼尊。 她就那樣靜靜地躺著酬诀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪骆撇。 梳的紋絲不亂的頭發(fā)上瞒御,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音神郊,去河邊找鬼肴裙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涌乳,可吹牛的內(nèi)容都是我干的蜻懦。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼夕晓,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宛乃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起运授,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤烤惊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吁朦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體柒室,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年逗宜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雄右。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片空骚。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖擂仍,靈堂內(nèi)的尸體忽然破棺而出囤屹,到底是詐尸還是另有隱情,我是刑警寧澤逢渔,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布肋坚,位于F島的核電站,受9級(jí)特大地震影響肃廓,放射性物質(zhì)發(fā)生泄漏智厌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一盲赊、第九天 我趴在偏房一處隱蔽的房頂上張望铣鹏。 院中可真熱鬧,春花似錦哀蘑、人聲如沸诚卸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽合溺。三九已至,卻和暖如春脊髓,著一層夾襖步出監(jiān)牢的瞬間辫愉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工将硝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恭朗,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓依疼,卻偏偏與公主長(zhǎng)得像痰腮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子律罢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361