Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

[GenerateAuthoringComponent] vs IConvertGameObjectToEntity for component data authoring

Discussion in 'Entity Component System' started by KwahuNashoba, Apr 14, 2020.

  1. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    I'm starting new project, and I'm determent to implement it in DOTS, at least whatever makes sense to do so, so I'm trying to make a picture about how should I author my data.

    So, for those of you that has been playing with DOTS for a while, could you write some good practices like when should be used what of these approaches, what are you using most often...etc?

    Thanks!
     
  2. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    [GenerateAuthoringComponent] is very useful when it works. However, I like to keep groups of ECS components in a single file and this makes it not work. So instead I find myself writing large authoring components, each setting up multiple ECS components all at once.

    The other benefit of manual authoring components is you can rename and group things in the inspector. You can also group values, apply unit conversions, initialize multiple components with a single inspector field (Health and MaxHealth), add some tag components, and even conditionally add components.

    I have a handful of code examples at the ready from a game I am working on if you are interested in how I might go about a particular conversion process.
     
    CristianOG, lclemens and NotaNaN like this.
  3. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,695
    Are those examples on your git?
     
  4. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Not yet. Kinda have to finish it first. It is taking me a lot longer than I would like, especially in the non-programming department (help always appreciated ;):p).

    But I can always share early previews on the forums!
     
    NotaNaN likes this.
  5. NotaNaN

    NotaNaN

    Joined:
    Dec 14, 2018
    Posts:
    324
    Something I would recommend is the use of is [System.Serialize].
    I learned this the hard way after making authoring components that had their own variable for every variable on the respective IComponentData(s) and / or IBufferElementData(s). The amount of times I had forgotten to check if I actually updated the Authoring Component correctly (or at all) has made me lose countless hours to stupidly simple mistakes. Not to mention creating Authoring Components take way too much time, in my opinion. (But I still do it). :cool:

    Now, instead of writing to each variable individually with a respective "mirror variable" on the Authoring Component (which was a massive pain), I now just use the IComponentData struct as the Type of a single variable in the Authoring Component and BAM, problem solved!
    And let me tell you, I really wish I knew about it earlier. o_O

    Something to note is that certain variables types—such as Entity—do not work in the inspector because Entity's don't exist until the runtime. To bypass this, I just create a variable of Type GameObject in the Authoring Component and write to the Entity field in my struct once I know what Entity I'm referencing during conversion.
    Is it a little nasty in the inspector? Yeah...
    But it's WAY better and purely visual. :D


    One thing that would be awesome would be Entity references used in this way automatically being represented as GameObject references in the inspector and automatically getting the respective Entity during conversion.
    How would this even be done? No idea. But it'd be awesome. :p



    EDIT: If what I have suggested is not bad practice—I really feel like Unity should mention that it is good practice somewhere in the documentation. I have some components that have close to fifty variables in them. (Try maintaining that with 100% custom Authoring Components, why don'tcha?). I don't want people to go down the same path I did and waste a lot of time. :(
     
    Last edited: Apr 15, 2020
    lclemens likes this.
  6. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    825
    Generally I find that most everything requires more than just an empty component(which is what [GenerateAuthoringComponent] will give you. Sometimes its just some default values, sometimes its a host of other components(easier to have one script with IConvertGameObjectToEntity that adds 5-10 components and sets them up via script vs doing that by hand in the inspector and manually putting in default values).

    I know conversion is kinda disliked but I think it makes sense in my own usage, though some of these names are kinda long and a pain to type out so often.
    IDeclareReferencedPrefabs is really just when you need entity prefabs created from regular prefabs, you will need it when you need it so to speak.
     
    KwahuNashoba likes this.
  7. TLRMatthew

    TLRMatthew

    Joined:
    Apr 10, 2019
    Posts:
    65
    [GenerateAuthoringComponent] does this - if the component has an Entity field, the authoring component will automatically have a GameObject field, and it'll wire it up automatically. Not sure if there's a way to leverage the same magic in custom authoring components, but it'd certainly make sense and be nice!
     
    NotaNaN likes this.
  8. axxessdenied

    axxessdenied

    Joined:
    Nov 29, 2016
    Posts:
    33
    I find using scriptable objects is a nice way of authoring component data.
     
  9. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    I would like to see an example demonstrating conversion of GameObjects references. When I say references, I'm not talking about parent/child IComponentData instances, but rather one Entity references another one. Let say you have scenario where one Entity should follow another, how should you achieve this? I know that building logic around references is kind of discouraged, but we are building games, and you will have need this kind of behavior from time to time. Follow is pretty much common case.

    What you said here is that you've found a workaround on limitation regarding the single component per file? So you, lets say, write set of components in one file and then use derivative of MonoBehavior, IConvertGameObjectToEntity in another file with properties that have created components types? And it shows "union" of all data in a single script?

    As mentioned, Unity already does this, here is snippet from Entities manual pages:

    What I'm wandering is, what does they mean by referenced Prefabs? Do we still need to instantiate it? Or can it be used in the runtime to reference the Entity of created GO from scene, just like that?

    Care to elaborate your process steps? What are the key elements you use in your approach?
     
  10. NotaNaN

    NotaNaN

    Joined:
    Dec 14, 2018
    Posts:
    324
    Ah, I see.
    Perhaps I did not make it clear that I was not using [GenerateAuthoringComponent].
    Anyway, when description fails, code snippets succeed. Here's an example of what I do:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Entities;
    5. using Unity.Mathematics;
    6. using FDS.DA.Combat.Hitboxes;
    7. using FDS.DA.Actions;
    8. using FDS.DA.Platforming.Dashing.Burst.Down;
    9. using FDS.DA.Platforming.Jumping.Ground;
    10.  
    11. public class BurstDashDownAuthoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    12. {
    13.     public void OnEnable() { }
    14.  
    15.     public GameObject HitboxPrefab; // Prefab reference since Entity won't work. (last time I checked that is... Which was over half a year ago).
    16.     public DashData DashData; // <- IComponentData struct
    17.  
    18.     public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    19.     {
    20.         DashData.HitboxPrefab = conversionSystem.GetPrimaryEntity(HitboxPrefab);
    21.  
    22.         dstManager.AddComponentData(entity, DashData);
    23.  
    24.         dstManager.AddComponentData(entity, new IsDashing
    25.         {
    26.  
    27.         });
    28.  
    29.         dstManager.AddComponentData(entity, new DashTimers
    30.         {
    31.  
    32.         });
    33.     }
    34.  
    35.     public void DeclareReferencedPrefabs(List<GameObject> referencedGameObjects)
    36.     {
    37.         referencedGameObjects.Add(HitboxPrefab);
    38.     }
    39. }
    As can be seen, it's all a custom Authoring Component that's manually created. The boon is since you use the IComponentData's struct as the type for most (if not all) of the variable types in the Authoring Component, any changes made to the actual IComponentData struct get reflected onto the Authoring Component AND you can have multiple IComponentData's being authored this way.

    In certain cases, this does not always work super well, but it does most of the time. If worst comes to worst, you can always go completely manual and create the "mirror variables" I talked about in a previous post. lastly, a useful thing about this is that you always know which structs you are doing this too since [System.Serialize] will always be preceding them.


    As side note that's completely unrelated to to using this method for creating Authoring Components, I also tend to try to have all of the variables that are going to be readonly (not going to be changed during runtime by the main system that provides function) all within the same component (IComponentData).

    To use my snippet as an example—DashData is completely and only comprised of variables that will not have their values changed by the system that processes it. This allows me to mark the IComponentData as an IN instead of a REF and have anything that would be a REF in another component(s).
    I'm pretty sure this helps performance a bit, but I would need confirmation by one of the Savants.
    Even if the performance boost is negligible, it's still nice knowing everything in that component is practically never going to change.
     
    Last edited: Apr 15, 2020
    lclemens likes this.
  11. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    Yeah, much more clear now, thanks. What I meant by workaround, lets take
    public DashData DashData;
    as an example. Will all those variables of
    DashData
    component be previewed in the inspector, or just a single value that expects that type of component?
     
    NotaNaN likes this.
  12. NotaNaN

    NotaNaN

    Joined:
    Dec 14, 2018
    Posts:
    324
    All of the variables will appear in the Inspector within a dropdown that has the same name as the variable's name (in this case, DashData will be the name of the dropdown).

    Another thing to note is that you can use Unity's modifier thingies such as [Space], [Tooltip("?")], [Header("?")], etc inside the IComponentData struct (or any struct with [System.Serialize] for that matter) and the modifiers will show up in the Inspector.
    It's really helpful for making the formatting nice and readable. Not sure if [GenerateAuthoringComponent] allows you to do that (it probably does).
     
    Last edited: Apr 15, 2020
  13. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    This is not quite what you are asking for, but the closest example I have. So in my game, I use authoring GameObjects which exist in the Spaceship's hierarchy which describe things like the camera mount point or the points where bullets should spawn from. Rather than search the Child buffer for these entities at runtime, I save direct references to these during conversion. This authoring component shows off quite a few other tricks as well. Ignore the odd code formatting. My auto-formatter could use some more development effort.
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using Unity.Entities;
    3. using Unity.Mathematics;
    4. using UnityEngine;
    5.  
    6. namespace Lsss.Authoring
    7. {
    8.     [DisallowMultipleComponent]
    9.     [RequiresEntityConversion]
    10.     public class SpaceshipAuthoring : MonoBehaviour, IDeclareReferencedPrefabs, IConvertGameObjectToEntity
    11.     {
    12.         [Header("Speed")]
    13.         public float topSpeed     = 1f;
    14.         public float boostSpeed   = 2f;
    15.         public float reverseSpeed = 0.5f;
    16.         public float turnSpeed    = 60f;  //deg/s
    17.  
    18.         [Header("Acceleration")]
    19.         public float acceleration      = 1f;
    20.         public float deceleration      = 2f;
    21.         public float boostAcceleration = 3f;
    22.  
    23.         [Header("Boost Tank")]
    24.         public float boostCapacity      = 5f;
    25.         public float boostDepletionRate = 1f;
    26.         public float boostRechargeRate  = 0.25f;
    27.         public float initialBoost       = 5f;
    28.  
    29.         [Header("Chassis")]
    30.         public float              health                 = 100f;
    31.         public float              collisionDamageToOther = 100f;
    32.         public GameObject         cameraMountPoint;
    33.         public ExplosionAuthoring explosionPrefab;
    34.         public List<GameObject>   gunTips = new List<GameObject>();
    35.  
    36.         [Header("Bullets")]
    37.         public float           fireRate       = 2f;
    38.         public int             bulletsPerClip = 10;
    39.         public float           clipReloadTime = 3f;
    40.         public BulletAuthoring bulletPrefab;
    41.  
    42.         public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    43.         {
    44.             if (explosionPrefab != null)
    45.                 referencedPrefabs.Add(explosionPrefab.gameObject);
    46.             if (bulletPrefab != null)
    47.                 referencedPrefabs.Add(bulletPrefab.gameObject);
    48.         }
    49.  
    50.         public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    51.         {
    52.             dstManager.AddComponentData(entity, new ShipSpeedStats
    53.             {
    54.                 topSpeed     = topSpeed,
    55.                 boostSpeed   = boostSpeed,
    56.                 reverseSpeed = reverseSpeed,
    57.                 turnSpeed    = math.radians(turnSpeed),
    58.  
    59.                 acceleration      = acceleration,
    60.                 deceleration      = deceleration,
    61.                 boostAcceleration = boostAcceleration,
    62.  
    63.                 boostCapacity     = boostCapacity,
    64.                 boostDepleteRate  = boostDepletionRate,
    65.                 boostRechargeRate = boostRechargeRate
    66.             });
    67.             dstManager.AddComponentData(entity, new ShipBoostTank { boost = initialBoost });
    68.             dstManager.AddComponentData(entity, new Speed { speed         = 0f });
    69.  
    70.             dstManager.AddComponentData(entity, new ShipHealth { health                   = health });
    71.             dstManager.AddComponentData(entity, new Damage { damage                       = collisionDamageToOther });
    72.             dstManager.AddComponentData(entity, new CameraMountPoint { mountPoint         = conversionSystem.TryGetPrimaryEntity(cameraMountPoint) });
    73.             dstManager.AddComponentData(entity, new ShipExplosionPrefab { explosionPrefab = conversionSystem.TryGetPrimaryEntity(explosionPrefab) });
    74.             var gunBuffer                                                                 = dstManager.AddBuffer<ShipGunPoint>(entity);
    75.             foreach(var gunTip in gunTips)
    76.             {
    77.                 gunBuffer.Add(new ShipGunPoint { gun = conversionSystem.GetPrimaryEntity(gunTip) });
    78.             }
    79.  
    80.             dstManager.AddComponentData(entity, new ShipReloadTime
    81.             {
    82.                 bulletReloadTime    = 0f,
    83.                 maxBulletReloadTime = math.rcp(fireRate),
    84.                 bulletsRemaining    = bulletsPerClip,
    85.                 bulletsPerClip      = bulletsPerClip,
    86.                 clipReloadTime      = clipReloadTime,
    87.                 maxClipReloadTime   = clipReloadTime
    88.             });
    89.             dstManager.AddComponentData(entity, new ShipBulletPrefab { bulletPrefab = conversionSystem.TryGetPrimaryEntity(bulletPrefab) });
    90.  
    91.             dstManager.AddComponent<ShipDesiredActions>(entity);
    92.             dstManager.AddComponent<ShipTag>(           entity);
    93.         }
    94.     }
    95. }
     
    NotaNaN likes this.
  14. WAYNGames

    WAYNGames

    Joined:
    Mar 16, 2019
    Posts:
    939
    My issue with this king of huge authoring component is that you lose a bit of what is actually added as components at runtime. Yes you see it in the script but from the editor it's not clear. And you also have to be aware of other authroing components on the same game object that add the same runtime components.

    If you want to keep some flexibility, it's quite difficult.

    Let me take an exemple:

    Let's say you have player that has a health stat. you make a player authoring compoenent that add the health runtime component.
    All is good, know you want you player to recover health over time so you can either add the info to the same runtime component (I don't addvise that) or add another one and add it through the same authoring component.

    No to make your game more fun you want to add enemies, they also need a health but for some reason, you don't want them to regain health over time, plus you probably don't want the player input (wich you propably added to your player authoring component at this point) to affect the enemy.

    So you make another authroing component, fine.

    Know you team member come up to you and say, let's make an enemy boss ! but the boss can regain helth over time... here come the 3 umongus authroing component...

    It would be much simpler to just have authoring components based on the unitary behaviour so let's make it that way.

    Our player, enemy and boss are all Alive, so they get a health authoring component that just add the health runtime component.

    Now for the regain health of the player and the both, you can add another simple authroing component that add the data for regaining heal over time.
    But wait, it does not make sense to regain health over time if you don't have health so this authoring component should also add the health component... oh oh... now I have 2 authoring components that ad the same runtime component and are in 2 places in the inspector... so they can have distinct values... which one will I get at runtime?? (not to mention the error you'll get when trying to add a runtime component that already exisit)

    I tried to tackle the problem some time ago with the RequireComponent(typeof()) attibute and some custom editor windows to make it more readable. I haven't opened it in a while and it was not perfect but if you see my point, or are facing this kind of issue, feel free to take a look
    https://forum.unity.com/threads/requirement-based-conversion-workflow.756308/
    https://github.com/WAYNGROUP/UnityPackage-RequirementBasedAuthoringWorkflow/
    https://github.com/WAYNGROUP/AutoringWorkflowDemo
     
    lclemens, KwahuNashoba and NotaNaN like this.
  15. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    If modularity is what you are looking for, you will find yourself writing smaller authoring components and replacing IConvertGameObjectToEntity with GameObjectConversionSystems (which can reason about multiple components at once). You should also be using the DOTS editor to preview conversion if you care about the runtime representation. However, this could also add more work to the designers (what components am I missing?) or programmers (how do I design an authoring solution for all these combinations that are supported by our runtime?) so there is a tradeoff here that will be different for each project and each team.

    In my case, I am having a hard enough time finding designers who want to play with a DOTS project in development ;)(probably because I'm not doing this in any commercial capacity:p and plan to release this publicly on GitHub when it is done:cool:) to design ships, factions, space stations, shader graphs, and the like, let alone design game mechanic variations. I am also trying to keep the code as simple as possible as I expect this to be used as a learning resource.

    So because of all those factors, I have designed authoring the way I have, and ended up with the SpaceshipAuthoring.cs I have shared. Would I have done things differently if I could have multiple instances of [GenerateAuthoringComponent] in a single file? Probably. But I choose my battles.
     
    KwahuNashoba and NotaNaN like this.
  16. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    825


    Heres an authoring script for my player weapons with a bunch of entity references(kinda all over the place).

    I create several springs(starts at 56) which are later used when the player actually picks up the weapon and wields/shoots. Their respective entities are plugged into the relevant components at line 74.

    There are also entities created from their respective prefabs for the projectile and muzzle prefabs(83)and I store the entities for the converted arm and weapon model entities at 103 and 107.
    I also create a trigger and rigidbody for each(133) and it gets plugged into a component containing both at 142

    For entity following(the trigger and original weapon entity follow the rigidbody entity), lines 136 & 137 show where I add an AttachEntity(which simply stores an entity). The code for the system and that component are here
    Code (CSharp):
    1.     [GenerateAuthoringComponent]
    2.     public struct AttachEntity : IComponentData
    3.     {
    4.         public Entity Value;
    5.     }
    6.  
    7.     public class AttachEntitySystem : SystemBase
    8.     {
    9.         protected override void OnUpdate()
    10.         {
    11.             var localToWorldFromEntity = GetComponentDataFromEntity<LocalToWorld>();
    12.             var disabledFromEntity = GetComponentDataFromEntity<Disabled>(true);
    13.          
    14.             // Entities Codegen
    15.             Dependency = Entities
    16.                 .WithName("Attach_Transform_Job")
    17.             .WithNativeDisableParallelForRestriction(localToWorldFromEntity)
    18.             .WithReadOnly(disabledFromEntity)
    19.             .ForEach((Entity entity, ref AttachEntity attachEntity, ref Translation translation, ref Rotation rotation) =>
    20.             {
    21.                 if (disabledFromEntity.Exists(attachEntity.Value) || !localToWorldFromEntity.Exists(attachEntity.Value)) // why are we ignoring if this doesnt exist?
    22.                     return;
    23.  
    24.                 var localToWorld = localToWorldFromEntity[attachEntity.Value];
    25.  
    26.                 translation.Value = localToWorld.Position;
    27.                 rotation.Value = new quaternion(localToWorld.Value);
    28.  
    29.                 if (localToWorldFromEntity.Exists(entity))
    30.                 {
    31.                     localToWorldFromEntity[entity] = localToWorld;
    32.                 }
    33.  
    34.             }).WithBurst().ScheduleParallel(Dependency);
    35.         }
    36.     }

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using com.thelebaron.Entities;
    4. using com.thelebaron.Springs;
    5. using GameComponents;
    6. using Unity.Entities;
    7. using Unity.Mathematics;
    8. using Unity.Physics;
    9. using Unity.Physics.Authoring;
    10. using Unity.Rendering;
    11. using Unity.Transforms;
    12. using UnityEngine;
    13. using PhysMaterial = Unity.Physics.Material;
    14.  
    15. namespace Modules.PlayerWeapons
    16. {
    17.     /// <summary>
    18.     /// Note does not work in subscenes currently due to object data stored in components.
    19.     /// </summary>
    20.     [SelectionBase]
    21.     [RequiresEntityConversion]
    22.     [ExecuteInEditMode]
    23.     [ConverterVersion("chris", 6)]
    24.     public class WeaponAuthoring : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
    25.     {
    26.         [SerializeField] private float scale = 1f;
    27.         [SerializeField] private AudioClip audioClip;
    28.      
    29.         // add notebox, requires three child objects, arms and muzzleflash
    30.         private                  WeaponSprings    weaponSprings;
    31.         [SerializeField] private WeaponHandling weaponHandling;
    32.         [SerializeField] private Shooter shooter;
    33.         [SerializeField] private AmmoData       ammoData;
    34.         [SerializeField] private bool       isEquipped;
    35.         [SerializeField] private GameObject projectilePrefab;
    36.         [SerializeField] private GameObject fpWeaponChild;
    37.         [SerializeField] private GameObject armsChildGameobject;
    38.         [SerializeField] private GameObject muzzleChildGameobject;
    39.         [SerializeField] private Vector3 triggerCenter;
    40.         [SerializeField] private Vector3 triggerDimensions = Vector3.one;
    41.         [SerializeField] private PhysicsCategoryTags triggerBelongsTo;
    42.         [SerializeField] private PhysicsCategoryTags triggerCollidesWith;
    43.         [SerializeField] private Vector3             rigidCenter;
    44.         [SerializeField] private Vector3             rigidDimensions = Vector3.one / 2;
    45.         [SerializeField] private PhysicsCategoryTags rigidBelongsTo;
    46.         [SerializeField] private PhysicsCategoryTags rigidCollidesWith;
    47.  
    48.         public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    49.         {
    50.             //Unity.Jobs.LowLevel.Unsafe.JobsUtility.JobWorkerCount = 3;
    51.             // temp hack
    52.             if (transform.parent != null)
    53.                 isEquipped = true;
    54.          
    55.             // Create the springs
    56.             var posSpring = conversionSystem.CreateAdditionalEntity(gameObject);
    57.             dstManager.AddComponentData(posSpring, new Spring(weaponHandling.PositionExitOffset, 0.0f));
    58.             dstManager.AddBuffer<SoftForce>(posSpring);
    59.             var posSpring2 = conversionSystem.CreateAdditionalEntity(gameObject);
    60.             dstManager.AddComponentData(posSpring2, new Spring(float3.zero, 0.00001f));
    61.             dstManager.AddBuffer<SoftForce>(posSpring2);
    62.          
    63.             var rotSpring = conversionSystem.CreateAdditionalEntity(gameObject);
    64.             dstManager.AddComponentData(rotSpring, new Spring(weaponHandling.RotationExitOffset, 0.00001f));
    65.             dstManager.AddBuffer<SoftForce>(rotSpring);
    66.             var rotSpring2 = conversionSystem.CreateAdditionalEntity(gameObject);
    67.             dstManager.AddComponentData(rotSpring2, new Spring(float3.zero, 0.00001f));
    68.             dstManager.AddBuffer<SoftForce>(rotSpring2);
    69.          
    70.             var rotationPivotSpring = conversionSystem.CreateAdditionalEntity(gameObject);
    71.             dstManager.AddComponentData(rotationPivotSpring, new Spring(float3.zero, 0.00001f));
    72.             dstManager.AddBuffer<SoftForce>(rotationPivotSpring);
    73.          
    74.             weaponSprings.PositionSpring = posSpring;
    75.             weaponSprings.PositionSpring2 = posSpring2;
    76.             weaponSprings.RotationSpring = rotSpring;
    77.             weaponSprings.RotationSpring2 = rotSpring2;
    78.             weaponSprings.RotationPivotSpring = rotationPivotSpring;
    79.          
    80.          
    81.             dstManager.AddComponentData(entity, weaponSprings);
    82.  
    83.             // todo load into separate component
    84.             shooter.ProjectilePrefabEntity = conversionSystem.GetPrimaryEntity(projectilePrefab);
    85.             shooter.ProjectileSpawnPoint = conversionSystem.GetPrimaryEntity(muzzleChildGameobject);
    86.             shooter.Enabled = true; // could move into separate component
    87.          
    88.             // Convert the rest of it, should be straightforward
    89.             dstManager.AddComponentData(entity, shooter);
    90.          
    91.             weaponHandling.m_Refresh = true; // just reset springs
    92.             dstManager.AddComponentData(entity, weaponHandling);
    93.             dstManager.AddComponentData(entity, ammoData);
    94.  
    95.  
    96.  
    97.             // Convert the muzzleflash gameobject
    98.             var muzzleflashEntity = conversionSystem.GetPrimaryEntity(muzzleChildGameobject);
    99.             dstManager.AddComponentData(entity, new WeaponMuzzleModel {MuzzleEntity = muzzleflashEntity});
    100.             if (isEquipped && dstManager.HasComponent<Disabled>(muzzleflashEntity)) dstManager.RemoveComponent<Disabled>(muzzleflashEntity);
    101.          
    102.             // Convert the arms gameobject
    103.             var armsRenderEntity = conversionSystem.GetPrimaryEntity(armsChildGameobject);
    104.             var weaponRenderEntity = conversionSystem.GetPrimaryEntity(fpWeaponChild);
    105.             dstManager.AddComponentData(entity, new WeaponArmsModel
    106.             {
    107.                 WeaponModelEntity = conversionSystem.GetPrimaryEntity(fpWeaponChild),
    108.                 ArmsModelEntity = armsRenderEntity
    109.             });
    110.          
    111.          
    112.          
    113.             // Add all extra transform centric components, moved to weapon attach system as it conflicts with the transform
    114.             // attach system
    115.             /*
    116.             dstManager.AddComponentData(entity, new CompositeRotation());
    117.             dstManager.AddComponentData(entity, new PostRotation());
    118.             dstManager.AddComponentData(entity, new PostRotationEulerXYZ());
    119.             dstManager.AddComponentData(entity, new RotationEulerXYZ());
    120.             //dstManager.AddComponentData(entity, new RotationPivot {Value = rotationPivot});*/
    121.             // this pivot component remains, shouldnt affect entity rotation without a CompositeRotation and just be inert
    122.             dstManager.AddComponentData(entity, new Scale{Value = scale});
    123.  
    124.             /*
    125.             // Create rendermesh entity group and add any materials containing field of view shaders to it.
    126.             dstManager.AddBuffer<WeaponMaterialGroup>(entity);
    127.             var materialGroupBuffer = dstManager.GetBuffer<WeaponMaterialGroup>(entity);
    128.             materialGroupBuffer.Add(new WeaponMaterialGroup {Value = entity}); // Add weapon model(this)
    129.             materialGroupBuffer.Add(new WeaponMaterialGroup {Value = armsEntity}); // Add arms
    130.             */
    131.          
    132.             // Create the trigger volume
    133.             CreateTrigger(entity, dstManager, conversionSystem, out var trigger);
    134.             CreateRigidbody(entity, dstManager, conversionSystem, out var rigidbody);
    135.  
    136.             dstManager.AddComponentData(entity, new AttachEntity {Value = rigidbody});
    137.             dstManager.AddComponentData(trigger, new AttachEntity {Value = rigidbody});
    138.          
    139.             // Add entity references(maybe move arms and muzzle here? and projectile prefab?)
    140.             dstManager.AddComponentData(entity, new WeaponPickupState
    141.             {
    142.                 PickupTrigger = trigger,
    143.                 PickupRigidbody = rigidbody
    144.             });
    145.  
    146.             // Sounds - hybrid diy link
    147.             var audioGo = new GameObject {name = name + "+Audio"};
    148.             var soundContainer = new AudioStore
    149.             {
    150.                 Source = audioGo.AddComponent<AudioSource>(),
    151.                 Clip = audioClip
    152.             };
    153.             soundContainer.Source.clip = soundContainer.Clip;
    154.             dstManager.AddComponentObject(entity, soundContainer);
    155.          
    156.             //var soundClip = audioGo.AddComponent<SoundClip>();
    157.             //soundClip.Clip = GetComponent<SoundClip>().Clip;
    158.             //audioGo.AddComponent<AudioSource>();
    159.          
    160.             //dstManager.AddComponentObject(entity, GetComponent<SoundClip>());
    161.          
    162.             //dstManager.AddComponentObject(entity, GetComponent<SoundClip>());
    163.             //dstManager.AddComponentObject(entity, GetComponent<AudioSource>());
    164.             //conversionSystem.AddHybridComponent(GetComponent<SoundClip>());
    165.             //conversionSystem.AddHybridComponent(GetComponent<AudioSource>());
    166.             dstManager.AddComponentData(entity, new SimpleSound());
    167.          
    168.          
    169.  
    170.             // Create linkedlist for all entities
    171.             dstManager.AddBuffer<LinkedEntityGroup>(entity);
    172.             var linked = dstManager.GetBuffer<LinkedEntityGroup>(entity);
    173.             linked.Add(new LinkedEntityGroup {Value = posSpring});
    174.             linked.Add(new LinkedEntityGroup {Value = posSpring2});
    175.             linked.Add(new LinkedEntityGroup {Value = rotSpring});
    176.             linked.Add(new LinkedEntityGroup {Value = rotSpring2});
    177.             linked.Add(new LinkedEntityGroup {Value = rotationPivotSpring});
    178.             linked.Add(new LinkedEntityGroup {Value = muzzleflashEntity});
    179.             linked.Add(new LinkedEntityGroup {Value = armsRenderEntity});
    180.             linked.Add(new LinkedEntityGroup {Value = trigger});
    181.             linked.Add(new LinkedEntityGroup {Value = rigidbody});
    182.          
    183.          
    184. #if UNITY_EDITOR
    185.             dstManager.SetName(conversionSystem.GetPrimaryEntity(projectilePrefab), name + " - Projectile prefab: " + projectilePrefab.name);
    186.             dstManager.SetName(posSpring, name + " Position Spring");
    187.             dstManager.SetName(posSpring2, name + " Position Spring 2");
    188.             dstManager.SetName(rotSpring, name + " Rotation Spring");
    189.             dstManager.SetName(rotSpring2, name + " Rotation Spring 2");
    190.             dstManager.SetName(rotationPivotSpring, name + " Rotation Pivot Spring");
    191.          
    192.             dstManager.SetName(conversionSystem.GetPrimaryEntity(armsChildGameobject), name + " Arms");
    193.             dstManager.SetName(conversionSystem.GetPrimaryEntity(muzzleChildGameobject), name + " Muzzle");
    194. #endif
    195.             // Create add weapon event
    196.             if (isEquipped) //dstManager.AddComponentData(entity, new Equipped());
    197.             {
    198.                 var addweaponEvent = dstManager.CreateEntity();
    199.                 dstManager.AddComponentData(addweaponEvent, new TryAddWeaponEvent
    200.                 {
    201.                     WeaponEntity        = entity,
    202.                     WeaponTriggerEntity = trigger,
    203.                     WeaponRigidbodyEntity = rigidbody
    204.                 });
    205.             }
    206.          
    207.             /*// Note: adding a child buffer appears to get cleared on run time(play mode not subscene conversion).
    208.             // Todo follow up either through bugreport or forums
    209.             // Could make a specific component for arms reference
    210.             dstManager.AddBuffer<DisabledChild>(entity);
    211.             var childbuffer = dstManager.GetBuffer<DisabledChild>(entity);
    212.             var child = conversionSystem.GetPrimaryEntity(transform.GetChild(0).gameObject);
    213.             childbuffer.Add(new DisabledChild{Value = child});*/
    214.          
    215.          
    216.          
    217.             // Set rendermesh layer
    218.             var muzzleEntity = conversionSystem.GetPrimaryEntity(muzzleChildGameobject);
    219.             var muzzleRender = dstManager.GetSharedComponentData<RenderMesh>(muzzleEntity);
    220.             muzzleRender.layer = 18; // todo get from static class that handles all layers
    221.             dstManager.SetSharedComponentData(muzzleEntity, muzzleRender);
    222.             var armsRender = dstManager.GetSharedComponentData<RenderMesh>(armsRenderEntity);
    223.             armsRender.layer = 18; // todo get from static class that handles all layers
    224.             dstManager.SetSharedComponentData(armsRenderEntity, armsRender);
    225.             var weaponRender = dstManager.GetSharedComponentData<RenderMesh>(weaponRenderEntity);
    226.             weaponRender.layer = 18;
    227.             dstManager.SetSharedComponentData(weaponRenderEntity, weaponRender);
    228.          
    229.             // Add a backup of the weapon
    230.             var worldSpaceRenderMesh = dstManager.GetSharedComponentData<RenderMesh>(entity);
    231.             dstManager.AddComponentObject(entity, new DisabledRenderMesh{ RenderMesh = worldSpaceRenderMesh });
    232.         }
    233.  
    234.         public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
    235.         {
    236.             referencedPrefabs.Add(projectilePrefab);
    237.         }
    238.      
    239.      
    240.         /// <summary>
    241.         /// Trigger creation for trigger queries
    242.         /// </summary>
    243.         private void CreateTrigger(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem, out Entity trigger)
    244.         {
    245.             trigger = conversionSystem.CreateAdditionalEntity(gameObject);
    246. #if UNITY_EDITOR
    247.             dstManager.SetName(trigger, name + " + Trigger");
    248. #endif
    249.             var filter = CollisionFilter.Default;
    250.             filter.BelongsTo    = triggerBelongsTo.Value;
    251.             filter.CollidesWith = triggerCollidesWith.Value;
    252.             var material = PhysMaterial.Default;
    253.             material.Flags = PhysMaterial.MaterialFlags.IsTrigger;
    254.      
    255.             var blob = Unity.Physics.BoxCollider.Create(new BoxGeometry
    256.             {
    257.                 BevelRadius = 0.001f,
    258.                 Center      = triggerCenter,
    259.                 Orientation = quaternion.identity,
    260.                 Size        = triggerDimensions
    261.             }, filter, material);
    262.          
    263.             // Link this trigger to the weapon
    264.             dstManager.AddComponentData(trigger, new PlayerInteractTrigger{Target = entity});
    265.             dstManager.AddComponentData(trigger, new PhysicsCollider { Value = blob });
    266.             dstManager.AddComponentData(trigger, new PhysicsMass());
    267.             dstManager.AddComponentData(trigger, new Translation {Value = transform.position});
    268.             dstManager.AddComponentData(trigger, new Rotation{Value     = transform.rotation});
    269.             dstManager.AddComponentData(trigger, new LocalToWorld());
    270.             if (isEquipped) dstManager.AddComponentData(trigger, new Disabled());
    271.         }
    272.      
    273.         /// <summary>
    274.         /// Weapon mesh collider & rigidbody creation
    275.         /// </summary>
    276.         private void CreateRigidbody(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem, out Entity rigidbody)
    277.         {
    278.             rigidbody = conversionSystem.CreateAdditionalEntity(gameObject);
    279. #if UNITY_EDITOR
    280.             dstManager.SetName(rigidbody, name + " + Rigidbody");
    281. #endif
    282.             var filter = CollisionFilter.Default;
    283.             filter.BelongsTo    = rigidBelongsTo.Value;
    284.             filter.CollidesWith = rigidCollidesWith.Value;
    285.             var material = PhysMaterial.Default;
    286.      
    287.             var blob = Unity.Physics.BoxCollider.Create(new BoxGeometry
    288.             {
    289.                 BevelRadius = 0.0025f,
    290.                 Center      = rigidCenter,
    291.                 Orientation = quaternion.identity,
    292.                 Size        = rigidDimensions
    293.             }, filter, material);
    294.          
    295.          
    296.          
    297.             dstManager.AddComponentData(rigidbody, new PhysicsCollider { Value      = blob });
    298.          
    299.          
    300.             var massdist = new MassDistribution
    301.             {
    302.                 Transform = new RigidTransform(quaternion.identity, float3.zero),
    303.                 InertiaTensor = new float3(2f/5f)
    304.             };
    305.             var massProperties = new MassProperties
    306.             {
    307.                 AngularExpansionFactor = 0,
    308.                 MassDistribution = massdist,
    309.                 Volume = 1
    310.             };
    311.          
    312.             dstManager.AddComponentData(rigidbody, PhysicsMass.CreateDynamic(blob.Value.MassProperties, 12));
    313.             dstManager.AddComponentData(rigidbody, new PhysicsVelocity());
    314.             dstManager.AddComponentData(rigidbody, new Translation {Value = transform.position});
    315.             dstManager.AddComponentData(rigidbody, new Rotation{Value     = transform.rotation});
    316.             dstManager.AddComponentData(rigidbody, new LocalToWorld{Value = float4x4.TRS(transform.position, transform.rotation, transform.localScale)});
    317.             //dstManager.AddComponentData(rigidbody, new MirrorTranslation{Target = entity});
    318.             if (isEquipped) dstManager.AddComponentData(rigidbody, new Disabled());
    319.         }
    320.      
    321.         public void Reset()
    322.         {
    323.             triggerBelongsTo.Category13 = true;
    324.             triggerCollidesWith.Category03 = true;
    325.             rigidBelongsTo.Category01 = true;
    326.             rigidCollidesWith.Category03 = true;
    327.         }
    328.  
    329.         public void OnDrawGizmos()
    330.         {
    331.             Gizmos.color = new Color(1f, 1f, 1f, 0.47f);
    332.             var position = transform.position;
    333.             Gizmos.DrawCube(triggerCenter + position, triggerDimensions);
    334.             Gizmos.color = new Color(0f, 0f, 1f, 0.5f);
    335.             Gizmos.DrawCube(rigidCenter + position, rigidDimensions);
    336.         }
    337.      
    338.         public void OnDrawGizmosSelected()
    339.         {
    340.             Gizmos.color = Color.white;
    341.             var position = transform.position;
    342.             Gizmos.DrawWireCube(triggerCenter + position, triggerDimensions);
    343.             Gizmos.color = Color.blue;
    344.             Gizmos.DrawWireCube(rigidCenter + position, rigidDimensions);
    345.         }
    346.     }
    347.  
    348.  
    349.     public class DisabledRenderMesh : IComponentData, IEquatable<DisabledRenderMesh>
    350.     {
    351.         public RenderMesh RenderMesh;
    352.  
    353.         public bool Equals(DisabledRenderMesh other)
    354.         {
    355.             if (ReferenceEquals(null, other)) return false;
    356.             if (ReferenceEquals(this, other)) return true;
    357.             return RenderMesh.Equals(other.RenderMesh);
    358.         }
    359.  
    360.         public override bool Equals(object obj)
    361.         {
    362.             if (ReferenceEquals(null, obj)) return false;
    363.             if (ReferenceEquals(this, obj)) return true;
    364.             if (obj.GetType() != this.GetType()) return false;
    365.             return Equals((DisabledRenderMesh) obj);
    366.         }
    367.  
    368.         public override int GetHashCode()
    369.         {
    370.             return RenderMesh.GetHashCode();
    371.         }
    372.     }
    373.  
    374.     /*
    375.     public class WeaponSimpleSoundConversionSystem : GameObjectConversionSystem
    376.     {
    377.         protected override void OnUpdate()
    378.         {
    379.             Entities.ForEach((AudioSource audioSource, SoundClip soundClip) =>
    380.             {
    381.                 //AddHybridComponent(audioSource);
    382.                 //AddHybridComponent(soundClip);
    383.             });
    384.         }
    385.     }*/
    386. }
     
    jconsole and nicolasgramlich like this.
  17. axxessdenied

    axxessdenied

    Joined:
    Nov 29, 2016
    Posts:
    33
    You can use [System.Serializable] on IComponentData for example and then assign values inside a scriptable object outside of a scene. From there it's just a matter of loading in scriptable prefab with your preferred manner and then assigning the data to your entity. You can load in pre-built prefabs and convert them without having to use any scene objects easily with scriptable objects as well. https://forum.unity.com/threads/architecture-with-scriptable-objects-and-ecs.559789/ is a good place to start
     
    Mikael-H and KwahuNashoba like this.
  18. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    @DreamingImLatios I see...slick. So, since all the entities are created inside conversion world at the beginning of conversion process, from (sub)scene or as referenced ones, you take your chance to get references on those. You don't actually care where they are in the GO hierarchy, you just grab them and assign data components to them. Am I wrong/right about this?

    @thelebaron
    This is lots of code, but I managed to pick up few interesting things :)
    What does
    CreateAdditionalEntity
    do and why are you passing root GO to it? I assume it's creating one more Entity, but is there something more behind this?
    Why are you calling
    dstManager.AddBuffer<SoftForce>()
    over and over again? Aren't buffers type oriented, so once you call AddBuffer with that type, it's gonna create buffer where it will put all components of that type? You are adding these spring components to buffer and then assigning them to new component, what are you using the ones in buffer for?

    Why do you prefer adding physics/render components like this instead of manually adding/adjusting them in scene since via Unity created authoring components?


    BTW, I came across this really good talk that explains what happens under the hood for most of mechanisms I could read from your examples
     
  19. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    @thelebaron I have few questions about your system also, I'll write comments in your code to check if I'm understanding well what are you doing inside there.

    Code (CSharp):
    1. public class AttachEntitySystem : SystemBase
    2.     {
    3.         protected override void OnUpdate()
    4.         {
    5.            // you are getting ALL the components from scene with LTW? So basically most of components from scene
    6.             var localToWorldFromEntity = GetComponentDataFromEntity<LocalToWorld>();
    7.            // the same here, but these are read only
    8.             var disabledFromEntity = GetComponentDataFromEntity<Disabled>(true);
    9.      
    10.             // Entities Codegen
    11.             Dependency = Entities
    12.              // I have no idea what most of these query modifiers do (if I can call them like that)
    13.              .WithName("Attach_Transform_Job")
    14.             .WithNativeDisableParallelForRestriction(localToWorldFromEntity)
    15.             .WithReadOnly(disabledFromEntity)
    16.             // grabbing all the Entities with AttachEntity,...
    17.             .ForEach((Entity entity, ref AttachEntity attachEntity, ref Translation translation, ref Rotation rotation) =>
    18.             {
    19.                 // for every single entity in query result, checking against all (potentially thousands) of components if any of
    20.                 // them belongs to entity
    21.                 if (disabledFromEntity.Exists(attachEntity.Value) || !localToWorldFromEntity.Exists(attachEntity.Value))
    22.                     return;
    23.                 var localToWorld = localToWorldFromEntity[attachEntity.Value];
    24.                // I guess this is follow part of code, where you set current entity on position of entity it is "attached" to
    25.                // with this component
    26.                 translation.Value = localToWorld.Position;
    27.                 rotation.Value = new quaternion(localToWorld.Value);
    28.                 if (localToWorldFromEntity.Exists(entity))
    29.                 {
    30.                     localToWorldFromEntity[entity] = localToWorld;
    31.                 }
    32.             })
    33.             .WithBurst().ScheduleParallel(Dependency);
    34.         }
    35.     }
    Not sure what kind of data structure GetComponentData returns, but unless it is something with O(1) complexity, this looks petty expensive to me. I might be completely wrong though :)
     
  20. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    That particular authoring component goes on the root of a spaceship prefab. The hierarchy is always converted all at once, so the authoring component knows all the children GameObjects have entities associated with them and does its thing. It does not actually add any components to the children entities. It just adds references to the children entities on the root entity.

    Is it foolproof? No. But I am not working with fools either. Actually, I'm not working with anyone who has even touched this authoring component. I kinda need to fix that if I want to get this projects into people's hands relatively soonish. :D
     
    NotaNaN likes this.
  21. thelebaron

    thelebaron

    Joined:
    Jun 2, 2013
    Posts:
    825
    CreateAdditionalEntity - When using conversion with subscenes and the new "live conversion" feature, the conversion system needs a mapping of gameobject -> entity(thus passing in the original gameobject)

    Buffer - Not really sure what you mean by oriented, but adding/removing buffers basically work like components, in this case each spring entity needs its own dynamicbuffer component. Technically I could(probably should) make a system to handle this automatically(ie a system that iterates on every entity with just a spring component, and then automatically adds a softforce buffer to it).

    Physics - I make the physics components because at the moment if you use the builtin authoring components, you cant convert a single gameobject to both a trigger and rigidbody entity, only one or the other.

    Render - For render components some of them should be hidden from view and set to a certain layer for a custom fov renderpass(ie view model for first person weapon + arms + muzzle) until they are "equipped", and then the original should be hidden.

    -

    For the system, I think some of these things can be optimized with HasComponent which is new to SystemBase(which does GetComponentDataFromEntity under the hood).

    Basically:
    Iterate over every entity that has
    [with all localToworld components, disabling safety system and writing to them in parallel]
    [with an AttachEntity, Translation & Rotation component as well as a Disabled component(WithReadOnly) tag*]

    copy the localToworld to local position(translation) and rotation, and if it has one, its own localtoworld

    *tag is a component that has nothing in it

    For now this system handles my weapon pickups and "attaching" particle emitter entities to dynamic objects(like blood particle spray attached to a damaged limb) and the actual quantity of entities is very low.
    My own project's scale is just a throwback fps so given the relatively small scale of things Ive not worried about performance considerations of these random access from using CDFE, everything is faster than the old way object orientated way. If you wanted to use this for something at the scale of megacity, well there would probably be better ways of approaching it, but someone else can weigh in on more efficient methods :)

    Theres actually a builtin copy system for entity > gameobject or gameobject > entity CopyTransformFromGameObject component/system/job you might find interesting (Library\PackageCache\com.unity.entities\Unity.Transforms.Hybrid\CopyTransformToGameObjectSystem.cs).
     
  22. TLRMatthew

    TLRMatthew

    Joined:
    Apr 10, 2019
    Posts:
    65