Unity手游開發(fā)札記——我們是如何使用Lua來開發(fā)大型游戲的蔽豺?(下)

粘貼的原因,代碼比較亂棘利,原文請查看:https://zhuanlan.zhihu.com/p/34666702

上一部分的文章聊了一下我對于使用Lua的一些觀點橱野,但是核心的內(nèi)容還是在說如何彌補Lua語言在開發(fā)中的不足。一些朋友表示對于觀點不敢茍同善玫,我也希望不贊同的朋友可以多討論水援,畢竟我也是屁股決定腦袋,在用多這門語言的時候總是覺得很多東西是習(xí)慣和順手的茅郎。從心態(tài)上說我也是很開放的蜗元,比如自己會去關(guān)注ET這樣純C#的框架,想看看他們是如何做的系冗,比用Lua好在哪里奕扣。

本來第二部分的開始計劃是想先聊聊如何劃分Lua和C#這兩種語言的職責(zé),但我想還是直奔主題掌敬,來聊下那些可以佐證我前面拋出的兩個觀點的那些方面惯豆。

3. 讓Lua代碼更好調(diào)試

游戲開發(fā)過程中總會寫出或者遇到各種各樣的bug,如何快速地定位和修復(fù)bug涝开,也會對于開發(fā)效率有很大的影響循帐。針對這點,我前面提到的一個觀點是:

使用Lua這樣的腳本語言舀武,調(diào)試bug的效率并不低拄养,甚至可能比C#這樣的靜態(tài)語言還要高

我想從以下幾個方面來聊聊這個點银舱。

3.1 節(jié)省編譯時間

像Lua這樣的動態(tài)語言是解釋執(zhí)行的瘪匿,因此和靜態(tài)語言相比,它們雖然運行時效率比較低寻馏,但是不需要編譯的過程棋弥。這對于需要頻繁修改代碼,然后運行游戲測試結(jié)果的的bug修復(fù)過程诚欠,本身就是優(yōu)勢顽染。比如之前做引擎C++代碼的修改漾岳,無論是使用increbuild這樣分布式的編譯工具也好,還是購買更強力的機器也好粉寞,一次編譯鏈接的時間總會需要等上那么一會尼荆,修改了頭文件就更加痛苦……

嗯,曾經(jīng)感同身受

當(dāng)然唧垦,Unity的C#語言編譯通常沒有這么夸張捅儒,可以通過將部分模塊提前編譯為dll,或者將不常用的組件(比如第三方庫)放置到Unity特殊的目錄下來加快編譯過程振亮。然而巧还,到項目中后期,我們的體驗是修改C#代碼之后坊秸,切換到unity總需要那么幾秒鐘的時間來進行編譯麸祷。而修改Lua代碼后,是不需要這幾秒鐘的等待時間的妇斤,重啟游戲是絲般順滑摇锋,哈哈丹拯。

有些朋友可能會覺得這幾秒鐘的等待無所謂站超,而對于一個用慣了腳本語言進行邏輯開發(fā)的人,可能會感受到這其中的差異乖酬。當(dāng)然不會有人為了節(jié)約這點時間而去在Unity中集成Lua語言死相,它只是使用腳本語言一個順帶的福利而已。

當(dāng)然咬像,如果有朋友知道如果減少修改C#編譯時間的方法算撮,也歡迎指教~我們項目也很需要這樣的經(jīng)驗。

3.2 斷點調(diào)試支持

前文已經(jīng)說了县昂,Lua也是支持?jǐn)帱c調(diào)試的肮柜,有朋友評論里分享了他們的調(diào)試方法:

感謝hhy分享的調(diào)試方法

我們團隊內(nèi)部的是使用VS Code+luaide來進行斷點調(diào)試的。使用過程中偶爾遇到過一些變量值顯示不正確的異常情況倒彰,但整體上基本可以滿足斷點調(diào)試的需求审洞。

在調(diào)試方面,個人體驗Lua的確不如C#這樣的代碼方便待讳,需要自己集成調(diào)試插件芒澜,然后啟動調(diào)試的時候還要有額外的步驟,而當(dāng)已經(jīng)確認(rèn)要使用Lua之后创淡,盡早讓團隊的成員學(xué)習(xí)和熟悉斷點調(diào)試的方法和工具痴晦,可以節(jié)省掉不少調(diào)試的時間。

3.3 基于Reload的調(diào)試方法

想象一下這樣的調(diào)試過程——

運行游戲過程中琳彩,你發(fā)現(xiàn)一些問題誊酌,根據(jù)經(jīng)驗和代碼邏輯你大概定位到問題原因部凑,在IDE中修改一些代碼,或者添加一些log碧浊,按下Ctrl+S保存代碼砚尽,游戲中的邏輯自動被替換為修改后的代碼邏輯,不需要重新啟動游戲辉词,在游戲中重新觸發(fā)相關(guān)的邏輯必孤,就可以看到新的log輸出,或者驗證你的修改的代碼是否已經(jīng)修復(fù)了之前的問題瑞躺。

這是我在網(wǎng)易時敷搪,團隊內(nèi)已經(jīng)在大范圍地使用的調(diào)試方法,而基于這種方法幢哨,很多同學(xué)都懶得去學(xué)習(xí)和部署斷點調(diào)試的工具赡勘。

越是大型的游戲,啟動越慢捞镰,我們團隊也做了一些事情來加快編輯器下的游戲啟動時間闸与,比如:

編輯器下關(guān)閉閃屏過程;

提供自動登錄岸售、自動創(chuàng)建角色等功能践樱;

提供游戲全局加速、角色速度加速等GM指令凸丸;

……

這些工作都是為了提高整個程序團隊乃至整個游戲研發(fā)團隊的工作效率拷邢,因為重新啟動游戲是一件在游戲開發(fā)中太過頻繁的操作。而當(dāng)一個程序需要嘗試重現(xiàn)并修復(fù)一個bug的時候屎慢,可能需要多次這樣的過程瞭稼,而這也是基于log調(diào)試最為令人詬病的地方——你可能無法一次就精準(zhǔn)地知道要在哪些地方添加log,還要根據(jù)log的輸出結(jié)果調(diào)整log的位置或者輸出的信息內(nèi)容腻惠,如果這一過程都需要不斷地重啟游戲环肘,那調(diào)試效率之低可以想象。

腳本語言提供的Reload功能集灌,可以幫我們實現(xiàn)無需重啟進程就可以更新代碼的效果悔雹。在我們工程中使用了Tango這個非常古老的庫來做進程間的跨Lua虛擬機訪問,它的底層也是基于Socket來實現(xiàn)的绝页,整個更新流程的結(jié)構(gòu)示意圖如下:

基于Tango的代碼自動Reload結(jié)構(gòu)示意圖

在這個流程中荠商,需要自己開發(fā)一個簡單的IDE插件,或者把IDE的快捷鍵映射到本地的一個執(zhí)行程序上续誉。在需要reload的時候莱没,獲取要reload的文件,比如是當(dāng)前IDE打開的文件酷鸦,然后通過Tango的客戶端嘗試連接本地的Tango服務(wù)器饰躲,如果連接成功牙咏,就將reload的請求發(fā)送過去,游戲進程中開啟的Tango服務(wù)器收到請求之后嘹裂,執(zhí)行reload操作妄壶,代碼就被更新了。

需要說明的是寄狼,這個流程看起來并不復(fù)雜丁寄,但是也經(jīng)歷過幾個步驟的演化過程:

首先在最初的時候,只提供了reload模塊的功能泊愧,IDE中修改了代碼之后伊磺,需要手動在游戲內(nèi)通過GM指令或者Telnet上去的Shell控制臺執(zhí)行Reload操作;

后來引入了rpyc和Tango這樣的跨進程通訊的模塊之后删咱,在外部制作了一個簡單的ui工具屑埋,可以記錄之前操作過的指令,方便快速reload痰滋;

最后才引入了IDE插件摘能,將reload功能直接集成到保存操作中,實現(xiàn)自動Reload敲街。

Tango雖然遠(yuǎn)沒有Python的rpyc好用团搞,經(jīng)過簡單的改造之后也基本滿足我們的需求。如果了解有更好用庫的朋友非常歡迎推薦~具體的實現(xiàn)細(xì)節(jié)不做過多的討論聪富,這里只聊一下reload的實現(xiàn)莺丑。

在Python中原生就有reload函數(shù)著蟹,Lua中的實現(xiàn)要通過loadfile或者loadstring這樣的函數(shù)來實現(xiàn)墩蔓。當(dāng)然,你有可以暴力地刪除掉原來已經(jīng)require過的模塊萧豆,然后重新require它奸披,但這可能只能夠正確處理非常少的情況,畢竟其他模塊可能已經(jīng)保留的對于原模塊的引用涮雷。一個完備的reload過程需要保留之前模塊中的上下文數(shù)據(jù)阵面,只替換對應(yīng)的邏輯和需要添加的數(shù)據(jù)內(nèi)容,這樣才能能夠保證進程不重啟的條件下洪鸭,下次執(zhí)行的正確性样刷。

這里截取部分代碼來說明這個流程的復(fù)雜性和基本原理:

-- Reload.lua-- 并沒有給出完整代碼,僅供參考localOld=_ImportModule[PathFile]ifOldandnotReloadthenreturnOldend-- 先loadfile再clear環(huán)境localfunc,err=loadfile(PathFile)ifnotfuncthenlogerror(func,err)returnfunc,errend-- 第一次載入览爵,不存在更新的問題ifnotOldthen_ImportModule[PathFile]={}localNew=_ImportModule[PathFile]-- 設(shè)置原始環(huán)境setmetatable(New,{__index=_G})localret=setfenv(func,New)()_ImportResult[PathFile]=retreturnNewend-- 先緩存原來的舊內(nèi)容localOldCache={}fork,vinpairs(Old)doOldCache[k]=vOld[k]=nilend-- 使用原來的module作為fenv置鼻,可以保證之前的引用可以更新到localret=setfenv(func,Old)()_ImportResult[PathFile]=ret-- 更新以后的模塊, 里面的table的reference將不再有效,需要還原.localNew=Old-- 還原table(copy by value)fork,vinpairs(OldCache)dolocalTmpNewData=New[k]-- 默認(rèn)不更新New[k]=vifTmpNewDatatheniftype(v)=="table"theniftype(TmpNewData)=="table"then-- 如果是一個class則需要全部更新,其他則可能只是一些數(shù)據(jù)蜓竹,不需要更新ifv.__IsClassthenlocalmt=getmetatable(v)ifrawget(v,"__IsClass")then-- 是class要更新其mtlocalold_mt=v.mtlocalindex=old_mt.__indexReplaceTbl(v,TmpNewData)v.mt=old_mtold_mt.__index=indexendendlocalmt=getmetatable(TmpNewData)ifmtthensetmetatable(v,mt)endend-- 函數(shù)段必須用新的elseiftype(v)=="function"thenNew[k]=TmpNewDataendendend

整個Reload的過程中需要考慮的內(nèi)容比較多箕母,但是即便如此储藐,對于那些比如local func = xxx.foo這樣被緩存的函數(shù),依然可能存在更新不到的情況嘶是,對于某些被緩存在閉包中的函數(shù)钙勃,也有類似的問題。

相比于客戶端調(diào)試用的Reload邏輯聂喇,如果是使用Lua語言實現(xiàn)邏輯的服務(wù)端辖源,當(dāng)需要Refresh邏輯的時候,需要更加完備的更新考慮希太,所以如果對這塊感興趣的朋友同木,可以找一些開源的Lua服務(wù)端框架來看下,看看是否有可以參考的代碼跛十。而如果僅僅是調(diào)試使用彤路,則可以使用相對少的精力實現(xiàn)最為核心的基礎(chǔ)功能,做到對于大部分函數(shù)的重新加載就夠用了芥映。

不僅僅針對Lua語言洲尊,在使用任何語言進行游戲開發(fā)的過程中,善用語言的特性奈偏,在加上不斷改進的心坞嘀,就可以做出很多提升團隊效率的工具。

3.4 Lua內(nèi)存數(shù)據(jù)的查看和修改

在開發(fā)和調(diào)試過程中惊来,經(jīng)常會遇到需要查看內(nèi)存數(shù)據(jù)的需求丽涩,一方面Unity在編輯器模式下提供了非常便利的場景數(shù)據(jù)查看的方式,在設(shè)備上也可以集成之前推薦過很多次的插件Hdg Remote Debug裁蚁,另外一方面C#和Lua的內(nèi)存通過斷點調(diào)試工具來進行查看矢渊。

在我們游戲的開發(fā)中,基于Tango制作了另外的內(nèi)存查看和修改工具枉证。原理非常簡單矮男,基于Tango跨Lua進程的特性,配合一個基于pyQT的gui界面室谚,就可以做到:

直接輸入代碼輸出和修改游戲內(nèi)的數(shù)據(jù)毡鉴;

可以直接指定游戲內(nèi)的一個table獲取代碼,然后查看其所有內(nèi)容秒赤。

通過ip訪問可以直接連接移動設(shè)備進行操作猪瞬。

我們在用的工具截圖如下:

Lua內(nèi)存查看和修改工具截圖

截圖中左側(cè)是內(nèi)存對象的逐層展示功能,可以看到當(dāng)前內(nèi)存中通過代碼獲取的某個table中的具體信息入篮,比如QA要驗證角色數(shù)值計算的正確性陈瘦,就會使用這個工具來進行查看。右側(cè)更多的提供給程序崎弃,用于執(zhí)行一些代碼和邏輯甘晤,直接查看游戲進程中的Lua數(shù)據(jù)含潘,并可以進行實時的修改。同時右側(cè)的功能還有一個單純的Shell版本线婚,基于iLua可以做補全等操作遏弱,方便很多。

3.5 小結(jié)

上述的這些工具的開發(fā)部署的確會花費團隊一些時間和精力塞弊,但是有了這些工具漱逸,不斷根據(jù)需求進行完善和改進,可以讓程序團隊可以更加高效地進行錯誤的調(diào)試和修復(fù)游沿,提高整個團隊的工作效率饰抒。

4. 更快修復(fù)線上問題

對于線上問題的修復(fù),Patch的部分其實很多項目大同小異诀黍,不過這里面細(xì)節(jié)也有很多袋坑,有時間的時候可以整理和分享一下我們在這部分做的工作。這篇文章的線上問題修復(fù)我們來著重聊聊Hotfix眯勾。

前文描述觀點的時候已經(jīng)說了Hotfix可以實現(xiàn)的效果枣宫,我其實不知道業(yè)內(nèi)使用這一方式進行線上問題修復(fù)的普及程度是怎么樣的。在網(wǎng)易的時候因為大家都用腳本吃环,Hotfix是游戲上線的標(biāo)配功能也颤,出來創(chuàng)業(yè)之后,大家在聊熱更新什么的郁轻,從來不會單獨提這塊翅娶,所以我并不知道這種修復(fù)方式在行業(yè)內(nèi)是正在被廣泛應(yīng)用呢,還是并不常用的一種方式好唯。

4.1 Hotfix的優(yōu)勢

現(xiàn)在手游開發(fā)的周期越來越短竭沫,開發(fā)速度要求越來越快,往往會在上線之后遇到一些影響了玩家體驗或者阻礙了玩家流程的客戶端bug需要線上修復(fù)渠啊,這時候修改代碼制作patch然后放出去输吏,對于已經(jīng)在線的玩家,如果是強制patch的方式替蛉,要把玩家踢掉線讓其重啟客戶端或者到Patch更新界面去進行Patch的下載操作,這其實對于玩家的體驗非常不好拄氯。而Hotfix的優(yōu)勢正是在玩家無感知的情況下修復(fù)緊急的bug躲查。

4.2 基本原理

Hotfix的基本原理依然是基于動態(tài)語言的Reload功能,更加準(zhǔn)確的說是Function Reload译柏。下圖簡單描述了整個Hotfix的流程:

Hotfix的應(yīng)用流程

更加具體地可以描述為:

程序發(fā)現(xiàn)要修復(fù)的bug镣煮,編寫特殊的Hotfix代碼進行修復(fù),測試通過后上傳到svn服務(wù)器鄙麦;

通過發(fā)布指令典唇,將svn上更新后的Hotfix代碼同步到服務(wù)器上镊折;

服務(wù)器發(fā)現(xiàn)Hotfix代碼有更新,則將其壓縮序列化后通過socket發(fā)送給所有在線的客戶端介衔,同時帶上字符串的MD5值供客戶端驗證恨胚;

客戶端收到Hofix消息之后,首先反序列化數(shù)據(jù)得到代碼內(nèi)容炎咖,校驗MD5值之后赃泡,如果和本地已經(jīng)執(zhí)行過的Hotfix的MD5值不同,則執(zhí)行替換邏輯乘盼,并記錄當(dāng)前已經(jīng)執(zhí)行過Hotfix的MD5值升熊,如果相同則不再執(zhí)行;

客戶端連接服務(wù)器的時候會主動請求一次Hofix绸栅。

4.3 實現(xiàn)方式

執(zhí)行Hotfix執(zhí)行的代碼非常簡單级野,基于loadstring函數(shù)即可:

localf=loadstring(GameContext.HotfixData)iffthenClientUtils.trycall(f)end

這里的實現(xiàn)就沒有reload那么復(fù)雜,但是也是有一定的限制粹胯,比如local的函數(shù)或者在閉包內(nèi)的函數(shù)依然很難做正確的hotfix勺阐,需要編寫特殊的Hotfix代碼。

而如果使用了類似于我們這樣復(fù)雜的Class結(jié)構(gòu)矛双,有大量Function的緩存的話渊抽,需要額外的處理函數(shù)來保證這些緩存的函數(shù)對象被正確替換,比如針對我前文提供的Class方式议忽,需要這樣的代碼來執(zhí)行Class級別的函數(shù)替換:

-- 類的繼承關(guān)系數(shù)據(jù)懒闷,用于處理Hotfix等邏輯。-- 數(shù)據(jù)形式:key為ClassType栈幸,value為繼承自它的子類列表愤估。local__InheritRelationship={}localfunction__getInheritChildren(classType,output)ifoutput[classType]thenreturnelseoutput[classType]=trueif__InheritRelationship[classType]thenforindex,childTypeinpairs(__InheritRelationship[classType])do__getInheritChildren(childType,output)endendendendlocalfunction__HotfixClassFunction(classType,funcName,newFunc)localclassVtbl=__ClassTypeList[classType]ifclassVtblandfuncNameandnewFuncthenlocalpreFunc=classVtbl[funcName]classVtbl[funcName]=newFunclocalchildren={}__getInheritChildren(classType,children)forreplaceClass,valueinpairs(children)dolocalvtbl=__ClassTypeList[replaceClass]ifrawget(vtbl,funcName)==preFuncthenvtbl[funcName]=newFuncendifreplaceClass~=classTypethenlocalsuper=replaceClass.superifrawget(super,funcName)==preFuncthensuper[funcName]=newFuncendendendendendif(notIsGLDeclared("HotfixClassFunction"))or(notHotfixClassFunction)thenGLDeclare("HotfixClassFunction",__HotfixClassFunction)end

給出一段簡單的Hotfix代碼示例如下:

-- hotfixlocalfunctionWorldEntity_destroy(self)-- New Code Hereend-- 替換函數(shù)localfunctionReplaceFunc()HotfixClassFunction(WorldEntity,"destroy",WorldEntity_destroy)end-- 替換數(shù)據(jù)localfunctionReplaceData()ResDungeon[1011]['member_num']=1ResDungeon[1012]['member_num']=1endReplaceFunc()ReplaceData()

注意:如果你像我們一樣在戰(zhàn)斗中使用了幀同步的方式,對于戰(zhàn)斗中邏輯或者數(shù)據(jù)的Hotfix一定要非常小心速址,一場戰(zhàn)斗中的玩家玩焰,無論是開始就進入的還是在斷線重連上的時候,都必須使用同樣的Hotfix代碼芍锚,否則不同客戶端幀同步計算的結(jié)果就不同了昔园。

4.4 小結(jié)

如果你們團隊在外放前擁有更長的測試周期,擁有更加專業(yè)的團隊成員并炮,可以盡量減少線上問題出現(xiàn)的概率默刚,那大家都會非常開心,也就可能不會需要我們正在使用的這種Hofix修復(fù)線上問題的方法逃魄。如果你們在使用Lua或者其他的腳本語言荤西,具有動態(tài)reload代碼的特性,你也可以實現(xiàn)一下這種fix方式,以備不時之需邪锌。當(dāng)然勉躺,還是建議對于這套東西多加測試,更加希望所有團隊都不需要修復(fù)這么緊急的線上問題~

無痛的人流可能并不存在觅丰,但是對于玩家無感的bug修復(fù)饵溅,是可以存在的。

5. 分享經(jīng)驗舶胀,而不爭辯好壞

回頭來看概说,寫這篇文章最初的心態(tài)帶著那么一點點為Lua“正名”的意思,想告訴大家其實Lua雖然的確有些難用的地方嚣伐,但是經(jīng)過一些工具構(gòu)建糖赔,加上對于動態(tài)語言特性的善用,可以做很多事情轩端,在某些方面甚至可以比靜態(tài)語言做得更好放典。

回頭看這個想法有些可笑,其實一個語言真正好用與否基茵,完全取決于用的人奋构。團隊的歷史經(jīng)驗、團隊的整體實力拱层、所要做的游戲類型等等不同弥臼,都會有完全不同的結(jié)論。無論什么樣的框架根灯,什么樣的語言径缅,什么樣的技術(shù),都只有最合適的烙肺,沒有最好的纳猪。又或者換個角度,只有經(jīng)過項目和團隊的磨礪桃笙,技術(shù)這把雙刃劍氏堤,朝向問題的那面才能更鋒利,朝向自己的那面才會更加圓潤搏明。

而我能做的鼠锈,是把我覺得我們項目中對于Lua的使用較好的部分分享給你,如果你必須使用Lua熏瞄,我希望你能用得舒服一點脚祟,如果你依然覺得它是“狗屎”,那就選擇合適你的强饮。

之前我習(xí)慣使用Python,出來創(chuàng)業(yè)之后要使用Lua開始還有些膽怯为黎,花了一個多月的時間和團隊一起構(gòu)建上述的這些基礎(chǔ)框架邮丰、調(diào)試工具以及維護流程行您,并用一個項目的時間來改進磨礪它們。現(xiàn)在剪廉,一年多之后娃循,我覺得我們把它用得挺順手了。但我依然告訴自己要保持開放的態(tài)度斗蒋,下一個項目捌斧,我們可能繼續(xù)使用Lua,也可能會嘗試ILRuntime泉沾,又或者其他什么新鮮的東西捞蚂。我相信,對于之前經(jīng)驗的總結(jié)跷究、工具的改進讓我們走得更穩(wěn)姓迅,對于不熟悉的技術(shù)的學(xué)習(xí)和討論讓我們跑得更快。

對于技術(shù)俊马,放寬心態(tài)丁存,分享經(jīng)驗心得而不爭辯孰好孰壞,這是我現(xiàn)在的態(tài)度柴我。期望讀者可以跟我分享你們的經(jīng)驗~

2018年3月18日夜 于杭州家中

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末解寝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子艘儒,更是在濱河造成了極大的恐慌聋伦,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彤悔,死亡現(xiàn)場離奇詭異嘉抓,居然都是意外死亡,警方通過查閱死者的電腦和手機晕窑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門抑片,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杨赤,你說我怎么就攤上這事敞斋。” “怎么了疾牲?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵植捎,是天一觀的道長。 經(jīng)常有香客問我阳柔,道長焰枢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮济锄,結(jié)果婚禮上暑椰,老公的妹妹穿的比我還像新娘。我一直安慰自己荐绝,他們只是感情好一汽,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著低滩,像睡著了一般召夹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上恕沫,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天监憎,我揣著相機與錄音,去河邊找鬼昏兆。 笑死枫虏,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爬虱。 我是一名探鬼主播隶债,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼跑筝!你這毒婦竟也來了死讹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤曲梗,失蹤者是張志新(化名)和其女友劉穎赞警,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虏两,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡愧旦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了定罢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笤虫。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祖凫,靈堂內(nèi)的尸體忽然破棺而出琼蚯,到底是詐尸還是另有隱情,我是刑警寧澤惠况,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布遭庶,位于F島的核電站,受9級特大地震影響稠屠,放射性物質(zhì)發(fā)生泄漏峦睡。R本人自食惡果不足惜翎苫,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赐俗。 院中可真熱鬧拉队,春花似錦弊知、人聲如沸阻逮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叔扼。三九已至,卻和暖如春漫雷,著一層夾襖步出監(jiān)牢的瞬間瓜富,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工降盹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留与柑,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓蓄坏,卻偏偏與公主長得像价捧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子涡戳,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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