Search Unity

Question Spawning Prefab in the right position based on LocalToWorld component of a moving parent.

Discussion in 'Graphics for ECS' started by Dan-Foster, Nov 8, 2021.

  1. Dan-Foster

    Dan-Foster

    Joined:
    Aug 7, 2019
    Posts:
    53
    I'm currently facing an issue where I'm trying to spawn a bullet trail that's coming out of the muzzle of the gun in the player's hand in an FPS game so it looks like it's coming out of the gun when the player is moving. But the trail is spawning in the player's position from the previous frame, so when you're running to the right the bullet looks like it's coming from next to the gun, rather than out the barrel.

    Here's the setup that I have currently in the project.

    The attach point for the muzzle is a child of the gun object.
    The gun is a Child of the GunAttachPoint of the Player.
    The Player has no parent.

    The Player's Translation + Rotation component is being moved early in the frame.

    Then using an EntityCommandBuffer I Instantiate the bullet trail prefab, set it's Translation component using the LocalToWorld component from the MuzzleAttachPoint. The EntityCommandBuffer used calls playback before the TransformSystemGroup is updated.

    The TransformSystemGroup then updates, which updates the WorldToLocal matrixes. This means when the scene gets rendered I've spawned the bullet trail in the position that the MuzzleAttachPoint was in the previous frame.

    I've also tried spawning the Bullet Trail in the LateSimulationSystemGroup, which is updated after the TransformSystemGroup, but the prefab doesn't seem to be rendered until the next frame, I think because it doesn't have the LocalToWorld Component added, and to add it I need to run the TransformSystemGroup.

    So I'm in a bit of a pickle.

    Is there a good way to solve this problem?
     
  2. Dan-Foster

    Dan-Foster

    Joined:
    Aug 7, 2019
    Posts:
    53
    A solution that I've come up with since posting this and looking through some of the TransformSystemGroup code is to have another TransformSystemGroup run.

    All of the systems that are put into the TransformSystemGroup inherit from intentionally abstract classes, similar to how an EntityCommandBufferSystem works. It seems like Unity want you to have multiple of them in a frame if you need it!

    So I've copied the code from com.unity.entities@0.17.0-preview.42\Unity.Transforms\EndFrameTransformSystems.cs into my own file and renamed the Systems inside.

    Now I'm spawning my bullet trail in LateSimulationSystemGroup, and I've setup the new TransformSystemGroup to OrderLast in LateSimulationSystemGroup.

    My Entity Debugger looks like this.

    upload_2021-11-8_17-55-48.png

    And the new file I've made to copy the TransformSystemGroup looks like this.
    Code (CSharp):
    1. using Unity.Burst;
    2. using Unity.Collections;
    3. using Unity.Entities;
    4. using Unity.Jobs;
    5. using Unity.Mathematics;
    6. using Unity.Transforms;
    7.  
    8. [UnityEngine.ExecuteAlways]
    9. [UpdateInGroup( typeof( LateSimulationSystemGroup ), OrderLast = true )]
    10. public class LateTransformSystemGroup : ComponentSystemGroup
    11. {
    12. }
    13.  
    14. [UnityEngine.ExecuteAlways]
    15. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    16. public class LateParentSystem : ParentSystem
    17. {
    18. }
    19.  
    20. [UnityEngine.ExecuteAlways]
    21. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    22. public class LateCompositeScaleSystem : CompositeScaleSystem
    23. {
    24. }
    25.  
    26. [UnityEngine.ExecuteAlways]
    27. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    28. public class LateRotationEulerSystem : RotationEulerSystem
    29. {
    30. }
    31.  
    32. [UnityEngine.ExecuteAlways]
    33. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    34. public class LatePostRotationEulerSystem : PostRotationEulerSystem
    35. {
    36. }
    37.  
    38. [UnityEngine.ExecuteAlways]
    39. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    40. [UpdateAfter( typeof( LateRotationEulerSystem ) )]
    41. public class LateCompositeRotationSystem : CompositeRotationSystem
    42. {
    43. }
    44.  
    45. [UnityEngine.ExecuteAlways]
    46. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    47. [UpdateAfter( typeof( LateCompositeRotationSystem ) )]
    48. [UpdateAfter( typeof( LateCompositeScaleSystem ) )]
    49. [UpdateBefore( typeof( LateLocalToParentSystem ) )]
    50. public class LateTRSToLocalToWorldSystem : TRSToLocalToWorldSystem
    51. {
    52. }
    53.  
    54. [UnityEngine.ExecuteAlways]
    55. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    56. [UpdateAfter( typeof( LateParentSystem ) )]
    57. [UpdateAfter( typeof( LateCompositeRotationSystem ) )]
    58. public class EndFrameParentScaleInverseSystem : ParentScaleInverseSystem
    59. {
    60. }
    61.  
    62. [UnityEngine.ExecuteAlways]
    63. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    64. [UpdateAfter( typeof( LateCompositeRotationSystem ) )]
    65. [UpdateAfter( typeof( LateCompositeScaleSystem ) )]
    66. [UpdateAfter( typeof( EndFrameParentScaleInverseSystem ) )]
    67. public class EndFrameTRSToLocalToParentSystem : TRSToLocalToParentSystem
    68. {
    69. }
    70.  
    71. [UnityEngine.ExecuteAlways]
    72. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    73. [UpdateAfter( typeof( EndFrameTRSToLocalToParentSystem ) )]
    74. public class LateLocalToParentSystem : LocalToParentSystem
    75. {
    76. }
    77.  
    78. [UnityEngine.ExecuteAlways]
    79. [UpdateInGroup( typeof( LateTransformSystemGroup ) )]
    80. [UpdateAfter( typeof( LateTRSToLocalToWorldSystem ) )]
    81. [UpdateAfter( typeof( LateLocalToParentSystem ) )]
    82. public class EndFrameWorldToLocalSystem : WorldToLocalSystem
    83. {
    84. }

    This does seem almost extreme to do, and I worry about running this code twice as the development of the game goes on. But for now it is working.

    Have I done the right thing here?
     
  3. JussiKnuuttila

    JussiKnuuttila

    Unity Technologies

    Joined:
    Jun 7, 2019
    Posts:
    351
    To ensure your prefab entity has every component required to render, we provide the RenderMeshUtility.AddComponents API which adds every component required by the Hybrid Renderer. It's called automatically for every entity that is converted from GameObjects by the Hybrid Renderer conversion system, but if you created the entity yourself then you might want to call it manually.

    If you know you are specifically missing the LocalToWorld component (which is one of the required components, not having it would cause the entity to not render), you can also add it yourself, you don't need to rely on the TransformSystemGroup to do it. The aforementioned RenderMeshUtility.AddComponents API also adds it.

    When rendering, the transform matrix in LocalToWorld will be used regardless of what code updated it (i.e. it could be TransformSystemGroup, or it can be set by the user directly). If you know exactly the transform that you want at the point of spawning, perhaps you could set LocalToWorld to that value directly when the entity is spawned, and then let TransformSystemGroup update it on subsequent frames?
     
  4. Dan-Foster

    Dan-Foster

    Joined:
    Aug 7, 2019
    Posts:
    53
    Hi JussiKnuuttila, Thanks very much for the reply!

    This morning I've come to realise that where ever I've been using the default "EndSimulationEntityCommandBufferSystem" to spawn rendered entities, they have not been rendered at the origin for a single frame before getting their matrices updated. This isn't visible to the user, but it's still adding perceived latency to the game, not ideal for a networked game!

    Just changing to use an EntityCommandBufferSystem that's played back before the TransformSystemGroup solved this issue, and is something I'll be able to apply across the game when spawning rendered entities.

    Now after reading your message I'm thinking about putting a system in that runs in the LateSimulationSystemGroup that's able to do as you suggest. I could have a component that specifies an Entity as a parent, get it's LocalToWorld matrix, then manually calculate the child's LocalToWorld matrix ready for rendering.

    Doing this would be a lot less CPU work than running a whole second TransformSystemGroup, and it would only be working on relevant components. And should be reusable for any spawned prefab.

    I'll check back in later in the week when I've revisited this,

    Thanks!
    Dan.