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!

You can achieve this by overriding LocalToWorld component at the right time, for example:

scene hierarchy:
scene hierarchy

inspector A

inspector B mesh only

results is this (very much expected):

expected results

but, if you add a ConstantRotationCompoent to this B and rotate it a bit:

inspector B constant rotation

it will keep that rotation as constant:

new results

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

}