Unity的ECS里的组件只能保存Blittable,Unmanaged的数据,所以C#里的Action,Func,delegate都无法使用了。这里用了一个比较“歪门邪道”的方法来实现,会比较破坏ECS的设计,也可能对性能有点影响。其实ECS的System遍历和处理各种数据是很方便的,不是必须的情况下,不要在组件里保存回调。
实现上就是在IComponentData里保存一个Unmanaged的函数指针,C#里的函数指针写法有点特别,跟C里的很不一样,且必须在C#9.0
及以上才有。然后这个回调函数必须是static的,不能是class或struct的实例方法。还有它的传参有一定限制,无法像常规方法那样随便传参,如果要BurstCompile的话,限制就更多了,不过可以随便传指针进去。如果要用Entities.ForEach,也会有点不方便,但是一般可以根据Unity编辑器里的报错提示进行一定的适配。
其他没啥好说的,用一个简单的例子演示一下,然后直接贴代码,看代码就清楚了。例子就是创建一个Entity添加一个带double字段的Component,然后再创建一个Entity添加一个带函数指针的Component,再实现一个回调函数,里边就是修改带double字段的Component的数据,并返回一个double数据。看完整代码:
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
public unsafe readonly struct Callback {
public readonly delegate* unmanaged[Cdecl]<SystemState*, double> FunctionPointer;
public Callback(delegate* unmanaged[Cdecl]<SystemState*, double> fp) {
FunctionPointer = fp;
}
}
public struct CallbackInstance : IComponentData {
public Callback Callback;
}
public struct ValueInstance : IComponentData {
public double Value;
}
public partial struct CallbackComponentSystem : ISystem
{
[BurstCompile]
static unsafe double StaticFunction(SystemState* statePtr) {
ref var state = ref UnsafeUtility.AsRef<SystemState>(statePtr);
var time = state.Time.ElapsedTime;
var componentTypes = new NativeArray<ComponentType>(1, Allocator.Temp);
componentTypes[0] = ComponentType.ReadOnly<ValueInstance>();
var query = state.GetEntityQuery(componentTypes);
var entities = query.ToEntityArray(Allocator.Temp);
foreach (var entity in entities) {
state.EntityManager.SetComponentData(entity, new ValueInstance{ Value = time });
}
componentTypes.Dispose();
entities.Dispose();
return time;
}
public void OnCreate(ref SystemState state)
{
var valueEntity = state.EntityManager.CreateEntity();
state.EntityManager.AddComponent<ValueInstance>(valueEntity);
unsafe {
var callbackEntity = state.EntityManager.CreateEntity();
delegate* managed<SystemState*, double> fp = &StaticFunction;
state.EntityManager.AddComponentData(callbackEntity, new CallbackInstance{ Callback = new Callback((delegate* unmanaged[Cdecl]<SystemState*, double>)fp) });
}
}
public void OnUpdate(ref SystemState state)
{
unsafe {
var callbackInstance = state.GetSingleton<CallbackInstance>();
var value = callbackInstance.Callback.FunctionPointer((SystemState*)UnsafeUtility.AddressOf<SystemState>(ref state));
// state.Entities.ForEach((ref ValueInstance vi) => {
// vi.Value = value;
// }).Run();
}
}
public void OnDestroy(ref SystemState state)
{
}
}
运行环境:必须Unity2021级以上、Entities0.51及以上。因为2021开始才支持C#9.0,然后2021只能运行0.51的ECS,其他版本会报错。也可以用2022和Entities1.0。
点击运行并选中那个带double组件的Entity,就可以看到数据被一直改变了。注意不要选那个带回调组件的Entity,不然会报错,因为这个非常规数据类型ECS还不支持,选中后右侧面板也看不到啥。