Hi, I want to use unity’s classic animator for my character (not the experimantal dots package). The problem is that all the bones gets converted in my subscene, so the animator cant access the gameobjects anymore, and it cant be stopped with “convert to entity (stop)”. It seems subscenes doesn’t support having references to gameobjects, yet it is possible to have hybrid components in them, how does that work? Ideally I would like my player entity to have a hybrid component which is the animator and then have the companion gameobject be the parent to all the bones etc. so the animator can access them. But I’m having lots of trouble setting this up. Has anyone done this and can share a solution?
This won’t get you the exact structure you are looking for, but you can try instantiating a child GameObject prefab in the Awake method of a hybrid component.
After testing this approach for some time now, I’d like to warn anyone coming across this. Reason is because it doesn’t work properly in some cases. For example; the prefab instantiation only works if the subscene is closed. If open the the prefab will be instantiated in “editing state” rather than “live game state”. I also had trouble implementing foot IK; OnAnimatorIK is only called once if the subscene is closed but is called as expected when subscene is open. This means I cant have both IK and animations at the same time. Maybe I’ll skip subscenes and use convert to entity instead.
Here’s something I came up with. It uses Resources.FindObjectsOfTypeAll and then a .Where so it is most likely horribly slow and does not scale at all, but if you don’t have a lot of rigged animators you might get away with it as a temporary solution. And then you have to generate a GUID and copy+paste it into your “receiver” component so its definitely not user-friendly at all(though i made a button to automatically generate the GUID). Might work faster if you use a smaller datatype than string128, but I wanted to be able to just generate a GUID randomly and not have it overlap.
Reference sender component class + system (put this on your entity):
using System;
using System.Linq;
using Unity.Collections;
using Unity.Entities;
using UnityEngine;
namespace Assets.DOTS.Hybrid
{
public class GUIDReferenceSender : MonoBehaviour, IConvertGameObjectToEntity
{
public string GUID;
public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
{
dstManager.AddComponent<GUIDReferenceSenderComponentData>(entity);
dstManager.SetComponentData(entity, new GUIDReferenceSenderComponentData(GUID));
}
internal void SetGuid(Guid guid)
{
GUID = guid.ToString();
}
}
internal struct GUIDReferenceSenderComponentData : IComponentData
{
public FixedString128 GUID;
public GUIDReferenceSenderComponentData(string guid)
{
GUID = guid;
}
}
//[UpdateInGroup(typeof(InitializationSystemGroup))]
public class EntityGUIDReferenceSenderSystem : SystemBase
{
EntityCommandBufferSystem _entityCommandBufferSystem;
protected override void OnCreate()
{
_entityCommandBufferSystem = World.GetOrCreateSystem<EntityCommandBufferSystem>();
}
protected override void OnUpdate()
{
Debug.Log("Running GUID reference sender");
////can't use fields in Entities.ForEach; have to copy reference
EntityCommandBuffer buffer = _entityCommandBufferSystem.CreateCommandBuffer();
Entities
.WithoutBurst()
.ForEach((Entity e, in GUIDReferenceSenderComponentData s) => {
Debug.Log($"Running GUID reference sender: ForEach (Entity: {e})");
SendEntityByGUID(e, s.GUID);
buffer.RemoveComponent<GUIDReferenceSenderComponentData>(e);
}).Run();//Have to run on main thread for FindObjects
}
/// <summary>
/// Sends an entity reference to all <see cref="GUIDReferenceReciever"/> components in your scene.
/// </summary>
public static void SendEntityByGUID(Entity entity, FixedString128 GUID)
{
var recievers = Resources.FindObjectsOfTypeAll<GUIDReferenceReciever>().Where((x) =>
{
string guidStr = GUID.ToString();
return (x.GUID).Equals(guidStr);
});
if (recievers.Count() == 0)
{
Debug.LogError("Reference sender has no recievers! Entity: " + entity);
}
else
{
foreach (var item in recievers)
{
item.SetEntity(entity);
}
}
}
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(GUIDReferenceSender))]
public class GUIDReferenceEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
GUILayout.Label($"Use this component to pass an entity reference to a game object even when there is a scene mismatch: copy the GUID and paste it into a {nameof(GUIDReferenceReciever)} component. This allows you to use a hybrid DOTS solution while still converting entities before runtime (using SubScenes)");
if (GUILayout.Button("Generate GUID"))
{
((GUIDReferenceSender)target).SetGuid(Guid.NewGuid());
}
base.OnInspectorGUI();
}
}
#endif
}
Reference receiver component (put these on your gameObject. You could definitely refactor this into a single component but I have other uses for the EntityReferenceReciever):
#define DEBUG_ENTREF
using Unity.Entities;
using UnityEngine;
public class EntityReferenceReciever : MonoBehaviour
{
public Entity? _e;
public Entity GetEntity() => _e ?? Entity.Null;
public void SetEntity(Entity entity) => _e = entity;
#if DEBUG_ENTREF
private void Update()
{
if (Time.frameCount % 30 == 0)
{
Debug.Log(string.Format("Entity reference: {0}", _e.HasValue ? _e.ToString() : "NOT ASSIGNED"), gameObject);
}
}
#endif
}
using Unity.Entities;
using UnityEngine;
namespace Assets.DOTS.Hybrid
{
[RequireComponent(typeof(EntityReferenceReciever))]
public class GUIDReferenceReciever : MonoBehaviour
{
public string GUID;
public void SetEntity(Entity entity)
{
GetComponent<EntityReferenceReciever>().SetEntity(entity);
//remove GUID component from gameobject
if (Application.isPlaying)
{
Destroy(this);
}
}
}
}
There are probably about a million things wrong with this code, but it works for me. Let me know if this works for you or if I might have forgot to include anything. This should allow you to just set up a reference to an entity in your monobehaviour, though the first few frames the reference might still be null. This is an example of how I make a GameObject follow a translation:
using Unity.Entities;
using Unity.Transforms;
using UnityEngine;
[RequireComponent(typeof(EntityReferenceReciever))]
public class EntityReferenceFollower : MonoBehaviour
{
[SerializeField] Transform _rootTransform;
[SerializeField] EntityReferenceReciever _reciever;
private EntityManager _em;
public void Start()
{
_em = World.DefaultGameObjectInjectionWorld.EntityManager;
}
public void Update()
{
Entity entityToFollow = _reciever.GetEntity();
if (entityToFollow == Entity.Null)
{
if (Time.frameCount > 10)//First update loops run before entity-to-gameObject references get sent; ignore if entity is null in first 10 frames
{
Debug.LogError($"Entity is null when trying to follow transform(frame: {Time.frameCount})", gameObject);
}
return;
}
Debug.Log("Moving gameobject to entity");
if (_em.HasComponent<Translation>(entityToFollow))
{
_rootTransform.position = _em.GetComponentData<Translation>(entityToFollow).Value;
}
}
}
Honestly, this whole solution is probably not worth it and you’d be better off just using ConvertToEntity for now because as you can see, it still might take a few frames for the reference to not be null anyways so I’d assume this means its slower than just ConvertToEntity + GetPrimaryEntity.
I just hope that Unity doesn’t deprecate ConvertToEntity before giving us a better solution.
Thanks! It is always appreciated to see how others are doing hybrid DOTS. I have not yet tried your code, but will do if I hit a roadblock with with ConvertToEntity. Your approach is something I hadn’t considered so it’s nice of you to share it.