漫談C++游戲引擎開(kāi)發(fā)

前言

最近我用 C++ 寫(xiě)了一個(gè)游戲引擎纹安,并用該引擎開(kāi)發(fā)了一個(gè)名為 Hop Out 的小型手游流济。先來(lái)看看實(shí)際運(yùn)行效果:


(譯者注 這里本來(lái)有個(gè)小視頻奔则,放到附件里了赢笨,感興趣的朋友請(qǐng)下載觀看未蝌,文件不到4MB。)

創(chuàng)一個(gè)小群茧妒,供大家學(xué)習(xí)交流聊天

如果有對(duì)學(xué)C++方面有什么疑惑問(wèn)題的萧吠,或者有什么想說(shuō)的想聊的大家可以一起交流學(xué)習(xí)一起進(jìn)步呀。

也希望大家對(duì)學(xué)C++能夠持之以恒

C++愛(ài)好群桐筏,

如果你想要學(xué)好C++最好加入一個(gè)組織纸型,這樣大家學(xué)習(xí)的話就比較方便,還能夠共同交流和分享資料九昧,給你推薦一個(gè)學(xué)習(xí)的組織:

快樂(lè)學(xué)習(xí)C++組織 可以點(diǎn)擊組織二字绊袋,可以直達(dá)


Hop Out 是一款類(lèi)似復(fù)古街機(jī)游戲,但擁有 3D 卡通外觀的游戲铸鹰。闖關(guān)方式為改變所有墊子的顏色癌别,這一點(diǎn)和 Q*Bert 游戲很相似。


Hop Out 仍在開(kāi)發(fā)當(dāng)中蹋笼,不過(guò)游戲引擎部分基本完工了展姐,所以我想在這里分享關(guān)于游戲引擎開(kāi)發(fā)的一些技巧。


在我看來(lái)剖毯,開(kāi)發(fā)游戲引擎比較尷尬的一個(gè)情況就是你可能不知不覺(jué)地就造就出一個(gè)龐然大物圾笨,然后你一看到它就頭皮發(fā)麻,所以我的主張是保持事物的可控性逊谋,具體將從以下三個(gè)方面進(jìn)行闡述:

采用迭代方法

先三思而后合并

認(rèn)識(shí)到序列化是個(gè)很大的主題

采用迭代方法

我的第一條建議是先快速地讓程序運(yùn)行起來(lái)擂达,然后迭代地進(jìn)行開(kāi)發(fā)。


如果條件允許的話胶滋,找個(gè)樣例程序板鬓,然后以此為基礎(chǔ)開(kāi)始。以我為例究恤,先下載?SDL?再打開(kāi) Xcode-iOS/Test/TestiPhoneOS.xcodeproj 俭令,然后在 iPhone 上運(yùn)行 testgles2 樣例程序。立刻我就得到了一個(gè)很可愛(ài)的旋轉(zhuǎn)立方體部宿,如下圖抄腔。



然后我下載一個(gè)別人做好的馬里奧 3D 模型。隨后編寫(xiě)了一個(gè)文件格式不太復(fù)雜的 OBJ 文件加載程序,接著修改樣例程序赫蛇,讓馬里奧取代立方體绵患,如下圖。還有棍掐,我集成了SDL_image?來(lái)幫助加載紋理藏雏。



再然后拷况,我實(shí)現(xiàn)了雙搖桿控制來(lái)移動(dòng)馬里奧作煌,如下圖。



接下來(lái)我想著研究一下骨骼動(dòng)畫(huà)赚瘦,所以我打開(kāi)?Blender?制作了一個(gè)觸手模型粟誓,并通過(guò)一段可以前后擺動(dòng)的有兩根骨頭的骨架來(lái)操控它。



不過(guò)這里我放棄了使用 OBJ 文件格式起意,轉(zhuǎn)而編寫(xiě)了一個(gè)將數(shù)據(jù)從 Blender 導(dǎo)出到自定義 JSON 文件的 Python 腳本鹰服,這些 JSON 文件存儲(chǔ)了皮膚網(wǎng)格、骨骼揽咕、動(dòng)畫(huà)等數(shù)據(jù)悲酷。在?C++ JSON library?的幫助下我將這些文件加載到了游戲中。



上述過(guò)程成功后亲善,我接著使用 Blender 制作更加精致的人物设易。下圖展示了我制作出的第一個(gè)可操控的 3D 人物。



后來(lái)我又做了一大堆的工作蛹头,不過(guò)這里我想強(qiáng)調(diào)的重點(diǎn)是顿肺,我沒(méi)有在動(dòng)手編程之前先規(guī)劃好引擎架構(gòu)。事實(shí)上渣蜗,每當(dāng)要添加一個(gè)新特性時(shí)屠尊,我只著眼于用最簡(jiǎn)單的代碼將其實(shí)現(xiàn),然后觀察這些代碼耕拷,看看它們自然而然呈現(xiàn)出的是一種什么架構(gòu)讼昆。這里所講的引擎架構(gòu),指的是組成游戲引擎的模塊集骚烧、模塊之間的依賴關(guān)系浸赫,以及模塊之間交互所使用的?API?。



這是一種迭代開(kāi)發(fā)的方法止潘,這種方法在編寫(xiě)游戲引擎時(shí)非常有用掺炭,其優(yōu)點(diǎn)在于不管開(kāi)發(fā)工作進(jìn)行到哪個(gè)階段,你始終都有一個(gè)可運(yùn)行的程序凭戴。如果在后續(xù)提取代碼模塊時(shí)出現(xiàn)問(wèn)題涧狮,你可以通過(guò)與上一次可正常運(yùn)行的代碼對(duì)比以快速地找出錯(cuò)誤。顯然,這里我假設(shè)你使用了某種源代碼控制軟件者冤。


也許你認(rèn)為這種開(kāi)發(fā)方法會(huì)浪費(fèi)大量的時(shí)間肤视,因?yàn)橹虚g過(guò)程會(huì)產(chǎn)生許多后續(xù)需要清理的垃圾代碼。但是涉枫,大部分的清理工作無(wú)非就是將代碼從一個(gè) .cpp 文件移動(dòng)到另一個(gè) .cpp 文件邢滑、將函數(shù)聲明提取到 .h 文件、或者一些其他簡(jiǎn)單的操作愿汰。決定代碼的歸屬其實(shí)是一件相當(dāng)困難的工作困后,但是顯然,當(dāng)代碼呈現(xiàn)在你面前時(shí)衬廷,這個(gè)工作就會(huì)簡(jiǎn)單許多摇予。


況且在我看來(lái),先絞盡腦汁地想出一個(gè)你認(rèn)為能滿足未來(lái)所有需求的架構(gòu)吗跋,然后再著手編程侧戴,會(huì)比迭代開(kāi)發(fā)浪費(fèi)更多的時(shí)間。這里推薦一下我最喜歡的關(guān)于介紹過(guò)度工程危害的兩篇文章跌宛,一篇是 Tomasz D?browski 的?The Vicious Circle of Generalization?酗宋,另一篇是 Joel Spolsky 的?Don’t Let Architecture Astronauts Scare You?。


但是請(qǐng)注意疆拘,我并沒(méi)有說(shuō)你永遠(yuǎn)都不應(yīng)該先在紙面上解決問(wèn)題蜕猫,然后編程實(shí)現(xiàn)它。我也并沒(méi)有說(shuō)你不應(yīng)該提前規(guī)劃好你想要的功能入问。就我而言丹锹,我從一開(kāi)始就想要游戲引擎能夠在后臺(tái)線程中加載所有 assets 文件,但是我一開(kāi)始并沒(méi)有去設(shè)計(jì)如何實(shí)現(xiàn)這個(gè)功能芬失,而且一開(kāi)始也確實(shí)沒(méi)有實(shí)現(xiàn)這個(gè)功能楣黍,實(shí)際上我一開(kāi)始只實(shí)現(xiàn)了加載部分 assets 文件的功能。

先三思而后合并

作為程序員棱烂,我們似乎會(huì)本能地避免代碼重復(fù)租漂、統(tǒng)一代碼風(fēng)格以讓源代碼看起來(lái)美觀、優(yōu)雅颊糜。然而哩治,我的第二條建議是不要盲目地遵循這種本能。

給 DRY 原則放個(gè)假

為了給你一個(gè)示例衬鱼,我的引擎包含了幾個(gè) smart pointer 模板類(lèi)业筏,類(lèi)似于 std::shared_ptr 。通過(guò)作為一個(gè) raw pointer 的包裝器鸟赫,它們個(gè)個(gè)都能防止內(nèi)存泄漏蒜胖。

Owned<> 用于被單個(gè)對(duì)象擁有的動(dòng)態(tài)分配的對(duì)象消别。

Reference<> 使用引用計(jì)數(shù)來(lái)以便一個(gè)對(duì)象被多個(gè)對(duì)象擁有。

audio::AppOwned<> 被音頻混頻器外的代碼使用台谢。它允許游戲系統(tǒng)擁有音頻混頻器使用的對(duì)象寻狂,比如當(dāng)前正在播放的聲音。

audio::AudioHandle<> 使用一個(gè)引用計(jì)數(shù)系統(tǒng)內(nèi)部的音頻混頻器朋沮。

看起來(lái)似乎這些類(lèi)的功能有重復(fù)的地方蛇券,違背了?DRY(Don't Repeat Yourself) 原則。事實(shí)確實(shí)如此樊拓,在開(kāi)發(fā)早期纠亚,我曾想方設(shè)法地盡可能多地重用現(xiàn)有的 Reference<> 類(lèi)。但是后來(lái)我發(fā)現(xiàn)音頻對(duì)象的生命周期受一些特殊的規(guī)則控制:如果音頻對(duì)象已經(jīng)完成了播放骑脱,并且游戲也沒(méi)有一個(gè)指向該音頻對(duì)象的指針菜枷,那么該音頻對(duì)象就可以立即排隊(duì)等待刪除了。如果游戲有一個(gè)指向該音頻對(duì)象的指針叁丧,那么該音頻對(duì)象就不該被刪除。如果游戲有一個(gè)指向該音頻對(duì)象的指針岳瞭,但是該指針的擁有者在聲音沒(méi)有播放完成之前被破壞掉了拥娄,那么該聲音就該被取消。我認(rèn)為瞳筏,與其增加Reference<>的復(fù)雜度稚瘾,還不如引入單獨(dú)的模板類(lèi),況且后者顯然更實(shí)用一點(diǎn)姚炕。


95%的情況下摊欠,重用已有代碼是沒(méi)毛病的。然而柱宦,當(dāng)你感覺(jué)到重用代碼變了味些椒、或者你正在把簡(jiǎn)單的東西變得復(fù)雜的時(shí)候,你就該仔細(xì)想想要不要堅(jiān)持重用代碼掸刊。

大膽地使用不同的調(diào)用約定

Java 有一點(diǎn)我很不喜歡免糕,那就是每個(gè)函數(shù)都必須定義在類(lèi)中。在我看來(lái)忧侧,這根本就是胡來(lái)石窑,這樣做也許使你的代碼看起來(lái)更整齊一點(diǎn),但其實(shí)它變相地鼓勵(lì)了過(guò)度工程(over-engineering)蚓炬,而且也不能很好地支持我先前所提到地迭代開(kāi)發(fā)方法松逊。


在我的 C++ 引擎中,有些函數(shù)屬于類(lèi)肯夏,有些函數(shù)不屬于類(lèi)经宏。例如楼咳,游戲中的每個(gè)敵人都是一個(gè)類(lèi),敵人的大多數(shù)行為都是在類(lèi)中實(shí)現(xiàn)烛恤,但是球體滾動(dòng)這個(gè)行為是通過(guò)調(diào)用函數(shù) sphereCast() 實(shí)現(xiàn)的母怜,該函數(shù)屬于 physics 命名空間,但是函數(shù) sphereCast() 并不屬于任何類(lèi)——它就是 physics 模塊的一部分缚柏。

我通過(guò)一個(gè)構(gòu)建系統(tǒng)組織代碼苹熏,該構(gòu)建系統(tǒng)用于管理模塊之間的依賴關(guān)系。將這個(gè)函數(shù)強(qiáng)行塞進(jìn)一個(gè)類(lèi)中對(duì)于改進(jìn)代碼組織來(lái)講沒(méi)多大意義币喧。


再來(lái)談?wù)劧鄳B(tài)(polymorphism))中的動(dòng)態(tài)調(diào)度(dynamic dispatch)轨域。我們經(jīng)常需要在不知道對(duì)象確切類(lèi)型的情況下調(diào)用函數(shù)獲取對(duì)象。大多數(shù) C++ 程序員的第一反應(yīng)是使用虛函數(shù)定義抽象基類(lèi)杀餐,然后在派生類(lèi)中重載這些函數(shù)干发。

這的確是一種行之有效的方法,但這只是實(shí)現(xiàn)該功能的眾多方法中的一種罷了史翘。還有一些可以不引入多余的代碼枉长,或者帶有其他好處的動(dòng)態(tài)調(diào)度技術(shù):

C++11 引入了 std::function ,這是一種很方便的存儲(chǔ)回調(diào)函數(shù)的方法琼讽。你還可以編寫(xiě)一個(gè) std::function 個(gè)人版本必峰,這樣在調(diào)試器中單步執(zhí)行時(shí)或許就沒(méi)那么痛苦了。

許多回調(diào)函數(shù)可以用一對(duì)指針來(lái)實(shí)現(xiàn): 一個(gè)函數(shù)指針和一個(gè) opaque 參數(shù)钻蹬,只需要在回調(diào)函數(shù)內(nèi)部進(jìn)行顯式轉(zhuǎn)換即可吼蚁。純 C 庫(kù)中有很多這種例子。

有時(shí)侯, 底層類(lèi)型實(shí)際上在編譯時(shí)是已知的, 因此你可以綁定函數(shù)調(diào)用而無(wú)需額外的運(yùn)行時(shí)開(kāi)銷(xiāo)问欠。Turf?肝匆,是我在游戲引擎中使用的一個(gè)庫(kù), 就大量使用了這種技術(shù)。感興趣的可以看看?turf::Mutex?顺献。

不過(guò)有時(shí)侯最直接的方法莫過(guò)于自己構(gòu)建和維護(hù)一個(gè)原始函數(shù)指針表旗国。我在音頻混頻器和序列化系統(tǒng)中使用了這種方法。正如下文將要提到的滚澜,Python 解釋器也大量使用了此技術(shù)粗仓。

甚至你可以將函數(shù)指針存儲(chǔ)在哈希表中, 將函數(shù)名作為鍵。我使用此技術(shù)調(diào)度輸入事件, 如多點(diǎn)觸摸事件设捐。這是一個(gè)記錄游戲輸入并使用回放系統(tǒng)重新播放策略的一部分借浊。

動(dòng)態(tài)調(diào)度是一個(gè)很大的課題,我只是隨便舉些例子罷了萝招,實(shí)際上還有很多方法都可以實(shí)現(xiàn)蚂斤。隨著編寫(xiě)的可擴(kuò)展底層代碼(在開(kāi)發(fā)游戲引擎中很常見(jiàn))越來(lái)越多,你會(huì)探索出越來(lái)越多的方法槐沼。

如果你不習(xí)慣這種編程方式曙蒸,那么 Python 解釋器或許對(duì)你來(lái)是是一個(gè)非常好的學(xué)習(xí)資源捌治。它使用 C 編寫(xiě),實(shí)現(xiàn)了一個(gè)強(qiáng)大的對(duì)象模型:每個(gè) PyObject 都指向了一個(gè) PyTypeObject 纽窟,而每個(gè) PyTypeObject 都包含了一個(gè)用于動(dòng)態(tài)調(diào)度的函數(shù)指針表肖油。如果你感興趣的話,可以從閱讀文檔?Defining New Types?開(kāi)始臂港。

認(rèn)識(shí)到序列化是一個(gè)很大的主題

序列化(Serialization)指的是將運(yùn)行時(shí)對(duì)象轉(zhuǎn)化為字節(jié)序列森枪,換句話講,就是保存和加載數(shù)據(jù)审孽。


對(duì)于許多游戲引擎來(lái)講县袱,游戲內(nèi)容是以各種可編輯格式創(chuàng)建的,如 .png 佑力、 .json 式散、 .blend 或者一些專有格式等,最終再將其轉(zhuǎn)化為游戲引擎可以快速加載的平臺(tái)特定的游戲格式打颤。這個(gè)管道中的最后一個(gè)應(yīng)用程序通常被稱為 cooker 暴拄。cooker 也許會(huì)被集成到其他工具中,甚至分布在多臺(tái)機(jī)器上瘸洛。通常上揍移,cooker 和許多工具是隨游戲引擎本身一起開(kāi)發(fā)和維護(hù)的。



在建立這樣一個(gè)管道時(shí)反肋,其中每個(gè)階段的文件格式都由你設(shè)定。你也許會(huì)自己定義一些文件格式踏施,這些文件格式可能會(huì)隨著引擎功能的不斷添加演變石蔗。隨著它們的演變,有一天你或許會(huì)發(fā)現(xiàn)必須使某些程序與以前保存的文件格式保持兼容畅形。但是养距,無(wú)論何種格式,你最終都得用 C++ 進(jìn)行序列化日熬。


C++ 實(shí)現(xiàn)序列化的方法數(shù)不勝數(shù)棍厌,一個(gè)比較容易想到的方法是在你想要序列化的 C++ 類(lèi)中添加 load 函數(shù)和 save 函數(shù)。在文件頭部中存儲(chǔ)版本號(hào)竖席,然后將版本號(hào)傳遞到每個(gè) load 函數(shù)中耘纱,你就可以實(shí)現(xiàn)向后兼容性。這種辦法可行毕荐,不過(guò)可能導(dǎo)致代碼非常冗雜而難以維護(hù)束析。

void load(InStream& in, u32 fileVersion) {

????????// Load expected member variables

????????in >> m_position;

????????in >> m_direction;

????????// Load a newer variable only if the file version being loaded is 2 or greater


????????if (fileVersion >= 2) {

????????????in >> m_velocity;

????????}

????}

不過(guò)我們可以寫(xiě)出更靈活、更不容易出錯(cuò)的序列化代碼憎亚,這里用到了反射(reflection))员寇,具體來(lái)講是創(chuàng)建描述 C++ 類(lèi)型布局的運(yùn)行時(shí)數(shù)據(jù)弄慰。如果想要快速了解一下如何在序列化時(shí)使用反射,可以看看開(kāi)源項(xiàng)目?Blender?蝶锋。



當(dāng)你從源代碼構(gòu)建 Blender 時(shí)陆爽,會(huì)發(fā)生許多事情。首先扳缕,一個(gè)名為 makesdna 的程序會(huì)被編譯并運(yùn)行馆类。這個(gè)程序會(huì)解析 Blender 源樹(shù)中的一組 C 頭文件逗威,然后輸出一個(gè)包含了被稱為?SDNA?的自定義格式的文件,該文件中存放了這些頭文件內(nèi)部定義的所有 C 類(lèi)型的緊湊摘要,這些 SDNA 數(shù)據(jù)就是反射數(shù)據(jù)(reflection data)京革。

然后 這些 SDNA 數(shù)據(jù)被鏈接到 Blender ,并和 Blender 所寫(xiě)的每個(gè) .blend 文件一起保存冠跷。從此以后合是,每加載一個(gè) .blend 文件,Blender 就會(huì)比較該 .blend 文件的 SDNA 數(shù)據(jù)與運(yùn)行時(shí)鏈接到當(dāng)前版本的 SDNA 數(shù)據(jù)飒赃,并使用通用序列化代碼來(lái)處理差異利花。

這種策略使得 Blender 的向前和向后兼容性非常強(qiáng)大。你可以在最新版中加載?1.0?版的文件载佳,也可以在舊版本中加載新版本的 .blend 文件炒事。


和 Blender 類(lèi)似,許多游戲引擎和與之相關(guān)的工具都會(huì)生成并使用自己的反射數(shù)據(jù)蔫慧。有很多方法做到這一點(diǎn):你可以像 Blender 那樣解析自己的 C/C++ 源代碼來(lái)提取類(lèi)型信息挠乳。你也可以創(chuàng)建一門(mén)獨(dú)立的數(shù)據(jù)描述語(yǔ)言,并編寫(xiě)一個(gè)工具來(lái)生成此語(yǔ)言的 C++ 類(lèi)型定義和反射數(shù)據(jù)姑躲。你還可以使用預(yù)處理器宏和 C++ 模板來(lái)生成運(yùn)行時(shí)反射數(shù)據(jù)睡扬。一旦有了可用的反射數(shù)據(jù),有無(wú)數(shù)種方法基于它編寫(xiě)一個(gè)通用序列化程序黍析。


顯然卖怜,我在此省略了許多細(xì)節(jié)。我只想說(shuō)明確實(shí)有很多種方法來(lái)序列化數(shù)據(jù)阐枣,其中有一些方法是相當(dāng)復(fù)雜的马靠。程序員們通常并不會(huì)像討論其他引擎系統(tǒng)那樣討論序列化,雖然事實(shí)上大部分其他的引擎系統(tǒng)都依賴序列化蔼两。

例如甩鳄,GDC 2017?上的96個(gè)編程會(huì)談中,我統(tǒng)計(jì)了下宪哩,31個(gè)是關(guān)于圖形學(xué)的娩贷,11個(gè)關(guān)于在線的,10個(gè)關(guān)于工具的锁孟,4個(gè)關(guān)于AI的彬祖,3個(gè)關(guān)于物理的茁瘦,2個(gè)關(guān)于音頻的,但是只有1個(gè)直接涉及到了序列化储笑。

總結(jié)

開(kāi)發(fā)游戲引擎甜熔,哪怕規(guī)模很小,也是一項(xiàng)艱巨的任務(wù)突倍。關(guān)于此我還有很多東西可說(shuō)腔稀,但是考慮到博客長(zhǎng)度,老實(shí)來(lái)講羽历,這就是我能想到的最實(shí)用的建議了:迭代開(kāi)發(fā)焊虏、稍微控制一下統(tǒng)一代碼的沖動(dòng)、認(rèn)識(shí)到序列化是一個(gè)很大的課題秕磷,你也許就能根據(jù)此確定出一個(gè)比較合適的策略了诵闭。根據(jù)我的經(jīng)驗(yàn),如果忽略了這些東西澎嚣,它們很可能就會(huì)成為你的絆腳石疏尿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市易桃,隨后出現(xiàn)的幾起案子褥琐,更是在濱河造成了極大的恐慌,老刑警劉巖晤郑,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敌呈,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡造寝,警方通過(guò)查閱死者的電腦和手機(jī)驱富,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)匹舞,“玉大人,你說(shuō)我怎么就攤上這事线脚〈突” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵浑侥,是天一觀的道長(zhǎng)姊舵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)寓落,這世上最難降的妖魔是什么括丁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮伶选,結(jié)果婚禮上史飞,老公的妹妹穿的比我還像新娘尖昏。我一直安慰自己,他們只是感情好构资,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布抽诉。 她就那樣靜靜地躺著,像睡著了一般吐绵。 火紅的嫁衣襯著肌膚如雪迹淌。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天己单,我揣著相機(jī)與錄音唉窃,去河邊找鬼。 笑死纹笼,一個(gè)胖子當(dāng)著我的面吹牛纹份,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播允乐,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼矮嫉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了牍疏?” 一聲冷哼從身側(cè)響起蠢笋,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳞陨,沒(méi)想到半個(gè)月后昨寞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厦滤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年援岩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掏导。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡享怀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趟咆,到底是詐尸還是另有隱情添瓷,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布值纱,位于F島的核電站鳞贷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏虐唠。R本人自食惡果不足惜搀愧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咱筛,春花似錦搓幌、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至沙热,卻和暖如春叉钥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篙贸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工投队, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爵川。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓敷鸦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親寝贡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扒披,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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