当前位置:   article > 正文

游戏引擎开发总结:权限系统、本地脚本系统_游戏引擎 脚本系统实现

游戏引擎 脚本系统实现
权限系统

权限系统是我编出来的词,具体的作用呢我定义为:赋予组件权限,并在类中完善对应的权限,当向组件系统加入组件时,组件系统会识别组件的权限,并进行不同的操作;

为什么要有权限系统呢[Why and What]?

很简单,当将一个组件加入到ecs系统的时候,我们不免需要对一些组件进行不同的额外操作,比如那些组件应该显示在HierarchyPanel上,那些一个被序列化,那些支持运行时拷贝...etc,权限系统让组件系统对不同组件进行不同的组件所需要的额外操作

这写操作有但不限于:赋值、分类...etc

怎么实现[How]?

那具体是怎么实现呢?

其核心是标准库在的is_base_of模板,这个模板为权限识别提供了可能;

我们可以用一个结构体作为一种权限,当组件继承这个结构体时可以利用is_base_of对其进行识别;

例如:

  1. struct Visual{virtual visual() =0 } //权限
  2. struct MyComponent : Visual{
  3. //数据
  4.     void visual() {...impelment...}
  5. }
  6. ///组件系统
  7.     if constexpr(is_base_of_v<Visual,MyComponent>)
  8.         //Do something
  9. ///

当然,应该要有双向性,什么意思呢?权限一般都是要对应的完善的,比如上面的实现一个虚函数,那么就有,如果你实现了虚函数,那么你就一定要继承这个权限类,反过来如果你继承了这个类,那么你就要实现这个虚函数,对于这方面,编译器会帮你,但是有写权限需要实现的是静态函数,那么这个双向性就会被打破,即继承了权限类,可以在运行时不完善对应的权限,完善了对应的权限也可以不继承对应的类,这就很危险,同样解决这个问题我们要用is_base_of;

具体是怎么解决呢?好像是只能实现完善权限,限制必须继承权限类,反过来...以后再研究;

Assistant类[具体是什么,可以看我序列化系统的那篇文章]

这边我是用了宏来修饰继承权限的过程,目的是为了区分普通继承;

权限的继承

一些Assistant类宏

本地脚本系统
为什么要有本地脚本系统[Why]?

就我的理解来看,是为了实现那些不适合作为组件实现的功能。什么意思呢?一些功能只能适用于特定的实体,比如根据键盘输入来移动的组件,这个的话,就只能适用于摄像头、玩家等等实体;

什么是本地脚本系统[What]?

这种功能呢,是不适合作为脚本的,所以,我们就需要一个将这种功能组件系统相隔离,而且这种功能需要能进入脚本系统,即需要将这种功能抽象为一个唯一的组件,我们称这种功能为脚本,这个组件可以链接不同的脚本,这个组件我们称之为NativeScriptComponent

怎么实现本地脚本系统[How]?

既然需要抽象,那我们就需要继承了,看结构:

  1. namespace Litchi {
  2. class ScriptableCarrier:public Serializable {
  3. public:
  4. Carrier carrier;
  5. ScriptableCarrier() = default;
  6. template<typename T>
  7. T* GetComponent() {
  8. return carrier.GetComponent<T>();
  9. }
  10. virtual void OnDestory(){}
  11. virtual void OnUpdate(Timestep ts){}
  12. virtual void OnCreate(){}
  13. virtual void Serializer(YAML::Emitter & out){
  14. LC_CORE_ASSERT(false, "ScriptableCarrier should not to instance");
  15. }
  16. static ScriptableCarrier* DeSerializer(YAML::Node& node, Carrier & carrier){
  17. LC_CORE_ASSERT(false, "ScriptableCarrier should not to instance");
  18. return nullptr;
  19. }
  20. SCRIPTSERIALIZABLE(ScriptableCarrier)
  21. COMPONENTINFO(ScriptableCarrier)
  22. };
  23. #define Accessor ScriptableCarrier
  24. struct NativeScriptComponent :Push(SERIALIZE, ACCESSOR, DEFCOPY) {
  25. Scope<ScriptableCarrier> instance = nullptr;
  26. std::string scriptName;
  27. std::function<void(Carrier carrier)> InstantiateFunction = nullptr;;
  28. std::function<void()> DestoryInstanceFunction= nullptr;;
  29. std::function<void(ScriptableCarrier*)> OnDestoryFunction = nullptr;
  30. std::function<void(ScriptableCarrier*)> OnCreateFunction = nullptr;
  31. std::function<void(ScriptableCarrier*, Timestep)> OnUpdateFunction = nullptr;;
  32. template<typename T>
  33. void Bind() {
  34. if (!std::is_base_of_v<ScriptableCarrier, T>) {
  35. LC_CORE_ASSERT(false, "Local script must inherit ScriptCarrier!");
  36. return;
  37. }
  38. //Rely on : instance 实现介绍宏
  39. InstantiateFunction = [&](Carrier carrier) {
  40. instance = new T();
  41. scriptName = ((T*)instance)->GetName();
  42. instance->carrier = carrier;
  43. this->carrier = carrier;
  44. };
  45. //Rely on: nothing
  46. DestoryInstanceFunction = [&]() {
  47. delete ((T*)instance);
  48. instance = nullptr;
  49. };
  50. OnDestoryFunction = [](ScriptableCarrier* instance) {
  51. ((T*)instance)->OnDestory();
  52. };
  53. OnCreateFunction = [](ScriptableCarrier* instance) {
  54. ((T*)instance)->OnCreate();
  55. };
  56. OnUpdateFunction = [](ScriptableCarrier* instance, Timestep ts) {
  57. if (instance == nullptr)
  58. return;
  59. ((T*)instance)->OnUpdate(ts);
  60. };
  61. }
  62. //绑定序列化出来的脚本,因为不知道脚本的具体类型
  63. void BindSerializeScript(ScriptableCarrier* inst) {
  64. instance.reset(inst);
  65. OnDestoryFunction = [](ScriptableCarrier* instance) {
  66. instance->OnDestory();
  67. };
  68. OnCreateFunction = [](ScriptableCarrier* instance) {
  69. instance->OnCreate();
  70. };
  71. OnUpdateFunction = [](ScriptableCarrier* instance, Timestep ts) {
  72. if (instance == nullptr)
  73. return;
  74. instance->OnUpdate(ts);
  75. };
  76. }
  77. virtual void Serializer(YAML::Emitter & out) override;
  78. static void DeSerializer(YAML::Node& node, Carrier & carrier);
  79. SERIALIZABLE(NativeScriptComponent)
  80. COMPONENTINFO(NativeScriptComponent)
  81. DEFAULTCOPY(NativeScriptComponent)
  82. };
  83. }

其实很简单,我们只要想实体上加入NativeScriptComponent,然后调用Bind方法绑定脚本,最后就可以调用脚本方法了;

NativeScriptComponent就相当于一个插座,可以向这个插座上加入各种各样的脚本,主要脚本继承了ScriptableCarrier类即可;

而且我们可以发现,这个脚本的类型会被恢复,就没有了虚函数开销,但是序列化后的脚本就会有这个开销,因为在序列化的时候,我们丢失了类型信息,当然我们可以增加额外的操作,来恢复类型信息,但是那肯定是后话了;

这边还有一个缺陷,那就是,脚本组件只支持一个脚本,当然想支持多个脚本也特别简单,只需要加入一个unordered即可,但又要考虑序列化和其他问题,所以暂时只支持一个;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/136016
推荐阅读
相关标签
  

闽ICP备14008679号