DOTS 1.4 升级指南:与老版本对比的五大核心变化

1️⃣ 系统基类全面改为 struct

旧版写法(class 系统)

public class MoveSystem : SystemBase
{
    protected override void OnUpdate()
    {
        Entities.ForEach((ref Translation pos, in MoveSpeed speed) =>
        {
            pos.Value += math.forward() * speed.Value * Time.DeltaTime;
        }).ScheduleParallel();
    }
}
  • 系统基于 class,需要 GC Heap 分配。

  • 生命周期类似 MonoBehaviour,有一定调度开销。

新版写法(struct 系统)

public partial struct MoveSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        float dt = SystemAPI.Time.DeltaTime;
        foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<MoveSpeed>>())
        {
            transform.ValueRW.Position += transform.ValueRW.Forward() * speed.ValueRO.Value * dt;
        }
    }
}
  • 系统实现 ISystem,为 struct,无GC分配。

  • 调度零开销,性能更优,完全数据驱动。


2️⃣ GameObject 转换改为 Baking Workflow

旧版使用 ConvertToEntityIConvertGameObjectToEntity,运行时转换Prefab为Entity,加载慢且调试麻烦。

新版使用 Baker,所有转换在编辑器完成:

public class MoveAuthoring : MonoBehaviour
{
    public float Speed;
    class Baker : Baker<MoveAuthoring>
    {
        public override void Bake(MoveAuthoring authoring)
        {
            AddComponent(new MoveSpeed { Value = authoring.Speed });
        }
    }
}
  • 无运行时开销。

  • 转换过程清晰、可调试。

  • 替代旧的 ConvertToEntity 和 Conversion Workflow。

这里需要介绍一下新版本中非常重要的SubScene

SubScene是Dots在1.0版本后提供的,批量将GameObject对象转为Entity的工具,在Hierarchy右键鼠标,找到NewSubScene 新建,名字随意。

SubScene的作用是在Runtime时,将SubScene中的GameObject转成Entity。

设计SubScene的原因是当项目很大的时候,可以用不同的SubScene将不同的工作分类开,不执行的系统整个SubScene卸载掉。

在美术设计环节,比如做一个很大的场景,当场景中有数百万个对象,可以存放在SubScene中,但不载入它,这样不占用内存,只在RunTime的时候载入,这样场景设计时不用加载太多的模型


3️⃣ Entities.ForEach 改为 SystemAPI.Query

旧版:

Entities.ForEach((ref Translation pos, in MoveSpeed speed) => { ... });

新版:

foreach (var (transform, speed) in SystemAPI.Query<RefRW<LocalTransform>, RefRO<MoveSpeed>>())
{
    transform.ValueRW.Position += transform.ValueRW.Forward() * speed.ValueRO.Value * dt;
}
  • 查询更接近原生 foreach,编译速度更快。

  • RefRO / RefRW 明确标注读写权限,Burst优化更好。


4️⃣ Transform 组件合并为 LocalTransform

旧版:

public struct Translation : IComponentData { public float3 Value; }
public struct Rotation : IComponentData { public quaternion Value; }

新版:

public struct LocalTransform : IComponentData
{
    public float3 Position;
    public quaternion Rotation;
    public float Scale;
}
  • 组件数量减少。

  • 数据连续存储,缓存友好。

  • 代码更简洁。


5️⃣ IJobChunk 改为 IJobEntity

旧版需要手动处理Chunk:

public struct MoveJob : IJobChunk
{
    public ComponentTypeHandle<Translation> translationType;
    public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex) { ... }
}

新版可直接访问组件:

[BurstCompile]
partial struct MoveJob : IJobEntity
{
    public float DeltaTime;
    void Execute(ref LocalTransform transform, in MoveSpeed speed)
    {
        transform.Position += transform.Forward() * speed.Value * DeltaTime;
    }
}
  • 无需手动管理组件句柄。

  • 更易读、模块化,支持 ScheduleParallel。

本来想用新版Dots做一个鱼群效果,但是发现新版本的DOTS好像并不是很完善,蒙皮动画并不兼容,还要再使用别的过渡插件,预感后面还会有很大的更新,文档也很少,只能去啃官方的文档,现在学习成本太高,感觉可以等一个稳定版本再用。

最后附上一个简单的鱼群脚本

这个是代码结构图

Component脚本

using Unity.Entities;
using Unity.Mathematics;

public struct FishData : IComponentData
{
    public float3 velocity;
    public float speed;
}

public struct FishTag : IComponentData{}

public struct FishSpawnerData : IComponentData
{
    public Entity Prefab;
    public int Count;
    public float3 AreaCenter;
    public float3 AreaSize;
    public float SpeedMin;
    public float SpeedMax;
}

这个脚本挂在一个空对象上,然后放到SubScene中 ,fishPrefab把鱼的预制件放上去

using UnityEngine;
using Unity.Entities;

public class FishSpawnerAuthoring : MonoBehaviour
{
    [Header("预制体 & 数量")] public GameObject fishPrefab;
    public int fishCount = 100;
    public Vector3 areaCenter;
    public Vector3 areaSize;
    public Vector2 speedRange = new Vector2(0.1f, 0.2f);
    class Baker : Baker<FishSpawnerAuthoring>
    {
        public override void Bake(FishSpawnerAuthoring authoring)
        {
            var entity = GetEntity(TransformUsageFlags.Dynamic);
            AddComponent(entity, new FishSpawnerData()
            {
                Prefab = GetEntity(authoring.fishPrefab, TransformUsageFlags.Dynamic),
                Count = authoring.fishCount,
                AreaCenter = authoring.areaCenter,
                AreaSize = authoring.areaSize,
                SpeedMin = authoring.speedRange.x,
                SpeedMax = authoring.speedRange.y
            });
        }
    }
}

 鱼类的脚本,挂在鱼对象上,然后把鱼拖成预制件

using Unity.Entities;
using UnityEngine;

public class GoldFishAuthoring : MonoBehaviour
{
    public float speed;
    class Baker : Baker<GoldFishAuthoring>
    {
        public override void Bake(GoldFishAuthoring authoring)
        {
            var entity = GetEntity(TransformUsageFlags.Dynamic);
            AddComponent(entity, new FishData
            {
                speed = authoring.speed
            });
        }
    }
}

 然后是两个系统脚本

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

public partial struct FishSpawnerSystem : ISystem
{
   private bool _spawned;
   public void OnCreate(ref SystemState state)
   {
      _spawned = false;
   
   }

   public void OnUpdate(ref SystemState state)
   {
      if(_spawned)return;
      var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);
      foreach (var spawner in SystemAPI.Query<FishSpawnerData>())
      {
         for (int i = 0; i < spawner.Count; i++)
         {
            var fish = ecb.Instantiate(spawner.Prefab);

            // 给每条鱼一个随机位置
            float3 pos = spawner.AreaCenter + new float3(
               UnityEngine.Random.Range(-spawner.AreaSize.x/2f, spawner.AreaSize.x/2f),
               UnityEngine.Random.Range(-spawner.AreaSize.y/2f, spawner.AreaSize.y/2f),
               UnityEngine.Random.Range(-spawner.AreaSize.z/2f, spawner.AreaSize.z/2f)
            );
            var dir = math.normalize(new float3(
               UnityEngine.Random.Range(-1f,1f),
               0,
               UnityEngine.Random.Range(-1f,1f)
            ));

            ecb.SetComponent(fish, LocalTransform.FromPositionRotationScale(pos, quaternion.LookRotationSafe(dir, math.up()), 1));
            ecb.AddComponent(fish, new FishData
            {
               velocity = dir,
               speed = UnityEngine.Random.Range(spawner.SpeedMin, spawner.SpeedMax)
            });
            ecb.AddComponent<FishTag>(fish);
         }
      }

      ecb.Playback(state.EntityManager);
      _spawned = true; // 防止重复生成
   }
}
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;

[BurstCompile]
[UpdateInGroup(typeof(SimulationSystemGroup))]
public partial struct FishMoveSystem : ISystem
{
    private const float NeighborRadius = 2f;
    private const float AvoidDistance = 1f;
    private const float CellSize = 3f;
    private const int MaxSampleCount = 8; // 每条鱼最多采样8个邻居

    private NativeParallelMultiHashMap<int, int> grid;

    public void OnCreate(ref SystemState state)
    {
        grid = new NativeParallelMultiHashMap<int, int>(1024, Allocator.Persistent);
    }

    public void OnDestroy(ref SystemState state)
    {
        if (grid.IsCreated) grid.Dispose();
    }

    public void OnUpdate(ref SystemState state)
    {
        var query = SystemAPI.QueryBuilder().WithAll<FishData, LocalTransform>().Build();
        var entityArray = query.ToEntityArray(Allocator.Temp);
        int count = entityArray.Length;

        if (count == 0) return;

        var positions = new NativeArray<float3>(count, Allocator.TempJob);
        var velocities = new NativeArray<float3>(count, Allocator.TempJob);
        var speeds = new NativeArray<float>(count, Allocator.TempJob);

        var eManager = state.EntityManager;
        for (int i = 0; i < count; i++)
        {
            var tr = eManager.GetComponentData<LocalTransform>(entityArray[i]);
            var data = eManager.GetComponentData<FishData>(entityArray[i]);
            positions[i] = tr.Position;
            velocities[i] = data.velocity;
            speeds[i] = data.speed;
        }

        // 清空并填充格子(持久化容器)
        grid.Clear();
        if (grid.Capacity < count)
        {
            grid.Capacity = count * 2;
        }
        for (int i = 0; i < count; i++)
        {
            int hash = Hash(positions[i]);
            grid.Add(hash, i);
        }

        var newPositions = new NativeArray<float3>(count, Allocator.TempJob);
        var newVelocities = new NativeArray<float3>(count, Allocator.TempJob);

        var job = new FishJob
        {
            positions = positions,
            velocities = velocities,
            speeds = speeds,
            newPositions = newPositions,
            newVelocities = newVelocities,
            grid = grid,
            cellSize = CellSize,
            deltaTime = SystemAPI.Time.DeltaTime
        };

        job.Schedule(count, 64).Complete();

        // 写回数据
        for (int i = 0; i < count; i++)
        {
            var tr = eManager.GetComponentData<LocalTransform>(entityArray[i]);
            var data = eManager.GetComponentData<FishData>(entityArray[i]);

            data.velocity = math.normalize(newVelocities[i]);
            tr.Position = newPositions[i];
            tr.Rotation = quaternion.LookRotationSafe(data.velocity, math.up());

            eManager.SetComponentData(entityArray[i], data);
            eManager.SetComponentData(entityArray[i], tr);
        }

        positions.Dispose();
        velocities.Dispose();
        speeds.Dispose();
        newPositions.Dispose();
        newVelocities.Dispose();
    }

    private static int Hash(float3 pos)
    {
        int x = (int)math.floor(pos.x / CellSize);
        int z = (int)math.floor(pos.z / CellSize);
        return x * 73856093 ^ z * 19349663;
    }

    [BurstCompile]
    private struct FishJob : IJobParallelFor
    {
        [ReadOnly] public NativeArray<float3> positions;
        [ReadOnly] public NativeArray<float3> velocities;
        [ReadOnly] public NativeArray<float> speeds;
        [ReadOnly] public NativeParallelMultiHashMap<int, int> grid;
        [ReadOnly] public float cellSize;
        [ReadOnly] public float deltaTime;

        public NativeArray<float3> newPositions;
        public NativeArray<float3> newVelocities;

        private int Hash(float3 pos)
        {
            int x = (int)math.floor(pos.x / cellSize);
            int z = (int)math.floor(pos.z / cellSize);
            return x * 73856093 ^ z * 19349663;
        }

        public void Execute(int index)
        {
            var selfPos = positions[index];
            var selfVel = velocities[index];
            float speed = speeds[index];

            float3 center = float3.zero;
            float3 avoid = float3.zero;
            float speedSum = 0f;
            int count = 0;
            int sampleCount = 0;

            // 遍历相邻格子
            for (int dx = -1; dx <= 1; dx++)
            {
                for (int dz = -1; dz <= 1; dz++)
                {
                    int hash = ((int)math.floor((selfPos.x / cellSize) + dx) * 73856093) ^
                               ((int)math.floor((selfPos.z / cellSize) + dz) * 19349663);

                    if (grid.TryGetFirstValue(hash, out var other, out var it))
                    {
                        do
                        {
                            if (other == index) continue;

                            float dist = math.distance(selfPos, positions[other]);
                            if (dist < NeighborRadius)
                            {
                                center += positions[other];
                                speedSum += speeds[other];
                                count++;

                                if (dist < AvoidDistance)
                                    avoid += (selfPos - positions[other]);

                                // 限制采样数量
                                sampleCount++;
                                if (sampleCount >= MaxSampleCount)
                                    break;
                            }
                        } while (sampleCount < MaxSampleCount && grid.TryGetNextValue(out other, ref it));
                    }
                }
            }

            float3 direction = selfVel;

            if (count > 0)
            {
                center /= count;
                speed = math.clamp(speedSum / count, 0.1f, 1f);
                direction = math.normalize((center + avoid) - selfPos);
            }

            float3 pos = selfPos + direction * speed * deltaTime;

            newPositions[index] = pos;
            newVelocities[index] = direction;
        }
    }
}

 运行之后,生成了1w只在游动的鱼,跑起来有60多帧,主要是鱼群算法开销有点大。

关掉鱼群算法,停止鱼的移动后,帧率还是很高的。 

 

资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪小心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值