Ok, this took a lot of effort but I have it working. I couldn’t find any tutorial on how to do this so here is how I got it to work. But first, my setup: Entities 0.17 preview 42, Unity 2020.3.16f1 HDRP project, TMP 3.0.6 (modified with new shaders from here - Plans For Hdrp Compatibility For Tmp? page-2#post-7081120). I moved the TextMeshPro folder out of the “Library” folder and into the “Packages” folder, then deleted all of the shaders from 3.0.6 and replaced them with the ones from the forum - if you leave the 3.0.6 shaders in the project then you will get some errors. I was unable to get the 3.2.0 preview tmp package to work.
If you only need a handful of labels then what I describe below is overkill. If you only need a few then do this:
Create a TMP Gameobject, and have it “Convert and Inject Game Object” this will generate two copies of the TMP, one in the mono and one in the ECS wold. If you only need to translate the label (i.e. no rotation) then you can add the “CopyTransformToGameObject” IComponentData as talked about above and then use “Translation” in the ECS world and the two copies will sync up. If you need to rotate then you need to do a little more work.
First add a tag:
using Unity.Entities;
[GenerateAuthoringComponent]
public struct TextTag : IComponentData {}
The TMP GameObject uses a RectTransform instead of a Transform and the ECS Rotation does not work. In order to rotate the Gameobject (and keep the ECS and GO in sync), you need to gain access to the RectTransform and then rotate that manually. They talk about not doing this in the docs but it’s the only way I could make it work.
Entities.WithAll<TextTag>().ForEach((Entity entity) =>
{
EntityManager.RemoveComponent<TextTag>(entity);
var tmp= EntityManager.GetComponentObject<TextMeshPro>(entity);
tmp.text = "hello world";
tmp.fontSize = 128;
var recT = EntityManager.GetComponentObject<RectTransform>(entity);
recT.position = new float3(0, t, 0);
recT.rotation = Quaternion.Euler(0, 0, 1.578f);
}).WithStructuralChanges().WithoutBurst().Run();
This works for a small number of TMP’s but if you need hundreds then do this:
The basic concepts for the solution came from the “StressTests/HybridComponent” folder in the ECCS sample projects where they are injecting a non-ECS-supported object into the ECS world (a gizmo in their case).
Create the TextTag from above
Create this IComponentData file:
using Unity.Entities;
namespace Optkl.Data
{
public struct TextMeshHybrid : IComponentData
{
public Entity prefab;
}
}
Create an empty gameobject (Spawner), set it to “Convert and Destroy” and attach this script to it:
using System.Collections.Generic;
using Optkl.Data;
using Unity.Entities;
using UnityEngine;
// ReSharper disable once InconsistentNaming
[AddComponentMenu("Spawner")]
[ConverterVersion("joe", 1)]
public class TextMeshAuthoringHybrid : MonoBehaviour, IConvertGameObjectToEntity, IDeclareReferencedPrefabs
{
public GameObject prefab;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponentData(entity, new TextMeshHybrid()
{
prefab = conversionSystem.GetPrimaryEntity(prefab)
});
}
public void DeclareReferencedPrefabs(List<GameObject> referencedPrefabs)
{
referencedPrefabs.Add(prefab);
}
}
this code will give you access to the “Prefab” in ECS than you can create copies from. You can add this script to your empty gameobject by selecting the “AddComponent” button in the inspector and then selecting “Spawner” or you can change the [AddComponentMenu…] to [GenerateAuthoringScript] and drag it onto the empty gameobject.
Add this to your system folder:
using TMPro;
using Unity.Entities;
using UnityEngine;
namespace Optkl.Utilities
{
[ConverterVersion("Flash", 1)]
public class TextMeshConversion : GameObjectConversionSystem
{
protected override void OnUpdate()
{
Entities.ForEach((Entity entity, TextMeshPro textMeshPro, RectTransform rectTransform, MeshRenderer meshRenderer) =>
{
AddHybridComponent(textMeshPro);
AddHybridComponent(rectTransform);
AddHybridComponent(meshRenderer);
});
}
}
}
This is the hybrid injection process where it takes the Gameobject components and gives you access to them in ECS.
Finally to instantiate the objects you can do this (you can also access things in an Enties.Foreach Loop if you want but did it with a query):
var entityTickLabelsQuery = GetEntityQuery( typeof(TextMeshHybrid));
var textPrefabParent = entityTickLabelsQuery.ToEntityArray(Allocator.Temp)[0];
var textPrefab = EntityManager.GetComponentData<TextMeshHybrid>(textPrefabParent);
var _tempTextArray = new NativeArray<Entity>(_tickLabelTranslation.Length, Allocator.Persistent);
EntityManager.Instantiate(textPrefab.prefab, _tempTextArray);
for (var i = 0; i < _tempTextArray.Length; i++)
{
EntityManager.SetComponentData<Translation>(_tempTextArray[i], _tickLabelTranslation[i]);
EntityManager.SetComponentData<Rotation>(_tempTextArray[i], _tickLabelRotation[i]);
var tmp = EntityManager.GetComponentObject<TextMeshPro>(_tempTextArray[i]);
tmp.text = _tickLabel[i].ToString();
tmp.fontSize = 350;
}
The “_tickLabelTranslation” and “_tickLabelRotation” are NativeLists that I fill in a job using this (partial code):
var _tickLabelTranslation = new NativeList<Translation>(iris.Length, Allocator.TempJob);
var _tickLabelRotation = new NativeList<Rotation>(iris.Length, Allocator.TempJob);
var _tickLabel = new NativeList<int>(iris.Length, Allocator.TempJob);
var buildTickJobFor = new BuildTickJobFor
{
tickLabelTranslation = _tickLabelTranslation,
tickLabelRotation = _tickLabelRotation,
tickLabel = _tickLabel
};
buildTickJobFor.ScheduleParallel(iris.Length, 512, new JobHandle())
and the job is something like this (partial code):
[BurstCompile(CompileSynchronously = true)]
private struct BuildTickJobFor : IJobFor
{
[ReadOnly] (a bunch of readonly variables)
[NativeDisableParallelForRestriction] [WriteOnly] public NativeList<Translation> tickLabelTranslation;
[NativeDisableParallelForRestriction] [WriteOnly] public NativeList<Rotation> tickLabelRotation;
[NativeDisableParallelForRestriction] [WriteOnly] public NativeList<int> tickLabel;
void IJobFor.Execute(int i)
{
....
tickLabelTranslation.Add(new Translation
{
Value = new float3((radius + 125) * math.cos(theta), (radius + 125) * math.sin(theta), 0)
});
tickLabelRotation.Add(new Rotation()
{
Value = quaternion.Euler(0, 0, theta - math.PI / 2)
});
tickLabel.Add(i);
}
.....
I removed a bunch of the code from the job system but I wanted to give you an idea of where those variables came from.
Hope this helps somebody out.