Utilisez Unity DOTS pour créer une formation d'épées de Tai Chi avec 40 000 épées volantes !

[Chronique USparkle] Si vous avez de grandes compétences, aimez "faire des recherches", êtes prêt à partager et à apprendre des autres, nous attendons avec impatience votre participation, laissez les étincelles de sagesse se heurter et s'entremêler, et laissez la transmission des connaissances être sans fin!

1. Effet de démonstration

En raison des changements importants apportés aux différentes versions des packages liés à DOTS, de nombreux anciens didacticiels sont obsolètes, ce qui cause beaucoup de problèmes à ceux qui souhaitent explorer. J'ai donc essayé d'utiliser DOTS pour créer un réseau d'épées aussi cool composé de 42 804 épées volantes. Chaque fois que je clique sur le sol, 10 000 épées volantes s'envoleront du réseau pour attaquer le point cible, puis reviendront. Il peut être considéré comme un hommage à la scène du roman de Gu Long "L'épée Qi s'étend sur 30 000 miles et la lumière d'une épée refroidit dix-neuf continents". Cet article partage le processus de production et l'expérience d'apprentissage, dans l'espoir de donner quelques références à des amis qui explorent également le DOTS.


L'article est long, et ceux qui ne sont pas intéressés par la préparation peuvent directement passer au répertoire 6.

2. Créer un projet

Tout d'abord, nous avons téléchargé Unity 2020.3.3f1 et sélectionné Universal Render Pipeline pour créer un projet.

Les packages DOTS pertinents n'ont pas été publiés et ne peuvent pas être recherchés dans le gestionnaire de packages. Nous devons télécharger ces packages manuellement.

3. Téléchargez les packages liés au DOTS

Ouvrez la barre de menu supérieure, cliquez sur l'icône + dans le coin supérieur gauche pour sélectionner Ajouter un package à partir de l'URL git, puis saisissez :

  • com.unity.entities
  • com.unity.rendering.hybride
  • com.unity.physics

Attendez un moment et le package sera téléchargé, mais nos préparatifs ne sont pas encore terminés.

4. Paramètre de lot dynamique

Puisque nous allons générer un grand nombre d'épées volantes du même matériau, il est nécessaire de les regrouper pour réduire DrawCall.

Cliquez d'abord sur Modifier > Paramètres de projet > Qualité > pour afficher le fichier de paramètres de rendu actuellement utilisé et fermez le SRP Batcher :

Créez un nouveau matériau et cochez Enable GPU Instancing :

Vous constaterez que Unity rendra les objets avec le même matériau par lots :

Il convient de mentionner que j'ai réduit manuellement le nombre de sommets à 105 pour les épées volantes dans ce projet, car je générerai environ 40 000 épées volantes à la fin, et le modèle original d'épée volante a des milliers de sommets. les points fixes feront presque geler ma scène.Dans le test final, le nombre maximum de sommets que mon ordinateur peut supporter est d'environ 10M.

5. Faites briller l'épée volante

J'espère que chaque épée volante a un effet de halo lumineux.Dans le projet URP de la version 2020.3.3, Unity y a mis le package de post-traitement par défaut, nous n'avons donc pas besoin de télécharger nous-mêmes le package de post-traitement comme le ancienne version Sac. Utilisez-le simplement directement.

Créez d'abord une nouvelle balle de shader, cochez Emission et lancez-la sur Feijian, puis faites un clic droit dans la scène pour créer un nouveau volume global :

En même temps, cliquez sur l'appareil photo principal, cochez Solid Color, choisissez une couleur noirâtre, puis cochez Post Processing :

Nous n'avons pas besoin de skybox dans notre scène, alors cliquez sur Window>Rendering>Lighting>Environment et réglez le skybox material sur None :

Enfin, j'espère que l'effet de post-traitement ne fonctionnera que sur les objets au niveau spécifié. Ici, le volume global et l'objet doivent être réglés au même niveau, et ce niveau doit être spécifié dans l'appareil photo principal :

Enfin, créez un nouveau fichier de configuration, cliquez sur Add Override et ajoutez l'effet Bloom.


Le travail préparatoire est terminé, et il est enfin temps d'entrer dans le lien de pratique. Cet article ne répétera pas les concepts liés au DOTS. Il existe de nombreux articles sur le DOTS sur Internet. Les documents officiels expliquent le DOTS en détail. Ici, je vais mettre un lien pour ceux qui ne connaissent pas le DOTS. Les amis peuvent d'abord comprendre ces concepts, puis pratiquer :

Documentation officielle :
Entity Component System | Entities | 0.17.0-preview.42

6. Les entités du groupe des épées volantes qui génèrent des modèles de Taiji

Trouvez d'abord un modèle de la formation des huit diagrammes de Tai Chi, puis pincez le sceau de Tai Chi! Criez la formule :

  • Le monde est illimité, et l'univers emprunte la loi !
  • Taiyi Tianzun, dépêche-toi comme une loi !

Puis j'ai constaté que la partie blanche de l'image devenait transparente :

Ensuite, nous devons obtenir la position de chaque pixel dans l'image, puis le convertir proportionnellement à l'espace Unity, et laisser Feijian remplir ces positions. Ainsi, l'image ne peut pas être trop grande, ici nous venons de la définir dans Unity lors de l'importation :

code afficher comme ci-dessous:

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);
               }
           }
       }
   }

La position est obtenue, puis un grand nombre d'épées volantes doivent être générées en fonction de la position, et les épées volantes sont converties en entité. Nous pouvons choisir d'ajouter le script Convertir en entité au préfabriqué de l'épée volante à convertir. :

Mais cliquez sur Convert To Entity pour voir, et a constaté qu'il hérite également de MonoBehaviour, évidemment il ne sera pas converti dans l'éditeur par lui-même, nous devons donc convertir le préfabriqué de Feijian en Entity pendant le processus d'exécution, le code est le suivant :

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);
    }

Générez ensuite l'entité épée volante en fonction de la position et imprimez le nombre d'épées volantes au passage :

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);
   }

Exécutez ensuite la scène et appuyez sur W. Avec le Burst Complier et les Job Threads activés par défaut, vous pouvez voir que l'épée volante est rapidement générée au centre de la scène sans aucun décalage :

Sept, entité de débogage

Chaque entité ne peut pas être affichée dans la fenêtre Hiérarchie pour le débogage. Unity fournit un outil d'analyse. Cliquez successivement sur : Fenêtre> Analyse> Débogueur d'entité, et vous pouvez afficher les composants contenus dans chaque entité dans le débogueur d'entité, ainsi que les modifications correspondantes dans valeurs de propriété :

Dans la figure, nous pouvons voir que la longueur de la liste est de 42802, mais le nombre d'Entités est plus que doublé, car le préfabriqué de Feijian contient un sous-objet, et l'EntityManager convertira les sous-objets du objet en entité pendant le processus de conversion. :

Vous pouvez également cliquer sur Filtrer pour rechercher l'entité que vous souhaitez afficher en filtrant le composant. La liste des systèmes sur la gauche affiche les systèmes du projet et le temps nécessaire au système pour exécuter chaque infrastructure. Vous pouvez activer et désactiver des systèmes à partir de la liste pour le débogage en utilisant les cases à cocher fournies pour chaque système.

8. Le réseau d'épées tourne autour du centre

L'épée volante a été générée, puis il faut créer un Système pour mettre à jour ces Entités et les faire tourner.
Définissez d'abord un composant à ajouter au préfabriqué, la fonction de ce composant est un label :

using Unity.Entities;

[GenerateAuthoringComponent]
public struct SwordTag : IComponentData{}

Créez ensuite un nouveau système et héritez de la classe de base SystemBase. Il convient de noter que les deux classes de base, JobComponentSystem et ComponentSystem, seront abandonnées (comme indiqué clairement dans le document officiel du package Entities 0.17.0), et SystemBase sera la seule l'une des classes System Base, il est donc recommandé d'utiliser SystemBase autant que possible.

Ensuite, implémentez l'interface système, filtrez l'entité Sword via la méthode Entitie.ForEach et mettez à jour l'emplacement :

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 trouvera automatiquement la classe qui hérite de SystemBase dans le système, cliquez pour exécuter la scène, et l'épée volante peut maintenant tourner le long du point central :

9. Les épées volantes sortent et reviennent

Vient ensuite la fonction centrale de l'ensemble de l'épée.J'ai également pratiqué deux idées dans le processus de réalisation.

La première façon de penser est la suivante :
cliquez sur le carré ci-dessous pour obtenir la position du point de collision entre le rayon et le carré, et créez le composant Tag2 comme suit :

using Unity.Entities;
using Unity.Mathematics;

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

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

}
  1. Créez System1, traversez les épées volantes sans Tag2 dans System1, extrayez 10 000 épées volantes, ajoutez Tag2 et créez 10 000 TempEntities pour les remplacer et continuer à tourner. Emplacement d'accès Tag2 et informations Tempentity correspondantes.
  2. Créez System2, traversez toutes les épées volantes avec Tag2 dans System2 et faites-les voler vers le point cible.
  3. Traversez toutes les épées volantes avec Tag2 dans System2, lorsque les épées volantes dans System2 sont très proches du point cible, supprimez Tag2 et ajoutez Tag3.
  4. Créez System3, traversez toutes les épées volantes avec Tag3 dans System3, rattrapez leur Tempentity correspondant, puis détruisez le Tempentity correspondant.

Cette solution a deux problèmes :
1. Séquence d'exécution entre les systèmes
Tous les systèmes qui héritent de SystemBase et implémentent la méthode OnUpdate() s'exécutent sur le thread principal, nous pouvons donc facilement planifier System1, 2 et 3 L'ordre d'exécution, le code est le suivant :

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

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

2. Problèmes de synchronisation EntityCommandBuffer
Dans ECS, toutes les entités sont stockées dans des blocs. Toutes les entités d'un bloc doivent avoir le même nombre et le même type de composants. Une fois que le nombre ou le type de composants d'une entité change, il n'appartient pas au bloc actuel , il sera déplacé vers un autre bloc, et il n'y a aucun problème avec cette opération exécutée dans le thread principal.

Mais j'espère utiliser toutes les performances du processeur pour exécuter ce tableau d'épées, donc je dois affecter certaines tâches aux sous-threads pour l'exécution. JobSystem nous aide à résoudre ce problème. Il bloque les opérations directes sur les threads, mais encapsule la logique asynchrone . Chaque "Job" est planifié par le moteur et affecté au thread approprié pour l'exécution. L'exemple de code officiel est ici :

Unity - API de script : IJob

En plus d'utiliser JobSystem dans la méthode ci-dessus, ECS fournit la méthode Entities.ForEach couramment utilisée dans la classe SystemBase pour convertir les données en sous-threads pour le traitement. Nous pouvons utiliser Schedule() ou ScheduleParallel() pour exécuter la méthode Lambda du Emploi:

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
    }
}

À ce stade, en supposant que le composant d'une entité est supprimé dans Job1, que l'entité est déplacée vers un autre Chunk et que son Job2 parallèle fonctionne également sur cette entité, il y aura un conflit (fonctionnement d'un composant qui n'existe pas ou fonctionnement une entité de bloc d'erreur dans), donc Job2 doit attendre que Job1 finisse de lire et d'écrire des données, qui est le point de synchronisation dur (points de synchronisation).

Les opérations suivantes qui entraînent des modifications structurelles généreront des points de synchronisation matérielle :

  • créer une entité
  • supprimer l'entité
  • Ajouter des composants aux entités
  • Supprimer le composant de l'entité
  • Modifier la valeur dans le composant partagé

Chaque point de synchronisation provoque une pause, attendant que toutes les tâches précédemment planifiées dans le monde actuel soient terminées. Trop de points de synchronisation réduiront considérablement l'efficacité, mais les opérations ci-dessus sont inévitables.

Afin de résoudre ce problème, Unity fournit EntityCommandBuffer (Entity Command Buffer, appelé ECB) pour résoudre ce problème.

L'ECB peut mettre en file d'attente les commandes qui provoquent des modifications structurelles, et les commandes stockées dans l'ECB peuvent être rejouées pour être exécutées ultérieurement dans une trame. Cela réduit plusieurs points de synchronisation répartis sur les images à un seul point de synchronisation lors de la lecture des ECB. Le cas suivant utilise l'ECB fourni avec le système, ce qui peut minimiser les points de synchronisation :

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);
    }
}

De cette façon, l'opération de Feijian ajoutant un composant et créant une entité temporaire ne semble pas poser de problème, mais lorsqu'elle est en cours d'exécution, on constatera que Feijian ne revient pas à la position d'origine, qui est évidemment une image plus lente que le tableau d'épée d'origine :

Comme mentionné précédemment, les commandes dans EntityCommandBuffer ne seront pas exécutées immédiatement, mais seront utilisées par EntityCommandBufferSystem dans la trame suivante, avec un délai de trame. Nous générons l'entité qui remplace l'épée volante, et elle sera en fait générée et tournera avec la formation de l'épée dans l'image suivante, de sorte que la position de synchronisation finale de l'épée volante et de l'entité est naturellement une image plus lente. La solution actuelle consiste à calculer la distance d'une image supplémentaire, mais cela est à la fois gênant et sujet aux erreurs, alors passons à une façon de penser simple.

Les amis qui ont des doutes sur le timing d'EntityCommandBuffer peuvent lire cet article :
Pratique de codage Unity DOTS : EntityCommandBuffer

La deuxième façon de penser est la suivante :
générer TempEntity et Feijian dans le tableau de potins, ajouter le composant Tag1 à Feijian et enregistrer TempEntity à la même position.

using Unity.Entities;
using Unity.Mathematics;

public struct Tag1 : IComponentData
{
    //对应的TempEntity 
    public Entity TempEntity;
}
  1. Créez System1, parcourez toutes les TempEntities, faites-les pivoter autour du centre.
  2. Créez System2, traversez toutes les épées volantes sans composants Tag2 et synchronisez avec la position TempEntity correspondante à chaque image.
  3. (Exécuté dans System2) Si un événement de clic se produit, 10 000 épées volantes sont extraites et ajoutées à Tag2, et Tag2 enregistre la position du point cible.
using Unity.Entities;
using Unity.Mathematics;

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

Le code principal est le suivant :
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();
    }
}

Système2:

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;
            }
        }
    }
}

Système3:

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);

    }
}

Générez ensuite l'épée volante et TempEntity dans le thread principal. Il convient de noter que le sol de la scène doit également être converti en entité et que des composants liés à la physique doivent être ajoutés. Les autres codes ne seront pas publiés. Les partenaires intéressés peuvent télécharger le projet et l'essayer. Je le mets à la fin de l'article. Enfin, regardons l'effet de la vue de dessus :

Mon ordinateur est un i5-7500 quad-core + GTX1050. Il y a 128 000 entités dans la scène. Il peut fonctionner à 85 ips en fonctionnement normal et il peut en maintenir environ 60 lorsque 10 000 épées volantes sont déployées :

10. Référence

C'est la fin de l'article. Rétrospectivement, il y a beaucoup de pièges. J'espère que cet article pourra aider mes amis, et j'attends vos corrections avec impatience !

Pour les étudiants intéressés par le processus de conversion de GameObject en Entity, vous pouvez passer à cet article :

Sauce au sucre : notes de lecture du code Dots - La vie de ConvertToEntity

document officiel

Adresse du projet : Unity-ECS-TaiJi-Swords-Array


Ceci est le 1437e article de Yuhu Technology, merci à l'auteur Yanquan pour sa contribution. Bienvenue à republier et partager, veuillez ne pas réimprimer sans l'autorisation de l'auteur. Si vous avez des idées ou des découvertes uniques, veuillez nous contacter et discuter ensemble.

Page d'accueil de l'auteur : journal de développement de jeux de ydwj

Merci encore pour le partage de Yanquan. Si vous avez des idées ou des découvertes uniques, veuillez nous contacter et discuter ensemble.

Je suppose que tu aimes

Origine blog.csdn.net/UWA4D/article/details/132056357
conseillé
Classement