Files
EVEngine/docs/usr/modules/editor.md
T

9.3 KiB
Raw Blame History

编辑器构件模块

脚本入口: eve.Editor()

引擎不附带完整 3D 场景编辑器或 2D 地图编辑器,而是提供组装自定义编辑器所需的构件。新的工具协议不在核心中枚举“瓦片笔刷、地形隆起、摆放单位”等工具类型;任何实现 IEditorTool 的代码都能进入同一个会话。

推荐把编辑器拆成五类可替换组件:

协议 职责
IEditorTool 生命周期、输入与手势;原生或 Squirrel 工具一视同仁
IEditableTarget + capability 被编辑对象;工具只查询自己需要的能力
IBrushKernel / IFieldBrushOperation 笔刷形状、衰减和“画什么”分别组合
IEditCommand / IEditConstraint 通用撤销事务与项目美术/玩法限制
IEditorOverlay / IEditorInspector 与渲染器和 UI 框架无关的预览、属性呈现

因此红警 2 / 帝国时代 2 风格的格子地图、魔兽 3 风格的 3D 地图和连续高度场不需要三套会话。它们分别提供目标 capability、工具和视口坐标转换即可。

设计参考了 Three.js TransformControls、Babylon.js GizmoManager、ImGuizmo、Unity GridBrushBase 与 Godot 编辑器插件的常见 API 形态;详见编辑器模块设计。

基本用法

local editor = eve.Editor();

// 3D 操作框
local gizmo = editor.newGizmo();
gizmo.setMode("translate");   // translate | rotate | scale | bound
gizmo.setSpace("world");      // local | world
gizmo.setPosition(0, 1, 0);
gizmo.setSize(1.0);

// 每帧:用相机射线拾取并拖拽
local axis = gizmo.pick(rayOx, rayOy, rayOz, rayDx, rayDy, rayDz);
if (axis != "" && mouseDown) {
    gizmo.beginDrag(axis, rayOx, rayOy, rayOz, rayDx, rayDy, rayDz);
}
if (gizmo.isDragging()) {
    gizmo.updateDrag(rayOx, rayOy, rayOz, rayDx, rayDy, rayDz);
    // 把 gizmo.getPosition* / getRotation* / getScale* 写回场景节点
}
if (mouseUp) gizmo.endDrag();

// 绘制:遍历 getPart* 用调试线 / 网格画出轴、环、平面
for (local i = 0; i < gizmo.getPartCount(); i++) {
    local kind = gizmo.getPartKind(i); // axis | plane | ring | box | center | handle
    // ...
}
// 高度图地形网格(配合 procgen.generateHeightmap / procgen.newHeightmap)。
// Smooth 变体用高度场梯度生成平滑顶点法线,坑/坡连续着色(无平直三角片)。
local mesh = editor.newHeightmapMeshSmooth(hm, 0.5, 3.2);
terrainEnt.setMesh(mesh);
// 原生圆形线性衰减笔刷;strength 为正时升高,为负时降低,返回实际修改的采样数。
local changed = editor.applyHeightmapBrush(hm, cellX, cellY, radius, strength);
// 高度图编辑后原地更新(复用 GPU 缓冲,指针不变)
if (changed > 0)
    editor.updateHeightmapMeshSmooth(mesh, gfx, hm, 0.5, 3.2);

接口式工具会话(C++)

EditorSession session;
TileBufferTarget target("ground", &tiles);
ConstantBrushFalloff hardEdge;
CircleBrushKernel circle(&hardEdge);
PaintIntFieldOperation paintGrass(17);
FieldBrushTool paint("paint-grass", "Paint Grass", &circle, &paintGrass);

session.bindTarget(&target);
session.addTool(&paint);              // 接受任意 IEditorTool
session.activateTool("paint-grass");

EditorPointerEvent down;
down.phase = EditorPointerEvent::Phase::Down;
down.x = tileX;
down.y = tileY;
session.dispatchPointer(down);        // 自动开启一次可撤销 stroke

将 TileBufferTarget 换成 HeightmapTarget、把操作换成 AddScalarFieldOperation,同一个 FieldBrushTool 就成为带衰减的地形升降笔刷。项目也可以实现新的 IEditableTarget capability(对象放置、道路、区域、体素等)以及对应操作;无需修改 EditorSession。

项目限制

实现 IEditConstraint::evaluate() 并注册到 session.constraints()。约束可以允许、给出警告或拒绝任意 IEditCommand,例如锁定水岸坡度、限定可用 tile、吸附建筑朝向、阻止穿过地图边界。所有命令都经 EditorContext::execute() 进入约束和事务,拒绝的命令不会污染撤销栈。

自定义呈现

视口实现 IEditorOverlay,Inspector 实现 IEditorInspector。工具只输出圆、线、矩形、文本和属性意图,所以可同时接入 2.5D 正交视口、3D 透视视口、ImGui 或游戏自己的 UI。

Squirrel 自定义工具

脚本工具也实现相同的 IEditorTool 协议。回调返回位标志:1 表示已处理,2 表示捕获指针,4 表示释放指针。

local editor = eve.Editor();
local session = editor.newSession();
local road = editor.newScriptTool("road", "Road");

road.setActivateCallback(function() { previewRoad(); });
road.setPointerCallback(function(phase, pointerId, button, x, y, dx, dy,
                                 pressure, shift, control, alt) {
    if (phase == 0) { beginRoad(x, y); return 1 | 2; } // Down
    if (phase == 1) { updateRoad(x, y); return 1; }    // Move
    if (phase == 2) { finishRoad(); return 1 | 4; }    // Up
    cancelRoad(); return 1 | 4;
});

session.addTool(road);
session.activateTool("road");
// 视口负责把屏幕/射线坐标变换到工具坐标后转发。
session.dispatchPointer(0, 0, 0, mapX, mapY, 0.0, 0.0, 1.0);

EditorSession 和工具都是非拥有关系;脚本必须像上例一样持有 road,直到从会话移除。

地图笔刷

旧的 Brush / EditorHistory API 为兼容已有脚本保留。新编辑器优先使用上面的协议式会话;旧 API 适合很小的纯 tile 工具。

local buf = editor.newTileBuffer(64, 64);
local brush = editor.newBrush();
brush.setTool("paint");  // paint | erase | fill | line | rect | stamp
brush.setTile(3);
brush.setSize(3);
brush.setShape("circle");
brush.paintAt(buf, tx, ty);

// 同步到 map.TileLayer(示例)
for (local y = 0; y < buf.getHeight(); y++)
    for (local x = 0; x < buf.getWidth(); x++)
        layer.setTile(x, y, buf.getGid(x, y));

编辑器壳

local dock = editor.newDock();
dock.setRegionSize("left", 200);
dock.layout(config.width, config.height);
// 用 dock.getRegionX/Y/W/H("center"|"left"|...) 放置 ui 窗口

local toolbar = editor.newToolbar();
toolbar.addTool("move", "Move");
toolbar.addTool("paint", "Paint");
toolbar.setShortcut("move", "W");

local insp = editor.newInspector();
insp.addChoice("mode", "Mode", "translate,rotate,scale", "translate");
insp.addFloat3("pos", "Position", 0, 0, 0);

对象关系

类型 职责
TransformGizmo 单对象 TRS/包围盒手柄、射线交互、绘制部件
GizmoManager 多 mode 开关 + attach,转发 pick/drag(Babylon 风格)
TileBuffer 与 map 解耦的 GID 网格
Brush 笔刷工具;产出 preview / change 列表
EditorHistory 撤销栈;瓦片分组可 applyLastToBuffer
Toolbar / Inspector / Dock 仅状态与矩形,由 ui 渲染

目标导向指南

给场景节点加平移旋转缩放

创建 GizmoManager,attach() 后按需 setPositionEnabled / setRotationEnabled / setScaleEnabled;把相机 screenToRay 交给 pick / updateDrag,再写回 scene 节点 local TRS。

做简易 tile 地图编辑器

TileBuffer + Brush + EditorHistory:每次 paintAt 后把 getChange* 记入 beginGroup/recordTile/endGroup;Undo 时 applyLastToBuffer。用 EditorDock 划分调色板与视口,用 EditorToolbar 切换 paint/erase/fill。

常见问题

  • 期望引擎弹出完整编辑器窗口——本模块只提供构件,需自行用 ui / graphics 组装。
  • 笔刷直接改 TileLayer——请先画在 TileBuffer,再同步,避免模块硬耦合。
  • getPart* 在 mode/TRS 变化后过期——调用 rebuildParts() 或任意 set*(多数 set 已自动重建)。

API 快查

  • 模块:newSession / newScriptTool / newGizmo / newGizmoManager / newTileBuffer / newBrush / newToolbar / newInspector / newDock / newHistory / applyHeightmapBrush / newHeightmapMesh / updateHeightmapMesh / newHeightmapMeshSmooth / updateHeightmapMeshSmooth
  • 会话:addTool / removeTool / clearTools / activateTool / getActiveToolId / dispatchPointer / hasPointerCapture / update / cancelActiveTool / undo / redo
  • 脚本工具:setShortcut / setActivateCallback / setDeactivateCallback / setPointerCallback / setKeyCallback / setUpdateCallback / setCancelCallback
  • Gizmo:setMode / setSpace / setPosition / setRotationEuler / setScale / setBounds / setSnap* / pick / beginDrag / updateDrag / endDrag / getPart*
  • Manager:set*Enabled / attach / detach / getGizmo / pick / beginDrag / updateDrag
  • Brush:setTool / setSize / setShape / setTile / paintAt / eraseAt / floodFill / paintLine / paintRect / preview* / getChange*
  • History:push / beginGroup / recordTile / endGroup / undo / redo / applyLastToBuffer

使用要点

  • 模块与辅助对象应长期持有;不要每帧 newGizmo()。
  • 枚举一律 string,非法值抛异常。
  • 绘制与输入由宿主完成;本模块不做 GPU 提交。

源码: src/modules/editor/
设计文档: docs/dev/编辑器模块设计.md
相关测试: test/editor.cpp