Unity 用Entities(ECS)做我的世界及ECS的一些問(wèn)題

這個(gè)是unity官方演示ECS
https://connect.unity.com/p/zhi-bo-hui-gu-shi-yong-unity-ecskai-fa-wo-de-shi-jie

環(huán)境搭設(shè)

打開(kāi)PlayerSetting確保.NET庫(kù)在4.X以上


image.png

進(jìn)入工程文件夾


image.png

把下面腳本加入進(jìn)入
{
{
"dependencies": {
"com.unity.entities": "0.0.12-preview.16"
},
"registry": "https://packages.unity.com",
"testables": [
"com.unity.collections",
"com.unity.entities",
"com.unity.jobs"
]
}
image.png

然后就自動(dòng)下載


image.png

我試了很多版本都報(bào)迷之錯(cuò)誤,這個(gè)不會(huì)報(bào)錯(cuò)


image.png

image.png

這里可以查看安裝胯杭,有的版本可以直接在這個(gè)界面安裝,就不用那樣Json輸入然后又匹配不對(duì)了

然后環(huán)境算是安裝好了
我們可以看到ECS方式創(chuàng)建預(yù)制體磺送,一定有一個(gè)GameObjectEntity還有一堆Component


image.png

打開(kāi)EntitiesDebug界面
image.png

創(chuàng)建對(duì)比

然后是三種創(chuàng)建方式對(duì)比下

using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Collections;

public class EntitiesTest : MonoBehaviour
{
    //宣告內(nèi)存字段放在一起 而不是雜亂無(wú)序的 提高速率
    public static EntityArchetype blockArchetype;

    public EntityManager manager;
    public Mesh blockMesh;
    public Material blockMaterial;

    public GameObject go;

    //宣告在加載場(chǎng)景之前運(yùn)行 可以理解為預(yù)存
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initialize()
    {
        //宣告管理器
        EntityManager manager = World.Active.GetOrCreateManager<EntityManager>();
        //宣告內(nèi)存區(qū)塊(區(qū)域)
        blockArchetype = manager.CreateArchetype(
            typeof(Position)
            );
    }

    //[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    void Start()
    {
        //Unity自己產(chǎn)生預(yù)制體 但是要添加GameObjectEntity Entities才能標(biāo)記
        //或者 Instantiate   
        GameObject.CreatePrimitive(PrimitiveType.Cube)
            .AddComponent<GameObjectEntity>()
            .transform.position = new Vector3(-2, 0, 0);

        //PureECS 下面是不用Unity自帶組件創(chuàng)建預(yù)制體 就是說(shuō)脫離Unity也可以用的代碼
        manager = World.Active.GetOrCreateManager<EntityManager>();

        //在內(nèi)存塊中設(shè)置Pos和創(chuàng)建一個(gè)tag(并沒(méi)有用)
        Entity entities = manager.CreateEntity(blockArchetype);
        manager.SetComponentData(entities, new Position { Value = new int3(2, 0, 0) });
        //這個(gè)是自定義的我們沒(méi)有..
        //manager.AddComponentData(entities, new BlockTag());

        //添加材質(zhì)
        manager.AddSharedComponentData(entities, new MeshInstanceRenderer
        {
            mesh = blockMesh,
            material = blockMaterial,
        });

        //Hybrid ECS 引用unity自帶GameObject創(chuàng)建預(yù)制體
        if (go)
        {
            //產(chǎn)生一個(gè)新的陣列
            using (NativeArray<Entity> entityArray = new NativeArray<Entity>(1, Allocator.Temp))
            {
                manager.Instantiate(go, entityArray);
                manager.SetComponentData(entities, new Position { Value = new float3(4, 0f, 0f) });
            }
        }

    }
}

這個(gè)是預(yù)制體創(chuàng)建要掛的腳本,框架自帶


image.png

image.png

前兩個(gè)是不依賴(lài)Unity所需要的網(wǎng)格和材質(zhì)
最后一個(gè)就是以產(chǎn)生預(yù)制體方式產(chǎn)生


image.png

通過(guò)Entities創(chuàng)建的預(yù)制體不會(huì)在這里顯示
image.png

在自帶的EntitiesDebug窗口中可看見(jiàn)
image.png

選擇一個(gè)灿意,右邊有相應(yīng)的信息,可是只能代碼動(dòng)態(tài)調(diào)整崇呵,以后會(huì)改成可調(diào)整缤剧,也就是說(shuō)和GameObject對(duì)比起來(lái)沒(méi)有什么區(qū)別的樣子


image.png

世界生成

然后是我的世界生成世界是隨機(jī)的,這里要用到柏林噪聲域慷,有規(guī)則的亂序生成荒辕,保證了之后地圖再生成的銜接問(wèn)題,不會(huì)出現(xiàn)地圖生成斷開(kāi)了


這個(gè)是生成的圖
image.png

這個(gè)是3D效果
image.png

上去官方開(kāi)源gitHub扒素材學(xué)習(xí) 建議源碼看視屏寫(xiě)不出來(lái) 有bug再看 因?yàn)檫@個(gè)教程都是干貨 2小時(shí)含金量特別大
https://github.com/UnityTechnologies/MinecraftECS
找到這四個(gè)文件犹褒,放入unity
image.png

生成地圖

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//生成一個(gè)方塊
public class PerlinNoiseGenerator
{
    public static int BlockFaces = 0;
    public static Texture2D noiseHeightMap;
    //長(zhǎng)寬的地圖
    int texWidth = 200, texHeight = 200;

    //噪聲縮放值 值越大越密集
    float scale1 = 1f;
    float scale2 = 10f;
    float scale3 = 20f;

    //隨機(jī)采樣偏移
    float offectX;
    float offectY;

    public PerlinNoiseGenerator()
    {
        offectX = Random.Range(0, 99999);
        offectY = Random.Range(0, 99999);
    }

    /// <summary>
    /// 根據(jù)長(zhǎng)短創(chuàng)建200X200的每一個(gè)點(diǎn)
    /// </summary>
    /// <returns></returns>
    public Texture2D GenerateHeightMap()
    {
        Texture2D heightMap = new Texture2D(texWidth, texHeight);

        for (int i = 0; i < texWidth; i++)
        {
            for (int j = 0; j < texHeight; j++)
            {
                Color color = CakculateColor(i, j);
                heightMap.SetPixel(i, j, color);
            }
        }

        heightMap.Apply();
        return heightMap;
    }

    /// <summary>
    /// 用unity自帶的2維柏林噪聲計(jì)算每個(gè)點(diǎn)的偏移值
    /// </summary>
    /// <param name="x"></param>
    /// <param name="y"></param>
    /// <returns></returns>
    Color CakculateColor(int x, int y)
    {
        //根據(jù)我們的偏移值 計(jì)算出類(lèi)似于波形的圖 就是黑白黑白間隔的圖
        float xCoord1 = (float)x / texWidth * scale1 + offectX;
        float yCoord1 = (float)y / texHeight * scale1 + offectY;
        float xCoord2 = (float)x / texWidth * scale2 + offectX;
        float yCoord2 = (float)y / texHeight * scale2 + offectY;
        float xCoord3 = (float)x / texWidth * scale3 + offectX;
        float yCoord3 = (float)y / texHeight * scale3 + offectY;

        //返回值為0.0 ~ 1.0之間的小數(shù) 可能會(huì)略大于一
        float sample1 = Mathf.PerlinNoise(xCoord1, yCoord1) / 15;
        float sample2 = Mathf.PerlinNoise(xCoord2, yCoord2) / 15;
        float sample3 = Mathf.PerlinNoise(xCoord3, yCoord3) / 15;

        return new Color(sample1 + sample2 + sample3, sample1 + sample2 + sample3, sample1 + sample2 + sample3);
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;

/// <summary>
/// 加載一堆方塊
/// </summary>
public class SpawnNumBlocks : MonoBehaviour
{
    public Texture2D heightmap;

    public static EntityArchetype blockArchetype;

    //10X10的地方留著 之外的不顯示或刪除 類(lèi)似于遮擋剔除
    [Header("Wrold = ChunkBase x ChunkBase")]
    public int chunckBase = 1;

    [Header("Mesh Info")]
    public Mesh blockMesh;

    [Header("For Log")]
    public Material[] mats;

    Material maTemp;

    public EntityManager manager;
    public Entity entities;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initialize()
    {
        EntityManager manager = World.Active.GetOrCreateManager<EntityManager>();

        blockArchetype = manager.CreateArchetype(
            typeof(Position)
            );
    }

    void Start()
    {
        manager = World.Active.GetOrCreateManager<EntityManager>();
        PerlinNoiseGenerator perlin = new PerlinNoiseGenerator();
        heightmap = perlin.GenerateHeightMap();
        ChunkGenerator(chunckBase);
    }

    void ChunkGenerator(int amount)
    {
        //一個(gè)chunckBase相當(dāng)于1500個(gè)方塊
        int totalamount = (amount * amount) * 1500;

        int highlevel;
        bool airChecker;

        for (int y = 0; y < 15; y++)
        {
            for (int x = 0; x < 10 * amount; x++)
            {
                for (int z = 0; z < 10 * amount; z++)
                {
                    //返回像素顏色 數(shù)很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    Vector3 posTemp = new Vector3(x, y, z);
              
                    if (highlevel>=0& highlevel< mats.Length-1)
                    {
                        maTemp = mats[highlevel];
                    }
                    else
                    {
                         //超過(guò)的視為空氣
                            maTemp = mats[mats.Length - 1];
                            airChecker = true;
                    }
                  
                    if (!airChecker)
                    {
                        Entity entities = manager.CreateEntity(blockArchetype);
                        manager.SetComponentData(entities, new Position { Value = new int3(x, y, z) });
                        //manager.AddComponentData(entities, new BlockTag { });

                        manager.AddSharedComponentData(entities, new MeshInstanceRenderer
                        {
                            mesh = blockMesh,
                            material = maTemp,
                        });
                    }
                }
            }
        }
    }


}

然后是找到數(shù)字掛在


image.png
image.png

運(yùn)行


image.png

其實(shí)本來(lái)應(yīng)該運(yùn)行Batches數(shù)到達(dá)2w的但為什么這么低


image.png

因?yàn)楣戳擞肎PU Instance創(chuàng)建預(yù)制體抵窒,在大量物體創(chuàng)建時(shí)會(huì)進(jìn)行優(yōu)化,如果是少量就和直接Instance沒(méi)有區(qū)別

然后我們就可以根據(jù)層生成不同的方塊


image.png

樹(shù)就是從根部開(kāi)始 柱子0-7 然后再周?chē)訕?shù)葉


image.png

把之前代碼進(jìn)一步擴(kuò)充

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;
using System;

public class GameSetting : MonoBehaviour
{
    Texture2D heightmap;

    public static EntityArchetype blockArchetype;

    [Header("Wrold = ChunkBase x ChunkBase")]
    public int chunckBase = 1;

    [Header("Mesh Info")]
    public Mesh blockMesh;
    public Mesh surfaceMesh;
    public Mesh tallGrassMesh;

    [Header("Nature Block Type")]
    public Material stoneMat;
    public Material woodMat;
    public Material leavesMat;
    public Material surfaceMat;
    public Material cobbleMat;
    public Material dirtMaterial;
    public Material tallGrassMat;
    public Material roseMat;
    public Material CloudMat;

    [Header("Other Block Type")]
    public Material glassMat;
    public Material brickMat;
    public Material plankMat;
    public Material tntMat;
    //找不到用粉色
    [Header("")]
    public Material pinkMat;

    public bool createCollider = true;

    public GameObject boxCollider;
    Mesh meshTemp;
    Material maTemp;

    EntityManager manager;
    Entity entities;

    int random;

    ColliderPool colPool;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initialize()
    {
        //檢查場(chǎng)景是否有 有得到?jīng)]有創(chuàng)建
        EntityManager manager = World.Active.GetOrCreateManager<EntityManager>();

        blockArchetype = manager.CreateArchetype(
            typeof(Position)
            );
    }

    void Start()
    {
        manager = World.Active.GetOrCreateManager<EntityManager>();

        PerlinNoiseGenerator perlin = new PerlinNoiseGenerator();
        heightmap = perlin.GenerateHeightMap();

        //創(chuàng)建一個(gè)碰撞池
        colPool = new ColliderPool(boxCollider, transform);

        ChunckGenerator(chunckBase);
    }

    void ChunckGenerator(int amount)
    {
        int totalamount = (amount * amount) * 1500;

        int highlevel;
        bool airChecker;

        for (int y = 0; y < 15; y++)
        {
            for (int x = 0; x < 10 * amount; x++)
            {
                for (int z = 0; z < 10 * amount; z++)
                {
                    //返回像素顏色 數(shù)很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    switch (highlevel)
                    {
                        //表層 根據(jù)一個(gè)單位 和 多個(gè)單位 分開(kāi)方法創(chuàng)建
                        case 0:
                            random = UnityEngine.Random.Range(1, 201);
                            if (random <= 20)
                            {
                                //草
                                PlantGenerator(x, y, z, 1);
                            }
                            else if (random == 198)
                            {
                                //云
                                CloudGenerator(x, y, z);
                            }
                            else if (random == 199)
                            {
                                //樹(shù)
                                TreeGenerator(x, y, z);
                            }
                            else if (random == 200)
                            {
                                //花
                                PlantGenerator(x, y, z, 2);
                            }
                            airChecker = true;
                            break;
                        case 1:
                            //綠色帶土的方塊
                            meshTemp = surfaceMesh;
                            maTemp = surfaceMat;
                            break;
                        case 2:
                        case 3:
                        case 4:
                            //土
                            meshTemp = blockMesh;
                            maTemp = dirtMaterial;
                            break;
                        case 5:
                        case 6:
                            //石頭
                            meshTemp = blockMesh;
                            maTemp = stoneMat;
                            break;
                        case 7:
                        case 8:
                            //鵝卵石
                            meshTemp = blockMesh;
                            maTemp = cobbleMat;
                            break;
                        default:
                            airChecker = true;
                            break;
                    }

                    if (!airChecker)
                    {
                        CreatePrefab(x, y, z, meshTemp, maTemp);
                    }
                }
            }
        }
    }

    void TreeGenerator(int x, int y, int z)
    {
        for (int i = y; i < y + 7; i++)
        {
            //軀干部分          
            if (i == y + 6)
            {
                //樹(shù)頂
                maTemp = leavesMat;
            }
            else
            {
                maTemp = woodMat;
            }

            CreatePrefab(x, i, z, blockMesh, maTemp);

            //樹(shù)葉 就是個(gè)正方形
            if (i >= y + 3 && i <= y + 6)
            {
                for (int j = x - 1; j <= x + 1; j++)
                {
                    for (int k = z - 1; k <= z + 1; k++)
                    {
                        //不能隨機(jī)到軀干
                        if ( j != x||k != z)
                        {
                            CreatePrefab(j, i, k, blockMesh, leavesMat);
                        }

                    }
                }
            }
        }
    }

    void PlantGenerator(int x, int y, int z, int plantType)
    {
        switch (plantType)
        {
            case 1:
                maTemp = tallGrassMat;
                break;
            default:
                maTemp = roseMat;
                break;

        }

        CreatePrefab(x, y, z, tallGrassMesh, maTemp, (entities) => { manager.AddComponentData(entities, new Rotation { Value = Quaternion.Euler(0, 45, 0) }); });
    }

    void CloudGenerator(int x, int y, int z)
    {
        random = UnityEngine.Random.Range(4, 7);

        //提升y的高度 產(chǎn)生一個(gè)方形的云
        for (int i = 0; i < random; i++)
        {
            for (int j = 0; j < random; j++)
            {
                CreatePrefab(x + i, y + 15, z + j, blockMesh, CloudMat);
            }
        }
    }

    delegate void CreateFunc(Entity entities);

    void CreatePrefab(int x, int y, int z, Mesh mesh, Material ma, CreateFunc func = null)
    {
        AddCollider(new Vector3(x, y, z));
        Entity entities = manager.CreateEntity(blockArchetype);
        manager.SetComponentData(entities, new Position { Value = new int3(x, y, z) });
        //manager.AddComponentData(entities, new BlockTag { });

        //找不到是粉色方塊
        if (!maTemp)
            maTemp = pinkMat;

        func?.Invoke(entities);
        manager.AddSharedComponentData(entities, new MeshInstanceRenderer
        {
            mesh = mesh,
            material = ma,
        });
    }

    //在相同位置生成一個(gè)1x1x1的正方形碰撞
    void AddCollider(Vector3 vec3)
    {
        if (createCollider)
        {
            colPool.AddCollider(vec3);
        }
    }


}

還有個(gè)碰撞腳本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColliderPool 
{
    GameObject boxCollider;
    Transform parent;

    public ColliderPool(GameObject boxCollider, Transform parent)
    {
        this.boxCollider = boxCollider;
        this.parent = parent;
    }
    //在同樣位置創(chuàng)建一個(gè)單位碰撞
    public void AddCollider(Vector3 vec3)
    {
        GameObject obj = GameObject.Instantiate(boxCollider);
        obj.transform.position = vec3;
        obj.transform.parent = parent;
        obj.layer = 9;
    }
}

這么掛腳本


image.png

運(yùn)行


image.png

把Chunckbase改為100卡了N久700w個(gè)物體
image.png

Collider就是(1,1,1)的方塊的碰撞


image.png

我寫(xiě)到現(xiàn)在出過(guò)幾次錯(cuò)誤叠骑,就是mesh被拉伸,加加減減比較多

人物控制

從這里下載官方標(biāo)準(zhǔn)資源包李皇,導(dǎo)入


image.png

把第一人稱(chēng)控制器拖出來(lái)就是第一個(gè),因?yàn)樗詭z像機(jī)宙枷,把一開(kāi)始世界的攝像機(jī)刪掉


image.png

因?yàn)槲覀儾菀彩荂ube碰撞掉房,所以沒(méi)辦法從草上傳過(guò)去
第一人稱(chēng)自動(dòng)把鼠標(biāo)屏蔽了 ESC就可以移動(dòng)出Game視圖了

實(shí)現(xiàn)挖掘

然后先把壓縮包的音效文件導(dǎo)入


image.png

然后創(chuàng)建一個(gè)新Tag Blocks


image.png

找到碰撞器 改變layer
image.png
/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Audio;
using Unity.Entities;
using System;
using Unity.Transforms;
using Unity.Rendering;

public class PickaxeController : MonoBehaviour
{
    public LayerMask blockLayer;

    GameObject player;

    public static Transform blockToDestroy;

    Material blockToPlace;

    public static int blockID = 1;

    //音效
    public AudioClip grass_audio;
    public AudioClip stone_audio;
    public AudioClip dirt_audio;
    public AudioClip wood_audio;

    AudioSource AS;

    //挖掘噴發(fā)的特效
    public ParticleSystem digEffect;

    EntityManager manager;

    GameSetting gs;
    // Start is called before the first frame update
    void Start()
    {
        AS = transform.GetComponent<AudioSource>();
        gs = FindObjectOfType<GameSetting>();
        player = gameObject;
        Cursor.lockState = CursorLockMode.Locked;

        manager = World.Active.GetOrCreateManager<EntityManager>();
    }

    // Update is called once per frame
    void Update()
    {
        //滑動(dòng)過(guò)界就改變?yōu)榱硪贿?        if (blockID > 7)
        {
            blockID = 1;
        }
        if (blockID < 1)
        {
            blockID = 7;
        }
        //滑動(dòng)選擇方塊
        if (Input.GetAxis("Mouse ScrollWheel") < 0)
        {
            blockID++;
        }
        if (Input.GetAxis("Mouse ScrollWheel") > 0)
        {
            blockID--;
        }

        //看是選擇哪個(gè)磚塊
        switch (blockID)
        {
            case 1:
                blockToPlace = gs.stoneMat;
                break;
            case 2:
                blockToPlace = gs.plankMat;
                break;
            case 3:
                blockToPlace = gs.glassMat;
                break;
            case 4:
                blockToPlace = gs.woodMat;
                break;
            case 5:
                blockToPlace = gs.cobbleMat;
                break;
            case 6:
                blockToPlace = gs.tntMat;
                break;
            case 7:
                blockToPlace = gs.brickMat;
                break;
        }
        //左鍵放方塊 右鍵刪除方塊
        if (Input.GetMouseButtonDown(1))
        {
            PlaceBlock(blockToPlace);
        }
        else if (Input.GetMouseButtonDown(0))
        {
            DestroyBlock();
        }
    }

    private void PlaceBlock(Material blockToPlace)
    {
        RaycastHit hit;
        //向角色正前方發(fā)射射線(xiàn)
        Physics.Raycast(transform.position, transform.forward, out hit, blockLayer);
        if (hit.transform != null)
        {
            //根據(jù)不同方塊播放音效
            if (blockID == 1 || blockID == 3 || blockID == 5 || blockID == 7)
            {
                AS.PlayOneShot(stone_audio);
            }
            else if (blockID == 2 || blockID == 4)
            {
                AS.PlayOneShot(wood_audio);
            }
            //創(chuàng)建一個(gè)方塊在射線(xiàn)前方法線(xiàn)的位置 加上碰撞
            Position pos = new Position { Value = hit.transform.position + hit.normal };
            Entity entities = manager.CreateEntity(GameSetting.blockArchetype);
            manager.SetComponentData(entities, pos);
            manager.AddComponentData(entities, new BlockTag { });
            manager.AddSharedComponentData(entities, new MeshInstanceRenderer
            {
                mesh = gs.blockMesh,
                material = blockToPlace
            });

            gs.colPool.AddCollider(pos.Value);

        }
    }


    private void DestroyBlock()
    {
        RaycastHit hit;
        //向角色正前方發(fā)射射線(xiàn)
        Physics.Raycast(transform.position, transform.forward, out hit,7, blockLayer);
        if (hit.transform!=null)
        {
            //在同樣位置
            Entity entities = manager.CreateEntity(GameSetting.blockArchetype);
            manager.SetComponentData(entities, new Position { Value=hit.transform.position});
            manager.AddComponentData(entities, new DestoryTag { });

            if (digEffect&&!digEffect.isPlaying)
            {
                digEffect.transform.position = hit.transform.position;
                digEffect.Play();
            }
            //放音效刪除
            AS.PlayOneShot(dirt_audio);
            Destroy(hit.transform.gameObject);
        }
    }

}

然后在第一人稱(chēng)控制器的子節(jié)點(diǎn)掛載腳本


image.png

然后還是不能刪除茧跋,因?yàn)閯h除的只有碰撞

刪除系統(tǒng)

到現(xiàn)在ECS只用到EC沒(méi)說(shuō)S
之前我們的面向?qū)ο笫且粋€(gè)Class貫穿這么多處理


image.png

轉(zhuǎn)化為System的話(huà) 是有個(gè)System處理專(zhuān)門(mén)每一個(gè)模塊(用JobSystem)


image.png

所以做挖掘(刪除)需要處理,這個(gè)就是之前我們打不出來(lái)的Tag卓囚,根據(jù)Tag區(qū)分


image.png

這個(gè)是Tag腳本

/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System;
using Unity.Entities;
//都只是作為一個(gè)標(biāo)簽
//比如自帶的Position 如果需要的屬性沒(méi)有可以自己在這邊定義
[Serializable]
public struct BlockTag : IComponentData { }
public class BlockTagComponment : ComponentDataWrapper<BlockTag> { };

[Serializable]
public struct DestoryTag : IComponentData { }
public class DestoryTagComponment : ComponentDataWrapper<DestoryTag> { };

[Serializable]
public struct SurfacePlantTag : IComponentData { }
public class SurfacePlantTagComponment : ComponentDataWrapper<SurfacePlantTag> { };

刪除系統(tǒng) 這個(gè)OnUpdate就跟unity的update一樣

/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Collections;
using Unity.Mathematics;

//IJobProcessComponentData可以放到JobSystem處理
public class DestroySystem : ComponentSystem
{
    //類(lèi)似于Select語(yǔ)句
    struct BlockGroup
    {
        [ReadOnly] public readonly int Length;
        [ReadOnly] public EntityArray entity;
        //就是檢索語(yǔ)句 符合的條件都在這里面
        [ReadOnly] public ComponentDataArray<Position> postions;
        [ReadOnly] public ComponentDataArray<BlockTag> tags;

    }

    struct DestoryBlockGroup
    {
        [ReadOnly] public readonly int Length;
        [ReadOnly] public EntityArray entity;
        [ReadOnly] public ComponentDataArray<Position> postions;
        [ReadOnly] public ComponentDataArray<DestoryTag> tags;
    }
    //某些方塊上的花
    struct SurfacePlantGroup
    {
        [ReadOnly] public readonly int Length;
        [ReadOnly] public EntityArray entity;
        [ReadOnly] public ComponentDataArray<Position> postions;
        [ReadOnly] public ComponentDataArray<SurfacePlantTag> tags;
    }
    //群組 Inject是注入數(shù)據(jù)
    [Inject] BlockGroup targetBlocks;
    [Inject] DestoryBlockGroup sourceBlocks;
    [Inject] SurfacePlantGroup surfacePlants;

    protected override void OnUpdate()
    {
        for (int i = 0; i < sourceBlocks.Length; i++)
        {
            for (int j = 0; j < targetBlocks.Length; j++)
            {
                Vector3 offect = targetBlocks.postions[j].Value - sourceBlocks.postions[i].Value;
                //平方
                float sqrLen = offect.sqrMagnitude;
                //就是刪除方塊組中有和總方塊組一樣的 就刪除
                if (sqrLen == 0)
                {
                    //同時(shí)尋找磚塊上是否有草 有了也刪除 就是草的位置y-1如果等于現(xiàn)在位置就刪除
                    for (int k = 0; k < surfacePlants.Length; k++)
                    {
                        float3 pos = new float3(surfacePlants.postions[k].Value.x, surfacePlants.postions[k].Value.y + Vector3.down.y, surfacePlants.postions[k].Value.z);
                        offect = targetBlocks.postions[j].Value - pos;
                        sqrLen = offect.sqrMagnitude;

                        if (sqrLen == 0)
                        {
                            PostUpdateCommands.DestroyEntity(surfacePlants.entity[k]);
                        }
                    }

                    //刪除 刪除方塊組和總方塊組的該entity
                    PostUpdateCommands.DestroyEntity(sourceBlocks.entity[i]);
                    PostUpdateCommands.DestroyEntity(targetBlocks.entity[j]);
                }
            }
        }
    }
}

然后把剛才寫(xiě)的挖掘Destory的Tag解開(kāi)


image.png

不要忘了解除生成方塊的Tag


image.png

然后把GameSetting改一下
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;
using System;

public class GameSetting : MonoBehaviour
{
    Texture2D heightmap;

    public static EntityArchetype blockArchetype;

    [Header("Wrold = ChunkBase x ChunkBase")]
    public int chunckBase = 1;

    [Header("Mesh Info")]
    public Mesh blockMesh;
    public Mesh surfaceMesh;
    public Mesh tallGrassMesh;

    [Header("Nature Block Type")]
    public Material stoneMat;
    public Material woodMat;
    public Material leavesMat;
    public Material surfaceMat;
    public Material cobbleMat;
    public Material dirtMaterial;
    public Material tallGrassMat;
    public Material roseMat;
    public Material CloudMat;

    [Header("Other Block Type")]
    public Material glassMat;
    public Material brickMat;
    public Material plankMat;
    public Material tntMat;
    //找不到用粉色
    [Header("")]
    public Material pinkMat;

    public bool createCollider = true;

    public GameObject boxCollider;
    Mesh meshTemp;
    Material maTemp;

    EntityManager manager;
    Entity entities;

    int random;

    public ColliderPool colPool;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initialize()
    {
        //檢查場(chǎng)景是否有 有得到?jīng)]有創(chuàng)建
        EntityManager manager = World.Active.GetOrCreateManager<EntityManager>();

        blockArchetype = manager.CreateArchetype(
            typeof(Position)
            );
    }

    void Start()
    {
        manager = World.Active.GetOrCreateManager<EntityManager>();

        PerlinNoiseGenerator perlin = new PerlinNoiseGenerator();
        heightmap = perlin.GenerateHeightMap();

        //創(chuàng)建一個(gè)碰撞池
        colPool = new ColliderPool(boxCollider, transform);

        ChunckGenerator(chunckBase);
    }

    void ChunckGenerator(int amount)
    {
        int totalamount = (amount * amount) * 1500;

        int highlevel;
        bool airChecker;

        for (int y = 0; y < 15; y++)
        {
            for (int x = 0; x < 10 * amount; x++)
            {
                for (int z = 0; z < 10 * amount; z++)
                {
                    //返回像素顏色 數(shù)很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    switch (highlevel)
                    {
                        //表層 根據(jù)一個(gè)單位 和 多個(gè)單位 分開(kāi)方法創(chuàng)建
                        case 0:
                            random = UnityEngine.Random.Range(1, 201);
                            if (random <= 20)
                            {
                                //草
                                PlantGenerator(x, y, z, 1);
                            }
                            else if (random == 198)
                            {
                                //云
                                CloudGenerator(x, y, z);
                            }
                            else if (random == 199)
                            {
                                //樹(shù)
                                TreeGenerator(x, y, z);
                            }
                            else if (random == 200)
                            {
                                //花
                                PlantGenerator(x, y, z, 2);
                            }
                            airChecker = true;
                            break;
                        case 1:
                            //綠色帶土的方塊
                            meshTemp = surfaceMesh;
                            maTemp = surfaceMat;
                            break;
                        case 2:
                        case 3:
                        case 4:
                            //土
                            meshTemp = blockMesh;
                            maTemp = dirtMaterial;
                            break;
                        case 5:
                        case 6:
                            //石頭
                            meshTemp = blockMesh;
                            maTemp = stoneMat;
                            break;
                        case 7:
                        case 8:
                            //鵝卵石
                            meshTemp = blockMesh;
                            maTemp = cobbleMat;
                            break;
                        default:
                            airChecker = true;
                            break;
                    }

                    if (!airChecker)
                    {
                        CreatePrefab(x, y, z ,meshTemp, maTemp, new BlockTag { });
                    }
                }
            }
        }
    }

    void TreeGenerator(int x, int y, int z)
    {
        for (int i = y; i < y + 7; i++)
        {
            //軀干部分          
            if (i == y + 6)
            {
                //樹(shù)頂
                maTemp = leavesMat;
            }
            else
            {
                maTemp = woodMat;
            }

            CreatePrefab(x, i, z, blockMesh, maTemp, new BlockTag { });

            //樹(shù)葉 就是個(gè)正方形
            if (i >= y + 3 && i <= y + 6)
            {
                for (int j = x - 1; j <= x + 1; j++)
                {
                    for (int k = z - 1; k <= z + 1; k++)
                    {
                        //不能隨機(jī)到軀干
                        if (j != x || k != z)
                        {
                            CreatePrefab(j, i, k, blockMesh, leavesMat, new BlockTag { });
                        }

                    }
                }
            }
        }
    }

    void PlantGenerator(int x, int y, int z, int plantType)
    {
        switch (plantType)
        {
            case 1:
                maTemp = tallGrassMat;
                break;
            default:
                maTemp = roseMat;
                break;

        }

        CreatePrefab(x, y, z, tallGrassMesh, maTemp, new SurfacePlantTag { },(entities) => { manager.AddComponentData(entities, new Rotation { Value = Quaternion.Euler(0, 45, 0) }); });
    }

    void CloudGenerator(int x, int y, int z)
    {
        random = UnityEngine.Random.Range(4, 7);

        //提升y的高度 產(chǎn)生一個(gè)方形的云
        for (int i = 0; i < random; i++)
        {
            for (int j = 0; j < random; j++)
            {
                CreatePrefab(x + i, y + 15, z + j, blockMesh, CloudMat,new BlockTag { });
            }
        }
    }

    delegate void CreateFunc(Entity entities);

    void CreatePrefab<T>(int x, int y, int z, Mesh mesh, Material ma,T componentData ,CreateFunc func = null)where T :struct,IComponentData
    {
        AddCollider(new Vector3(x, y, z));
        Entity entities = manager.CreateEntity(blockArchetype);
        manager.SetComponentData(entities, new Position { Value = new int3(x, y, z) });
        manager.AddComponentData(entities, componentData);

        //找不到是粉色方塊
        if (!maTemp)
            maTemp = pinkMat;

        func?.Invoke(entities);
        manager.AddSharedComponentData(entities, new MeshInstanceRenderer
        {
            mesh = mesh,
            material = ma,
        });
    }

    //在相同位置生成一個(gè)1x1x1的正方形碰撞
    void AddCollider(Vector3 vec3)
    {
        if (createCollider)
        {
            colPool.AddCollider(vec3);
        }
    }


}

然后是左鍵可以銷(xiāo)毀Cube 右鍵可以放置Cube瘾杭,不過(guò)會(huì)出問(wèn)題,就是銷(xiāo)毀后他本體不會(huì)消失哪亿,添加還會(huì)重疊233粥烁,最后再找這些BUG,先上UI

UI

他做好的直接拿出來(lái)用


image.png

是這個(gè)樣子


image.png

下面的腳本都丟失了蝇棉,我們寫(xiě)一個(gè)
image.png
/**
 *Copyright(C) 2019 by #COMPANY#
 *All rights reserved.
 *FileName:     #SCRIPTFULLNAME#
 *Author:       #AUTHOR#
 *Version:      #VERSION#
 *UnityVersion:#UNITYVERSION#
 *Date:         #DATE#
 *Description:   
 *History:
*/
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ToolbarScript : MonoBehaviour
{
    public int blockNum;
    public Transform select;
    // Start is called before the first frame update
    void Start()
    {
        select = transform.GetChild(0);
    }

    // Update is called once per frame
    void Update()
    {
        if (PickaxeController.blockID==blockNum)
        {
            select.GetComponent<RawImage>().enabled = true;
        }
        else
        {
            select.GetComponent<RawImage>().enabled = false;
        }
    }
}

然后像這樣每個(gè)都處理下


image.png

來(lái)找找BUG問(wèn)題

運(yùn)行后發(fā)現(xiàn)DestorySystem有點(diǎn)不正常


image.png

第一個(gè)是草走不過(guò)去页徐,因?yàn)榻o草加了碰撞 修改如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
using Unity.Rendering;
using System;

public class GameSetting : MonoBehaviour
{
    Texture2D heightmap;

    public static EntityArchetype blockArchetype;

    [Header("Wrold = ChunkBase x ChunkBase")]
    public int chunckBase = 1;

    [Header("Mesh Info")]
    public Mesh blockMesh;
    public Mesh surfaceMesh;
    public Mesh tallGrassMesh;

    [Header("Nature Block Type")]
    public Material stoneMat;
    public Material woodMat;
    public Material leavesMat;
    public Material surfaceMat;
    public Material cobbleMat;
    public Material dirtMaterial;
    public Material tallGrassMat;
    public Material roseMat;
    public Material CloudMat;

    [Header("Other Block Type")]
    public Material glassMat;
    public Material brickMat;
    public Material plankMat;
    public Material tntMat;
    //找不到用粉色
    [Header("")]
    public Material pinkMat;

    public bool createCollider = true;

    public GameObject boxCollider;
    Mesh meshTemp;
    Material maTemp;

    EntityManager manager;
    Entity entities;

    int random;

    public ColliderPool colPool;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initialize()
    {
        //檢查場(chǎng)景是否有 有得到?jīng)]有創(chuàng)建
        EntityManager manager = World.Active.GetOrCreateManager<EntityManager>();

        blockArchetype = manager.CreateArchetype(
            typeof(Position)
            );
    }

    void Start()
    {
        manager = World.Active.GetOrCreateManager<EntityManager>();

        PerlinNoiseGenerator perlin = new PerlinNoiseGenerator();
        heightmap = perlin.GenerateHeightMap();

        //創(chuàng)建一個(gè)碰撞池
        colPool = new ColliderPool(boxCollider, transform);

        ChunckGenerator(chunckBase);
    }

    void ChunckGenerator(int amount)
    {
        int totalamount = (amount * amount) * 1500;

        int highlevel;
        bool airChecker;

        for (int y = 0; y < 15; y++)
        {
            for (int x = 0; x < 10 * amount; x++)
            {
                for (int z = 0; z < 10 * amount; z++)
                {
                    //返回像素顏色 數(shù)很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    switch (highlevel)
                    {
                        //表層 根據(jù)一個(gè)單位 和 多個(gè)單位 分開(kāi)方法創(chuàng)建
                        case 0:
                            random = UnityEngine.Random.Range(1, 201);
                            if (random <= 20)
                            {
                                //草
                                PlantGenerator(x, y, z, 1);
                            }
                            else if (random == 198)
                            {
                                //云
                                CloudGenerator(x, y, z);
                            }
                            else if (random == 199)
                            {
                                //樹(shù)
                                TreeGenerator(x, y, z);
                            }
                            else if (random == 200)
                            {
                                //花
                                PlantGenerator(x, y, z, 2);
                            }
                            airChecker = true;
                            break;
                        case 1:
                            //綠色帶土的方塊
                            meshTemp = surfaceMesh;
                            maTemp = surfaceMat;
                            break;
                        case 2:
                        case 3:
                        case 4:
                            //土
                            meshTemp = blockMesh;
                            maTemp = dirtMaterial;
                            break;
                        case 5:
                        case 6:
                            //石頭
                            meshTemp = blockMesh;
                            maTemp = stoneMat;
                            break;
                        case 7:
                        case 8:
                            //鵝卵石
                            meshTemp = blockMesh;
                            maTemp = cobbleMat;
                            break;
                        default:
                            airChecker = true;
                            break;
                    }

                    if (!airChecker)
                    {
                        CreatePrefab(x, y, z ,meshTemp, maTemp, new BlockTag { });
                    }
                }
            }
        }
    }

    void TreeGenerator(int x, int y, int z)
    {
        for (int i = y; i < y + 7; i++)
        {
            //軀干部分          
            if (i == y + 6)
            {
                //樹(shù)頂
                maTemp = leavesMat;
            }
            else
            {
                maTemp = woodMat;
            }

            CreatePrefab(x, i, z, blockMesh, maTemp, new BlockTag { });

            //樹(shù)葉 就是個(gè)正方形
            if (i >= y + 3 && i <= y + 6)
            {
                for (int j = x - 1; j <= x + 1; j++)
                {
                    for (int k = z - 1; k <= z + 1; k++)
                    {
                        //不能隨機(jī)到軀干
                        if (j != x || k != z)
                        {
                            CreatePrefab(j, i, k, blockMesh, leavesMat, new BlockTag { });
                        }

                    }
                }
            }
        }
    }

    void PlantGenerator(int x, int y, int z, int plantType)
    {
        switch (plantType)
        {
            case 1:
                maTemp = tallGrassMat;
                break;
            default:
                maTemp = roseMat;
                break;

        }

        CreatePrefab(x, y, z, tallGrassMesh, maTemp, new SurfacePlantTag { },false,(entities) => { manager.AddComponentData(entities, new Rotation { Value = Quaternion.Euler(0, 45, 0) }); });
    }

    void CloudGenerator(int x, int y, int z)
    {
        random = UnityEngine.Random.Range(4, 7);

        //提升y的高度 產(chǎn)生一個(gè)方形的云
        for (int i = 0; i < random; i++)
        {
            for (int j = 0; j < random; j++)
            {
                CreatePrefab(x + i, y + 15, z + j, blockMesh, CloudMat,new BlockTag { },false);
            }
        }
    }

    delegate void CreateFunc(Entity entities);

    void CreatePrefab<T>(int x, int y, int z, Mesh mesh, Material ma,T componentData ,bool isCollider=true,CreateFunc func = null)where T :struct,IComponentData
    {
        if(isCollider)
        AddCollider(new Vector3(x, y, z));

        Entity entities = manager.CreateEntity(blockArchetype);
        manager.SetComponentData(entities, new Position { Value = new int3(x, y, z) });
        manager.AddComponentData(entities, componentData);

        //找不到是粉色方塊
        if (!maTemp)
            maTemp = pinkMat;

        func?.Invoke(entities);
        manager.AddSharedComponentData(entities, new MeshInstanceRenderer
        {
            mesh = mesh,
            material = ma,
        });
    }

    //在相同位置生成一個(gè)1x1x1的正方形碰撞
    void AddCollider(Vector3 vec3)
    {
        if (createCollider)
        {
            colPool.AddCollider(vec3);
        }
    }


}

還有發(fā)現(xiàn)之前手動(dòng)改layer沒(méi)必要,都自動(dòng)加了银萍,但是要確認(rèn)下是不是第九層就Blocks層


image.png

對(duì)照源碼变勇,需要掛到子節(jié)點(diǎn)自己再加個(gè)AudioSource


image.png

當(dāng)然特效預(yù)制體要拖出來(lái)用這個(gè)
image.png

很明顯刪除組21個(gè)沒(méi)有刪除


image.png

然后仔細(xì)看了看因?yàn)槎疾皇钦麛?shù)
image.png

連很多碰撞坐標(biāo)都不是整數(shù)
image.png

找了半天是因?yàn)槟_本掛燈光上了,燈光有旋轉(zhuǎn)贴唇,自帶45度所以都坐標(biāo)亂了搀绣,以后要找個(gè)空物體掛上。然后刪除沒(méi)人問(wèn)題了戳气,創(chuàng)建距離近的話(huà)會(huì)跑到頭上

然后打斷點(diǎn)發(fā)現(xiàn)如果距離夠近链患,碰撞物體就是玩家自己


image.png

因?yàn)橹剌d,這個(gè)不是layer而是距離長(zhǎng)度瓶您,最遠(yuǎn)距離


image.png

所以改成
image.png

問(wèn)題解決

優(yōu)化處理及問(wèn)題

我們Collider比較多麻捻,性能消耗海星,因?yàn)闆](méi)有Rigibody不會(huì)一直去檢測(cè)
如果還覺(jué)得性能消耗比較大


image.png

把Blocks Blocks之間的影響取消掉呀袱,只剩下Default也就是我們第一人稱(chēng)控制器才有影響


image.png

Q&A
1.Entity并非GameObject贸毕,可以在編輯器內(nèi)調(diào)整嘛?
現(xiàn)在還不行夜赵,預(yù)期以后會(huì)整和與GameObject一樣
2.ECS除了移動(dòng)物件坐標(biāo)之外明棍,能處理讀數(shù)據(jù)庫(kù)這樣很花時(shí)間的工作嗎?
可以寇僧,ECS就是Data和Data的處理摊腋,把數(shù)據(jù)拉出來(lái)存到ComponentData去處理
3.我可以產(chǎn)生出來(lái)的Entity在新增修改移除Component嘛?
可以嘁傀,需要先把指令放在ComponentBuffer里兴蒸,確保(沒(méi)聽(tīng)清什么Type)密合的,會(huì)耗用一點(diǎn)時(shí)間细办,太頻繁問(wèn)題會(huì)比較大
4.ECS對(duì)于有經(jīng)驗(yàn)的Unity是否對(duì)沒(méi)經(jīng)驗(yàn)的學(xué)的難
一時(shí)之間會(huì)比較難接受橙凳,還是上手比較快,其實(shí)都差不多,都是新東西痕惋。
5.有性能瓶頸舊方案区宇,有什么快的方法轉(zhuǎn)換ECS
首先要升級(jí)到Unity2018,第二是你需要寫(xiě)物理系統(tǒng)值戳,還有特效系統(tǒng)议谷,萬(wàn)一你寫(xiě)好了,官方也寫(xiě)好了就尷尬了堕虹,現(xiàn)在ECS主要是做大量物件出現(xiàn)和消失卧晓。舊的整個(gè)改其實(shí)不是太合適,最好2019之后赴捞。
6.全新的游戲逼裆,是否策劃也要考慮ECS
策劃(比較高級(jí)的)需要考慮System和ComponentData之間的關(guān)系處理,主要是偏向程序員考慮赦政。
7.PureECS和HybirdECS可以混合用嗎
可以胜宇,這個(gè)游戲就是,PureECS性能高于HybirdECS恢着,個(gè)人感覺(jué)比較難用
8.System可以分不同場(chǎng)景運(yùn)作嘛
可以桐愉,但是System是沒(méi)有場(chǎng)景概念的,所有場(chǎng)景的東西都會(huì)處理掰派,只要有Entity就會(huì)運(yùn)行


image.png

但是可以用代碼在不同場(chǎng)景不需要System關(guān)閉掉从诲,現(xiàn)在好像沒(méi)有,之后會(huì)出Api

這個(gè)是System也分先后初始化靡羡,這個(gè)就是死亡系統(tǒng)在渲染系統(tǒng)之前進(jìn)行系洛,不加的話(huà)可能造成渲染之后才刪除,會(huì)短暫閃一下


image.png

這個(gè)是GitHub
https://github.com/1004019267/Minecraft-with-ECS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末略步,一起剝皮案震驚了整個(gè)濱河市描扯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纳像,老刑警劉巖荆烈,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異竟趾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)宫峦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)岔帽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人导绷,你說(shuō)我怎么就攤上這事犀勒。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵贾费,是天一觀的道長(zhǎng)钦购。 經(jīng)常有香客問(wèn)我,道長(zhǎng)褂萧,這世上最難降的妖魔是什么押桃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮导犹,結(jié)果婚禮上唱凯,老公的妹妹穿的比我還像新娘。我一直安慰自己谎痢,他們只是感情好磕昼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著节猿,像睡著了一般票从。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滨嘱,一...
    開(kāi)封第一講書(shū)人閱讀 49,929評(píng)論 1 290
  • 那天峰鄙,我揣著相機(jī)與錄音,去河邊找鬼九孩。 笑死先馆,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躺彬。 我是一名探鬼主播煤墙,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼宪拥!你這毒婦竟也來(lái)了仿野?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤她君,失蹤者是張志新(化名)和其女友劉穎脚作,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缔刹,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡球涛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了校镐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亿扁。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸟廓,靈堂內(nèi)的尸體忽然破棺而出从祝,到底是詐尸還是另有隱情襟己,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布牍陌,位于F島的核電站擎浴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏毒涧。R本人自食惡果不足惜贮预,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望链嘀。 院中可真熱鬧萌狂,春花似錦、人聲如沸怀泊。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)霹琼。三九已至务傲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枣申,已是汗流浹背售葡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留忠藤,地道東北人挟伙。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像模孩,于是被迫代替她去往敵國(guó)和親尖阔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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