关卡编辑工具讨论

发布于 2023-10-11  87 次阅读


关卡编辑工具讨论

一个关卡编辑工具框架的相关讨论:尚未完成

国庆妄图拐骗找工作的朋友入伙帮我把很久之前构思的究↗极↘关卡编辑器做出来,目前看来他兴致缺缺,留档先

image

考虑可设计一个所见即所得的关卡工具,通过Odin Inspector框架实现

关卡工具如以下要素组成:

  • Prefab关卡模块:以 Prefab 的形式呈现,所见即所得

    • 如一个路人的模块,可被拖拽至场景中,inspector 面板显示相关配置信息,并可视化相关表现

    • 其所需组件有:

      • Transform:用于标识和持久化位置信息,是被收集的核心操作:

      • Indexer:用于标识该信息点位所在的关卡,以及自动分配 ID 实现索引

        • (被实例化时,会自动调用 OnEnable 函数,可以通过这个连接编辑器)
        • 当编辑器当前已打开关卡文件时,为组件分配 ID,并获取当前 prefab 的信息类型,并将 inspector 上的组件信息储存至关卡编辑器中。
      • InfoComponent:关卡信息组件,用于快捷编辑组件信息

        • 当相关组件信息被更改时(如 transform 组件),将自身改变更新至关卡编辑器:

          • 考虑通过 Update​​​ 监听、CustomEditor​​​ 重载、OnValidate​​​ 事件等形式实现

          • 所有信息组件默认:

            void OnValidate()
            {
                var Indexer = GetComponent<LevelEditorIndexer>()
                Indexer.Refresh()
            }
            
      • Render:渲染器,用于将组件信息可视化

  • InfoComponent信息组件:

    • 关卡信息组件用于储存每个关卡对象运作所需的信息:

      • (从这个角度来说,Transform 也是一种特殊的关卡信息组件

      • 我们会以 Entity-Component System 的形式对该组件的组合进行组织

        • (对这种关卡编辑器的想发最早是从 Unity Dots 框架的设计中来的)
        • 基于这种关卡编辑模式,能有效将美术资源与关卡信息绑定
      • LevelEditorIndexer 是独立的关卡信息组件,用于管理细化的关卡信息

        • 以 Unity ECS 为例,Indexer 定义了其下管理组件的 ArcheType 原型
    • 关卡信息组件通常是具体的Component,仅包含数据,即对ArcheType下模块信息的显示

    • 可能有两个方向:

      • 对于ArcheType下每个信息Component,均创建InfoComponent
      • 对ArcheType内的所有信息进行序列化展示
      • (我不好评估哪种更合适
  • ArcheType原型

    • 是关卡编辑器中储存的核心数据,整合多个Component

      • 比方说对于一个球,可能包含如下信息Component:

        • Transform
        • Radius
        • Color ...
    • 大致以如下形式构建:

      public class ArcheType
      {
          private ArcheTypeConfig _config;
          private Dictionary<ModuleType, IInfoModule> _modules = new Dictionary<ModuleType, IInfoModule>();
      
          public ArcheType(ArcheTypeConfig config)
          {
              _config = config;
          }
      
          public void AddModule(ModuleType moduleType, IInfoModule module)
          {
              if (_config.AllowedModules.Contains(moduleType))
              {
                  _modules[moduleType] = module;
              }
              else
              {
                  throw new InvalidOperationException($"Invalid module type for {_config.ArcheTypeName}: {moduleType}");
              }
          }
      
          // ... 其他方法 ...
      }
      
      // 由配置表导出多种ArcheTypeConfig配置文件
      public class ArcheTypeConfig
      {
          public string ArcheTypeName { get; set; }
          public List<ModuleType> AllowedModules { get; set; } = new List<ModuleType>();
      }
      
      
    • 在ArcheType上依据选定原型添加对于Modules储存数据,并满足渲染和储存需要

  • Render绘制组件:

    • 绘制组件和Indexer联动,基于ArcheType自动加载渲染模块,用于在场景中可视化相关关卡模块(避免了直接使用MeshRender不方便编辑

      using System;
      using UnityEngine;
      
      public class ArcheTypeRendererFactory
      {
          // 传入ArcheType的类型和GameObject,返回相关的渲染器。
          // 如果没有找到对应的渲染器,返回默认的渲染器。
          public static MonoBehaviour CreateRendererForArcheType(ArcheType archeType, GameObject gameObject)
          {
              string archeTypeName = archeType.GetType().Name;
              string rendererTypeName = archeTypeName + "Renderer";
      
              // 使用类型名称动态查找Renderer类型
              Type rendererType = Type.GetType(rendererTypeName);
              if (rendererType != null)
              {
                  // 找到了对应的Renderer类型
                  return (MonoBehaviour)gameObject.AddComponent(rendererType);
              }
              else
              {
                  // 没有找则添加默认的Renderer
                  return gameObject.AddComponent<DefaultArcheTypeRenderer>();
              }
          }
      }
      
    • 比方说如果是对路人的渲染器,可能直接在逻辑里写了路人的渲染样式,随机挑选路人实例化。

    • 此外,还会按需绘制部分基础信息,如点位名称ID等到地图中

    • 关卡编辑器编辑时对模块的隐藏也是通过关闭绘制渲染。

  • 关卡编辑器(TBD):用于收集全部组件的信息,并完成序列化

    • 用于过滤和筛选模组,并赋予相关ID

      • (比方说对一类ArcheType可能直接赋予特定ID段
    • 通过数据库管理和过滤(提前准备后续项目

    • 整合显示ArcheType上的GUI信息

    • 相关批处理功能接口

    • 批量创建等快捷方式

@startuml
!define RECT class

RECT LevelEditor {
+Refresh()
+GetIndex()
+Init()
-database: Database
}

RECT Database {
+Store(archeType: ArcheType)
+Retrieve(id: ID): ArcheType
}

RECT Prefab {
}

RECT ArcheType {
-_config: ArcheTypeConfig
-_modules: Dictionary<ModuleType, IInfoModule>
+AddModule(moduleType: ModuleType, module: IInfoModule)
}

RECT ArcheTypeConfig {
-ArcheTypeName: string
-AllowedModules: List<ModuleType>
}

RECT Indexer {
+OnEnable()
+Refresh()
+levelId: ID
+PointId: ID
}

RECT InfoComponent {
}

RECT Render {
}

ArcheTypeConfig "1" -- "1" ArcheType : contains
ArcheType "1" o-- "*" Indexer : managed by
ArcheType "1" o-- "*" InfoComponent : contains
ArcheType "1" o-- "*" Render : uses
LevelEditor o-- "*" Indexer : uses
LevelEditor o-- "*" InfoComponent : uses
LevelEditor o-- "*" Render : uses
LevelEditor -- Database : uses
Prefab o-- Indexer : contains
Prefab o-- InfoComponent : contains
Prefab o-- Render : contains
@enduml


百无一用是书生