Unity Entities在IComponentData中保存回调函数

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还不支持,选中后右侧面板也看不到啥。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值