- Home /
How to rotate child entity to face the camera?
Hey all!
I have a parent entity with a child entity:
Parent entity - is what moves around
Child entity - is what displays the sprite
I'd like to have the child entity always facing up towards the camera, but I'm not sure how to achieve that. Below is my attempt at putting something together, but it doesn't work properly. The sprites will face the right direction at times, but they also flip around/rotate at times.
var rotation = rotationLookup[entity];
var translation = translationLookup[entity];
var parentTranslation = translationLookup[parent.Value];
parentTranslation.Value.z += 10;
var relativePosition = parentTranslation.Value - translation.Value;
rotation.Value = quaternion.LookRotationSafe( relativePosition , math.up() );
Any help would be much appreciated!
After thinking through it a bit more, I feel like what I need to do is calculate the difference in rotation between the parent's current rotation and its rotation if it was facing up, then give the child this difference in rotation. From my understanding, "subtracting" quaternions is done by multiplying by the inverse of the value you want to remove.
I've got something working, but I'm getting "Invalid AABB a" errors now and the sprite shows up really big and distorted when it is first created. I feel like it might have something to do with math.inverse. One of the threads I came across mentioned that there isn't always a valid inverse and that Unity's Transform normalizes quaternions so there is. Maybe the DOTS Rotation component doesn't do this?
var rotation = rotationLookup[entity];
var parentRotation = rotationLookup[parent.Value];
var parentTranslation = translationLookup[parent.Value].Value;
var upTranslation = parentTranslation;
upTranslation.z += 10;
var endRotation = quaternion.LookRotationSafe(upTranslation - parentTranslation, math.up());
rotation.Value = math.mul(endRotation, math.inverse(parentRotation.Value));
Answer by andrew-lukasik · Mar 03 at 05:37 PM
You can achieve this by overriding LocalToWorld
component at the right time, for example:
scene hierarchy:
results is this (very much expected):
but, if you add a ConstantRotationCompoent
to this B and rotate it a bit:
it will keep that rotation as constant:
If you need them to "look at the camera" (camera-aligned variant) add the LookAtCameraComponent
to B (child) gameObject.
ConstantRotationComponent.cs:
using UnityEngine;
using Unity.Mathematics;
using Unity.Entities;
using Unity.Transforms;
namespace ConstantRotationSystem
{
public class ConstantRotationComponent : MonoBehaviour, IConvertGameObjectToEntity
{
void IConvertGameObjectToEntity.Convert ( Entity entity , EntityManager dstManager , GameObjectConversionSystem conversionSystem )
{
dstManager.AddComponent<ConstantRotation>( entity );
dstManager.SetComponentData( entity , new ConstantRotation{ Value = transform.rotation } );
}
}
public struct ConstantRotation : IComponentData
{
public quaternion Value;
}
[UpdateInGroup( typeof(TransformSystemGroup) )]
[UpdateAfter( typeof(EndFrameLocalToParentSystem) )]
public class ConstantRotationSystem : SystemBase
{
protected override void OnUpdate ()
{
Entities
.WithName("constant_rotation_job")
.ForEach(
( ref LocalToWorld transform , in ConstantRotation constantRotation ) =>
{
float3 scale = new float3{ x=math.length(transform.Value.c0) , y=math.length(transform.Value.c1) , z=math.length(transform.Value.c2) };
transform.Value = float4x4.TRS( transform.Position , constantRotation.Value , scale );
} )
.ScheduleParallel();
}
}
}
LookAtCameraComponent.cs
using UnityEngine;
using Unity.Mathematics;
using Unity.Entities;
using Unity.Transforms;
namespace LookAtCameraComponent
{
public class LookAtCameraComponent : MonoBehaviour, IConvertGameObjectToEntity
{
void IConvertGameObjectToEntity.Convert ( Entity entity , EntityManager dstManager , GameObjectConversionSystem conversionSystem )
=> dstManager.AddComponent<LookAtCamera>( entity );
}
public struct LookAtCamera : IComponentData {}
[UpdateInGroup( typeof(TransformSystemGroup) )]
[UpdateAfter( typeof(EndFrameLocalToParentSystem) )]
public class LookAtCameraSystem : SystemBase
{
protected override void OnUpdate ()
{
var camera = Camera.main;
if( camera==null ) return;
quaternion rotation = Quaternion.LookRotation( forward:-camera.transform.forward , upwards:-camera.transform.up );
Entities
.WithName("loot_at_camera_job")
.WithAll<LookAtCamera>()
.ForEach(
( ref LocalToWorld transform ) =>
{
float3 scale = new float3{ x=math.length(transform.Value.c0) , y=math.length(transform.Value.c1) , z=math.length(transform.Value.c2) };
transform.Value = float4x4.TRS( transform.Position , rotation , scale );
} )
.ScheduleParallel();
}
}
}
AnimateMeComponent.cs (just a test system to help with testing):
using UnityEngine;
using Unity.Entities;
using Unity.Transforms;
using Unity.Mathematics;
namespace AnimateMeSystem
{
public class AnimateMeComponent : MonoBehaviour, IConvertGameObjectToEntity
{
void IConvertGameObjectToEntity.Convert ( Entity entity , EntityManager dstManager , GameObjectConversionSystem conversionSystem )
=> dstManager.AddComponent<AnimateMe>( entity );
}
public struct AnimateMe : IComponentData {}
public class AnimateMeSystem : SystemBase
{
protected override void OnUpdate ()
{
float time = (float) Time.ElapsedTime;
Entities
.WithName("animation_job")
.WithAll<AnimateMe>()
.ForEach(
( ref LocalToWorld transform ) =>
{
float2 noiseScale = new float2{ x=100 , y=100 };
float2 noiseScaleHalved = noiseScale * 0.5f;
float ex = noise.pnoise( new float2(time,time+noiseScaleHalved.y) , noiseScale ) * math.PI;
float ey = noise.pnoise( new float2(time+noiseScaleHalved.x,time) , noiseScale ) * math.PI;
float ez = noise.pnoise( new float2(time,time)+noiseScaleHalved , noiseScale ) * math.PI;
transform.Value = float4x4.TRS( transform.Position , quaternion.Euler( ex , ey , ez ) , 1 );
} )
.ScheduleParallel();
}
}
}