Set world-rotation of child

Hello,

is there a way to set the world rotation of an entity which is the child of an entity which has a rotation itself?
This simple (I guess?) thing is really confusing because of all the quaternions. Isn’t there something like in the monobehaviour world “gameobject.rotation = myRotation”.

I would be really glad if someone could help me out here, or just point me in the right direction on how to solve this. Thanks in advance.

Look into setting LocalToWorld directly. This can be tricky if you need to modify only rotation and I think you’ll have to create TRS matrix from scratch:

                    localToWorld.Value = float4x4.TRS(position, localToWorld.Rotation, localToWorld.GetScale());

If you do this make sure that your system runs after TransformSystemGroup or value you set will be overwritten

1 Like

Thanks to the initial push in the right direction, I found the solution after some hours of maths and struggle :face_with_spiral_eyes:

var lookRot = float4x4.LookAt(worldPos, targetWorldPos, math.up());
ltw.Value = math.mul(lookRot, float4x4.Scale(ltw.Value.GetScale()));

LookAt creates a 4x4 matrix which encodes worldPos and the rotation needed that the objects looks at targetWorldPos, but it lacks the inital scale of the object. To also add the scale back to the object I had to extract it from the LocalToWorld matrix (ltw) by using this code.

public static float3 GetScale(this float4x4 matrix) => new float3(
            math.length(matrix.c0.xyz),
            math.length(matrix.c1.xyz),
            math.length(matrix.c2.xyz));

Then multiply it with the matrix and set it back to the LocalToWorld struct. After this you should have a working LookAtTarget code :slight_smile:

2 Likes

@xXPancakeXx Ah, nice. Things like this should be included in Unity.Transforms docs

I definitely agree. The math stuff could all be hidden for better usability.
I mean how many people know how to work with quaternions.

By the way I am struggling once again trying to animate the rotation. I think something is resetting my LocalToWorld struct. I have already trying to disable any other system and it still looks like it is being reset by something.

Hmm, I think LocalToWorld is built every frame based on Translation, Rotation and Scale. So the code above is the one that is overwriting LocalToWorld. Without it running every frame it will revert to whatever is calculated from components and hierarchy tree. If you want the to change to be permanent you need to change Rotation component itself. You could probably infer what should it be with relation to Parent by getting parents LocalToWorld with GetComponent() and doing some stuff with math.Invert(). I might be wrong tough so don’t take my word for it.

You’re right - here are the docs: https://docs.unity3d.com/Packages/com.unity.entities@0.14/manual/transform_system.html

1 Like

This thread nicely fits an issue I ran into today: I’m trying to set the local rotation, via Rotation. But for some reason, that rotation isn’t kept unless I write via commandBuffer.SetComponent(…). The strange thing about this is that it works fine when I set the position of root entities (that have no parent) via Translation. But when I try to set the Rotation of an entity that does have a parent, it only works via the command buffer.

So, I have one system that essentially does this (and it does exactly what I want - these entities have no parents):

[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(StepPhysicsWorld))]
[UpdateAfter(typeof(GameplayEventsActivitySystem))]
public class GameplayEventsMovementSystem : SystemBase {
    protected override void OnUpdate() {
        Entities
            .ForEach((Entity entity,
                      int entityInQueryIndex,
                      ref Translation translation,
                      in Movement movement) => {
              
                translation.Value = math.lerp(movement.PositionStart, movement.PositionEnd, timeFrac);
            })
            .WithName("MoveJob")
            .ScheduleParallel();
   }
}

And another one that doesn’t work when I write it this way (the rotations are not stored in Rotation or LocalToParent):

[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateBefore(typeof(StepPhysicsWorld))]
[UpdateAfter(typeof(GameplayEventsActivitySystem))]
public class GameplayEventsApplyDirectionSystem : SystemBase {
    protected override void OnUpdate() {
        Entities
            .WithAll<ApplyDirection>()
            .ForEach((
                Entity entity,
                int entityInQueryIndex,
                ref Rotation rotation,
                in GameplayEventRoot ger
            ) => {
                if (HasComponent<Directionality>(ger.Parent)) {
                    Directionality direction = GetComponent<Directionality>(ger.Parent);
                    rotation.Value = direction.Rotation;
                }
            })
            .WithName("GameplayEventApplyDirectionJob")
            .ScheduleParallel();
    }
}

When I replace the line rotation.Value = direction.Rotation; with:

commandBuffer.SetComponent(entityInQueryIndex, entity, new Rotation() {
    Value = direction.Rotation
});

I get exactly what I want (obviously, there’s also some boiler plate code then, which creates the command buffer). Does anyone have an idea why rotation.Value = direction.Rotation; might not work?

Another system overwriting this can be ruled out because in my actual code, I only do this once and it still works fine with the command buffer version - and it still doesn’t work when I write it every frame with the other version.

Interesting case – my first instinct was that a Physics system overwrites the Rotation value. Should not make a difference, but for completeness: when does the commandBuffer replay SetComponent?

1 Like

Hmmm, I found useful bit of info in the docs @florianhanke pointed us to. You can use WriteGroups to make TRSToLocalToWorldTransformSystem ignore your entities. This way system you created will be the only one writing to LocalToWorld. I think this solves everyones problems:

https://docs.unity3d.com/Packages/com.unity.entities@0.14/manual/transform_system.html#overriding-transform-components

Yeah, some other system overwriting Rotation would be the most obvious suspect. I believe the command buffer is actually executed in the next frame before the physics simulation (BeginSimulationEntityCommandBufferSystem). Consequently, keeping rotation.Value = direction.Rotation; is important to avoid a single frame rendered without the rotation.

But in the actual project, we only do this once, after instantiation, so if the Physics system would overwrite it, it would be overwritten in the next frame. And if it was overwritten only once, right after instantiation, keeping ApplyDirection instead of removing it (i.e. Rotation is written to on every frame) should do the trick. But it doesn’t.

Here’s the full code:

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

namespace NarayanaGames.BeatTheRhythm.DOTS.Gameplay {

    [UpdateInGroup(typeof(SimulationSystemGroup))]
    [UpdateBefore(typeof(StepPhysicsWorld))]
    [UpdateAfter(typeof(GameplayEventsActivitySystem))]
    public class GameplayEventsApplyDirectionSystem : SystemBase {
       
        private EntityCommandBufferSystem Barrier
            => World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
       
        protected override void OnUpdate() {
            var commandBuffer = Barrier.CreateCommandBuffer().ToConcurrent();
           
            Entities
                .WithAll<ApplyDirection>()
                .ForEach((
                    Entity entity,
                    int entityInQueryIndex,
                    ref Rotation rotation,
                    in GameplayEventRoot ger
                ) => {
                    commandBuffer.RemoveComponent<ApplyDirection>(entityInQueryIndex, entity);
                   
                    if (HasComponent<Directionality>(ger.GameplayEvent)) {
                        Directionality direction = GetComponent<Directionality>(ger.GameplayEvent);
                        rotation.Value = direction.Rotation;
                        commandBuffer.SetComponent(entityInQueryIndex, entity, new Rotation() {
                            Value = direction.Rotation
                        });
                    }
                   
                })
                .WithName("GameplayEventApplyDirectionJob")
                .ScheduleParallel();

            Barrier.AddJobHandleForProducer(Dependency);
        }
    }
}

Your thinking makes sense to me, so I don’t think I have anything to add. Personally, I always write at the latest in the EndSimulationEntityCommandBufferSystem so that all is written before the next frame (and changes are shown in the Debugger). But I may be cargo culting an earlier version of myself.

Sidenote: If we could step through each system and could look at the same entity’s components after each step, then issues like this one would be debugged much more easily (at least we could see when the data changes, exactly).

2 Likes

Another helpful feature to have would be to see which systems have manipulated the ComponentData this frame

1 Like

Turns out we kind of can - and interestingly enough, it was the physics system, ExportPhysicsWorld, to be precise.

I revisited this issue because we now sometimes delete all entities from the main thread for a cleanup before reloading, and that caused an exception because the command buffer still tried writing to the Rotation component from an Entity that no longer existed. There’s a workaround for that (disable the system, wait a frame, delete entities, re-activate system) … but I’d rather not have layers of patches in my new, otherwise clean DOTS-code, so I did more investigation.

First thing I tried was using WriteGroups, as @Srokaaa suggested. That didn’t work (probably because Unity Physics also uses WriteGroups and I’d have to do more to override those WriteGroups with my own). Then, I moved the system that writes my Rotation to the end of SimulationSystemGroup, and it worked.

Then I noticed that I can uncheck systems in the Entity Debugger. That is an incredibly useful feature (and it kind of lets you “step” through each system by disabling all systems after the one you want to have a look at)! Moved my system back to where it originally was, click, click, ExportPhysicsWorld is the problem. But actually, it turns out that I was dumb when I put my systems right after BuildPhysicsWorld, instead of right before.

So here’s what happened: Unity Physics built the PhysicsWorld before my system overwrote Rotation. Then, after my system overwrite Rotation, Unity Physics overwrote it again in ExportPhysicsWorld, with the value from BuildPhysicsWorld. It worked with the command buffer because then, the Rotation was overwritten (again) before BuildPhysicsWorld (in the next frame).

It’s kind of obvious when you think about it … but somehow I thought putting my systems before StepPhysicsWorld would be enough. No, it was not :wink: