Unity DOTS を使用して、40,000 の飛行剣を備えた太極拳の剣フォーメーションを作成しましょう!

[USparkle コラム] 優れたスキルを持ち、「研究する」のが好きで、他の人から共有して学ぶ意欲のある方、ぜひご参加をお待ちしています。知恵の火花を衝突させ、織り交ぜながら、知識の伝達を行ってください。エンドレス!

1.実証効果

DOTS 関連パッケージのさまざまなバージョンで大きな変更が加えられたため、多くの古いチュートリアルが古くなり、探索したい人にとっては多大な問題を引き起こしています。そこで DOTS を使って 42,804 本の飛翔剣で構成されるかっこいい剣配列を作ってみました 床をクリックするたびに 10,000 本の飛翔剣が配列から飛び出て、目標点を攻撃して戻ってくるというものです。これは、Gu Long の小説「Sword Qi は 30,000 マイルに渡り、剣の光は 19 大陸を冷やす」のシーンへのオマージュとみなすことができます。この記事では、同じく DOTS を検討している友人に参考にしてもらえるよう、制作プロセスと学習体験を共有します。


この記事は長いので、準備に興味がない人は直接ディレクトリ 6 に進んでください。

2. プロジェクトを作成する

まず、Unity 2020.3.3f1 をダウンロードし、Universal Render Pipeline を選択してプロジェクトを作成しました。

DOTS の関連パッケージはリリースされておらず、パッケージ マネージャーでも検索できないため、これらのパッケージを手動でダウンロードする必要があります。

3. DOTS 関連パッケージをダウンロードする

上部のメニュー バーを開き、左上隅の + アイコンをクリックして [Add package from git URL] を選択し、次のように入力します。

  • コミュニティエンティティ
  • com.unity.rendering.hybrid
  • コミュニティ.フィジックス

しばらく待つとパッケージがダウンロードされますが、まだ準備が完了していません。

4. 動的一括設定

同じ素材の飛剣を大量に生成するので、DrawCallを減らすためにまとめて処理する必要があります。

まず、[編集] > [プロジェクト設定] > [品質] をクリックして、現在使用されているレンダリング設定ファイルを表示し、SRP バッチャーを閉じます。

新しいマテリアルを作成し、[GPU インスタンスを有効にする] をオンにします。

Unity が同じマテリアルを使用してオブジェクトをバッチでレンダリングすることがわかります。

最終的に約 40,000 の飛剣が生成され、元の飛剣モデルには数千の頂点があるため、このプロジェクトでは飛翔剣の頂点の数を手動で 105 に減らしたことは言及する価値があります。固定点があるとシーンがほぼフリーズしてしまいますが、最終テストでは、コンピューターが耐えられる頂点の最大数は約 10M でした。

5. 飛行剣を光らせるエフェクトを作成する

バージョン 2020.3.3 の URP プロジェクトでは、Unity がデフォルトで後処理パッケージを入れているため、次のように後処理パッケージを自分でダウンロードする必要はありません。旧バージョンのバッグ。直接使用してください。

まず新しいシェーダ ボールを作成し、[放出] をチェックして Feijian に投げ、シーン内で右クリックして新しいグローバル ボリュームを作成します。

同時に、メインカメラをクリックし、[ソリッドカラー] をチェックして黒っぽい色を選択し、[後処理] をチェックします。

シーンにはスカイボックスは必要ないので、[ウィンドウ] > [レンダリング] > [照明] > [環境] をクリックし、スカイボックスのマテリアルを [なし] に設定します。

最後に、後処理エフェクトが指定されたレベルのオブジェクトにのみ機能することを望みます。ここでは、グローバル ボリュームとオブジェクトを同じレベルに設定する必要があり、このレベルはメイン カメラで指定する必要があります。

最後に、新しい構成ファイルを作成し、「オーバーライドの追加」をクリックして、ブルーム効果を追加します。


準備作業が完了し、いよいよ練習リンクに入ります。この記事では DOTS に関連する概念を繰り返しません。インターネット上には DOTS に関する記事が多数あります。公式ドキュメントでは DOTS について詳しく説明されています。ここでは、DOTS について説明します。 DOTS について知らない人のためのリンクです。友達は、まずこれらの概念を理解し、それから練習することができます。

公式ドキュメント:
エンティティ コンポーネント システム | エンティティ | 0.17.0-preview.42

6. 太極パターンを生成する飛剣群エンティティ

まず太極拳八卦の陣形のパターンを見つけてから、太極拳シールをつまみましょう!式を叫びます:

  • 世界は無限であり、宇宙は法則を借りています。
  • Taiyi Tianzun、法律のように急いでください!

すると、写真の白い部分が透明になっていることがわかりました。

次に、画像内の各ピクセルの位置を取得し、それを Unity 空間に比例して変換し、Feijian にこれらの位置を埋めさせる必要があります。したがって、画像は大きすぎてはいけません。ここでは、インポート時に Unity で設定するだけです。

コードは以下のように表示されます:

public SpriteRenderer spriteRenderer;
   //像素点相对位置
   public List<int2> posList;
   [Header("Drawing")]
   //剑阵密度
   public int drawDensity ;
   //剑阵离散程度
   public int disperseMin;
   public static GetPixel Instance;
   //图片宽高
   private int width;
   private int height;

   void Start()
   {
       Instance = this;
       width = spriteRenderer.sprite.texture.width;
       height = spriteRenderer.sprite.texture.height;
       Debug.Log("图片宽度" + width + "图片高度" + height);
       GetPixelPos();     
   }

   public void GetPixelPos()
   {
       int halfHeight= height / 2;
       int halfWidth = width / 2;
       int2 tempPos;
       for (int i = 0; i < height; i += drawDensity)
       {
           for (int j = 0; j < width; j += drawDensity)
           {
               //获取每个位置像素点的颜色
               Color32 c = spriteRenderer.sprite.texture.GetPixel(j, i);
               tempPos.y = (j-halfHeight)*disperseMin; 
              // Debug.Log("RGBA:" + c);
              //如果对应位置颜色不为透明,则记录坐标到List中
               if (c.a != 0)
               {
                   tempPos.x = (i-halfWidth)* disperseMin;
                   posList.Add(tempPos);
               }
           }
       }
   }

位置を取得し、位置に応じて多数の飛剣を生成し、飛剣をエンティティに変換します。変換するには、飛剣のプレハブに Convert To Entity スクリプトを追加することを選択できます。 :

ただし、「Convert To Entity」をクリックして表示すると、MonoBehaviour も継承していることがわかりました。明らかに、それ自体はエディターで変換されないため、実行プロセス中に Feijian のプレハブを Entity に変換する必要があります。コードは次のとおりです。

using Unity.Entities;

public class Test : MonoBehaviour
{
    //飞剑预制体
    public GameObject swordPrefab;
    //在 World 中, EntityManager 管理所有实体和组件
    private EntityManager _manager;
    //blobAssetStore是一个提供缓存的类,缓存能让你对象创建时更快。
    private BlobAssetStore _blobAssetStore;
    private GameObjectConversionSettings _settings;
    //转换后的飞剑实体
    private Entity swordEntity;

    void Start()
    {
       //获取该世界的EntityManager
        _manager = World.DefaultGameObjectInjectionWorld.EntityManager;
        _blobAssetStore = new BlobAssetStore();
        _settings = GameObjectConversionSettings.FromWorld(World.DefaultGameObjectInjectionWorld, _blobAssetStore);
        //将飞剑预制体转换为Entity
        swordEntity = GameObjectConversionUtility.ConvertGameObjectHierarchy(swordPrefab, _settings);
     }

     private void OnDestroy()
     { 
        _blobAssetStore.Dispose();
     }

}
再按位置生成飞剑Entity,顺便打印出来飞剑的数量:
  private void Update()
  {
        if (Input.GetKeyDown(KeyCode.W))
        {
            BurstGenerateSword();
        }
   }
 //大量生成飞剑!
  public void BurstGenerateSword()
  {
        Debug.Log("生成数量:" + GetPixel.Instance.posList.Count);
        //遍历位置列表,生成对应数量的飞剑Entity
        for (int i = 0; i < GetPixel.Instance.posList.Count; i++)
        {
          SpawnNewSword(GetPixel.Instance.posList[i]);
        }
    }

   public void SpawnNewSword(float2 pos)
    {
        Entity newSword = _manager.Instantiate(swordEntity);
        //创建一个translation Component实例,并赋值对应坐标
        Translation ballTrans = new Translation
        {
            Value = new float3(pos.x, 0f, pos.y)
        };
        _manager.AddComponentData(newSword, ballTrans);
    }

次に、位置に応じて飛剣エンティティを生成し、ちなみに飛剣の数を出力します。

private void Update()
 {
       if (Input.GetKeyDown(KeyCode.W))
       {
           BurstGenerateSword();
       }
  }
//大量生成飞剑!
 public void BurstGenerateSword()
 {
       Debug.Log("生成数量:" + GetPixel.Instance.posList.Count);
       //遍历位置列表,生成对应数量的飞剑Entity
       for (int i = 0; i < GetPixel.Instance.posList.Count; i++)
       {
         SpawnNewSword(GetPixel.Instance.posList[i]);
       }
   }

  public void SpawnNewSword(float2 pos)
   {
       Entity newSword = _manager.Instantiate(swordEntity);
       //创建一个translation Component实例,并赋值对应坐标
       Translation ballTrans = new Translation
       {
           Value = new float3(pos.x, 0f, pos.y)
       };
       _manager.AddComponentData(newSword, ballTrans);
   }

次に、シーンを実行して W キーを押します。デフォルトの Burst Complier と Job Threads がオンになっていると、飛行する剣が遅延なくシーンの中央に迅速に生成されることがわかります。

7、デバッグエンティティ

デバッグ用に各エンティティを [階層] ウィンドウに表示することはできません。Unity には分析用のツールが用意されています。[ウィンドウ] > [分析] > [エンティティ デバッガー] の順にクリックすると、エンティティ デバッガーで各エンティティに含まれるコンポーネントと、対応する変更を表示できます。プロパティ値:

図では、リストの長さは 42802 ですが、Entity の数が 2 倍以上になっていることがわかります。これは、Feijian のプレハブにサブオブジェクトが含まれており、EntityManager がそのサブオブジェクトを変換するためです。変換プロセス中にオブジェクトをエンティティに変換します。:

「フィルター」をクリックして、コンポーネントをフィルターして表示するエンティティーを見つけることもできます。左側のシステム・リストには、プロジェクト内のシステムと、システムが各フレームワークを実行するのにかかる時間が表示されます。各システムに提供されているチェックボックスを使用して、デバッグ用のリストからシステムをオンまたはオフにできます。

8. 剣の配列が中心を中心に回転します

飛行剣が生成されたら、これらのエンティティを更新して回転させるシステムを作成する必要があります。
まず、プレハブに追加するコンポーネントを定義します。このコンポーネントの機能はラベルです。

using Unity.Entities;

[GenerateAuthoringComponent]
public struct SwordTag : IComponentData{}

次に、新しい System を作成し、SystemBase 基本クラスを継承します。2 つの基本クラス、JobComponentSystem と ComponentSystem は放棄され (公式 Entities 0.17.0 パッケージ ドキュメントに明記されているように)、SystemBase が唯一のクラスになることに注意してください。 System Base クラスの 1 つであるため、できるだけ SystemBase を使用することをお勧めします。

次に、System インターフェイスを実装し、Entitie.ForEach メソッドを通じて Sword Entity をフィルタリングして、場所を更新します。

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

public class SwordRotateSystem : SystemBase
{
    protected override void OnUpdate()
    {
        float deltaTime = Time.DeltaTime;
        float angel = 0.01f;

        //获取所有包含Swordtag的Entity,并对他们的translation和rotation组件值进行更改
        Entities.
         WithAll<SwordTag>().
         ForEach((ref Translation translation, ref Rotation orientation) =>
         {
             var rotation = orientation;
             //目标点为(0,0,0)
             float3 targetPosition = float3.zero;
             var targetDir = targetPosition - translation.Value;

             //飞剑垂直向下面向中心点
             quaternion temp1 = Quaternion.FromToRotation(Vector3.left, targetDir);
             orientation.Value = math.slerp(orientation.Value, temp1, deltaTime);

             #region 飞剑围绕中心点(0,0,0)旋转

             float3 pos = translation.Value;
             //旋转轴和旋转角度
             quaternion rot = quaternion.AxisAngle(math.up(), angel);
             float3 dir = pos - float3.zero;
             dir = math.mul(rot, dir);
             translation.Value = float3.zero + dir;

             #endregion

         }).ScheduleParallel();
    }
}

Unity ECS はシステム内で SystemBase を継承するクラスを自動的に検出し、クリックしてシーンを実行します。これで、飛行剣が中心点に沿って回転できるようになります。

9. 飛剣出て還る

次に剣アレイ全体の核となる機能ですが、実現の過程で私も2つのアイデアを実践しました。

最初の考え方は、
下の四角形をクリックして光線と四角形の間の衝突点の位置を取得し、次のように Tag2 コンポーネントを作成することです。

using Unity.Entities;
using Unity.Mathematics;

public struct Tag2 : IComponentData
{
    //飞剑出阵后,要攻击的目标点坐标
    public float3 target;

    //替代出阵飞剑进行旋转的enitiy
    public Entity TempEntity;

}
  1. System1 を作成し、System1 の Tag2 なしで飛行剣をトラバースし、10,000 個の飛行剣を抽出し、Tag2 を追加し、それらを置き換えて回転し続ける 10,000 個の TempEntities を作成します。Tag2 のアクセス位置と対応する Tempentity 情報。
  2. System2を作成し、System2のTag2ですべての飛行剣をトラバースし、目標地点まで飛行させます。
  3. System2 の Tag2 を使用してすべての飛行剣をトラバースし、System2 の飛行剣がターゲット ポイントに非常に近づいたら、Tag2 を削除し、Tag3 を追加します。
  4. System3 を作成し、System3 の Tag3 を持つすべての飛行剣を横断し、対応するテンペンティティに追いつき、対応するテンペンティティを破壊します。

この解決策には 2 つの問題があります。
1. システム間の実行シーケンス
SystemBase を継承し、OnUpdate() メソッドを実装するすべてのシステムはメイン スレッドで実行されるため、System1、2、および 3 を簡単に計画できます。 実行順序、コードは次のとおりです。 :

[UpdateAfter(typeof(System1))]
public class System2 : SystemBase
{
    protected override void OnUpdate(){}
}

[UpdateAfter(typeof(System2))]
public class System3 : SystemBase
{
    protected override void OnUpdate(){}
}

2. EntityCommandBuffer のタイミングの問題
ECS では、すべてのエンティティはチャンクに保存されます。チャンク内のすべてのエンティティは、同じ数とタイプのコンポーネントを持つ必要があります。エンティティのコンポーネントの数またはタイプが変更されると、そのエンティティは現在のブロックに属しなくなります。 、他のブロックに移動されますが、この操作はメインスレッドで実行しても問題ありません。

ただし、この剣のアレイを実行するために CPU のパフォーマンスを最大限に活用したいので、実行のためにいくつかのタスクをサブスレッドに割り当てる必要があります。JobSystem は、この問題の解決に役立ちます。スレッド上の直接操作はブロックされますが、非同期ロジックはカプセル化されます。各「ジョブ」はエンジンによってスケジュールされ、実行のために適切なスレッドに割り当てられます。公式のサンプル コードは次のとおりです。

Unity - スクリプト API: IJob

上記のメソッドで JobSystem を使用することに加えて、ECS はデータをサブスレッドに変換して処理するために SystemBase クラスで一般的に使用される Entities.ForEach メソッドを提供します。仕事:

partial class ApplyVelocitySystem : SystemBase
{
    protected override void OnUpdate()
    {
        Entities
            .ForEach((ref Translation translation,
            in Velocity velocity) =>
            {
                translation.Value += velocity.Value;
            }).Schedule();
//Run()是立即在主线程上执行
//目前并没有找到官方的Schedule()和ScheduleParallel()之间的区别
//目前看到的一个解释是Schedule()强制每个Job单独一个线程
//而ScheduleParallel()则是在并行线程上处理Job,每个线程处理query中一个或多个chunk
    }
}

このとき、Job1でエンティティのコンポーネントが削除され、エンティティが別のチャンクに移動され、そのエンティティ上で並行してJob2も動作しているとすると、競合(存在しないコンポーネントの動作や動作)が発生します。エラー ブロック エンティティが存在するため)、Job2 は Job1 がデータの読み取りと書き込みを完了するまで待機する必要があります。これがハード同期ポイント (同期ポイント) です。

構造的な変更を引き起こす次の操作では、ハード同期ポイントが生成されます。

  • エンティティの作成
  • エンティティの削除
  • コンポーネントをエンティティに追加する
  • エンティティからコンポーネントを削除
  • 共有コンポーネントの値を変更する

各同期点により一時停止が発生し、現在の世界で以前にスケジュールされたすべてのジョブが完了するのを待ちます。同期ポイントが多すぎると効率が大幅に低下しますが、上記の操作は避けられません。

この問題を解決するために、Unity はこの問題を解決する EntityCommandBuffer (Entity Command Buffer、ECB と呼ばれます) を提供します。

ECB は、構造変更を引き起こすコマンドをキューに入れることができ、ECB に保存されたコマンドを後からフレーム内で実行するために再生できます。これにより、ECB を再生するときに、フレームにまたがる複数の同期ポイントが 1 つの同期ポイントに減ります。次のケースでは、システムに付属の ECB を使用します。これにより、同期ポイントを最小限に抑えることができます。

struct Lifetime : IComponentData
{
    public byte Value;
}

class LifetimeSystem : SystemBase
{
    EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;
    protected override void OnCreate()
    {
        base.OnCreate();
        // 从World中获取ECS系统并且存起来
        m_EndSimulationEcbSystem = World
            .GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate()
    {
        // 请求一个ECS并且转换成可并行的
        var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer().AsParallelWriter();
        Entities
            .ForEach((Entity entity, int entityInQueryIndex, ref Lifetime lifetime) =>
        {
            // 检测entity的lifetime,如果为0则销毁它
            if (lifetime.Value == 0)
            {
                // 将entityInQueryIndex传给操作,这样ECS回放时能保证正确的顺序
                ecb.DestroyEntity(entityInQueryIndex, entity);
            }
            else
            {
                lifetime.Value -= 1;
            }
        }).ScheduleParallel();

        // 保证ECB system依赖当前这个Job
        m_EndSimulationEcbSystem.AddJobHandleForProducer(this.Dependency);
    }
}

このように、Feijian が Component を追加して一時的な Entity を作成する動作は問題ないようですが、実際に実行すると、Feijian が元の位置に戻らず、明らかに 1 フレーム遅いことがわかります。オリジナルの剣の配列:

前述したように、EntityCommandBuffer 内のコマンドはすぐには実行されませんが、フレーム遅延を伴い、次のフレームで EntityCommandBufferSystem によって使用されます。飛翔剣に代わるEntityを生成し、実際に生成して次のフレームで剣陣とともに回転することになるため、飛翔剣とEntityの最終的な同期位置は当然1フレーム遅くなります。現状の解決策はもう1フレーム分の距離を計算することですが、これは面倒で間違いが発生しやすいので、シンプルな考え方に変更しましょう。

EntityCommandBuffer のタイミングについて疑問がある場合は、この記事を読んでください:
Unity DOTS コーディングの実践: EntityCommandBuffer

2 番目の考え方は、
ゴシップ配列に TempEntity と Feijian を生成し、Feijian に Tag1 コンポーネントを追加して、同じ位置に TempEntity を記録するというものです。

using Unity.Entities;
using Unity.Mathematics;

public struct Tag1 : IComponentData
{
    //对应的TempEntity 
    public Entity TempEntity;
}
  1. System1 を作成し、すべての TempEntities を反復処理して、中心を中心に回転させます。
  2. System2 を作成し、Tag2 コンポーネントを使用せずにすべての飛行剣をトラバースし、フレームごとに対応する TempEntity の位置と同期します。
  3. (System2で実行) クリックイベントが発生すると、10,000本の飛剣が抽出されてTag2に追加され、Tag2は目標点の位置を記録します。
using Unity.Entities;
using Unity.Mathematics;

public struct Tag2 : IComponentData
{
    //目标点位置
    public float3 targetpos;
}

メインコードは次のとおりです:
System1:

using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using Unity.Mathematics;

public struct TempEntityTag : IComponentData{}

public class TempEntityRotateSystem : SystemBase
{
    protected override void OnUpdate()
    {
        float deltaTime =Time.DeltaTime;
        float angel = 0.01f;

        Entities.
            WithAll<RotateTag,TempEntityTag>().
            ForEach(( ref Translation translation, in Target target) =>
        {
            #region TempEntity围绕剑阵中心点旋转

            float3 pos = translation.Value;
            //旋转轴和旋转角度
            quaternion rot = quaternion.AxisAngle(math.up(), angel);
            float3 dir = pos - target.Tpos;
            dir = math.mul(rot, dir);
            translation.Value = target.Tpos + dir;

            #endregion
        }).ScheduleParallel();
    }
}

システム2:

using Unity.Entities;
using Unity.Jobs;
using Unity.Transforms;
using Unity.Mathematics;
using UnityEngine;
using Unity.Collections;
using Unity.Physics.Systems;
using Unity.Physics;

[UpdateAfter(typeof(TempEntityRotateSystem))]
public class SwordRotateSystem : SystemBase
{
    EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;

    protected override void OnCreate()
    {
        base.OnCreate();
        // 从World中获取ECS系统并且存起来
        m_EndSimulationEcbSystem = World
            .GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate()
    {
        bool isGo = false;
        float3 hitpos = float3.zero;
        float deltaTime = Time.DeltaTime;

        // 请求一个ECS并且转换成可并行的
        var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer().AsParallelWriter();
        if (Input.GetMouseButtonDown(0))
        {
            //获取物理世界
            BuildPhysicsWorld physicsWorld = World.DefaultGameObjectInjectionWorld.GetExistingSystem<BuildPhysicsWorld>();
            NativeArray<RigidBody> rigidBodies = new NativeArray<RigidBody>(1, Allocator.TempJob);
            NativeArray<float3> rayHitPos = new NativeArray<float3>(1, Allocator.TempJob);
            //获取射线发射位置
            UnityEngine.Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastJobHandle raycastJonHande = new RaycastJobHandle()
            {
                mStartPos = ray.origin,
                mEndPos = ray.direction * 10000,
                physicsWorld = physicsWorld.PhysicsWorld,
                Bodies = rigidBodies,
                rayHitpos = rayHitPos
            };
            //需要依赖当前Job
            JobHandle jobHandle = raycastJonHande.Schedule(this.Dependency);
            jobHandle.Complete();

            if (rigidBodies[0].Entity != null)
            {
                Debug.Log("目标坐标:" + rayHitPos[0]);
                Debug.Log("射线击中目标" + rigidBodies[0].Entity);
                hitpos = rayHitPos[0];
                isGo = true;
            }
            rigidBodies.Dispose();
            rayHitPos.Dispose();
        }

        Entities.
         WithAll<SwordTag>().
         WithNone<GoTag>().
         ForEach((Entity entity, int entityInQueryIndex, ref Translation translation, ref Rotation orientation, ref Target target) =>
         {
             #region 飞剑群出击!
             if (isGo && entityInQueryIndex < 10000)
             {
                 GoTag tag = new GoTag
                 {
                     targetPos = hitpos,
                     TempEntity = target.targetTempentity,
                     originPos = translation.Value,
                     isBack = false
                 };
                 // 将entityInQueryIndex传给操作,这样ECS回放时能保证正确的顺序
                 ecb.AddComponent(entityInQueryIndex, entity, tag);
             }
             #endregion

             if (!HasComponent<LocalToWorld>(target.targetTempentity))
             {
                 return;
             }

             var rotation = orientation;
             float3 targetPosition = target.Tpos;
             var targetDir = targetPosition - translation.Value;

             //飞剑垂直向下面向中心点
             quaternion temp1 = Quaternion.FromToRotation(Vector3.left, targetDir);
             orientation.Value = temp1;
             LocalToWorld tempEntityPos = GetComponent<LocalToWorld>(target.targetTempentity);
             translation.Value = tempEntityPos.Position;

         }).ScheduleParallel();

        // 保证ECB system依赖当前这个Job
        m_EndSimulationEcbSystem.AddJobHandleForProducer(this.Dependency);
    }

    //发射射线Job
    public struct RaycastJobHandle : IJob
    {
        public NativeArray<RigidBody> Bodies;
        public NativeArray<float3> rayHitpos;
        public float3 mStartPos;
        public float3 mEndPos;
        public PhysicsWorld physicsWorld;

        public void Execute()
        {
            //创建输入
            RaycastInput raycastInput = new RaycastInput()
            {
                Start = mStartPos,
                End = mEndPos * 100,
                //声明碰撞过滤器,用来过滤某些层级下的物体是否进行射线检测
                Filter = new CollisionFilter() { BelongsTo = ~0u, CollidesWith = ~0u, GroupIndex = 0, }
            };
            Unity.Physics.RaycastHit raycastHit = new Unity.Physics.RaycastHit();

            // 发射射线去检测Entity实体
            if (physicsWorld.CollisionWorld.CastRay(raycastInput, out raycastHit))
            {
                //拿到我们射线击中的entity
                Bodies[0] = physicsWorld.Bodies[raycastHit.RigidBodyIndex];
                //拿到击中点的位置信息
                rayHitpos[0] = raycastHit.Position;
            }
        }
    }
}

システム3:

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

[UpdateAfter(typeof(SwordRotateSystem))]
public class GroupSystem : SystemBase
{
    EndSimulationEntityCommandBufferSystem m_EndSimulationEcbSystem;

    protected override void OnCreate()
    {
        base.OnCreate();
        // 从World中获取ECS系统并且存起来
        m_EndSimulationEcbSystem = World
            .GetOrCreateSystem<EndSimulationEntityCommandBufferSystem>();
    }

    protected override void OnUpdate()
    {
        // 请求一个ECS并且转换成可并行的
        var ecb = m_EndSimulationEcbSystem.CreateCommandBuffer().AsParallelWriter();
        float deltaTime = Time.DeltaTime;
        float angel = 0.01f;

        Entities
            .WithName("Group").
            ForEach((Entity entity, int entityInQueryIndex, ref Translation translation, ref Rotation orientation, ref GoTag goTag, ref Target target) =>
        {
            var rotation = orientation;
            float3 targetPosition = goTag.targetPos;
            float distance = math.distance(targetPosition, translation.Value);
            LocalToWorld targetTransform = GetComponent<LocalToWorld>(goTag.TempEntity);

            //距离目标点位置小于30,则返回剑阵
            if (distance < 30f)
            {
                if (goTag.TempEntity != null)
                {
                    goTag.isBack = true;
                }
            }

            //追上自己对应的Tempentity
            if (goTag.isBack)
            {
                float3 newPos = targetTransform.Position;
                var a = newPos - translation.Value;
                //飞剑剑头指向目标点
                quaternion b = Quaternion.FromToRotation(Vector3.down, a);
                orientation.Value = b;

                float d1 = math.distance(translation.Value, newPos);
                translation.Value += math.normalizesafe(a);
                float d2 = math.distance(translation.Value, newPos);
                float c = math.distance(newPos, float3.zero) / 100f;
                float d = d1 - d2;

                if (d1 >10+c)
                {
                    int loop = (int )((10 + c) / d);
                    for (int i = 0; i < loop; i++)
                    {
                        translation.Value += math.normalizesafe(a);
                    }
                }
                else
                {
                    target.Tpos = float3.zero;
                    translation.Value = targetTransform.Position;
                    float distance3 = math.distance(newPos, translation.Value);
                    ecb.RemoveComponent(entityInQueryIndex, entity, ComponentType.ReadWrite<GoTag>());
                }

                return;
            }

            #region 飞向目标点

            var targetDir = targetPosition - translation.Value;
            quaternion temp1 = Quaternion.FromToRotation(Vector3.down, targetDir);
            orientation.Value = temp1;
            float3 distancePos = goTag.targetPos - goTag.originPos;
            translation.Value += distancePos * deltaTime * target.randomSpeed / 5f; 
            #endregion

        }).ScheduleParallel();

        // 保证ECB system依赖当前这个Job
        m_EndSimulationEcbSystem.AddJobHandleForProducer(this.Dependency);

    }
}

次に、メインスレッドで飛剣と TempEntity を生成しますが、シーン内の地面も Entity に変換し、Physic 関連のコンポーネントを追加する必要があることに注意してください。他のコードは公開されません。興味のあるパートナーはプロジェクトをダウンロードして試してみてください。記事の最後に記載しました。最後に、上面ビューの効果を見てみましょう。

私のコンピューターは i5-7500 クアッドコア + GTX1050 です。シーンには 128,000 のエンティティがあります。通常の動作では 85 fps を実行でき、10,000 の飛行剣が展開された場合でも約 60 を維持できます。

10. 参考資料

これで記事は終わりです。振り返ってみると、落とし穴がたくさんありました。この記事が友達のお役に立てれば幸いです。修正をお待ちしています。

ゲームオブジェクトをエンティティに変換するプロセスに興味のある学生は、次の記事に進んでください。

シュガーソース: ドットコード読み取りメモ - ConvertToEntity の生活

公式文書

プロジェクトアドレス: Unity-ECS-TaiJi-Swords-Array


これは Yuhu Technology の 1437 番目の記事です。著者の Yanquan 氏の寄稿に感謝します。再投稿と共有を歓迎します。著者の許可なしに転載しないでください。ユニークな洞察や発見がありましたら、ぜひご連絡ください。一緒に議論してください。

作者ホームページ:ydwjのゲーム開発日記

Yanquan さんの情報共有に改めて感謝します。何かユニークな洞察や発見がありましたら、ぜひ私たちに連絡して一緒に話し合ってください。

おすすめ

転載: blog.csdn.net/UWA4D/article/details/132056357