赞
踩
权限系统是我编出来的词,具体的作用呢我定义为:赋予组件权限,并在类中完善对应的权限,当向组件系统加入组件时,组件系统会识别组件的权限,并进行不同的操作;
很简单,当将一个组件加入到ecs系统的时候,我们不免需要对一些组件进行不同的额外操作,比如那些组件应该显示在HierarchyPanel上,那些一个被序列化,那些支持运行时拷贝...etc,权限系统让组件系统对不同组件进行不同的组件所需要的额外操作
这写操作有但不限于:赋值、分类...etc
那具体是怎么实现呢?
其核心是标准库在的is_base_of模板,这个模板为权限识别提供了可能;
我们可以用一个结构体作为一种权限,当组件继承这个结构体时可以利用is_base_of对其进行识别;
例如:
- struct Visual{virtual visual() =0 } //权限
-
- struct MyComponent : Visual{
- //数据
- void visual() {...impelment...}
- }
-
- ///组件系统
-
- if constexpr(is_base_of_v<Visual,MyComponent>)
- //Do something
-
- ///
当然,应该要有双向性,什么意思呢?权限一般都是要对应的完善的,比如上面的实现一个虚函数,那么就有,如果你实现了虚函数,那么你就一定要继承这个权限类,反过来如果你继承了这个类,那么你就要实现这个虚函数,对于这方面,编译器会帮你,但是有写权限需要实现的是静态函数,那么这个双向性就会被打破,即继承了权限类,可以在运行时不完善对应的权限,完善了对应的权限也可以不继承对应的类,这就很危险,同样解决这个问题我们要用is_base_of;
具体是怎么解决呢?好像是只能实现完善权限,限制必须继承权限类,反过来...以后再研究;
Assistant类[具体是什么,可以看我序列化系统的那篇文章]
这边我是用了宏来修饰继承权限的过程,目的是为了区分普通继承;
权限的继承
一些Assistant类宏
就我的理解来看,是为了实现那些不适合作为组件实现的功能。什么意思呢?一些功能只能适用于特定的实体,比如根据键盘输入来移动的组件,这个的话,就只能适用于摄像头、玩家等等实体;
这种功能呢,是不适合作为脚本的,所以,我们就需要一个将这种功能组件系统相隔离,而且这种功能需要能进入脚本系统,即需要将这种功能抽象为一个唯一的组件,我们称这种功能为脚本,这个组件可以链接不同的脚本,这个组件我们称之为NativeScriptComponent
既然需要抽象,那我们就需要继承了,看结构:
namespace Litchi { class ScriptableCarrier:public Serializable { public: Carrier carrier; ScriptableCarrier() = default; template<typename T> T* GetComponent() { return carrier.GetComponent<T>(); } virtual void OnDestory(){} virtual void OnUpdate(Timestep ts){} virtual void OnCreate(){} virtual void Serializer(YAML::Emitter & out){ LC_CORE_ASSERT(false, "ScriptableCarrier should not to instance"); } static ScriptableCarrier* DeSerializer(YAML::Node& node, Carrier & carrier){ LC_CORE_ASSERT(false, "ScriptableCarrier should not to instance"); return nullptr; } SCRIPTSERIALIZABLE(ScriptableCarrier) COMPONENTINFO(ScriptableCarrier) }; #define Accessor ScriptableCarrier struct NativeScriptComponent :Push(SERIALIZE, ACCESSOR, DEFCOPY) { Scope<ScriptableCarrier> instance = nullptr; std::string scriptName; std::function<void(Carrier carrier)> InstantiateFunction = nullptr;; std::function<void()> DestoryInstanceFunction= nullptr;; std::function<void(ScriptableCarrier*)> OnDestoryFunction = nullptr; std::function<void(ScriptableCarrier*)> OnCreateFunction = nullptr; std::function<void(ScriptableCarrier*, Timestep)> OnUpdateFunction = nullptr;; template<typename T> void Bind() { if (!std::is_base_of_v<ScriptableCarrier, T>) { LC_CORE_ASSERT(false, "Local script must inherit ScriptCarrier!"); return; } //Rely on : instance 实现介绍宏 InstantiateFunction = [&](Carrier carrier) { instance = new T(); scriptName = ((T*)instance)->GetName(); instance->carrier = carrier; this->carrier = carrier; }; //Rely on: nothing DestoryInstanceFunction = [&]() { delete ((T*)instance); instance = nullptr; }; OnDestoryFunction = [](ScriptableCarrier* instance) { ((T*)instance)->OnDestory(); }; OnCreateFunction = [](ScriptableCarrier* instance) { ((T*)instance)->OnCreate(); }; OnUpdateFunction = [](ScriptableCarrier* instance, Timestep ts) { if (instance == nullptr) return; ((T*)instance)->OnUpdate(ts); }; } //绑定序列化出来的脚本,因为不知道脚本的具体类型 void BindSerializeScript(ScriptableCarrier* inst) { instance.reset(inst); OnDestoryFunction = [](ScriptableCarrier* instance) { instance->OnDestory(); }; OnCreateFunction = [](ScriptableCarrier* instance) { instance->OnCreate(); }; OnUpdateFunction = [](ScriptableCarrier* instance, Timestep ts) { if (instance == nullptr) return; instance->OnUpdate(ts); }; } virtual void Serializer(YAML::Emitter & out) override; static void DeSerializer(YAML::Node& node, Carrier & carrier); SERIALIZABLE(NativeScriptComponent) COMPONENTINFO(NativeScriptComponent) DEFAULTCOPY(NativeScriptComponent) }; }
其实很简单,我们只要想实体上加入NativeScriptComponent,然后调用Bind方法绑定脚本,最后就可以调用脚本方法了;
NativeScriptComponent就相当于一个插座,可以向这个插座上加入各种各样的脚本,主要脚本继承了ScriptableCarrier类即可;
而且我们可以发现,这个脚本的类型会被恢复,就没有了虚函数开销,但是序列化后的脚本就会有这个开销,因为在序列化的时候,我们丢失了类型信息,当然我们可以增加额外的操作,来恢复类型信息,但是那肯定是后话了;
这边还有一个缺陷,那就是,脚本组件只支持一个脚本,当然想支持多个脚本也特别简单,只需要加入一个unordered即可,但又要考虑序列化和其他问题,所以暂时只支持一个;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。