Using Hybrid ECS I’m looking to add/remove components from an entity in the OnUpdate() loop within a ComponentSystem class. However, I’m not sure how to get the entity object to pass the the AddComponent() / RemoveComponent() methods.
Here’s an example modeled after the sample in the docs:
class SomeComponent : MonoBehaviour
{
public int Value;
}
class SomeSystem : ComponentSystem
{
struct Group
{
public SomeComponent SomeComponent
}
protected override OnUpdate()
{
foreach (var e in GetEntities<Group>())
{
// not sure how to do this as e is not the right type...
//PostUpdateCommands.RemoveComponent<SomeComponent>(entity);
}
}
}
I’m probably missing something basic here but couldn’t find anything searching around and still trying to figure out how this all works.
public class SomeSystem : ComponentSystem
{
struct SomeGroup
{
public ComponentArray<Building> Component; //Building is MonoBehaviour component
public EntityArray Entity;
public int Length;
}
[Inject] SomeGroup group;
protected override void OnUpdate()
{
//for add ECS components in one loop
for (int i = 0; i < group.Length; i++)
{
PostUpdateCommands.AddComponent(group.Entity[i], new SomeComponentData());
}
//for remove ECS\MonoBehaviout components in one loop
for (int i = 0; i < group.Length; i++)
{
PostUpdateCommands.RemoveComponent<Building>(group.Entity[i]);
}
//But remember - this is NOT adding\removing components to GameObject, only to Entity
}
}
public struct SomeComponentData : IComponentData
{
//some blittable stuff
}
@eizenhorn thank you for the sample, that is very helpful!
I am curious, does anyone know if it’s required to use the [Inject] syntax to add/remove components as opposed to the GetEntities() loop? I think that’s where I got stuck as I opted to go with the loop method but don’t know if there’s syntax to do the same thing as shown above?
If not, then maybe I’ll just switch to using the [Inject] method everywhere for consistency.
GetEntities gives you ComponentGroupArray, ComponentGroup can’t has Entity (as i know).
GetEntities lets us efficiently iterate over all GameObjects (<- Important!) is simple way to update MonoBehaviour components data, but not for add\remove.
I’m trying to use Hybrid ECS and I’m also running into some issues with adding/removing components.
Is there a way to add/remove components and keeping the it synchonized between GameObject and entity? I tried it in a few ways, but it seems that gameObject.AddComponent() doesn’t get picked up by the entity and adding a component to an entity doesn’t add it to the game object.
For example i tried to add a component to the GameObject and the entity separately. Then I wanted to update it on the entity to the object of the component attached to the GameObject, so they’re the same object. Unfortunately it doesn’t work, because the SetComponentObject function in EntityManager is internal. After trying a few variations of this, I noticed that adding a MonoBehaviour component to an entity doesn’t actually add anything, so I’m not sure anymore if my approach makes sense at all.
var goe = gameObject.GetComponent<GameObjectEntity>();
goe.EntityManager.AddComponent(goe.Entity,ComponentType.Create<SomeMB>());
var comp = goe.EntityManager.GetComponentObject<SomeMB>(goe.Entity);
Debug.Log(comp); // comp is null
Why is the component null right after adding it? Is this supposed to work?
My current workaround to add components in Hybrid ECS and GameObject world synchronously looks like this. By disabling and enabling the object, the entity is destroyed and created again. Obviously not very performant, but it does the job.
SetActive(true\false) it’s a first what i do for this, but, as i think, is crutch. Some part of information you can find in this post . Now I create new objects (in a hybrid approach) and add components (with synchronization) to them in this way:
var citizen = UnityEngine.Object.Instantiate(Hub.hub.womanPrefab, pos, Quaternion.identity);
var citizenData = citizen.AddComponent<Citizen>();
citizenData.data = new CitizenData
{
citizenId = Hub.lastCitizenId,
resources = new ResourcesOnCitizen
{
hasResource = false,
food = 0,
wood = 0,
stone = 0,
iron = 0
}
};
GameObjectEntity.AddToEntityManager(EntityManager, citizen);
I remind you that these are all variations for the hybrid approach. For pure ECS, I did not observe any synchronization problems, everything is trivially added, changed, etc.
Quick follow-up as I’m having trouble with this bit of syntax:
PostUpdateCommands.AddComponent() requires a type so in this example the call would be something like this I think:
PostUpdateCommands.AddComponent<SomeComponentData>(group.Entity[i], new SomeComponentData());
However, when I try this in my own very similar code, I get this type of compile error:
error CS0453: The type `SomeComponentData' must be a non-nullable value type in order to use it as type parameter `T' in the generic type or method `Unity.Entities.EntityCommandBuffer.AddComponent<T>(Unity.Entities.Entity, T)'
I’ve tried this with my version of SomeComponentData deriving from either MonoBehavior or IComponentData, doesn’t seem to make the error go away. Am I missing something here?
I found my mistake, my component was a class deriving from MonoBehavior so to get rid of the error it needs to be a struct deriving from IComponentData.
It sounds like the ability to add a MonoBehavior component to an entity during runtime for Hybrid ECS is on the ‘to-do’ list for Unity. Please someone correct me if that’s not the case.
I am trying to do something similar. So far I have
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using static ComponentTypes;
using Unity.Collections;
public class MouseOverSystem : ComponentSystem
{
Ray ray;
RaycastHit hit;
public struct MouseOverTargetGroup
{
public ComponentArray<BoxCollider> collider;
[ReadOnly] public ComponentDataArray<MouseOverTarget> mouseOverTarget;
public EntityArray Entity;
public int Length;
}
[Inject] MouseOverTargetGroup group;
protected override void OnUpdate()
{
Debug.Log("did I add it?");
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
for (int i = 0; i < group.Length; i++)
{
if (group.collider[i].Raycast(ray, out hit, 1000.0f))
{
Debug.Log("did I add it?");
FallenBootstrap.entityManager.AddComponent(group.Entity[i], ComponentType.Create<MouseOver>());
}
}
}
}
My bootstrap class, FallenBootstrap is:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using UnityEngine.AI;
using Unity.Transforms;
using static BuildingProductionSystem;
using static ComponentTypes;
public class FallenBootstrap {
public static EntityManager entityManager;
private static EntityArchetype cameraArchetype;
private static EntityArchetype buildingArchetype;
private static EntityArchetype unitArchetype;
private static EntityArchetype groundArchetype;
// Use this for initialization
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
public static void Start () {
Debug.Log("I have started the game!");
entityManager = World.Active.GetOrCreateManager<EntityManager>();
cameraArchetype = entityManager.CreateArchetype(typeof(Rotation), typeof(Position), typeof(Camera));
buildingArchetype = entityManager.CreateArchetype(typeof(MeshRenderer), typeof(BoxCollider), typeof(BuildingInputSystem),
typeof(RectTransform), typeof(MeshFilter), typeof(MouseOverSystem), typeof(Position), typeof(MouseOverTarget));
unitArchetype = entityManager.CreateArchetype(typeof(MeshRenderer), typeof(SphereCollider), typeof(NavMeshAgent));
groundArchetype = entityManager.CreateArchetype(typeof(MeshRenderer), typeof(MeshCollider));
Entity building = entityManager.CreateEntity(buildingArchetype);
var proto = GameObject.Find("Building");
//var proto_position = proto.GetComponent<Position>().Value; //why are these different?
//var proto_rect_transform = proto.GetComponent<RectTransform>();
//Object.Destroy(proto);
// entityManager.SetComponentData(building, proto_position);
// var Camera = entityManager.CreateEntity(cameraArchetype);
}
}
and finally I have my structs…
using UnityEngine;
using Unity.Entities;
public class ComponentTypes
{
public struct MouseOverTarget : IComponentData
{
}
public struct MouseOver : IComponentData
{
}
public struct ClickTarget : IComponentData
{
}
public struct Clicked : IComponentData
{
}
public struct ClickHandled : IComponentData
{
}
}
unfortunately whenever I run my code I get the following:
NullReferenceException: Object reference not set to an instance of an object
MouseOverSystem.OnUpdate () (at Assets/GameCode/Shared/MouseOverSystem.cs:30)
Unity.Entities.ComponentSystem.InternalUpdate () (at [redacted]/packages.unity.com/com.unity.entities@0.0.12-preview.5/Unity.Entities/ComponentSystem.cs:290)
Unity.Entities.ScriptBehaviourManager.Update () (at [redacted]/packages/packages.unity.com/com.unity.entities@0.0.12-preview.5/Unity.Entities/ScriptBehaviourManager.cs:82)
Unity.Entities.ScriptBehaviourUpdateOrder+DummyDelagateWrapper.TriggerUpdate () (at [redacted]/packages/packages.unity.com/com.unity.entities@0.0.12-preview.5/Unity.Entities/ScriptBehaviourUpdateOrder.cs:732)
I am not sure what I am doing wrong here. Any advice?
Looks like the Group.collider instance value may be null for some reason. Verify they exist first, then perhaps report a bug?
Which version of the ECS package are you on? Your group struct may need to change public int Length; to public readonly int Length; if it’s one of the newer ones.
The components being tags or not seems irrelevant on first glance.