Unity 用Entities(ECS)做我的世界及ECS的一些问题

这个是unity官方演示ECS
https://connect.unity.com/p/zhi-bo-hui-gu-shi-yong-unity-ecskai-fa-wo-de-shi-jie

环境搭设

打开PlayerSetting确保.NET库在4.X以上


image.png

进入工程文件夹


image.png

把下面脚本加入进入
{
{
"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

然后就自动下载


image.png

我试了很多版本都报迷之错误,这个不会报错


image.png

image.png

这里可以查看安装,有的版本可以直接在这个界面安装,就不用那样Json输入然后又匹配不对了

然后环境算是安装好了
我们可以看到ECS方式创建预制体,一定有一个GameObjectEntity还有一堆Component


image.png

打开EntitiesDebug界面
image.png

创建对比

然后是三种创建方式对比下

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

public class EntitiesTest : MonoBehaviour
{
    //宣告内存字段放在一起 而不是杂乱无序的 提高速率
    public static EntityArchetype blockArchetype;

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

    public GameObject go;

    //宣告在加载场景之前运行 可以理解为预存
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void Initialize()
    {
        //宣告管理器
        EntityManager manager = World.Active.GetOrCreateManager();
        //宣告内存区块(区域)
        blockArchetype = manager.CreateArchetype(
            typeof(Position)
            );
    }

    //[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
    void Start()
    {
        //Unity自己产生预制体 但是要添加GameObjectEntity Entities才能标记
        //或者 Instantiate   
        GameObject.CreatePrimitive(PrimitiveType.Cube)
            .AddComponent()
            .transform.position = new Vector3(-2, 0, 0);

        //PureECS 下面是不用Unity自带组件创建预制体 就是说脱离Unity也可以用的代码
        manager = World.Active.GetOrCreateManager();

        //在内存块中设置Pos和创建一个tag(并没有用)
        Entity entities = manager.CreateEntity(blockArchetype);
        manager.SetComponentData(entities, new Position { Value = new int3(2, 0, 0) });
        //这个是自定义的我们没有..
        //manager.AddComponentData(entities, new BlockTag());

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

        //Hybrid ECS 引用unity自带GameObject创建预制体
        if (go)
        {
            //产生一个新的阵列
            using (NativeArray entityArray = new NativeArray(1, Allocator.Temp))
            {
                manager.Instantiate(go, entityArray);
                manager.SetComponentData(entities, new Position { Value = new float3(4, 0f, 0f) });
            }
        }

    }
}

这个是预制体创建要挂的脚本,框架自带


image.png

image.png

前两个是不依赖Unity所需要的网格和材质
最后一个就是以产生预制体方式产生


image.png

通过Entities创建的预制体不会在这里显示
image.png

在自带的EntitiesDebug窗口中可看见
image.png

选择一个,右边有相应的信息,可是只能代码动态调整,以后会改成可调整,也就是说和GameObject对比起来没有什么区别的样子


image.png

世界生成

然后是我的世界生成世界是随机的,这里要用到柏林噪声,有规则的乱序生成,保证了之后地图再生成的衔接问题,不会出现地图生成断开了


这个是生成的图
image.png

这个是3D效果
image.png

上去官方开源gitHub扒素材学习 建议源码看视屏写不出来 有bug再看 因为这个教程都是干货 2小时含金量特别大
https://github.com/UnityTechnologies/MinecraftECS
找到这四个文件,放入unity
image.png

生成地图

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

//生成一个方块
public class PerlinNoiseGenerator
{
    public static int BlockFaces = 0;
    public static Texture2D noiseHeightMap;
    //长宽的地图
    int texWidth = 200, texHeight = 200;

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

    //随机采样偏移
    float offectX;
    float offectY;

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

    /// 
    /// 根据长短创建200X200的每一个点
    /// 
    /// 
    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;
    }

    /// 
    /// 用unity自带的2维柏林噪声计算每个点的偏移值
    /// 
    /// 
    /// 
    /// 
    Color CakculateColor(int x, int y)
    {
        //根据我们的偏移值 计算出类似于波形的图 就是黑白黑白间隔的图
        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之间的小数 可能会略大于一
        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;

/// 
/// 加载一堆方块
/// 
public class SpawnNumBlocks : MonoBehaviour
{
    public Texture2D heightmap;

    public static EntityArchetype blockArchetype;

    //10X10的地方留着 之外的不显示或删除 类似于遮挡剔除
    [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();

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

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

    void ChunkGenerator(int amount)
    {
        //一个chunckBase相当于1500个方块
        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++)
                {
                    //返回像素颜色 数很小乘上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
                    {
                         //超过的视为空气
                            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,
                        });
                    }
                }
            }
        }
    }


}

然后是找到数字挂在


image.png
image.png

运行


image.png

其实本来应该运行Batches数到达2w的但为什么这么低


image.png

因为勾了用GPU Instance创建预制体,在大量物体创建时会进行优化,如果是少量就和直接Instance没有区别

然后我们就可以根据层生成不同的方块


image.png

树就是从根部开始 柱子0-7 然后再周围加树叶


image.png

把之前代码进一步扩充

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()
    {
        //检查场景是否有 有得到没有创建
        EntityManager manager = World.Active.GetOrCreateManager();

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

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

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

        //创建一个碰撞池
        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++)
                {
                    //返回像素颜色 数很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    switch (highlevel)
                    {
                        //表层 根据一个单位 和 多个单位 分开方法创建
                        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)
                            {
                                //树
                                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)
            {
                //树顶
                maTemp = leavesMat;
            }
            else
            {
                maTemp = woodMat;
            }

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

            //树叶 就是个正方形
            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++)
                    {
                        //不能随机到躯干
                        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的高度 产生一个方形的云
        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,
        });
    }

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


}

还有个碰撞脚本

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;
    }
    //在同样位置创建一个单位碰撞
    public void AddCollider(Vector3 vec3)
    {
        GameObject obj = GameObject.Instantiate(boxCollider);
        obj.transform.position = vec3;
        obj.transform.parent = parent;
        obj.layer = 9;
    }
}

这么挂脚本


image.png

运行


image.png

把Chunckbase改为100卡了N久700w个物体
image.png

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


image.png

我写到现在出过几次错误,就是mesh被拉伸,加加减减比较多

人物控制

从这里下载官方标准资源包,导入


image.png

把第一人称控制器拖出来就是第一个,因为他自带摄像机,把一开始世界的摄像机删掉


image.png

因为我们草也是Cube碰撞,所以没办法从草上传过去
第一人称自动把鼠标屏蔽了 ESC就可以移动出Game视图了

实现挖掘

然后先把压缩包的音效文件导入


image.png

然后创建一个新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;

    //挖掘喷发的特效
    public ParticleSystem digEffect;

    EntityManager manager;

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

        manager = World.Active.GetOrCreateManager();
    }

    // Update is called once per frame
    void Update()
    {
        //滑动过界就改变为另一边
        if (blockID > 7)
        {
            blockID = 1;
        }
        if (blockID < 1)
        {
            blockID = 7;
        }
        //滑动选择方块
        if (Input.GetAxis("Mouse ScrollWheel") < 0)
        {
            blockID++;
        }
        if (Input.GetAxis("Mouse ScrollWheel") > 0)
        {
            blockID--;
        }

        //看是选择哪个砖块
        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;
        //向角色正前方发射射线
        Physics.Raycast(transform.position, transform.forward, out hit, blockLayer);
        if (hit.transform != null)
        {
            //根据不同方块播放音效
            if (blockID == 1 || blockID == 3 || blockID == 5 || blockID == 7)
            {
                AS.PlayOneShot(stone_audio);
            }
            else if (blockID == 2 || blockID == 4)
            {
                AS.PlayOneShot(wood_audio);
            }
            //创建一个方块在射线前方法线的位置 加上碰撞
            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;
        //向角色正前方发射射线
        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);
        }
    }

}

然后在第一人称控制器的子节点挂载脚本


image.png

然后还是不能删除,因为删除的只有碰撞

删除系统

到现在ECS只用到EC没说S
之前我们的面向对象是一个Class贯穿这么多处理


image.png

转化为System的话 是有个System处理专门每一个模块(用JobSystem)


image.png

所以做挖掘(删除)需要处理,这个就是之前我们打不出来的Tag,根据Tag区分


image.png

这个是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;
//都只是作为一个标签
//比如自带的Position 如果需要的属性没有可以自己在这边定义
[Serializable]
public struct BlockTag : IComponentData { }
public class BlockTagComponment : ComponentDataWrapper { };

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

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

删除系统 这个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
{
    //类似于Select语句
    struct BlockGroup
    {
        [ReadOnly] public readonly int Length;
        [ReadOnly] public EntityArray entity;
        //就是检索语句 符合的条件都在这里面
        [ReadOnly] public ComponentDataArray postions;
        [ReadOnly] public ComponentDataArray tags;

    }

    struct DestoryBlockGroup
    {
        [ReadOnly] public readonly int Length;
        [ReadOnly] public EntityArray entity;
        [ReadOnly] public ComponentDataArray postions;
        [ReadOnly] public ComponentDataArray tags;
    }
    //某些方块上的花
    struct SurfacePlantGroup
    {
        [ReadOnly] public readonly int Length;
        [ReadOnly] public EntityArray entity;
        [ReadOnly] public ComponentDataArray postions;
        [ReadOnly] public ComponentDataArray tags;
    }
    //群组 Inject是注入数据
    [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)
                {
                    //同时寻找砖块上是否有草 有了也删除 就是草的位置y-1如果等于现在位置就删除
                    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]);
                }
            }
        }
    }
}

然后把刚才写的挖掘Destory的Tag解开


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()
    {
        //检查场景是否有 有得到没有创建
        EntityManager manager = World.Active.GetOrCreateManager();

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

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

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

        //创建一个碰撞池
        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++)
                {
                    //返回像素颜色 数很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    switch (highlevel)
                    {
                        //表层 根据一个单位 和 多个单位 分开方法创建
                        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)
                            {
                                //树
                                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)
            {
                //树顶
                maTemp = leavesMat;
            }
            else
            {
                maTemp = woodMat;
            }

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

            //树叶 就是个正方形
            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++)
                    {
                        //不能随机到躯干
                        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的高度 产生一个方形的云
        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(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,
        });
    }

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


}

然后是左键可以销毁Cube 右键可以放置Cube,不过会出问题,就是销毁后他本体不会消失,添加还会重叠233,最后再找这些BUG,先上UI

UI

他做好的直接拿出来用


image.png

是这个样子


image.png

下面的脚本都丢失了,我们写一个
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().enabled = true;
        }
        else
        {
            select.GetComponent().enabled = false;
        }
    }
}

然后像这样每个都处理下


image.png

来找找BUG问题

运行后发现DestorySystem有点不正常


image.png

第一个是草走不过去,因为给草加了碰撞 修改如下

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()
    {
        //检查场景是否有 有得到没有创建
        EntityManager manager = World.Active.GetOrCreateManager();

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

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

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

        //创建一个碰撞池
        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++)
                {
                    //返回像素颜色 数很小乘上100
                    highlevel = (int)(heightmap.GetPixel(x, z).r * 100) - y;
                    airChecker = false;

                    switch (highlevel)
                    {
                        //表层 根据一个单位 和 多个单位 分开方法创建
                        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)
                            {
                                //树
                                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)
            {
                //树顶
                maTemp = leavesMat;
            }
            else
            {
                maTemp = woodMat;
            }

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

            //树叶 就是个正方形
            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++)
                    {
                        //不能随机到躯干
                        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的高度 产生一个方形的云
        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(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,
        });
    }

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


}

还有发现之前手动改layer没必要,都自动加了,但是要确认下是不是第九层就Blocks层


image.png

对照源码,需要挂到子节点自己再加个AudioSource


image.png

当然特效预制体要拖出来用这个
image.png

很明显删除组21个没有删除


image.png

然后仔细看了看因为都不是整数
image.png

连很多碰撞坐标都不是整数
image.png

找了半天是因为脚本挂灯光上了,灯光有旋转,自带45度所以都坐标乱了,以后要找个空物体挂上。然后删除没人问题了,创建距离近的话会跑到头上

然后打断点发现如果距离够近,碰撞物体就是玩家自己


image.png

因为重载,这个不是layer而是距离长度,最远距离


image.png

所以改成
image.png

问题解决

优化处理及问题

我们Collider比较多,性能消耗海星,因为没有Rigibody不会一直去检测
如果还觉得性能消耗比较大


image.png

把Blocks Blocks之间的影响取消掉,只剩下Default也就是我们第一人称控制器才有影响


image.png

Q&A
1.Entity并非GameObject,可以在编辑器内调整嘛?
现在还不行,预期以后会整和与GameObject一样
2.ECS除了移动物件坐标之外,能处理读数据库这样很花时间的工作吗?
可以,ECS就是Data和Data的处理,把数据拉出来存到ComponentData去处理
3.我可以产生出来的Entity在新增修改移除Component嘛?
可以,需要先把指令放在ComponentBuffer里,确保(没听清什么Type)密合的,会耗用一点时间,太频繁问题会比较大
4.ECS对于有经验的Unity是否对没经验的学的难
一时之间会比较难接受,还是上手比较快,其实都差不多,都是新东西。
5.有性能瓶颈旧方案,有什么快的方法转换ECS
首先要升级到Unity2018,第二是你需要写物理系统,还有特效系统,万一你写好了,官方也写好了就尴尬了,现在ECS主要是做大量物件出现和消失。旧的整个改其实不是太合适,最好2019之后。
6.全新的游戏,是否策划也要考虑ECS
策划(比较高级的)需要考虑System和ComponentData之间的关系处理,主要是偏向程序员考虑。
7.PureECS和HybirdECS可以混合用吗
可以,这个游戏就是,PureECS性能高于HybirdECS,个人感觉比较难用
8.System可以分不同场景运作嘛
可以,但是System是没有场景概念的,所有场景的东西都会处理,只要有Entity就会运行


image.png

但是可以用代码在不同场景不需要System关闭掉,现在好像没有,之后会出Api

这个是System也分先后初始化,这个就是死亡系统在渲染系统之前进行,不加的话可能造成渲染之后才删除,会短暂闪一下


image.png

这个是GitHub
https://github.com/1004019267/Minecraft-with-ECS

你可能感兴趣的:(Unity 用Entities(ECS)做我的世界及ECS的一些问题)