diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..ee8d72a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,18 @@
+# Changelog
+
+# 1.0.1 / 2023-09-26
+
+**调整**
+
+- 行为树编辑器面板默认在`Cocos`主编辑器内打开(场景 Panel 旁边),更方便用户操作。
+- 切换`层级管理器`节点时,会自动切换为当前节点的行为树,如果当前节点没有行为树,则显示 mask 提示用户`添加组件`并`创建json`。
+- 修改`BehaviorEditor`组件按钮文案为`Create / Editor`。
+- 在已经打开行为树编辑器的情况下,再次点击`BehaviorEditor`组件的`Create / Editor`,可以起到重启插件的效果,在更新了外部资源(JSON,组件)。
+- 画布大小从`2400 1600`下降为` 2000 1400`。
+- 禁止设置非组合节点`Composite`的`Abort Type`属性。
+- 左下角增加`Root`按钮回到根节点处
+- 样式修改
+
+ # 1.0.0 / 2023-09-21
+
+ **首次发布**
diff --git a/dist/contributions/inspector/behavior-editor.js b/dist/contributions/inspector/behavior-editor.js
index 7739feb..eb2e1e4 100644
--- a/dist/contributions/inspector/behavior-editor.js
+++ b/dist/contributions/inspector/behavior-editor.js
@@ -3,8 +3,8 @@
Object.defineProperty(exports, "__esModule", { value: true });
exports.ready = exports.update = exports.$ = exports.template = void 0;
exports.template = `
-
-
Edit Tree
+
+ Create / Edit
`;
@@ -64,15 +64,17 @@ function ready() {
});
if (success) {
console.log(`JSON文件挂载成功`);
- Editor.Message.request("behavior-eden", "open-panel");
}
else {
console.warn("JSON文件挂载失败");
+ return;
}
}
- else {
- // 打开插件面板
- Editor.Message.request("behavior-eden", "open-panel");
+ // 打开插件面板
+ const success = await Editor.Message.request("behavior-eden", "open-panel");
+ // success为false,可能是已经打开了,通知面板刷新
+ if (!success) {
+ await Editor.Message.request("behavior-eden", "refresh-panel");
}
});
}
diff --git a/dist/main.js b/dist/main.js
index 05c2c41..240bc72 100644
--- a/dist/main.js
+++ b/dist/main.js
@@ -11,8 +11,13 @@ const package_json_1 = __importDefault(require("../package.json"));
* @zh 为扩展的主进程的注册方法
*/
exports.methods = {
- openPanel() {
- Editor.Panel.open(package_json_1.default.name);
+ async openPanel() {
+ return await Editor.Panel.openBeside("scene", package_json_1.default.name);
+ },
+ async refreshPanel() {
+ await Editor.Panel.close(package_json_1.default.name);
+ await new Promise((rs) => setTimeout(rs, 300));
+ await Editor.Panel.openBeside("scene", package_json_1.default.name);
},
};
/**
diff --git a/dist/panels/default/index.js b/dist/panels/default/index.js
index 5f7ba67..faac742 100644
--- a/dist/panels/default/index.js
+++ b/dist/panels/default/index.js
@@ -10,8 +10,8 @@ const node_1 = require("../../runtime/node");
const decorator_1 = require("../../runtime/core/decorator");
const utils_1 = require("../../runtime/core/utils");
// 画布宽高
-const CANVAS_WIDTH = 2400;
-const CANVAS_HEIGHT = 1600;
+const CANVAS_WIDTH = 2000;
+const CANVAS_HEIGHT = 1400;
// 节点box属性
const BOX_WIDTH = 100;
// const BOX_HEIGHT = 64; //height通过getBoxHeight方法动态计算
@@ -141,6 +141,21 @@ const ParentNodes = [...decorator_1.nodeClsMap.values()]
return false;
})
.map((cls) => cls.name);
+/***
+ * 获取所有Composite类型的节点
+ */
+const CompositeNodes = [...decorator_1.nodeClsMap.values()]
+ .filter((cls) => {
+ let parentCls = getParentCls(cls);
+ while (parentCls) {
+ if (parentCls.name === node_1.Composite.name) {
+ return true;
+ }
+ parentCls = getParentCls(parentCls);
+ }
+ return false;
+})
+ .map((cls) => cls.name);
const activeCursor = () => {
stage && (stage.container().style.cursor = "pointer");
};
@@ -151,7 +166,7 @@ const component = lib_1.Vue.extend({
template: lib_1.fs.readFileSync(path_1.default.join(__dirname, "../../../src/panels/static/template/vue/app.html"), "utf-8"),
$: {
tree: "#tree",
- left: "#left",
+ scroll: "#scroll",
},
filters: {
toUpperCase(value) {
@@ -189,6 +204,7 @@ const component = lib_1.Vue.extend({
*/
nodeCompMethodInfo: {},
logs: [],
+ maskText: "",
};
},
computed: {
@@ -253,12 +269,143 @@ const component = lib_1.Vue.extend({
},
},
async mounted() {
- this.initCanvas();
- await this.getAssets();
- await this.selectCurrentAsset();
- this.$refs.left.scroll((CANVAS_WIDTH - this.$refs.left.getBoundingClientRect().width) / 2, ROOT_Y - 50);
+ this.init();
},
methods: {
+ /***
+ * 初始化相关
+ */
+ async init() {
+ // 初始化画布
+ this.initCanvas();
+ // 根据当前选中节点初始化行为树
+ await this.initSelection();
+ this.backRoot();
+ },
+ initCanvas() {
+ // 初始化舞台
+ stage = new lib_1.Konva.Stage({
+ container: this.$refs.tree,
+ width: CANVAS_WIDTH,
+ height: CANVAS_HEIGHT,
+ });
+ // 基础层,目前只有一层,每层都会生成一个canvas
+ baseLayer = new lib_1.Konva.Layer();
+ // 背景组
+ bgGroup = this.generateBg();
+ baseLayer.add(bgGroup);
+ // 静态箭头层
+ staticArrowGroup = new lib_1.Konva.Group();
+ baseLayer.add(staticArrowGroup);
+ // 动态箭头层(用户手动拉出来的箭头)
+ dynamicArrowGroup = this.generateDynamicArrowGroup();
+ baseLayer.add(dynamicArrowGroup);
+ // Root组
+ rootGroup = this.generateRoot();
+ baseLayer.add(rootGroup);
+ // 节点层
+ nodeGroup = new lib_1.Konva.Group();
+ baseLayer.add(nodeGroup);
+ // 框选层
+ selectionGroup = this.generatesSelectionGroup();
+ baseLayer.add(selectionGroup);
+ // 层添加到舞台
+ stage.add(baseLayer);
+ },
+ // 获取json文件
+ async initAssets() {
+ const behaviorTreeComponentUuids = [];
+ const dfs = (node) => {
+ if (!node) {
+ return;
+ }
+ for (const comp of node.components) {
+ if (comp.type === BTreeCompName) {
+ behaviorTreeComponentUuids.push(comp.value);
+ break;
+ }
+ }
+ for (const item of node.children) {
+ dfs(item);
+ }
+ };
+ // 获取场景节点树
+ const sceneNode = await Editor.Message.request("scene", "query-node-tree");
+ // 收集场景上所有BehaviorTree组件uuid
+ dfs(sceneNode);
+ // 收集BehaviorTree组件上的json路径
+ const rawUrls = await Promise.all(behaviorTreeComponentUuids.map((uuid) => Editor.Message.request("scene", "execute-component-method", {
+ uuid: uuid,
+ name: "getAssetUrl",
+ })));
+ // 过滤空的并去重
+ const urls = [...new Set(rawUrls.filter(Boolean))];
+ // 根据url获取所有json文件信息
+ const assets = await Promise.all(urls.map((url) => Editor.Message.request("asset-db", "query-asset-info", url)));
+ this.assets = assets.map(({ name, source, file }) => ({ name, url: source, file: file, content: "" }));
+ },
+ async initSelection() {
+ // 找到当前选中的节点
+ const node = await Editor.Message.request("scene", "query-node", Editor.Selection.getSelected("node"));
+ // 未选中节点或者选中的是场景节点(场景节点不能添加组件)
+ if (!(node === null || node === void 0 ? void 0 : node.__comps__)) {
+ this.maskText = "请选中一个非场景根节点非空名称节点来开始行为树制作";
+ return;
+ }
+ // 找到BehaviorTree组件
+ const index = node.__comps__.findIndex((v) => v.type === BTreeCompName);
+ if (index === -1) {
+ this.maskText = "要制作行为树,需要先为当前节点添加行为树组件";
+ return;
+ }
+ const comp = node.__comps__[index];
+ // 调用BehaviorTree组件上的方法
+ const url = await Editor.Message.request("scene", "execute-component-method", {
+ uuid: comp.value.uuid.value,
+ name: "getAssetUrl",
+ });
+ // JSON文件不存在
+ if (!url) {
+ this.maskText = "当前行为树组件缺少JSON资源,点击BehaviorEditor组件按钮即可创建";
+ return;
+ }
+ this.maskText = "";
+ // 选中此文件,设置好currentAsset才能把组件nodes数据同步到JSON
+ this.handleSelectAsset(url);
+ },
+ async handleSelectAsset(url) {
+ var _a;
+ // 实时获取当前场景所有behaviorTree组件上的json文件
+ await this.initAssets();
+ const json = this.assets.find((e) => e.url === url);
+ if (!json) {
+ this.showWarn("JSON文件不存在");
+ return;
+ }
+ if (((_a = this.currentAsset) === null || _a === void 0 ? void 0 : _a.url) === (json === null || json === void 0 ? void 0 : json.url)) {
+ return;
+ }
+ this.currentAsset = json;
+ try {
+ const content = await lib_1.fs.readJSONSync(json.file);
+ this.currentAsset.content = content;
+ this.render();
+ }
+ catch (e) {
+ if (e instanceof SyntaxError) {
+ // JSON文件语法异常的情况下,初始化内容
+ this.showWarn("JSON文件内容初始化成功");
+ const content = { nodes: [] };
+ await lib_1.fs.writeJSONSync(json.file, content);
+ this.currentAsset.content = content;
+ this.render();
+ }
+ else {
+ // 输出其他异常
+ this.showWarn(e);
+ }
+ }
+ },
handlePanelChange(value) {
this.currentPanel = value;
},
@@ -275,7 +422,7 @@ const component = lib_1.Vue.extend({
this.render();
}
},
- async handleEventNodeChange(lifeCycle, uuid = "") {
+ async handleEventNodeChange(lifeCycle, uuid = "", shouldSave = true) {
if (!this.currentNode) {
this.showWarn("当前节点不存在");
return;
@@ -287,9 +434,13 @@ const component = lib_1.Vue.extend({
else {
this.currentNode.event[lifeCycle].comp = "";
this.currentNode.event[lifeCycle].method = "";
- this.currentNode.event[lifeCycle].data = "";
+ // 事件参数就不手动清空了
+ // this.currentNode.event[lifeCycle].data = "";
+ }
+ // 点击canvas某个节点的时候,会触发handleEventNodeChange获取场景节点数据,此时不保存数据
+ if (shouldSave) {
+ await this.saveAsset();
}
- await this.saveAsset();
},
async getNodeCompMethods(lifeCycle, uuid) {
const [nodeInfo, compMethodInfo] = await Promise.all([
@@ -331,132 +482,16 @@ const component = lib_1.Vue.extend({
this.currentNode.event[lifeCycle].data = data;
await this.saveAsset();
},
- async selectCurrentAsset() {
- // 找到点击组件的节点
- const node = await Editor.Message.request("scene", "query-node", Editor.Selection.getSelected("node"));
- if (!node) {
- this.showWarn(`未选中节点`);
- return;
- }
- // 找到BehaviorTree组件
- const index = node.__comps__.findIndex((v) => v.type === BTreeCompName);
- if (index === -1) {
- this.showWarn(`节点未挂载【${BTreeCompName}】组件`);
- return;
- }
- const comp = node.__comps__[index];
- // 调用BehaviorTree组件上的方法
- const url = await Editor.Message.request("scene", "execute-component-method", {
- uuid: comp.value.uuid.value,
- name: "getAssetUrl",
- });
- // JSON文件不存在
- if (!url) {
- this.showWarn(`【${BTreeCompName}】组件未指定JSON文件`);
- return;
- }
- // 选中此文件,设置好currentAsset才能把组件nodes数据同步到JSON
- this.handleSelectAsset(url);
- },
- async handleSelectAsset(url) {
- const json = this.assets.find((e) => e.url === url);
- if (!json) {
- this.showWarn("JSON文件不存在");
- return;
- }
- this.currentAsset = json;
- try {
- const content = await lib_1.fs.readJSONSync(json.file);
- this.currentAsset.content = content;
- this.render();
- }
- catch (e) {
- if (e instanceof SyntaxError) {
- // JSON文件语法异常的情况下,初始化内容
- this.showWarn("JSON文件内容初始化成功");
- const content = { nodes: [] };
- await lib_1.fs.writeJSONSync(json.file, content);
- this.currentAsset.content = content;
- this.render();
- }
- else {
- // 输出其他异常
- this.showWarn(e);
- }
- }
- },
- // 全部所有behaviorTree组件上使用到的json文件
- async getAssets() {
- const behaviorTreeComponentUuids = [];
- const dfs = (node) => {
- if (!node) {
- return;
- }
- for (const comp of node.components) {
- if (comp.type === BTreeCompName) {
- behaviorTreeComponentUuids.push(comp.value);
- break;
- }
- }
- for (const item of node.children) {
- dfs(item);
- }
- };
- // 获取场景节点树
- const sceneNode = await Editor.Message.request("scene", "query-node-tree");
- // 收集场景上所有BehaviorTree组件uuid
- dfs(sceneNode);
- // 收集BehaviorTree组件上的json路径
- const rawUrls = await Promise.all(behaviorTreeComponentUuids.map((uuid) => Editor.Message.request("scene", "execute-component-method", {
- uuid: uuid,
- name: "getAssetUrl",
- })));
- // 过滤空的并去重
- const urls = [...new Set(rawUrls.filter(Boolean))];
- // 根据url获取所有json文件信息
- const assets = await Promise.all(urls.map((url) => Editor.Message.request("asset-db", "query-asset-info", url)));
- this.assets = assets.map(({ name, source, file }) => ({ name, url: source, file: file, content: "" }));
- },
async saveAsset() {
var _a;
- const url = (_a = this.currentAsset) === null || _a === void 0 ? void 0 : _a.url;
- if (!url) {
+ const file = (_a = this.currentAsset) === null || _a === void 0 ? void 0 : _a.file;
+ if (!file) {
this.showWarn("数据存储失败,未指定JSON");
return;
}
- const content = JSON.stringify(this.currentAsset.content, null, 2);
+ const content = this.currentAsset.content;
// 修改json文件
- await Editor.Message.request("asset-db", "save-asset", url, content);
- },
- initCanvas() {
- // 初始化舞台
- stage = new lib_1.Konva.Stage({
- container: this.$refs.tree,
- width: CANVAS_WIDTH,
- height: CANVAS_HEIGHT,
- });
- // 基础层,目前只有一层,每层都会生成一个canvas
- baseLayer = new lib_1.Konva.Layer();
- // 背景组
- bgGroup = this.generateBg();
- baseLayer.add(bgGroup);
- // 静态箭头层
- staticArrowGroup = new lib_1.Konva.Group();
- baseLayer.add(staticArrowGroup);
- // 动态箭头层(用户手动拉出来的箭头)
- dynamicArrowGroup = this.generateDynamicArrowGroup();
- baseLayer.add(dynamicArrowGroup);
- // Root组
- rootGroup = this.generateRoot();
- baseLayer.add(rootGroup);
- // 节点层
- nodeGroup = new lib_1.Konva.Group();
- baseLayer.add(nodeGroup);
- // 框选层
- selectionGroup = this.generatesSelectionGroup();
- baseLayer.add(selectionGroup);
- // 层添加到舞台
- stage.add(baseLayer);
+ lib_1.fs.writeJSONSync(file, content);
},
/***
* 渲染画布
@@ -568,7 +603,7 @@ const component = lib_1.Vue.extend({
type,
abortType: this.AbortType.None,
x: (CANVAS_WIDTH - BOX_WIDTH) / 2,
- y: CANVAS_HEIGHT / 4,
+ y: ROOT_Y + 100,
isRoot: false,
children: [],
event: {
@@ -828,7 +863,7 @@ const component = lib_1.Vue.extend({
if (e.evt.buttons === 4) {
const movementX = e.evt.movementX;
const movementY = e.evt.movementY;
- this.$refs.left.scrollBy(-movementX, -movementY);
+ this.$refs.scroll.scrollBy(-movementX, -movementY);
return;
}
if (!selection.visible()) {
@@ -981,11 +1016,11 @@ const component = lib_1.Vue.extend({
selectedBoxes = [];
this.currentNode = node;
this.handlePanelChange(1);
- this.render();
// 获取节点事件相关信息
- this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node);
- this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node);
- this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node);
+ this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node, false);
+ this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node, false);
+ this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node, false);
+ this.render();
});
return wrapper;
},
@@ -1247,6 +1282,12 @@ const component = lib_1.Vue.extend({
}
return BOX_HEIGHT;
},
+ isComposite(node) {
+ if (!node) {
+ return false;
+ }
+ return CompositeNodes.includes(node.type);
+ },
showWarn(text) {
let time = getTime();
const content = `${time}:${text}`;
@@ -1256,6 +1297,11 @@ const component = lib_1.Vue.extend({
this.logs.push({ id: uuid(), content });
console.warn(content);
},
+ // 视角回到root节点
+ backRoot() {
+ var _a, _b;
+ (_b = (_a = this.$refs) === null || _a === void 0 ? void 0 : _a.scroll) === null || _b === void 0 ? void 0 : _b.scroll((CANVAS_WIDTH - this.$refs.scroll.getBoundingClientRect().width) / 2, ROOT_Y - 50);
+ },
},
});
const panelDataMap = new WeakMap();
@@ -1281,7 +1327,14 @@ module.exports = Editor.Panel.define({
data() {
return {};
},
- methods: {},
+ methods: {
+ initSelection() {
+ const vm = panelDataMap.get(this);
+ if (vm) {
+ vm.initSelection();
+ }
+ },
+ },
ready() {
if (this.$.app) {
const vm = new component();
diff --git a/dist/runtime/core/Blackboard.js b/dist/runtime/core/Blackboard.js
index 3cf8364..f4b72c7 100644
--- a/dist/runtime/core/Blackboard.js
+++ b/dist/runtime/core/Blackboard.js
@@ -22,5 +22,5 @@ class Blackboard {
this.map.clear();
}
}
-exports.Blackboard = Blackboard;
Blackboard.map = new Map();
+exports.Blackboard = Blackboard;
diff --git a/package.json b/package.json
index 3c1bec3..8c775b8 100644
--- a/package.json
+++ b/package.json
@@ -37,9 +37,14 @@
"openPanel"
]
},
- "send-to-panel": {
+ "refresh-panel": {
"methods": [
- "default.hello"
+ "refreshPanel"
+ ]
+ },
+ "selection:select": {
+ "methods": [
+ "default.initSelection"
]
}
},
diff --git a/src/contributions/inspector/behavior-editor.ts b/src/contributions/inspector/behavior-editor.ts
index 6d6b463..cf4066c 100644
--- a/src/contributions/inspector/behavior-editor.ts
+++ b/src/contributions/inspector/behavior-editor.ts
@@ -3,8 +3,8 @@
type Selector<$> = { $: Record };
export const template = `
-
-
Edit Tree
+
+ Create / Edit
`;
@@ -69,13 +69,16 @@ export function ready(this: Selector & any) {
});
if (success) {
console.log(`JSON文件挂载成功`);
- Editor.Message.request("behavior-eden", "open-panel");
} else {
console.warn("JSON文件挂载失败");
+ return;
}
- } else {
- // 打开插件面板
- Editor.Message.request("behavior-eden", "open-panel");
+ }
+ // 打开插件面板
+ const success = await Editor.Message.request("behavior-eden", "open-panel");
+ // success为false,可能是已经打开了,通知面板刷新
+ if (!success) {
+ await Editor.Message.request("behavior-eden", "refresh-panel");
}
});
}
diff --git a/src/main.ts b/src/main.ts
index 4821c49..f736cef 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -6,8 +6,13 @@ import packageJSON from "../package.json";
* @zh 为扩展的主进程的注册方法
*/
export const methods: { [key: string]: (...any: any) => any } = {
- openPanel() {
- Editor.Panel.open(packageJSON.name);
+ async openPanel() {
+ return await Editor.Panel.openBeside("scene", packageJSON.name);
+ },
+ async refreshPanel() {
+ await Editor.Panel.close(packageJSON.name);
+ await new Promise((rs) => setTimeout(rs, 300));
+ await Editor.Panel.openBeside("scene", packageJSON.name);
},
};
diff --git a/src/panels/default/index.ts b/src/panels/default/index.ts
index fd07529..cebe2a9 100644
--- a/src/panels/default/index.ts
+++ b/src/panels/default/index.ts
@@ -6,8 +6,8 @@ import { nodeClsMap } from "../../runtime/core/decorator";
import { buildTree, preOrder } from "../../runtime/core/utils";
// 画布宽高
-const CANVAS_WIDTH = 2400;
-const CANVAS_HEIGHT = 1600;
+const CANVAS_WIDTH = 2000;
+const CANVAS_HEIGHT = 1400;
// 节点box属性
const BOX_WIDTH = 100;
// const BOX_HEIGHT = 64; //height通过getBoxHeight方法动态计算
@@ -149,6 +149,24 @@ const ParentNodes = [...nodeClsMap.values()]
})
.map((cls) => cls.name);
+/***
+ * 获取所有Composite类型的节点
+ */
+const CompositeNodes = [...nodeClsMap.values()]
+ .filter((cls) => {
+ let parentCls = getParentCls(cls);
+ while (parentCls) {
+ if (parentCls.name === Composite.name) {
+ return true;
+ }
+
+ parentCls = getParentCls(parentCls);
+ }
+
+ return false;
+ })
+ .map((cls) => cls.name);
+
const activeCursor = () => {
stage && (stage.container().style.cursor = "pointer");
};
@@ -160,7 +178,7 @@ const component = Vue.extend({
template: fs.readFileSync(path.join(__dirname, "../../../src/panels/static/template/vue/app.html"), "utf-8"),
$: {
tree: "#tree",
- left: "#left",
+ scroll: "#scroll",
},
filters: {
toUpperCase(value) {
@@ -199,6 +217,7 @@ const component = Vue.extend({
*/
nodeCompMethodInfo: {},
logs: [],
+ maskText: "",
};
},
computed: {
@@ -267,12 +286,165 @@ const component = Vue.extend({
},
},
async mounted() {
- this.initCanvas();
- await this.getAssets();
- await this.selectCurrentAsset();
- this.$refs.left.scroll((CANVAS_WIDTH - this.$refs.left.getBoundingClientRect().width) / 2, ROOT_Y - 50);
+ this.init();
},
methods: {
+ /***
+ * 初始化相关
+ */
+ async init() {
+ // 初始化画布
+ this.initCanvas();
+ // 根据当前选中节点初始化行为树
+ await this.initSelection();
+ this.backRoot();
+ },
+ initCanvas() {
+ // 初始化舞台
+ stage = new Konva.Stage({
+ container: this.$refs.tree,
+ width: CANVAS_WIDTH,
+ height: CANVAS_HEIGHT,
+ });
+ // 基础层,目前只有一层,每层都会生成一个canvas
+ baseLayer = new Konva.Layer();
+
+ // 背景组
+ bgGroup = this.generateBg();
+ baseLayer.add(bgGroup);
+
+ // 静态箭头层
+ staticArrowGroup = new Konva.Group();
+ baseLayer.add(staticArrowGroup);
+
+ // 动态箭头层(用户手动拉出来的箭头)
+ dynamicArrowGroup = this.generateDynamicArrowGroup();
+ baseLayer.add(dynamicArrowGroup);
+
+ // Root组
+ rootGroup = this.generateRoot();
+ baseLayer.add(rootGroup);
+
+ // 节点层
+ nodeGroup = new Konva.Group();
+ baseLayer.add(nodeGroup);
+
+ // 框选层
+ selectionGroup = this.generatesSelectionGroup();
+ baseLayer.add(selectionGroup);
+
+ // 层添加到舞台
+ stage.add(baseLayer);
+ },
+ // 获取json文件
+ async initAssets() {
+ const behaviorTreeComponentUuids = [];
+ const dfs = (node) => {
+ if (!node) {
+ return;
+ }
+
+ for (const comp of node.components) {
+ if (comp.type === BTreeCompName) {
+ behaviorTreeComponentUuids.push(comp.value);
+ break;
+ }
+ }
+ for (const item of node.children) {
+ dfs(item);
+ }
+ };
+ // 获取场景节点树
+ const sceneNode = await Editor.Message.request("scene", "query-node-tree");
+
+ // 收集场景上所有BehaviorTree组件uuid
+ dfs(sceneNode);
+
+ // 收集BehaviorTree组件上的json路径
+ const rawUrls = await Promise.all(
+ behaviorTreeComponentUuids.map((uuid) =>
+ Editor.Message.request("scene", "execute-component-method", {
+ uuid: uuid,
+ name: "getAssetUrl",
+ })
+ )
+ );
+
+ // 过滤空的并去重
+ const urls = [...new Set(rawUrls.filter(Boolean))];
+ // 根据url获取所有json文件信息
+ const assets = await Promise.all(urls.map((url) => Editor.Message.request("asset-db", "query-asset-info", url)));
+ this.assets = assets.map(({ name, source, file }) => ({ name, url: source, file: file, content: "" }));
+ },
+ async initSelection() {
+ // 找到当前选中的节点
+ const node = await Editor.Message.request("scene", "query-node", Editor.Selection.getSelected("node"));
+
+ // 未选中节点或者选中的是场景节点(场景节点不能添加组件)
+ if (!node?.__comps__) {
+ this.maskText = "请选中一个非场景根节点非空名称节点来开始行为树制作";
+ return;
+ }
+
+ // 找到BehaviorTree组件
+ const index = node.__comps__.findIndex((v: any) => v.type === BTreeCompName);
+ if (index === -1) {
+ this.maskText = "要制作行为树,需要先为当前节点添加行为树组件";
+ return;
+ }
+
+ const comp = node.__comps__[index];
+ // 调用BehaviorTree组件上的方法
+ const url = await Editor.Message.request("scene", "execute-component-method", {
+ uuid: comp.value.uuid.value,
+ name: "getAssetUrl",
+ });
+
+ // JSON文件不存在
+ if (!url) {
+ this.maskText = "当前行为树组件缺少JSON资源,点击BehaviorEditor组件按钮即可创建";
+ return;
+ }
+
+ this.maskText = "";
+
+ // 选中此文件,设置好currentAsset才能把组件nodes数据同步到JSON
+ this.handleSelectAsset(url);
+ },
+ async handleSelectAsset(url) {
+ // 实时获取当前场景所有behaviorTree组件上的json文件
+ await this.initAssets();
+ const json = this.assets.find((e) => e.url === url);
+ if (!json) {
+ this.showWarn("JSON文件不存在");
+ return;
+ }
+
+ if (this.currentAsset?.url === json?.url) {
+ return;
+ }
+
+ this.currentAsset = json;
+
+ try {
+ const content = await fs.readJSONSync(json.file);
+ this.currentAsset.content = content;
+ this.render();
+ } catch (e) {
+ if (e instanceof SyntaxError) {
+ // JSON文件语法异常的情况下,初始化内容
+ this.showWarn("JSON文件内容初始化成功");
+ const content = { nodes: [] };
+ await fs.writeJSONSync(json.file, content);
+ this.currentAsset.content = content;
+ this.render();
+ } else {
+ // 输出其他异常
+ this.showWarn(e);
+ }
+ }
+ },
+
handlePanelChange(value) {
this.currentPanel = value;
},
@@ -289,7 +461,7 @@ const component = Vue.extend({
this.render();
}
},
- async handleEventNodeChange(lifeCycle, uuid = "") {
+ async handleEventNodeChange(lifeCycle, uuid = "", shouldSave = true) {
if (!this.currentNode) {
this.showWarn("当前节点不存在");
return;
@@ -301,9 +473,13 @@ const component = Vue.extend({
} else {
this.currentNode.event[lifeCycle].comp = "";
this.currentNode.event[lifeCycle].method = "";
- this.currentNode.event[lifeCycle].data = "";
+ // 事件参数就不手动清空了
+ // this.currentNode.event[lifeCycle].data = "";
+ }
+ // 点击canvas某个节点的时候,会触发handleEventNodeChange获取场景节点数据,此时不保存数据
+ if (shouldSave) {
+ await this.saveAsset();
}
- await this.saveAsset();
},
async getNodeCompMethods(lifeCycle, uuid) {
const [nodeInfo, compMethodInfo] = await Promise.all([
@@ -348,151 +524,18 @@ const component = Vue.extend({
this.currentNode.event[lifeCycle].data = data;
await this.saveAsset();
},
- async selectCurrentAsset() {
- // 找到点击组件的节点
- const node = await Editor.Message.request("scene", "query-node", Editor.Selection.getSelected("node"));
- if (!node) {
- this.showWarn(`未选中节点`);
- return;
- }
-
- // 找到BehaviorTree组件
- const index = node.__comps__.findIndex((v: any) => v.type === BTreeCompName);
- if (index === -1) {
- this.showWarn(`节点未挂载【${BTreeCompName}】组件`);
- return;
- }
-
- const comp = node.__comps__[index];
- // 调用BehaviorTree组件上的方法
- const url = await Editor.Message.request("scene", "execute-component-method", {
- uuid: comp.value.uuid.value,
- name: "getAssetUrl",
- });
-
- // JSON文件不存在
- if (!url) {
- this.showWarn(`【${BTreeCompName}】组件未指定JSON文件`);
- return;
- }
-
- // 选中此文件,设置好currentAsset才能把组件nodes数据同步到JSON
- this.handleSelectAsset(url);
- },
- async handleSelectAsset(url) {
- const json = this.assets.find((e) => e.url === url);
- if (!json) {
- this.showWarn("JSON文件不存在");
- return;
- }
- this.currentAsset = json;
-
- try {
- const content = await fs.readJSONSync(json.file);
- this.currentAsset.content = content;
- this.render();
- } catch (e) {
- if (e instanceof SyntaxError) {
- // JSON文件语法异常的情况下,初始化内容
- this.showWarn("JSON文件内容初始化成功");
- const content = { nodes: [] };
- await fs.writeJSONSync(json.file, content);
- this.currentAsset.content = content;
- this.render();
- } else {
- // 输出其他异常
- this.showWarn(e);
- }
- }
- },
- // 全部所有behaviorTree组件上使用到的json文件
- async getAssets() {
- const behaviorTreeComponentUuids = [];
- const dfs = (node) => {
- if (!node) {
- return;
- }
-
- for (const comp of node.components) {
- if (comp.type === BTreeCompName) {
- behaviorTreeComponentUuids.push(comp.value);
- break;
- }
- }
- for (const item of node.children) {
- dfs(item);
- }
- };
- // 获取场景节点树
- const sceneNode = await Editor.Message.request("scene", "query-node-tree");
-
- // 收集场景上所有BehaviorTree组件uuid
- dfs(sceneNode);
-
- // 收集BehaviorTree组件上的json路径
- const rawUrls = await Promise.all(
- behaviorTreeComponentUuids.map((uuid) =>
- Editor.Message.request("scene", "execute-component-method", {
- uuid: uuid,
- name: "getAssetUrl",
- })
- )
- );
-
- // 过滤空的并去重
- const urls = [...new Set(rawUrls.filter(Boolean))];
- // 根据url获取所有json文件信息
- const assets = await Promise.all(urls.map((url) => Editor.Message.request("asset-db", "query-asset-info", url)));
-
- this.assets = assets.map(({ name, source, file }) => ({ name, url: source, file: file, content: "" }));
- },
async saveAsset() {
- const url = this.currentAsset?.url;
- if (!url) {
+ const file = this.currentAsset?.file;
+ if (!file) {
this.showWarn("数据存储失败,未指定JSON");
return;
}
- const content = JSON.stringify(this.currentAsset.content, null, 2);
+ const content = this.currentAsset.content;
+
// 修改json文件
- await Editor.Message.request("asset-db", "save-asset", url, content);
+ fs.writeJSONSync(file, content);
},
- initCanvas() {
- // 初始化舞台
- stage = new Konva.Stage({
- container: this.$refs.tree,
- width: CANVAS_WIDTH,
- height: CANVAS_HEIGHT,
- });
- // 基础层,目前只有一层,每层都会生成一个canvas
- baseLayer = new Konva.Layer();
- // 背景组
- bgGroup = this.generateBg();
- baseLayer.add(bgGroup);
-
- // 静态箭头层
- staticArrowGroup = new Konva.Group();
- baseLayer.add(staticArrowGroup);
-
- // 动态箭头层(用户手动拉出来的箭头)
- dynamicArrowGroup = this.generateDynamicArrowGroup();
- baseLayer.add(dynamicArrowGroup);
-
- // Root组
- rootGroup = this.generateRoot();
- baseLayer.add(rootGroup);
-
- // 节点层
- nodeGroup = new Konva.Group();
- baseLayer.add(nodeGroup);
-
- // 框选层
- selectionGroup = this.generatesSelectionGroup();
- baseLayer.add(selectionGroup);
-
- // 层添加到舞台
- stage.add(baseLayer);
- },
/***
* 渲染画布
* renderNode:拖拽的时候不渲染节点
@@ -616,7 +659,7 @@ const component = Vue.extend({
type,
abortType: this.AbortType.None,
x: (CANVAS_WIDTH - BOX_WIDTH) / 2,
- y: CANVAS_HEIGHT / 4,
+ y: ROOT_Y + 100,
isRoot: false,
children: [],
event: {
@@ -895,7 +938,7 @@ const component = Vue.extend({
if (e.evt.buttons === 4) {
const movementX = e.evt.movementX;
const movementY = e.evt.movementY;
- this.$refs.left.scrollBy(-movementX, -movementY);
+ this.$refs.scroll.scrollBy(-movementX, -movementY);
return;
}
if (!selection.visible()) {
@@ -1063,11 +1106,11 @@ const component = Vue.extend({
selectedBoxes = [];
this.currentNode = node;
this.handlePanelChange(1);
- this.render();
// 获取节点事件相关信息
- this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node);
- this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node);
- this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node);
+ this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node, false);
+ this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node, false);
+ this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node, false);
+ this.render();
});
return wrapper;
@@ -1349,6 +1392,13 @@ const component = Vue.extend({
return BOX_HEIGHT;
},
+ isComposite(node) {
+ if (!node) {
+ return false;
+ }
+
+ return CompositeNodes.includes(node.type);
+ },
showWarn(text) {
let time = getTime();
const content = `${time}:${text}`;
@@ -1358,6 +1408,10 @@ const component = Vue.extend({
this.logs.push({ id: uuid(), content });
console.warn(content);
},
+ // 视角回到root节点
+ backRoot() {
+ this.$refs?.scroll?.scroll((CANVAS_WIDTH - this.$refs.scroll.getBoundingClientRect().width) / 2, ROOT_Y - 50);
+ },
},
});
const panelDataMap = new WeakMap() as WeakMap