Java&Lua游戲服務(wù)器戰(zhàn)斗框架

閑聊

游戲服務(wù)器

現(xiàn)在游戲服務(wù)器已經(jīng)非常普遍了,在游戲行業(yè)早期倾鲫,服務(wù)器大部分都還是C或者C++粗合,以追求更高的執(zhí)行效率萍嬉。而那個時候的Java,還被認為只能處理Web開發(fā)這樣的對延時要求稍低的應(yīng)用隙疚。

誰知道幾年后壤追,編程語言遍地開花,別說Java了供屉,Go行冰,Python,NodeJs(JavaScript)伶丐,甚至PHP都能作為游戲服務(wù)器了悼做。老大哥C++雖然執(zhí)行效率最高,但開發(fā)效率卻很低哗魂,更不用說維護成本和人才成本肛走。而近幾年新起的Go語言算是新起之秀,憑借它語言級的并發(fā)能力獲得一票支持录别。

相對來說朽色,Java算是穩(wěn)步發(fā)展,在十幾年的版本迭代升級中庶灿,把自己的IO纵搁,多線程吃衅,內(nèi)存往踢,GC,效率等一系列都進行全面升級徘层,如今也算是在服務(wù)器這塊站穩(wěn)了腳本峻呕,相對來說在市場上的相關(guān)從業(yè)人員也是最多的。得益于Java完善的開源社區(qū)的發(fā)展趣效,多到數(shù)不清的第三方開源框架也讓Java的生態(tài)變得非常完善了瘦癌。

所以如果現(xiàn)在要開發(fā)一個游戲服務(wù)器,從開發(fā)效率跷敬,人才成本讯私,維護成本和性能等綜合考慮來看,Java再適合不過了西傀。

游戲客戶端

回過頭來看斤寇,游戲的客戶端也一樣經(jīng)歷了一系列變化。最早的PC的游戲客戶端拥褂,可能大部分也都是C或者C++ 進行開發(fā)的娘锁,后來進入Web時代之后,出現(xiàn)了用Flash開發(fā)的頁游饺鹃。后來莫秆,大佬們發(fā)現(xiàn)间雀,大部分的游戲,都是那些東西镊屎,圖形惹挟,動畫,特效缝驳,物理等等匪煌,于是出現(xiàn)了游戲引擎。一開始的游戲引擎可能也都是公司自用党巾。到現(xiàn)在萎庭,大家也基本都知道了,出現(xiàn)了Egret齿拂、Laya驳规、Cocos、Unity署海、Unreal等游戲引擎吗购,它們也分別支持著不同的語言,有JavaScript砸狞、C#捻勉、C++等。

以上這么一大段刀森,其實也只是想說一下踱启,不管是前端還是后端,都有著很多種的語言研底。那能不能讓前后端使用同一種語言呢埠偿?這樣一來,我們寫一套游戲的邏輯代碼榜晦,不就能既跑在客戶端冠蒋,又跑在服務(wù)器了嗎,而不用分別在兩種語言上分別實現(xiàn)了嗎乾胶?

事實上抖剿,這也并不是不可行的,比如C++识窿,C#斩郎,JavaScript這幾種語言,就既能開發(fā)服務(wù)器腕扶,又能開發(fā)客戶端孽拷。然而實際上,在組建團隊的時候半抱,想要找到剛好前后端技術(shù)棧都匹配的技術(shù)人員脓恕,也并非易事膜宋。

Java&Lua方案

我目前的項目,其實就面臨這樣一個問題炼幔,想要讓服務(wù)器和客戶端使用同一個語言開發(fā)同一套游戲戰(zhàn)斗邏輯層代碼:當我們想讓游戲以單機運行的時候秋茫,這套邏輯層代碼可以完全跑在客戶端;而當我們想讓游戲以聯(lián)機運行的時候乃秀,我們就讓這套戰(zhàn)斗邏輯代碼跑在服務(wù)器肛著,僅對客戶端作狀態(tài)同步的表現(xiàn)《逖叮可是我們服務(wù)器是Java枢贿,客戶端是Unity,怎么辦呢刀脏?我們用了一個中間語言——Lua局荚,來作為邏輯層的代碼開發(fā)。Unity中使用Lua還算比較容易愈污,因為它本身是支持啟動Lua虛擬機的耀态。但是要在Java中調(diào)用Lua,就稍微有點頭疼了暂雹。

搜索了網(wǎng)上的解決方案首装,方案不是很多,其中Luaj算是使用起來最順手杭跪,運行效率也相對較高的方案仙逻,不過Luaj使用起來也有很多坑(后面細聊)。
通過Luaj的官網(wǎng)或者github可以詳細了解luaj的相關(guān)API和實現(xiàn)細節(jié)揍魂。

Lua共戰(zhàn)邏輯框架

經(jīng)過研究和討論后桨醋,我們最終得到的前后端的共戰(zhàn)框架開發(fā)模型是這樣的


在實際運行中棚瘟,我們會分別在服務(wù)端和客戶端啟動一套戰(zhàn)斗邏輯Lua代碼现斋,我們可以通過配置分別以兩種模型啟動戰(zhàn)斗:

  1. 單機模型


Client Lua:運行完整戰(zhàn)斗邏輯

這種模式相對比較容易理解,即在客戶端運行完整的戰(zhàn)斗邏輯

  1. 聯(lián)機模型


Client Lua:運行部分戰(zhàn)斗邏輯偎蘸,對服務(wù)器的狀態(tài)同步消息進行本地邏輯校正
Server Lua:運行完整戰(zhàn)斗邏輯庄蹋,并進行狀態(tài)同步廣播

這種模式即狀態(tài)同步模式,主要邏輯都在服務(wù)端運行迷雪,并且通過網(wǎng)絡(luò)通信把運行后的狀態(tài)同步到客戶端限书,而客戶端也可以通過同樣的Lua邏輯代碼進行邏輯的預(yù)演算,待收到服務(wù)端同步過來的狀態(tài)后章咧,再對預(yù)演算的結(jié)果進行校正

Java-Lua 戰(zhàn)斗開源框架

基于這套框架倦西,我對現(xiàn)有的Java&Lua戰(zhàn)斗框架做了獨立于業(yè)務(wù)的抽象,并做了開源赁严。
地址:https://github.com/hjcenry/lua-java-battle

基于luaj實現(xiàn)的java使用lua的戰(zhàn)斗框架

該框架基于luaj的二次封裝實現(xiàn)(https://luaj.sourceforge.net

提供功能

  • luaj基礎(chǔ)接口的調(diào)用封裝
  • 簡化luaj環(huán)境搭建步驟
  • 管理lua戰(zhàn)斗并提供接口
  • lua面向?qū)ο笫褂梅桨?class.lua)
  • lua戰(zhàn)斗框架使用示例
  • luaj踩坑指南

該框架提供Java-Lua的戰(zhàn)斗框架扰柠,有以下優(yōu)缺點

  • 優(yōu)點
  1. 公用邏輯lua代碼:前后端可以基于同一套語言使用同一套戰(zhàn)斗邏輯代碼粉铐,只需要設(shè)計好共戰(zhàn)框架,即可實現(xiàn)一份代碼兩處使用卤档。前后端程序員也可以基于這套框架共同開發(fā)蝙泼,這無論是對于狀態(tài)同步還是幀同步來說,都可以一定程度提升開發(fā)效率劝枣。
  2. luaj框架相比其他java調(diào)用lua方式汤踏,是目前為止效率最高的。
  3. lua代碼無需編譯即可直接使用舔腾,可以通過luaj設(shè)計一套熱更邏輯
  • 缺點踩坑指南):
  1. 占用jvm更多的堆和meta空間

luaj提供兩種編譯器溪胶,luac和luajc。其中l(wèi)uac在load文件之后稳诚,會創(chuàng)建一個LuaClosure對象载荔,其運行過程中會逐行解析lua命令,當然其運行效率也不會太高。
而luajc的原理是通過編譯成Java字節(jié)碼雪隧,并通過它的JavaLoader(繼承ClassLoader)加載到內(nèi)存蕾殴,相當于一次編譯多次運行。
但是熟悉的Java類加載機制的朋友應(yīng)該清楚工扎,這個過程中,JVM會在meta空間創(chuàng)建Klass信息衔蹲,并在ClassLoader保存Klass引用肢娘。與此同時,luaj的JavaLoader也做了一件事:緩存動態(tài)生成的字節(jié)碼byte[]
這種情況下舆驶,啟動一個lua環(huán)境則會增加JVM的堆和meta空間的占用橱健。

  1. 運行效率不如原生Java

luaj的作者描述luaj的運行效率基本和原生lua虛擬機運行效率相當,甚至反超沙廉。但在我的實際測試中拘荡,我沒有拿luaj和原生lua對比,而是拿luaj和原生java相比撬陵,其性能是遠不如原生Java的珊皿。
通過觀察luaj編譯后的源碼也能發(fā)現(xiàn),拿i++這樣一個操作來舉例巨税,原本在Java中蟋定,是可以直接對基本數(shù)據(jù)類型int進行操作的,而在luaj中草添,會對int進行類似Integer的包裝類(LuaInteger)進行包裝驶兜。

  • 既是優(yōu)點也是缺點:
  1. 靈活的lua代碼

靈活是一把雙刃劍,用好了可以大幅提升開發(fā)效率,而用的不好的話抄淑,不僅不能提升開發(fā)效率犀盟,還可能對開發(fā)和維護,都帶來極大的痛苦蝇狼,這非吃某耄考驗底層開發(fā)的能力。

綜上所述:是否使用lua作為Java服務(wù)器的戰(zhàn)斗邏輯代碼迅耘,需要根據(jù)實際情況而定贱枣,它的優(yōu)點是否給你代碼巨大好處,同時你也能忍受它的缺點或者有其他方案克服它的缺點颤专。

歡迎大家使用纽哥,有任何bug以及優(yōu)化需求,歡迎提issue討論

Java Doc

https://hjcenry.com/lua-java-battle/doc/

快速開始

完整代碼示例可以參考BattleDemoService

maven地址

<dependency>
    <groupId>io.github.hjcenry</groupId>
    <artifactId>lua-java-battle</artifactId>
    <version>1.0</version>
</dependency>

1. 指定Lua參數(shù)

LuaInit.LuaInitBuilder luaInit=LuaInit.builder();
luaInit.preScript("print('Hello Lua Battle!!!')");
// 設(shè)置lua根路徑
luaInit.luaRootPath("F:\\project\\lua-java-battle\\src\\main\\lua\\");
// 加載lua調(diào)用接口目錄
luaInit.luaLoadDirectories("interface");
// 加載lua主文件
luaInit.luaLoadFiles("FightManager.lua");
// 展示log
luaInit.showLog(true);

2. 初始化Lua環(huán)境

LuaBattleManager.getInstance().init(luaInit.build());

3. 初始化并緩存Java調(diào)用的Lua方法

// 初始所有需要用到的方法
this.xxxFunction=this.initFunction("XXX.xxx");
this.xxxFunction2=this.initFunction("xxx");

4. 調(diào)用Lua方法

this.xxxFunction.invoke();
this.xxxFunction2.invoke(LuaNumber.valueOf(123));

以上是簡單的示例這個框架應(yīng)該如何使用栖秕,BattleDemoService中提供了一套比較完成Lua戰(zhàn)斗框架的示例

使用方法以及例子

  1. 戰(zhàn)斗Service示例
  2. Lua-Java數(shù)據(jù)轉(zhuǎn)換工具使用示例
  3. Lua-Java庫轉(zhuǎn)換工具使用示例

相關(guān)資料

  1. https://www.lua.org/ lua官網(wǎng)
  2. https://luaj.sourceforge.net luaj官網(wǎng)

Java&Lua工具集合

除了基本接口以外春塌,我提供了Lua和Java之間的一些轉(zhuǎn)換工具類
用于需要互相調(diào)用,或者同一份代碼簇捍,需要兩邊語言都寫的情況

此工具類基于class.lua(src/test/lua/lib/class.lua)的面向?qū)ο竽J?/p>

一只壳、Java調(diào)用庫

Lua中需要調(diào)用java方法的地方,需要java創(chuàng)建調(diào)用類暑塑,然后在lua中調(diào)用吼句,創(chuàng)建對應(yīng)的lua類,能在lua 代碼中更方便的調(diào)用事格。

這個過程可以通過工具生成lua文件惕艳,并加到Lua統(tǒng)一調(diào)用接口ServerLib.lua中 Lua中所有調(diào)用Java方法的接口都通過ServerLib調(diào)用,如SERVER_LIB.battle:invokeBattleResult(
BATTLE_ROOM:GetBattleId(), unit_player:GetPlayerId(), self.battleResult:GetId())

使用方法:

1. 新建Java調(diào)用庫驹愚,增加類注解@LuaServerLib

參數(shù)名 描述 默認值
fieldName ServerLib字段名 Java類名首字母小寫
className lua類名 Java類名
fileDir lua文件目錄远搪,以luaRootPath為根路徑開始 工具類同目錄下:~/src/test/java/lua/
comment 注釋
addToServerLib 是否添加到ServerLib

2. 對需要調(diào)用的靜態(tài)方法增加方法注解@LuaServerLibFunc

參數(shù)名 描述 默認值
comment 注釋
returnComment 返回注釋

3. 對方法內(nèi)的參數(shù)增加參數(shù)注解@LuaParam

參數(shù)名 描述 默認值
comment 注釋
value lua字段名

因為luaj編譯后的class的字段名都變成arg0,arg1了,所以不加@LuaParam注解生成的參數(shù)名都不認識

4. 運行工具類:lua.LuaServerLibFileConverter逢捺,增加VM參數(shù)指定lua路徑

-DluaRootPath=lua項目根路徑
-DtemplateFilePath=模板文件路徑(默認取框架自帶模板)
-DjavaScanPackage=要掃描的Java包路徑(默認com.hjc)
-DserverLibFilePath=ServerLib文件路徑(默認Lib\\Server)

5. 刷新IDEA:File -> Reload All from Disk

參考代碼:


/**
 * lua戰(zhàn)斗核心調(diào)用Java類
 * <p>
 * 這個類里的接口和ServerLuaBattle.lua映射
 * </p>
 *
 * @author hejincheng
 * @version 1.0
 * @date 2022/2/16 18:55
 **/
@LuaServerLib(fieldName = "battle", className = "ServerLuaBattle", fileDir = "Lib/Server/")
public class LuaBattleFunction {

    /**
     * Lua腳本調(diào)用發(fā)送消息
     *
     * @param uid      玩家id
     * @param header   消息號
     * @param luaTable 推送參數(shù)
     */
    @LuaServerLibFunc(comment = "Lua腳本調(diào)用發(fā)送消息")
    public static void invokeSendMessageByLua(@LuaParam(value = "uid", comment = "玩家id") int uid,
                                              @LuaParam(value = "header", comment = "消息號") int header,
                                              @LuaParam(value = "luaTable", comment = "消息體") LuaTable luaTable) {
        IHumanService humanService = GameServiceManager.getService(IHumanService.class);
        if (humanService == null) {
            Log.battleLogger.error(String.format("%d.LuaBattle.invokeSendMessageByLua.server.err - header[%d].luaTable[%s]", uid, header, luaTable));
            return;
        }
        Human human = humanService.getHuman(uid);
        if (human == null) {
            Log.battleLogger.error(String.format("%d.LuaBattle.invokeSendMessageByLua.err.player.null - header[%d].luaTable[%s]", uid, header, luaTable));
            return;
        }
        human.push(header, luaTable);
    }
}

生成lua文件:

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by Administrator.
--- DateTime: 2022-08-22 22:57:29
---
--- 通過Java工具類自動生成谁鳍,請勿修改,重新生成會被覆蓋
---

require "Lib/class"

---@class ServerLuaBattle : table
ServerLuaBattle = class(nil, 'ServerLuaBattle');

function ServerLuaBattle:ctor()
end

-- 獲取戰(zhàn)斗核心
---@param battleId number 戰(zhàn)斗id
---@type function
---@return any
---@public
function ServerLuaBattle:getFightCoreLua(battleId)
    return
end

-- Lua腳本調(diào)用發(fā)送消息
---@param uid number 玩家id
---@param header number 消息號
---@param luaTable table 消息體
---@type function
---@return void
---@public
function ServerLuaBattle:invokeSendMessageByLua(uid, header, luaTable)
    return
end

-- Lua腳本調(diào)用廣播消息
---@param raidId number 副本id
---@param header number 消息號
---@param luaTable table 推送參數(shù)
---@param includeServer boolean 廣播服務(wù)端邏輯核
---@type function
---@return void
---@public
function ServerLuaBattle:invokeBroadcastMessageByLua(raidId, header, luaTable, includeServer)
    return
end

return ServerLuaBattle;

生成ServerLib.lua文件:

---
--- Generated by EmmyLua(https://github.com/EmmyLua)
--- Created by Administrator.
--- DateTime: 2022-08-22 22:57:30
---
--- 通過Java工具類自動生成蒸甜,請勿修改棠耕,重新生成會被覆蓋
---
--- 服務(wù)端Java調(diào)用庫

require "Lib/class"

---@class ServerLib : table
---@field battle ServerLuaBattle
---@field logTool ServerLogTool
ServerLib = class(nil, 'ServerLib');

function ServerLib:ctor()
    self.battle = luajava.bindClass("com.hjc.demo.convert.lib.LuaBattleFunction")
    self.logTool = luajava.bindClass("com.hjc.lua.log.LuaLogTool")
end

二、Java數(shù)據(jù)類

有的數(shù)據(jù)柠新,需要服務(wù)端全局共享,不能每場戰(zhàn)斗都獨一份lua數(shù)據(jù)辉巡,這種情況下可以在Java創(chuàng)建共享數(shù)據(jù)恨憎,這樣的Model類可以通過工具生成需要的lua文件。

使用方法:

1. 新建Java調(diào)用庫,增加類注解@LuaServerModel

參數(shù)名 描述 默認值
className lua類名 Java類名
fileDir lua文件目錄憔恳,以~/為根路徑開始 工具類同目錄下:~/src/test/java/lua/
comment 注釋

2. 運行工具類:lua.LuaServerModelFileConverter瓤荔,增加VM參數(shù)指定lua路徑

-DluaRootPath=lua項目根路徑
-DtemplateFilePath=模板文件路徑(默認取框架自帶模板)
-DjavaScanPackage=要掃描的Java包路徑(默認com.hjc)

3. 刷新IDEA:File -> Reload All from Disk

參考代碼: FallDictData.java

package com.hjc.helper;

import com.hjc.annotation.LuaParam;
import com.hjc.annotation.LuaServerModel;
import lombok.Data;

/**
 * @author hejincheng
 * @version 1.0
 * @date 2022/3/7 17:19
 **/
@LuaServerModel(className = "FallDictData", comment = "掉落表數(shù)據(jù)", fileDir = "Battle/Logic/Room/BattleObject/Fall")
@Data
public class FallDictData {

    @LuaParam(comment = "掉落條件")
    private int conditionType;

    @LuaParam(comment = "掉落條件參數(shù)")
    private float conditionParam;

    @LuaParam(comment = "生效次數(shù)")
    private int activeTimes;

    @LuaParam(comment = "掉落id")
    private int fallObjectId;

    @LuaParam(comment = "掉落數(shù)量")
    private int fallCount;

    @LuaParam(comment = "冷卻時間")
    private float cdLimitTime;

}

生成的lua:

--- 掉落表數(shù)據(jù)

require "Lib/class"

---@class FallDictData : table
---@field conditionType number 掉落條件
---@field conditionParam number 掉落條件參數(shù)
---@field activeTimes number 生效次數(shù)
---@field fallObjectId number 掉落id
---@field fallCount number 掉落數(shù)量
---@field cdLimitTime number 冷卻時間
FallDictData = class(nil, 'FallDictData');

function FallDictData:ctor(_conditionType, _conditionParam, _activeTimes, _fallObjectId, _fallCount, _cdLimitTime)
    self.conditionType = _conditionType
    self.conditionParam = _conditionParam
    self.activeTimes = _activeTimes
    self.fallObjectId = _fallObjectId
    self.fallCount = _fallCount
    self.cdLimitTime = _cdLimitTime
end

return FallDictData;

目前該框架底層基于第三方開源庫Luaj實現(xiàn),然后它是用起來最順手的钥组,但它也存在很多致命的缺點

Luaj踩坑指南

1. 不要創(chuàng)建多份Globals對象

如果你想每一場戰(zhàn)斗输硝,都啟動不同的Lua環(huán)境,那我勸你最好放棄這個想法程梦。因為每啟動一個luaj的Globals点把,load一次lua工程,他就會把lua工程編譯一次屿附,編譯的過程中郎逃,除了ClassLoader的load中本身對meta空間的占用外,luaj還會對轉(zhuǎn)換過來的字節(jié)碼byte[]進行緩存挺份,最后當你啟動上百上千場戰(zhàn)斗的時候褒翰,你會發(fā)現(xiàn)你的meta空間和堆空間根本不夠用(如果你的機器能支撐你啟動這么多Globals)
在我的開源框架中,我已經(jīng)把Globals的創(chuàng)建和初始化放到了全局Manager中匀泊,使用的時候只需要考慮你要調(diào)用的lua方法是什么即可优训。

2.盡量少的進行數(shù)學運算

我知道,這當然是不可能的各聘,戰(zhàn)斗邏輯怎么著也得進行數(shù)學運算的型宙。然而事實是,luaj會對每一個加減乘除進行裝箱拆箱伦吠,假如你有一個公式是這樣的

m=a + b * c / d-e

那么要計算這個m妆兑,它會new出來4個LuaDouble或LuaInteger對象。對象少量的計算毛仪,這還是可接收的搁嗓,可一旦涉及到大量的數(shù)學運算,這段lua代碼編譯出來Java代碼就會new出一大堆的臨時對象箱靴,對于JVM來說腺逛,年輕代的GC壓力就會變得很大。

3.盡量使用lua 5.2語法

這點其實還好衡怀,只要統(tǒng)一了語法棍矛,基本就沒什么太大問題,偶爾有一兩個新特性抛杨,我們甚至可以通過自定義的構(gòu)建Globals來注入一些我們自定義的方法够委。

后續(xù)計劃

鑒于目前所遇到的困難,后續(xù)打算把我開源框架的底層luaj進行優(yōu)化或者替換怖现。
目前對于數(shù)學運算茁帽,還是有辦法解決的玉罐,比如通過對所有的公式進行集中的提取,然后用java代碼繼承VaragsFunction來進行Java版本的實現(xiàn)潘拨,并覆蓋原來編譯的lua代碼吊输,后續(xù)也可以考慮在我的框架中,提供代碼注入的接口铁追。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末季蚂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子琅束,更是在濱河造成了極大的恐慌扭屁,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狰闪,死亡現(xiàn)場離奇詭異疯搅,居然都是意外死亡,警方通過查閱死者的電腦和手機埋泵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門幔欧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丽声,你說我怎么就攤上這事礁蔗。” “怎么了雁社?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵浴井,是天一觀的道長。 經(jīng)常有香客問我霉撵,道長磺浙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任徒坡,我火速辦了婚禮撕氧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喇完。我一直安慰自己伦泥,他們只是感情好,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布锦溪。 她就那樣靜靜地躺著不脯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刻诊。 梳的紋絲不亂的頭發(fā)上防楷,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音坏逢,去河邊找鬼域帐。 笑死赘被,一個胖子當著我的面吹牛是整,可吹牛的內(nèi)容都是我干的肖揣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼浮入,長吁一口氣:“原來是場噩夢啊……” “哼龙优!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起事秀,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤彤断,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后易迹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宰衙,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年睹欲,在試婚紗的時候發(fā)現(xiàn)自己被綠了供炼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡窘疮,死狀恐怖袋哼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闸衫,我是刑警寧澤涛贯,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蔚出,受9級特大地震影響弟翘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜骄酗,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一稀余、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酥筝,春花似錦滚躯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宙帝,卻和暖如春丧凤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背步脓。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工愿待, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留浩螺,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓仍侥,卻偏偏與公主長得像要出,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子农渊,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

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