丟掉Mask遮罩吨灭,更好的圓形Image組件[Unity]

全文解析圓形Image組件的實(shí)現(xiàn)原理,取關(guān)鍵代碼介紹算法細(xì)節(jié)刑巧,源碼已經(jīng)上傳Github下載地址喧兄,歡迎下載試用。

一啊楚、Unity原生Image組件實(shí)現(xiàn)圓形圖片的缺陷#

Mask渲染消耗##

許多游戲項(xiàng)目里免不了有很多圖片是以圓形形式展示的吠冤,如頭像,技能Icon等恭理,一般做法是使用Image組件拯辙,再加上一個(gè)圓形的Mask。實(shí)現(xiàn)非常簡(jiǎn)單颜价,但因?yàn)橛绊懶恃谋#S多關(guān)于ui方面的Unity效率優(yōu)化文章,都會(huì)建議開(kāi)發(fā)者少用Mask周伦。

  1. 使用Mask會(huì)額外消耗多一個(gè)Drawcall來(lái)創(chuàng)建Mask夕春,做像素剔除。
  2. Mask不利于層級(jí)合并专挪。原本同一圖集里的ui可以合并層級(jí)及志,僅需一個(gè)Drawcall渲染,如果加入Mask寨腔,就會(huì)將一個(gè)ui整體分割成了Mask下的子ui與其他ui速侈,兩者只能各自進(jìn)行層級(jí)合并,至少要兩個(gè)Drawcall迫卢。Mask用得多了倚搬,一個(gè)ui整體會(huì)被分割得四分五裂,就會(huì)嚴(yán)重影響層次合并的效率了靖避。

無(wú)法精確點(diǎn)擊##

Image+Mask的實(shí)現(xiàn)的圓形潭枣,點(diǎn)擊判斷不精確比默,點(diǎn)擊到圓形外的四個(gè)邊角仍會(huì)觸發(fā)點(diǎn)擊,雖然可以通過(guò)另外設(shè)置eventAlphaThreshold實(shí)現(xiàn)像素級(jí)判斷盆犁,但這個(gè)方法有天生缺陷命咐,并不是好的選擇。

二谐岁、應(yīng)運(yùn)而生的CircleImage組件#

了解了原有做法的缺陷后醋奠,我們希望自制圓形Image組件,解決這些問(wèn)題伊佃,并且盡量簡(jiǎn)單易用窜司。

干掉Mask##

雖說(shuō)少用Mask,但游戲項(xiàng)目里總免不了有些圖片要以圓形形式顯示航揉,不得不用塞祈,怎么辦?轉(zhuǎn)而從渲染層面思考帅涂,Image組件默認(rèn)以矩形形式渲染议薪,如果有辦法定制一個(gè)特殊Image組件,重新寫(xiě)入圓形形狀的渲染頂點(diǎn)媳友、三角面片信息斯议,根本不需要Mask就能渲染出圓形Image。

我們看到的屏幕顯示醇锚,是通過(guò)GPU渲染出來(lái)的哼御,而GPU渲染以三角面片為最小單元。所有的圖形畫(huà)面焊唬,本質(zhì)是由無(wú)數(shù)三角面片組成的恋昼,例如矩形是由兩個(gè)直角三角面片組成的;圓形可以由若干個(gè)相同的以圓心為頂點(diǎn)的等腰三角面片組成正多邊形赶促,近似模擬出來(lái)焰雕。三角面片分得多了,多邊形的邊越多芳杏,夾角越大矩屁,就越近似圓形。

綠色圓圈由60個(gè)等腰三角面片構(gòu)成爵赵,黃色圓圈由10個(gè)等腰三角形面片構(gòu)成

另一種精確點(diǎn)擊方案##

組件不再以像素Alpha值判斷是否點(diǎn)擊吝秕,而是用Ray-Crossing算法計(jì)算點(diǎn)擊點(diǎn)是否在落多邊形內(nèi),來(lái)實(shí)現(xiàn)精確點(diǎn)擊空幻。

三烁峭、組件實(shí)現(xiàn)#

繪制圓形##

Unity引擎并不開(kāi)源,好在其中ugui框架是開(kāi)源的,簡(jiǎn)單看下Image代碼:

public class Image : MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter

Image類繼承自MaskableGraphic约郁,實(shí)現(xiàn)了ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastFilter這三個(gè)接口缩挑。最關(guān)鍵的是MaskableGraphic類,MaskableGraphic負(fù)責(zé)繪制邏輯鬓梅,MaskableGraphic繼承自Graphic供置,Graphic里有個(gè)OnPopulateMesh函數(shù),這正是我們需要的函數(shù)绽快。

當(dāng)UI元素生成頂點(diǎn)數(shù)據(jù)時(shí)會(huì)調(diào)用OnPopulateMesh(VertexHelper vh)函數(shù)芥丧,我們只要繼承改寫(xiě)OnPopulateMesh函數(shù),將原先的矩形頂點(diǎn)數(shù)據(jù)清除坊罢,改寫(xiě)入圓形頂點(diǎn)數(shù)據(jù)续担,這樣渲染出來(lái)的自然是圓形圖片。

我們希望這個(gè)圓形Image組件活孩,能夠自定義某些參數(shù)物遇,比如自定義圓形等分面數(shù)(即由多少個(gè)三角形組成這個(gè)圓形),自定義圓形填充比例等憾儒。

由于Unity的限制挎挖,繼承UnityEngine基類的派生類不能在Inspector里顯示自定義參數(shù)。為了解決這點(diǎn)航夺,我們?cè)僭靷€(gè)小輪子,新建BaseImage類來(lái)代替Image類崔涂。原Image源碼有近千行代碼阳掐,BaseImage對(duì)其進(jìn)行了部分精簡(jiǎn),只支持Simple Image Type冷蚂,并去掉了eventAlphaThreshold的相關(guān)代碼缭保。經(jīng)過(guò)刪減,得到一個(gè)百行代碼的BaseImage類蝙茶,精簡(jiǎn)版Image就完成了艺骂。

接著,新建CircleImage類繼承BaseImage隆夯,重寫(xiě)OnPopulateMesh方法钳恕。

    protected override void OnPopulateMesh(VertexHelper vh)

OnPopulateMesh方法的VertexHelper參數(shù),保存著原來(lái)的頂點(diǎn)信息蹄衷,因?yàn)橐匦聜魅腠旤c(diǎn)信息忧额,需先調(diào)用Clear方法,清除VertexHelper原有頂點(diǎn)信息愧口。在計(jì)算頂點(diǎn)前睦番,通過(guò)DataUtility.GetOuterUV(overrideSprite)獲取貼圖uv信息,簡(jiǎn)單計(jì)算獲得中心點(diǎn),縮放等信息托嚣。

    protected override void OnPopulateMesh(VertexHelper vh)
    {
        vh.Clear();

        Vector4 uv = overrideSprite != null ? DataUtility.GetOuterUV(overrideSprite) : Vector4.zero;
        float uvCenterX = (uv.x + uv.z) * 0.5f;
        float uvCenterY = (uv.y + uv.w) * 0.5f;
        float uvScaleX = (uv.z - uv.x) / tw;
        float uvScaleY = (uv.w - uv.y) / th;

        ...
    }

知道了等分面片數(shù)segements巩检,我們可以算出每個(gè)面片的頂點(diǎn)夾角,面片數(shù)segements與填充比例fillPercent相乘示启,就知道要用多少個(gè)面片來(lái)顯示圓形/扇形

    float degreeDelta = (float)(2 * Mathf.PI / segements);
    int curSegements = (int)(segements * fillPercent);

通過(guò)RectTransform獲取矩形寬高兢哭,計(jì)算出半徑

    float tw = rectTransform.rect.width;
    float th = rectTransform.rect.height;
    float outerRadius = rectTransform.pivot.x * tw;

已經(jīng)有了半徑,夾角信息丑搔,根據(jù)圓形點(diǎn)坐標(biāo)公式(radius * cosA,radius * sinA)可以算出頂點(diǎn)坐標(biāo)厦瓢,每次迭代新建UIVertex,將求出的坐標(biāo)啤月,color煮仇,uv等參數(shù)傳入,再將UIVertex傳給VertexHelper谎仲。重復(fù)迭代n次浙垫,VertexHelper就獲得了多邊形頂點(diǎn)及圓心點(diǎn)信息了。

計(jì)算頂點(diǎn)郑诺、指定三角形

    float curDegree = 0;
    UIVertex uiVertex;
    int verticeCount;
    int triangleCount;
    Vector2 curVertice;

    curVertice = Vector2.zero;
    verticeCount = curSegements + 1;
    uiVertex = new UIVertex();
    uiVertex.color = color;
    uiVertex.position = curVertice;
    uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
            vh.AddVert(uiVertex);

    for (int i = 1; i < verticeCount; i++)
    {
          float cosA = Mathf.Cos(curDegree);
          float sinA = Mathf.Sin(curDegree);
          curVertice = new Vector2(cosA * outerRadius, sinA * outerRadius);
          curDegree += degreeDelta;

          uiVertex = new UIVertex();
          uiVertex.color = color;
          uiVertex.position = curVertice;
          uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
          vh.AddVert(uiVertex);

          outterVertices.Add(curVertice);
   }

知道了所有頂點(diǎn)信息夹姥,仍不足以渲染圖形,因?yàn)镚PU還不知道頂點(diǎn)之間的關(guān)系辙诞,不知道這些頂點(diǎn)分成了多少個(gè)三角面片辙售,所以還需要把所有三角形信息一一告訴GPU。VertexHelper是通過(guò)AddTriangle接口接受三角形信息:

public void AddTriangle(int idx0, int idx1, int idx2)

接口的傳入?yún)?shù)并不是UIVertex類型飞涂,而是int類型的索引值旦部。哪來(lái)的索引?還記得之前往VertexHelper傳入了一堆頂點(diǎn)嗎较店?按照傳入順序士八,第一個(gè)頂點(diǎn),索引記為0梁呈,依次類推婚度。每次傳入三個(gè)頂點(diǎn)的索引,就記錄下了一個(gè)三角形官卡。

需要注意蝗茁,GPU 默認(rèn)是做backface culling(背面剔除)的,GPU只渲染正對(duì)屏幕的三角面片寻咒,當(dāng)GPU認(rèn)為某個(gè)三角面片是背對(duì)屏幕時(shí)评甜,直接丟棄該三角面片,不做渲染仔涩。那么GPU怎么判斷我們傳入的某個(gè)三角形是正對(duì)屏幕忍坷,還是背對(duì)屏幕?答案是通過(guò)三個(gè)頂點(diǎn)的時(shí)針順序,當(dāng)三個(gè)頂點(diǎn)是呈順時(shí)針時(shí)佩研,判定為正對(duì)屏幕柑肴;呈逆時(shí)針時(shí),判定為背對(duì)屏幕旬薯。

左邊的圖中指定頂點(diǎn)的順序是順時(shí)針的晰骑,右邊是逆時(shí)針的

VertexHelper收到的第一個(gè)頂點(diǎn)是圓心,且算法是按逆時(shí)針?lè)较虬硇颍?jì)算出的多邊形頂點(diǎn)硕舆,并依次傳給VertexHelper。因此按(i, 0, i+1)(i>=1)的規(guī)律取索引骤公,就可以保證頂點(diǎn)順序是順時(shí)針的抚官。

    triangleCount = curSegements*3;
    for (int i = 0, vIdx = 1; i < triangleCount - 3; i += 3, vIdx++)
    {
         vh.AddTriangle(vIdx, 0, vIdx+1);
    }
    if (fillPercent == 1)
    {
          //首尾頂點(diǎn)相連
          vh.AddTriangle(verticeCount - 1, 0, 1);
    }

到這里為止,我們已經(jīng)完成了繪制圓形的工作了阶捆。

繪制圓環(huán)##

考慮還有可能要以圓環(huán)形式顯示凌节,組件也做了支持。圓環(huán)的情況稍微復(fù)雜:頂點(diǎn)集沒(méi)有圓心頂點(diǎn)了洒试,只有內(nèi)環(huán)倍奢、外環(huán)頂點(diǎn);三角形集也不是簡(jiǎn)單的切餅式分割垒棋,采用一種比較直觀的三角形劃分卒煞,讓內(nèi)外環(huán)相鄰的頂點(diǎn)類似一根鞋帶那樣互相連接,來(lái)劃分三角形叼架。

定義fill畔裕、thickness變量確定是否填充圖形、圓環(huán)寬度

    [Tooltip("是否填充圓形")]
    public bool fill = true;
    [Tooltip("圓環(huán)寬度")]
    public float thickness = 5;

計(jì)算頂點(diǎn)碉碉、指定三角形

        float tw = rectTransform.rect.width;
        float th = rectTransform.rect.height;
        float outerRadius = rectTransform.pivot.x * tw;
        float innerRadius = rectTransform.pivot.x * tw - thickness;

        float curDegree = 0;
        UIVertex uiVertex;
        int verticeCount;
        int triangleCount;
        Vector2 curVertice;

        verticeCount = curSegements*2;
        for (int i = 0; i < verticeCount; i += 2)
        {
            float cosA = Mathf.Cos(curDegree);
            float sinA = Mathf.Sin(curDegree);
            curDegree += degreeDelta;

            curVertice = new Vector3(cosA * innerRadius, sinA * innerRadius);
            uiVertex = new UIVertex();
            uiVertex.color = color;
            uiVertex.position = curVertice;
            uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
            vh.AddVert(uiVertex);
            innerVertices.Add(curVertice);

            curVertice = new Vector3(cosA * outerRadius, sinA * outerRadius);
            uiVertex = new UIVertex();
            uiVertex.color = color;
            uiVertex.position = curVertice;
            uiVertex.uv0 = new Vector2(curVertice.x * uvScaleX + uvCenterX, curVertice.y * uvScaleY + uvCenterY);
            vh.AddVert(uiVertex);
            outterVertices.Add(curVertice);
        }

        triangleCount = curSegements*3*2;
        for (int i = 0, vIdx = 0; i < triangleCount - 6; i += 6, vIdx += 2)
        {
            vh.AddTriangle(vIdx+1, vIdx, vIdx+3);
            vh.AddTriangle(vIdx, vIdx + 2, vIdx + 3);
        }
        if (fillPercent == 1)
        {
            //首尾頂點(diǎn)相連
            vh.AddTriangle(verticeCount - 1, verticeCount - 2, 1);
            vh.AddTriangle(verticeCount - 2, 0, 1);
        }

圓形Image的像素級(jí)點(diǎn)擊判斷##

雖然我們完成了圓形Image的繪制,但Unity還是以圖片矩形包圍盒來(lái)判斷點(diǎn)擊淮韭。點(diǎn)擊圓形之外4個(gè)邊角區(qū)域垢粮,仍會(huì)判定點(diǎn)擊,在要求精確點(diǎn)擊的場(chǎng)景下就有問(wèn)題了靠粪。
Unity本身提供了像素級(jí)點(diǎn)擊判斷方案蜡吧,通過(guò)設(shè)置eventAlphaThreshold屬性(在5.4以上版本中改為alphaHitTestMinimumThreshold),根據(jù)點(diǎn)擊像素點(diǎn)是否已超過(guò)Alpha閾值來(lái)判定是否觸發(fā)點(diǎn)擊占键。然而這個(gè)美好的方案卻有天生缺陷昔善,要求傳入圖片Texture Type不能為默認(rèn)的Sprite,需設(shè)置為Advanced畔乙,且需勾選上Read/Write Enabled君仆,這樣會(huì)導(dǎo)致圖片占用雙倍內(nèi)存,且不能合并入圖集。


綜合效率和易用性返咱,設(shè)置eventAlphaThreshold都不是一個(gè)合適的方案钥庇,那么有沒(méi)有別的辦法實(shí)現(xiàn)精確的點(diǎn)擊判斷?有的咖摹,換個(gè)角度思考评姨,我們只需要考慮點(diǎn)擊區(qū)域是在多邊形之內(nèi),還是之外就可以了萤晴。這個(gè)問(wèn)題早有人研究吐句,抽象嚴(yán)謹(jǐn)?shù)卣f(shuō),這個(gè)問(wèn)題可以描述為“如何判定一點(diǎn)是否在給定頂點(diǎn)的不規(guī)則封閉區(qū)域內(nèi)”店读,知乎上有相關(guān)回答嗦枢。拾前人牙慧,我們選用Ray-Crossing算法來(lái)判定屏幕點(diǎn)擊是否落在多邊形內(nèi)两入。

Ray-Crossing算法###

Ray-Crossing算法大概思路是從指定點(diǎn)p發(fā)出一條射線净宵,與多邊形相交,假若交點(diǎn)個(gè)數(shù)是奇數(shù)裹纳,說(shuō)明點(diǎn)p落在多邊形內(nèi)择葡,交點(diǎn)個(gè)數(shù)為偶數(shù)說(shuō)明點(diǎn)p在多邊形外。算法結(jié)論乍看難以理解剃氧,但在邏輯上是可證的敏储。假設(shè)有條射線,從起始點(diǎn)向無(wú)窮遠(yuǎn)處延伸朋鞍,無(wú)窮遠(yuǎn)處必定處于多邊形之外已添;而射線從起始點(diǎn)出發(fā)與多邊形相交的過(guò)程中,射線尾端狀態(tài)是呈二態(tài)性交替變化的滥酥,即在“多邊形外<->多邊形內(nèi)”兩種狀態(tài)里交替變化更舞,已知延長(zhǎng)線的狀態(tài),通過(guò)交點(diǎn)個(gè)數(shù)就可以倒推出起始點(diǎn)的狀態(tài)坎吻。

射線選取哪個(gè)方向并沒(méi)有限制缆蝉,但為了實(shí)現(xiàn)起來(lái)方便,考慮屏幕點(diǎn)擊點(diǎn)為點(diǎn)p瘦真,向水平方向右側(cè)發(fā)出射線的情況刊头,那么頂點(diǎn)v1,v2組成的線段與射線若有交點(diǎn)q,則點(diǎn)q必定滿足兩個(gè)條件:

  1. v2.y < q.y = p.y > v1.y
  2. p.x < q.x

我們根據(jù)這兩個(gè)條件诸尽,逐一跟多邊形線段求交點(diǎn)原杂,并統(tǒng)計(jì)交點(diǎn)個(gè)數(shù),最后判斷奇偶即可得知點(diǎn)擊點(diǎn)是否在圓形內(nèi)您机。

    public override bool IsRaycastLocationValid(Vector2 screenPoint, Camera eventCamera)
    {
        Sprite sprite = overrideSprite;
        if (sprite == null)
            return true;

        Vector2 local;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(rectTransform, screenPoint, eventCamera, out local);
        return Contains(local, outterVertices, innerVertices);
    }

    private bool Contains(Vector2 p, List<Vector3> outterVertices, List<Vector3> innerVertices)
    {
        var crossNumber = 0;
        RayCrossing(p, innerVertices, ref crossNumber);//檢測(cè)內(nèi)環(huán)
        RayCrossing(p, outterVertices, ref crossNumber);//檢測(cè)外環(huán)
        return (crossNumber & 1) == 1;
    }

    /// <summary>
    /// 使用RayCrossing算法判斷點(diǎn)擊點(diǎn)是否落在多邊形里
    /// </summary>
    /// <param name="p"></param>
    /// <param name="vertices"></param>
    /// <param name="crossNumber"></param>
    private void RayCrossing(Vector2 p, List<Vector3> vertices, ref int crossNumber)
    {
        for (int i = 0, count = vertices.Count; i < count; i++)
        {
            var v1 = vertices[i];
            var v2 = vertices[(i + 1) % count];

            //點(diǎn)擊點(diǎn)水平線必須與兩頂點(diǎn)線段相交
            if (((v1.y <= p.y) && (v2.y > p.y))
                || ((v1.y > p.y) && (v2.y <= p.y)))
            {
                //只考慮點(diǎn)擊點(diǎn)右側(cè)方向穿肄,點(diǎn)擊點(diǎn)水平線與線段相交年局,且交點(diǎn)x > 點(diǎn)擊點(diǎn)x,則crossNumber+1
                if (p.x < v1.x + (p.y - v1.y) / (v2.y - v1.y) * (v2.x - v1.x))
                {
                    crossNumber += 1;
                }
            }
        }
    }

至此被碗,一個(gè)能夠靈活地以圓形某宪,扇形,圓環(huán)形式展現(xiàn)圖片的CircleImage組件就完成了锐朴,無(wú)須使用Mask兴喂,無(wú)須消耗額外Drawcall,不影響圖集合并效率焚志,且能實(shí)現(xiàn)精確點(diǎn)擊衣迷。重新設(shè)置頂點(diǎn),點(diǎn)擊判斷等邏輯的時(shí)間復(fù)雜度為O(n)酱酬,與設(shè)置面片數(shù)相關(guān)壶谒,面片數(shù)最大支持設(shè)置到100,這個(gè)量級(jí)對(duì)運(yùn)算效率幾乎無(wú)影響膳沽,實(shí)際上汗菜,面片數(shù)設(shè)置為30已能達(dá)到較好效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挑社,一起剝皮案震驚了整個(gè)濱河市陨界,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌痛阻,老刑警劉巖菌瘪,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異阱当,居然都是意外死亡俏扩,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)弊添,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)录淡,“玉大人,你說(shuō)我怎么就攤上這事油坝〖灯荩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵免钻,是天一觀的道長(zhǎng)彼水。 經(jīng)常有香客問(wèn)我崔拥,道長(zhǎng)极舔,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任链瓦,我火速辦了婚禮拆魏,結(jié)果婚禮上盯桦,老公的妹妹穿的比我還像新娘。我一直安慰自己渤刃,他們只是感情好拥峦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著卖子,像睡著了一般略号。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上洋闽,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天玄柠,我揣著相機(jī)與錄音,去河邊找鬼诫舅。 笑死羽利,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刊懈。 我是一名探鬼主播这弧,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼虚汛!你這毒婦竟也來(lái)了匾浪?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泽疆,失蹤者是張志新(化名)和其女友劉穎户矢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體殉疼,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梯浪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓢娜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挂洛。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖眠砾,靈堂內(nèi)的尸體忽然破棺而出虏劲,到底是詐尸還是另有隱情,我是刑警寧澤褒颈,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布柒巫,位于F島的核電站,受9級(jí)特大地震影響谷丸,放射性物質(zhì)發(fā)生泄漏堡掏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一刨疼、第九天 我趴在偏房一處隱蔽的房頂上張望泉唁。 院中可真熱鬧鹅龄,春花似錦、人聲如沸亭畜。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拴鸵。三九已至玷坠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劲藐,已是汗流浹背侨糟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瘩燥,地道東北人秕重。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像厉膀,于是被迫代替她去往敵國(guó)和親溶耘。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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