Godot游戲開發(fā)實(shí)踐之三:容易被忽視的Resource

一摆昧、前言

首先绅你,特大喜訊忌锯,奔走相告, Godot 愛好者們又有新的窩了——我們國人自建的 Godot 論壇: Godot中文社區(qū)已經(jīng)正式開放张咳,這里有一手的開發(fā)資源晶伦,最新的科技動向,開發(fā)上有啥問題可以隨時(shí)發(fā)帖族沃,歡迎大家隨時(shí)到論壇來討論脆淹、交流和學(xué)習(xí)游戲開發(fā)的最新技術(shù)盖溺。 :grin:

那么,回過頭來昆禽,今天要探討的話題是 Godot 中極容易被新手忽視的 Resource 資源類。開發(fā)過 Unity 游戲的同學(xué)們知道一個(gè)叫 ScriptableObject 的很有用的類捡硅,它可以用于數(shù)據(jù)的包裝壮韭,在不少場合中應(yīng)該是非常有用的喷屋,那么在 Godot 中有沒有這個(gè)類似的特性呢屯曹?嗯是牢,也有陕截,這就是我們今天要談到的 Resource 資源類型农曲。

官網(wǎng)也有對 Resources 的相關(guān)介紹乳规,我們知道場景是不能拖拽的,也是固定不變的笙以,如果要用場景來保存一些普通數(shù)據(jù)猖腕,肯定不太合理倘感,這時(shí)候我們可以使用 Resource 資源類老玛。相比 Node 其優(yōu)點(diǎn)也很明顯蜡豹,使用非常靈活,同樣可以編寫腳本豹休,可以定義屬性和方法威根,創(chuàng)建資源文件方便洛搀,直接拖拽應(yīng)用即可留美。 "OK, FINE!" 這些我都會談到伸刃,更重要的是捧颅,我今天會利用 Resource 提出一個(gè)全新的碉哑、靈活的、“強(qiáng)力”解耦的 EventBus 全局事件模式妆毕。感興趣嗎笛粘?那我們繼續(xù)闰蛔。

主要內(nèi)容: Resource 的相關(guān)用法簡介
閱讀時(shí)間: 8 分鐘
永久鏈接: http://liuqingwen.me/2020/08/17/godot-game-devLog-3-talk-about-resource/
系列主頁: http://liuqingwen.me/introduction-of-godot-series/

二、正文

Resource 并不神秘蚤吹,但是很容易被忽視。其實(shí)我們平時(shí)創(chuàng)建的場景繁涂、節(jié)點(diǎn)中就包含了各種不同類型的資源文件扔罪,官網(wǎng)中的一張圖展示了某些節(jié)點(diǎn) Node 和資源 Resource 的關(guān)系:

Nodes and Resources

相信上圖中的名稱都不陌生矿酵,游戲場景開發(fā)過程中可能會使用上多種資源類型全肮,常見的就有:圖片資源辜腺、碰撞圖形评疗、各種材質(zhì)百匆、 UI 主題瞧毙、音頻流宙彪、漸變、曲線等等悲没,甚至我們常用的 AnimationPlayer 節(jié)點(diǎn)中創(chuàng)建的動畫示姿,以及 GDScript 腳本栈戳、著色器代碼也都是資源子檀。

常用資源類型

資源的創(chuàng)建和使用也非常簡單,不過亩进,目前在 Godot 3 版本中也存在一些局限性归薛,接下來我們詳細(xì)聊聊主籍。

Resource 的創(chuàng)建與使用

創(chuàng)建 Resource 資源的方式就有多種崇猫,平常都是在 Node 節(jié)點(diǎn)的屬性面板中直接創(chuàng)建需忿,比如 New 一個(gè)玩家的碰撞體圖形的形狀屋厘,或是動畫播放器中的各種動畫汗洒,粒子系統(tǒng)新建的材質(zhì)等等,這些資源有一個(gè)特點(diǎn):我們開箱即用瞻凤,很少保存阀参。

資源文件也可以單獨(dú)創(chuàng)建蛛壳,假設(shè)我們需要?jiǎng)?chuàng)建一個(gè)需要在很多地方使用的資源衙荐,比如通用的主題資源忧吟、字體資源斩披、瓦片地圖 TileSet 資源等等,那么我們可以單獨(dú)創(chuàng)建相應(yīng)類型的資源文件,保存起來乡话,在不同場景中輕松實(shí)現(xiàn)重復(fù)利用绑青。在屬性面板或者節(jié)點(diǎn)屬性中都可以新建資源文件:

創(chuàng)建并保存資源文件

新建資源文件后記得保存闸婴,保存的文件后綴名一般是 .tres 也有 .res 文件類型的邪乍,區(qū)別在于以文本格式保存還是二進(jìn)制文件格式保存:

保存資源為文件

保存好的資源文件我們可以隨時(shí)修改其相關(guān)屬性值庇楞,雙擊資源文件即可吕晌,另外,也可以創(chuàng)建多個(gè)副本烙心,比如字體資源復(fù)制( duplicate )一份淫茵,然后修改字體大小屬性痘昌,使用在不同的地方辆苔。

資源的使用方式就簡單了扼劈,可以直接拖拽到對應(yīng)屬性中,也可以在屬性下拉列表中點(diǎn)擊 Load 加載骑冗。系統(tǒng)自帶的資源比較齊全,當(dāng)然我們也可以自定義資源類型巧涧。資源從本質(zhì)上來說仍然是一種腳本文件遥倦,創(chuàng)建自定義資源首先需要?jiǎng)?chuàng)建一個(gè)繼承自 Resource 類的腳本:

# 繼承自 Resource 說明這是一個(gè)資源腳本
extends Resource
class_name CustomResource, 'res://CustomResource/custom_icon.svg'

# 資源也可以定義普通的屬性
export var variable1 := ''
export var variable2 := 0
# ...

# 資源也可以定義一些方法
func printInfo() -> void:
    # ...

在上面新建的代碼中我們聲明了資源的類名( CustomResource )以及資源的圖標(biāo)( res://CustomResource/custom_icon.svg )缩筛。創(chuàng)建好之后堡称,可以在新建資源列表中發(fā)現(xiàn)相對應(yīng)的自定義資源類型却紧,這一系列過程可以參考下圖:

創(chuàng)建自定義資源以及資源實(shí)例

是不是非常簡單啄寡?趕緊動手創(chuàng)建一個(gè)壓壓驚挺物。 :joy:

Resource 相關(guān)問題與局限

資源的創(chuàng)建和使用確實(shí)簡單识藤,不過 Godot 3 中對于自定義資源還是有點(diǎn)小坑,這里提出來稽穆,希望對新手朋友們有用舌镶。

1. 不能使用自定義 Resource 為變量類型

我們創(chuàng)建自定義資源時(shí)餐胀,可以給資源定義個(gè)類名 class_name CustomResource 否灾,但是在代碼中確不能定義該類型的資源變量:

var resource1 : Resource # 沒問題
var resource2 : CustomResource # 不支持鸣奔!

上面的代碼運(yùn)行會報(bào)錯(cuò):

built-in:4 - Parse Error: Invalid export type. Only built-in and native resource types can be exported.

避免這個(gè)問題的方法就是使用父類型 Resource 作為變量的類型,不過這樣會導(dǎo)致在 export 屬性中可以賦予任意類型的資源文件断楷,非常不方便崭别、不人道紊遵。當(dāng)然你可以在代碼中進(jìn)行判斷:

if resource && resource is CustomResource:
    # 代碼...

不過暗膜,好消息是這個(gè)問題會在 Godot 4.0 中得到解決学搜。

2. 使用 Resouce 要注意資源是引用類型

如果一個(gè)資源文件被多個(gè)節(jié)點(diǎn)使用瑞佩,這個(gè)時(shí)候你只要改變了某個(gè)節(jié)點(diǎn)下該資源的任意一個(gè)屬性炬丸,結(jié)果都會導(dǎo)致其他節(jié)點(diǎn)下該資源跟隨發(fā)生變化蜒蕾!

舉個(gè)例子咪啡,游戲資源中有一個(gè) font_resource.res 字體資源文件撤摸,當(dāng)你改變了資源屬性中字體的大小后,其他所有使用了該資源的 UI 界面字體都會發(fā)生改變钥飞。這也是為什么新手們經(jīng)常會遇到這種情況:創(chuàng)建一個(gè)節(jié)點(diǎn)代承,添加碰撞體论悴,新建一個(gè)碰撞體圖形,設(shè)置好之后復(fù)制該節(jié)點(diǎn)并重命名幔亥,修改新碰撞節(jié)點(diǎn)的圖片和碰撞體圖形帕棉,莫名發(fā)現(xiàn)之前節(jié)點(diǎn)的碰撞體圖形也發(fā)生了改變饼记,其實(shí)就是這個(gè)原因具则。 :grin:

所以,在 Godot 中一個(gè)小小的變量值改變都需要重新創(chuàng)建一個(gè)資源低斋,這也不算什么大問題膊畴,我們可以右鍵資源文件 Duplicate 復(fù)制一個(gè)唇跨,或者使用 Make Unique 方式使指定資源唯一化轻绞。

3. 使用 Resouce 要注意避免循環(huán)引用

如果你的項(xiàng)目中創(chuàng)建了不少自定義資源文件政勃,自定義資源代碼中又引用了其他類型的資源兼砖,那么有可能會出現(xiàn)這種錯(cuò)誤;

"scene/resources/resource_format_text.cpp:1387 - Circular reference to resource being saved found: 'res://src/.../???.tres' will be null next time it's loaded."

其實(shí)循環(huán)引用問題( Circular reference )在普通 GD 代碼中也會出現(xiàn)懒叛,而出現(xiàn)在自定義資源中則會變得難以發(fā)覺薛窥。解決這個(gè)問題的方法就是不要在編輯器中直接給資源賦值,轉(zhuǎn)而在運(yùn)行時(shí)判斷然后動態(tài)加載 Resource 佩番,示例如下:

export var resource : Resource       # 自定義資源
export var resourceFilePath : String # 資源路徑

func method() -> void:
    if resource == null:
        # 運(yùn)行時(shí)加載資源文件
        resource = load(resourceFilePath)
    # 代碼...

這種情況應(yīng)該比較少見趟畏,暫時(shí)不做深入討論赋秀,后面的文章遇到了再詳述律想,當(dāng)然技即,我們翹首以待的 4.0 版本會解決這個(gè)問題姥份。

4. 其他的小問題

如果修改資源腳本中的圖標(biāo)或者類名后澈歉,其他引用了這個(gè) Resource 的代碼就會報(bào)錯(cuò)屿衅,類似 Resource 類已經(jīng)損壞涤久,加載不完整之類。重新啟動項(xiàng)目就可以了考抄。

有時(shí)候還會遇到這種小 BUG :

core/script_language.cpp:244 - Condition "!global_classes.has(p_class)" is true. Returned: String()

有點(diǎn)莫名川梅,也不容易重現(xiàn)贫途,我估計(jì)是修改了 Resource 腳本類名引起的待侵,反正重啟項(xiàng)目就沒事了秧倾。 :joy:

這些小問題說明目前 Godot 的資源類型還不夠完善傀缩, Waiting for Godot 4.0 藥到病除扑毡,哈哈瞄摊!

創(chuàng)建 Resource 相當(dāng)于 DataContainer

創(chuàng)建自定義 Resource 的一個(gè)經(jīng)典用途就是當(dāng)做數(shù)據(jù)容器换帜。創(chuàng)建一個(gè)個(gè)資源文件就相當(dāng)于創(chuàng)建了一個(gè)個(gè)數(shù)據(jù)容器惯驼,這些數(shù)據(jù)容器一般沒有其他功能递瑰,只是獨(dú)立保存一些應(yīng)用數(shù)據(jù)抖部,不論是修改還是使用都非常方便且靈活。

舉個(gè)具有實(shí)際應(yīng)用場景的例子乡恕,在一個(gè) Player 或者 AI 腳本中傲宜,如果存在著大量數(shù)據(jù)屬性夫啊,而這些數(shù)據(jù)屬性一般不會發(fā)生改變,或者只是一些配置參數(shù)谆趾,那么我們完全可以將其抽離出來作為一個(gè)單獨(dú)的數(shù)據(jù)類——這也是《重構(gòu)-改善既有代碼的設(shè)計(jì)》一書中提倡的重構(gòu)方式之一沪蓬。

# 玩家類

export var name := 'player'
export var moveSpeed := 200
export var rotateSpeed := 5
# 其他一些屬性...

在 Godot 中這個(gè)所謂的單獨(dú)數(shù)據(jù)類可以使用內(nèi)部類進(jìn)行包裝:

# 玩家類

# 內(nèi)部類
class Data:
    var name := 'player'
    var moveSpeed := 200
    var rotateSpeed := 5
    func _init():
        pass

內(nèi)部類雖然可以封裝數(shù)據(jù)跷叉,但是在腳本范圍之外使用則非常蹩腳云挟,也不方便在編輯器中進(jìn)行編輯,這時(shí)候我們可以使用自定義資源類解決這個(gè)痛點(diǎn):

extends Resource

export var name := 'player'
export var moveSpeed := 200
export var rotateSpeed := 5

然后創(chuàng)建單個(gè)或者多個(gè)資源文件帖世,在編輯器的屬性面板中修改對應(yīng)的屬性值日矫,在其他代碼中使用起來非常方便:

export var dataResource : Resource = null

fun _ready() -> void:
    if dataResource != null:
        print(dataResource.name, dataResource.moveSpeed, dataResource.rotateSpeed)

作為數(shù)據(jù)容器和 ScriptableObject 有點(diǎn)類似哪轿,接下來我們看 Resource 的另一個(gè)非常有用的場景窃诉。

用 Resource 創(chuàng)建全局事件的 EventBus

可以說這是本文的重點(diǎn)飘痛,目前我還沒有看到有任何人在項(xiàng)目中使用過這種方式宣脉,且聽我慢慢道來~~~

首先,關(guān)于 Godot 中的 signal 信號以及觀察者模式相信大家都已經(jīng)駕輕就熟了堪遂,一般在游戲開發(fā)中我們都會準(zhǔn)守 signal up, call down 的準(zhǔn)則币旧,即往上層發(fā)送信號猿妈,往下層直接調(diào)用彭则。當(dāng)游戲變得越來越復(fù)雜的時(shí)候,信號可能已經(jīng)充滿了整個(gè)項(xiàng)目输瓜,比如某個(gè)多人游戲中信息面板需要接收并顯示多種不同類型的信號:玩家按下回車鍵發(fā)送的文字信息搔啊、玩家某個(gè)戰(zhàn)場獲得勝利發(fā)出的信號、某個(gè)玩家退出游戲發(fā)出的信號负芋、官方服務(wù)器推送的信息等等旧蛾,因?yàn)檫@些信息發(fā)生在不同的場景蚜点,處理起來并不簡單拌阴,我能想到的解決方式有這么幾種:

  1. 使用 get_node('../root/node_path') 方式陪拘,不推薦并表示強(qiáng)烈譴責(zé)纤壁,這會造成強(qiáng)耦合欠痴,擴(kuò)展喇辽、維護(hù)和重構(gòu)極其困難
  2. 使用 Global AutoLoad 菩咨,也就是 Singleton 單例模式抽米,有效解決耦合糙置,但是維護(hù)相當(dāng)困難云茸,牽一發(fā)而動全身,調(diào)試?yán)щy
  3. 使用 Resource 創(chuàng)建相應(yīng)的事件資源谤饭,強(qiáng)力解耦查辩,使用起來非常方便胖笛,調(diào)試也非常簡單,易擴(kuò)展和維護(hù)

關(guān)于第二種方式是大家推薦的模式宜岛,我在之前的示例中就使用過:(Godot游戲開發(fā)實(shí)踐之一:使用High Level Multiplayer API制作多人游戲(上))长踊, GDQuest 的文檔中也介紹了這種模式: https://www.gdquest.com/docs/guidelines/best-practices/godot-gdscript/event-bus/ ,示例代碼如下:

# 這是一個(gè) AutoLoad 單例
extends Node
# 可以定義多個(gè)通用信號
signal new_message(content)

# 其他代碼...

其他場景中使用也非常簡單:

# 場景 1 中發(fā)送信號:
GameConfig.emit_signal('new_message', '......')

# 場景 2 中接收處理信號:
GameConfig.connect('new_message', self, '_on_NewMessage_arrive')

但是這種方式有一個(gè)很大的缺陷:全局引用導(dǎo)致重構(gòu)困難萍倡。因?yàn)閱卫喈?dāng)于全局模式身弊,任何地方都可以引用列敲,重構(gòu)時(shí)一旦改動單例中某個(gè)方法或者屬性都有可能引起其他地方因?yàn)橐檬Ф鴮?dǎo)致運(yùn)行奔潰,尋找這些引用并不容易淮逊,這也為什么 GDQuest 推薦的 EventBus 模式是單獨(dú)創(chuàng)建的只有信號沒有其他代碼的腳本文件秧耗。

廢話一堆,一起來看看利用 Resource 創(chuàng)建的事件模式吧珠闰!首先創(chuàng)建一個(gè)事件資源:

# 自定義資源
extends Resource
class_name EventResource, 'res://EventResource/event_icon.svg'
# 自定義信號
signal custom_event(type, message)
# 可以定義一些屬性
export var type := 'defaultEvent'
# 自定義方法用于發(fā)送信號的包裝杭朱,也可以直接發(fā)送信號
func emitSignal(object) -> void:
    self.emit_signal('custom_event', type, object)

接下來空民,我們可以創(chuàng)建一些事件資源文件衔瓮,比如 message_event.tres trigger_event.tres 衔彻,不同的文件可以更改、配置不同的參數(shù),然后在其他腳本中使用:

export var messageEvent : Resource = null
export var triggerEvent : Resource = null

# 可以使用事件資源偵聽事件
func someMethod1() -> void:
    if triggerEvent && triggerEvent is EventResource:
        triggerEvent.connect('custom_event', self, '_onTriggerEventHandler')

# 也可以使用事件資源發(fā)送事件
func someMethod2() -> void:
    if messageEvent && messageEvent is EventResource:
            messageEvent.emitSignal(info)

因?yàn)檫@些事件都是資源類型,在節(jié)點(diǎn)屬性中可以直接拖拽使用,而且可有可無纬纪,均不影響整個(gè)項(xiàng)目的運(yùn)行靶庙,在本示例中玩家的屬性配置如下圖:

玩家相關(guān)設(shè)置

可以看到 Player1 只接收 message_event 事件, Player3 只派發(fā) trigger_event 事件,而 Player2 則無任何配置殴玛,可謂一目了然坦刀。

總結(jié)一下使用 Resource 創(chuàng)建事件的一些優(yōu)點(diǎn):

  1. 強(qiáng)力解耦!不依賴其他文件或者腳本、節(jié)點(diǎn),很容易進(jìn)行重構(gòu)
  2. 便于調(diào)試,代碼中只要注意 null 引用即可,刪除或者添加相關(guān)事件都非常友好
  3. 便于測試,修改事件相關(guān)屬性值非常方便,一改全改
  4. 可以考慮在大型項(xiàng)目中應(yīng)用

并沒有十全十美的萬能解決方案搅方,當(dāng)然也是有缺點(diǎn)的绣溜,比如一堆的只是改變了某一個(gè)變量值的 .res 文件等。重要的是,目前還沒有實(shí)際項(xiàng)目支持這個(gè)事件模式,有待大家的開發(fā)和探索啊。 :smile:

三、總結(jié)

好了,這篇就聊了一個(gè)簡單的 Resource 話題,希望能給新手朋友們帶來一點(diǎn)點(diǎn)幫助,給高手朋友們開拓一點(diǎn)點(diǎn)亮光,那這篇文章也就值了。

記住我們 Godot 愛好者的新家: Godot中文社區(qū) 镊讼,歡迎臣婷常回家看看螺垢!

本篇的 Demo 以及相關(guān)代碼已經(jīng)上傳到 Github 痊土,地址: https://github.com/spkingr/Godot-Demos , 后續(xù)繼續(xù)更新,原創(chuàng)不易鲸睛,希望大家喜歡晴股! :smile:

我的博客地址: http://liuqingwen.me ,我的博客即將同步至騰訊云+社區(qū)呢诬,邀請大家一同入駐: https://cloud.tencent.com/developer/support-plan?invite_code=3sg12o13bvwgc ,歡迎關(guān)注我的微信公眾號(第一時(shí)間更新+游戲開發(fā)資源+相關(guān)資訊):

IT自學(xué)不成才

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杏节,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子染突,更是在濱河造成了極大的恐慌降宅,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帐我,死亡現(xiàn)場離奇詭異抢肛,居然都是意外死亡瑞侮,警方通過查閱死者的電腦和手機(jī)酌住,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來带饱,“玉大人导梆,你說我怎么就攤上這事≌桌溃” “怎么了匿醒?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵立膛,是天一觀的道長。 經(jīng)常有香客問我瓤鼻,道長,這世上最難降的妖魔是什么影晓? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮豌鸡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痹兜。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脂男,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天坛增,我揣著相機(jī)與錄音,去河邊找鬼尽纽。 笑死咐蚯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜓斧。 我是一名探鬼主播仓蛆,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挎春!你這毒婦竟也來了看疙?” 一聲冷哼從身側(cè)響起豆拨,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎能庆,沒想到半個(gè)月后施禾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡搁胆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年弥搞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渠旁。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡攀例,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出顾腊,到底是詐尸還是另有隱情粤铭,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布杂靶,位于F島的核電站梆惯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吗垮。R本人自食惡果不足惜垛吗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烁登。 院中可真熱鬧怯屉,春花似錦、人聲如沸防泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捷泞。三九已至,卻和暖如春寿谴,著一層夾襖步出監(jiān)牢的瞬間锁右,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工讶泰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咏瑟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓痪署,卻偏偏與公主長得像码泞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子狼犯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353