mirror of
https://gitee.com/sli97/behavior-eden-coco-plugin.git
synced 2024-12-25 11:19:06 +00:00
version:1.0.1
This commit is contained in:
parent
47104caa6e
commit
2ee8352e9d
18
CHANGELOG.md
Normal file
18
CHANGELOG.md
Normal file
@ -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
|
||||||
|
|
||||||
|
**首次发布**
|
12
dist/contributions/inspector/behavior-editor.js
vendored
12
dist/contributions/inspector/behavior-editor.js
vendored
@ -3,8 +3,8 @@
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.ready = exports.update = exports.$ = exports.template = void 0;
|
exports.ready = exports.update = exports.$ = exports.template = void 0;
|
||||||
exports.template = `
|
exports.template = `
|
||||||
<div style="display:flex;justify-content:center;align-items:center;margin-top:10px;padding-right:8px;">
|
<div style="display:flex;justify-content:center;align-items:center;margin-top:10px;">
|
||||||
<ui-button class="editor" style="height:24px;padding:0 16px;">Edit Tree</ui-prop>
|
<ui-button class="editor" style="height:24px;padding:0 16px;">Create / Edit</ui-prop>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
@ -64,15 +64,17 @@ function ready() {
|
|||||||
});
|
});
|
||||||
if (success) {
|
if (success) {
|
||||||
console.log(`JSON文件挂载成功`);
|
console.log(`JSON文件挂载成功`);
|
||||||
Editor.Message.request("behavior-eden", "open-panel");
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.warn("JSON文件挂载失败");
|
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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
9
dist/main.js
vendored
9
dist/main.js
vendored
@ -11,8 +11,13 @@ const package_json_1 = __importDefault(require("../package.json"));
|
|||||||
* @zh 为扩展的主进程的注册方法
|
* @zh 为扩展的主进程的注册方法
|
||||||
*/
|
*/
|
||||||
exports.methods = {
|
exports.methods = {
|
||||||
openPanel() {
|
async openPanel() {
|
||||||
Editor.Panel.open(package_json_1.default.name);
|
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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
325
dist/panels/default/index.js
vendored
325
dist/panels/default/index.js
vendored
@ -10,8 +10,8 @@ const node_1 = require("../../runtime/node");
|
|||||||
const decorator_1 = require("../../runtime/core/decorator");
|
const decorator_1 = require("../../runtime/core/decorator");
|
||||||
const utils_1 = require("../../runtime/core/utils");
|
const utils_1 = require("../../runtime/core/utils");
|
||||||
// 画布宽高
|
// 画布宽高
|
||||||
const CANVAS_WIDTH = 2400;
|
const CANVAS_WIDTH = 2000;
|
||||||
const CANVAS_HEIGHT = 1600;
|
const CANVAS_HEIGHT = 1400;
|
||||||
// 节点box属性
|
// 节点box属性
|
||||||
const BOX_WIDTH = 100;
|
const BOX_WIDTH = 100;
|
||||||
// const BOX_HEIGHT = 64; //height通过getBoxHeight方法动态计算
|
// const BOX_HEIGHT = 64; //height通过getBoxHeight方法动态计算
|
||||||
@ -141,6 +141,21 @@ const ParentNodes = [...decorator_1.nodeClsMap.values()]
|
|||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
.map((cls) => cls.name);
|
.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 = () => {
|
const activeCursor = () => {
|
||||||
stage && (stage.container().style.cursor = "pointer");
|
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"),
|
template: lib_1.fs.readFileSync(path_1.default.join(__dirname, "../../../src/panels/static/template/vue/app.html"), "utf-8"),
|
||||||
$: {
|
$: {
|
||||||
tree: "#tree",
|
tree: "#tree",
|
||||||
left: "#left",
|
scroll: "#scroll",
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
toUpperCase(value) {
|
toUpperCase(value) {
|
||||||
@ -189,6 +204,7 @@ const component = lib_1.Vue.extend({
|
|||||||
*/
|
*/
|
||||||
nodeCompMethodInfo: {},
|
nodeCompMethodInfo: {},
|
||||||
logs: [],
|
logs: [],
|
||||||
|
maskText: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -253,12 +269,143 @@ const component = lib_1.Vue.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.initCanvas();
|
this.init();
|
||||||
await this.getAssets();
|
|
||||||
await this.selectCurrentAsset();
|
|
||||||
this.$refs.left.scroll((CANVAS_WIDTH - this.$refs.left.getBoundingClientRect().width) / 2, ROOT_Y - 50);
|
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
handlePanelChange(value) {
|
||||||
this.currentPanel = value;
|
this.currentPanel = value;
|
||||||
},
|
},
|
||||||
@ -275,7 +422,7 @@ const component = lib_1.Vue.extend({
|
|||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleEventNodeChange(lifeCycle, uuid = "") {
|
async handleEventNodeChange(lifeCycle, uuid = "", shouldSave = true) {
|
||||||
if (!this.currentNode) {
|
if (!this.currentNode) {
|
||||||
this.showWarn("当前节点不存在");
|
this.showWarn("当前节点不存在");
|
||||||
return;
|
return;
|
||||||
@ -287,9 +434,13 @@ const component = lib_1.Vue.extend({
|
|||||||
else {
|
else {
|
||||||
this.currentNode.event[lifeCycle].comp = "";
|
this.currentNode.event[lifeCycle].comp = "";
|
||||||
this.currentNode.event[lifeCycle].method = "";
|
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) {
|
async getNodeCompMethods(lifeCycle, uuid) {
|
||||||
const [nodeInfo, compMethodInfo] = await Promise.all([
|
const [nodeInfo, compMethodInfo] = await Promise.all([
|
||||||
@ -331,132 +482,16 @@ const component = lib_1.Vue.extend({
|
|||||||
this.currentNode.event[lifeCycle].data = data;
|
this.currentNode.event[lifeCycle].data = data;
|
||||||
await this.saveAsset();
|
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() {
|
async saveAsset() {
|
||||||
var _a;
|
var _a;
|
||||||
const url = (_a = this.currentAsset) === null || _a === void 0 ? void 0 : _a.url;
|
const file = (_a = this.currentAsset) === null || _a === void 0 ? void 0 : _a.file;
|
||||||
if (!url) {
|
if (!file) {
|
||||||
this.showWarn("数据存储失败,未指定JSON");
|
this.showWarn("数据存储失败,未指定JSON");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const content = JSON.stringify(this.currentAsset.content, null, 2);
|
const content = this.currentAsset.content;
|
||||||
// 修改json文件
|
// 修改json文件
|
||||||
await Editor.Message.request("asset-db", "save-asset", url, content);
|
lib_1.fs.writeJSONSync(file, 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);
|
|
||||||
},
|
},
|
||||||
/***
|
/***
|
||||||
* 渲染画布
|
* 渲染画布
|
||||||
@ -568,7 +603,7 @@ const component = lib_1.Vue.extend({
|
|||||||
type,
|
type,
|
||||||
abortType: this.AbortType.None,
|
abortType: this.AbortType.None,
|
||||||
x: (CANVAS_WIDTH - BOX_WIDTH) / 2,
|
x: (CANVAS_WIDTH - BOX_WIDTH) / 2,
|
||||||
y: CANVAS_HEIGHT / 4,
|
y: ROOT_Y + 100,
|
||||||
isRoot: false,
|
isRoot: false,
|
||||||
children: [],
|
children: [],
|
||||||
event: {
|
event: {
|
||||||
@ -828,7 +863,7 @@ const component = lib_1.Vue.extend({
|
|||||||
if (e.evt.buttons === 4) {
|
if (e.evt.buttons === 4) {
|
||||||
const movementX = e.evt.movementX;
|
const movementX = e.evt.movementX;
|
||||||
const movementY = e.evt.movementY;
|
const movementY = e.evt.movementY;
|
||||||
this.$refs.left.scrollBy(-movementX, -movementY);
|
this.$refs.scroll.scrollBy(-movementX, -movementY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selection.visible()) {
|
if (!selection.visible()) {
|
||||||
@ -981,11 +1016,11 @@ const component = lib_1.Vue.extend({
|
|||||||
selectedBoxes = [];
|
selectedBoxes = [];
|
||||||
this.currentNode = node;
|
this.currentNode = node;
|
||||||
this.handlePanelChange(1);
|
this.handlePanelChange(1);
|
||||||
this.render();
|
|
||||||
// 获取节点事件相关信息
|
// 获取节点事件相关信息
|
||||||
this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node);
|
this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node, false);
|
||||||
this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node);
|
this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node, false);
|
||||||
this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node);
|
this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node, false);
|
||||||
|
this.render();
|
||||||
});
|
});
|
||||||
return wrapper;
|
return wrapper;
|
||||||
},
|
},
|
||||||
@ -1247,6 +1282,12 @@ const component = lib_1.Vue.extend({
|
|||||||
}
|
}
|
||||||
return BOX_HEIGHT;
|
return BOX_HEIGHT;
|
||||||
},
|
},
|
||||||
|
isComposite(node) {
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return CompositeNodes.includes(node.type);
|
||||||
|
},
|
||||||
showWarn(text) {
|
showWarn(text) {
|
||||||
let time = getTime();
|
let time = getTime();
|
||||||
const content = `${time}:${text}`;
|
const content = `${time}:${text}`;
|
||||||
@ -1256,6 +1297,11 @@ const component = lib_1.Vue.extend({
|
|||||||
this.logs.push({ id: uuid(), content });
|
this.logs.push({ id: uuid(), content });
|
||||||
console.warn(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();
|
const panelDataMap = new WeakMap();
|
||||||
@ -1281,7 +1327,14 @@ module.exports = Editor.Panel.define({
|
|||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {
|
||||||
|
initSelection() {
|
||||||
|
const vm = panelDataMap.get(this);
|
||||||
|
if (vm) {
|
||||||
|
vm.initSelection();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
ready() {
|
ready() {
|
||||||
if (this.$.app) {
|
if (this.$.app) {
|
||||||
const vm = new component();
|
const vm = new component();
|
||||||
|
2
dist/runtime/core/Blackboard.js
vendored
2
dist/runtime/core/Blackboard.js
vendored
@ -22,5 +22,5 @@ class Blackboard {
|
|||||||
this.map.clear();
|
this.map.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exports.Blackboard = Blackboard;
|
|
||||||
Blackboard.map = new Map();
|
Blackboard.map = new Map();
|
||||||
|
exports.Blackboard = Blackboard;
|
||||||
|
@ -37,9 +37,14 @@
|
|||||||
"openPanel"
|
"openPanel"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"send-to-panel": {
|
"refresh-panel": {
|
||||||
"methods": [
|
"methods": [
|
||||||
"default.hello"
|
"refreshPanel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"selection:select": {
|
||||||
|
"methods": [
|
||||||
|
"default.initSelection"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
type Selector<$> = { $: Record<keyof $, any | null> };
|
type Selector<$> = { $: Record<keyof $, any | null> };
|
||||||
|
|
||||||
export const template = `
|
export const template = `
|
||||||
<div style="display:flex;justify-content:center;align-items:center;margin-top:10px;padding-right:8px;">
|
<div style="display:flex;justify-content:center;align-items:center;margin-top:10px;">
|
||||||
<ui-button class="editor" style="height:24px;padding:0 16px;">Edit Tree</ui-prop>
|
<ui-button class="editor" style="height:24px;padding:0 16px;">Create / Edit</ui-prop>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
`;
|
`;
|
||||||
@ -69,13 +69,16 @@ export function ready(this: Selector<typeof $> & any) {
|
|||||||
});
|
});
|
||||||
if (success) {
|
if (success) {
|
||||||
console.log(`JSON文件挂载成功`);
|
console.log(`JSON文件挂载成功`);
|
||||||
Editor.Message.request("behavior-eden", "open-panel");
|
|
||||||
} else {
|
} else {
|
||||||
console.warn("JSON文件挂载失败");
|
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");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,13 @@ import packageJSON from "../package.json";
|
|||||||
* @zh 为扩展的主进程的注册方法
|
* @zh 为扩展的主进程的注册方法
|
||||||
*/
|
*/
|
||||||
export const methods: { [key: string]: (...any: any) => any } = {
|
export const methods: { [key: string]: (...any: any) => any } = {
|
||||||
openPanel() {
|
async openPanel() {
|
||||||
Editor.Panel.open(packageJSON.name);
|
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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import { nodeClsMap } from "../../runtime/core/decorator";
|
|||||||
import { buildTree, preOrder } from "../../runtime/core/utils";
|
import { buildTree, preOrder } from "../../runtime/core/utils";
|
||||||
|
|
||||||
// 画布宽高
|
// 画布宽高
|
||||||
const CANVAS_WIDTH = 2400;
|
const CANVAS_WIDTH = 2000;
|
||||||
const CANVAS_HEIGHT = 1600;
|
const CANVAS_HEIGHT = 1400;
|
||||||
// 节点box属性
|
// 节点box属性
|
||||||
const BOX_WIDTH = 100;
|
const BOX_WIDTH = 100;
|
||||||
// const BOX_HEIGHT = 64; //height通过getBoxHeight方法动态计算
|
// const BOX_HEIGHT = 64; //height通过getBoxHeight方法动态计算
|
||||||
@ -149,6 +149,24 @@ const ParentNodes = [...nodeClsMap.values()]
|
|||||||
})
|
})
|
||||||
.map((cls) => cls.name);
|
.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 = () => {
|
const activeCursor = () => {
|
||||||
stage && (stage.container().style.cursor = "pointer");
|
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"),
|
template: fs.readFileSync(path.join(__dirname, "../../../src/panels/static/template/vue/app.html"), "utf-8"),
|
||||||
$: {
|
$: {
|
||||||
tree: "#tree",
|
tree: "#tree",
|
||||||
left: "#left",
|
scroll: "#scroll",
|
||||||
},
|
},
|
||||||
filters: {
|
filters: {
|
||||||
toUpperCase(value) {
|
toUpperCase(value) {
|
||||||
@ -199,6 +217,7 @@ const component = Vue.extend({
|
|||||||
*/
|
*/
|
||||||
nodeCompMethodInfo: {},
|
nodeCompMethodInfo: {},
|
||||||
logs: [],
|
logs: [],
|
||||||
|
maskText: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -267,12 +286,165 @@ const component = Vue.extend({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.initCanvas();
|
this.init();
|
||||||
await this.getAssets();
|
|
||||||
await this.selectCurrentAsset();
|
|
||||||
this.$refs.left.scroll((CANVAS_WIDTH - this.$refs.left.getBoundingClientRect().width) / 2, ROOT_Y - 50);
|
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
handlePanelChange(value) {
|
||||||
this.currentPanel = value;
|
this.currentPanel = value;
|
||||||
},
|
},
|
||||||
@ -289,7 +461,7 @@ const component = Vue.extend({
|
|||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleEventNodeChange(lifeCycle, uuid = "") {
|
async handleEventNodeChange(lifeCycle, uuid = "", shouldSave = true) {
|
||||||
if (!this.currentNode) {
|
if (!this.currentNode) {
|
||||||
this.showWarn("当前节点不存在");
|
this.showWarn("当前节点不存在");
|
||||||
return;
|
return;
|
||||||
@ -301,9 +473,13 @@ const component = Vue.extend({
|
|||||||
} else {
|
} else {
|
||||||
this.currentNode.event[lifeCycle].comp = "";
|
this.currentNode.event[lifeCycle].comp = "";
|
||||||
this.currentNode.event[lifeCycle].method = "";
|
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) {
|
async getNodeCompMethods(lifeCycle, uuid) {
|
||||||
const [nodeInfo, compMethodInfo] = await Promise.all([
|
const [nodeInfo, compMethodInfo] = await Promise.all([
|
||||||
@ -348,151 +524,18 @@ const component = Vue.extend({
|
|||||||
this.currentNode.event[lifeCycle].data = data;
|
this.currentNode.event[lifeCycle].data = data;
|
||||||
await this.saveAsset();
|
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() {
|
async saveAsset() {
|
||||||
const url = this.currentAsset?.url;
|
const file = this.currentAsset?.file;
|
||||||
if (!url) {
|
if (!file) {
|
||||||
this.showWarn("数据存储失败,未指定JSON");
|
this.showWarn("数据存储失败,未指定JSON");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const content = JSON.stringify(this.currentAsset.content, null, 2);
|
const content = this.currentAsset.content;
|
||||||
|
|
||||||
// 修改json文件
|
// 修改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:拖拽的时候不渲染节点
|
* renderNode:拖拽的时候不渲染节点
|
||||||
@ -616,7 +659,7 @@ const component = Vue.extend({
|
|||||||
type,
|
type,
|
||||||
abortType: this.AbortType.None,
|
abortType: this.AbortType.None,
|
||||||
x: (CANVAS_WIDTH - BOX_WIDTH) / 2,
|
x: (CANVAS_WIDTH - BOX_WIDTH) / 2,
|
||||||
y: CANVAS_HEIGHT / 4,
|
y: ROOT_Y + 100,
|
||||||
isRoot: false,
|
isRoot: false,
|
||||||
children: [],
|
children: [],
|
||||||
event: {
|
event: {
|
||||||
@ -895,7 +938,7 @@ const component = Vue.extend({
|
|||||||
if (e.evt.buttons === 4) {
|
if (e.evt.buttons === 4) {
|
||||||
const movementX = e.evt.movementX;
|
const movementX = e.evt.movementX;
|
||||||
const movementY = e.evt.movementY;
|
const movementY = e.evt.movementY;
|
||||||
this.$refs.left.scrollBy(-movementX, -movementY);
|
this.$refs.scroll.scrollBy(-movementX, -movementY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!selection.visible()) {
|
if (!selection.visible()) {
|
||||||
@ -1063,11 +1106,11 @@ const component = Vue.extend({
|
|||||||
selectedBoxes = [];
|
selectedBoxes = [];
|
||||||
this.currentNode = node;
|
this.currentNode = node;
|
||||||
this.handlePanelChange(1);
|
this.handlePanelChange(1);
|
||||||
this.render();
|
|
||||||
// 获取节点事件相关信息
|
// 获取节点事件相关信息
|
||||||
this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node);
|
this.handleEventNodeChange(this.onStart, this.currentNode.event[this.onStart].node, false);
|
||||||
this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node);
|
this.handleEventNodeChange(this.onUpdate, this.currentNode.event[this.onUpdate].node, false);
|
||||||
this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node);
|
this.handleEventNodeChange(this.onEnd, this.currentNode.event[this.onEnd].node, false);
|
||||||
|
this.render();
|
||||||
});
|
});
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
@ -1349,6 +1392,13 @@ const component = Vue.extend({
|
|||||||
|
|
||||||
return BOX_HEIGHT;
|
return BOX_HEIGHT;
|
||||||
},
|
},
|
||||||
|
isComposite(node) {
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompositeNodes.includes(node.type);
|
||||||
|
},
|
||||||
showWarn(text) {
|
showWarn(text) {
|
||||||
let time = getTime();
|
let time = getTime();
|
||||||
const content = `${time}:${text}`;
|
const content = `${time}:${text}`;
|
||||||
@ -1358,6 +1408,10 @@ const component = Vue.extend({
|
|||||||
this.logs.push({ id: uuid(), content });
|
this.logs.push({ id: uuid(), content });
|
||||||
console.warn(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<object, InstanceType<typeof component>>;
|
const panelDataMap = new WeakMap() as WeakMap<object, InstanceType<typeof component>>;
|
||||||
@ -1383,7 +1437,14 @@ module.exports = Editor.Panel.define({
|
|||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
methods: {},
|
methods: {
|
||||||
|
initSelection() {
|
||||||
|
const vm = panelDataMap.get(this);
|
||||||
|
if (vm) {
|
||||||
|
vm.initSelection();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
ready() {
|
ready() {
|
||||||
if (this.$.app) {
|
if (this.$.app) {
|
||||||
const vm = new component();
|
const vm = new component();
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
.wrapper {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #2b2b2b;
|
background-color: #2b2b2b;
|
||||||
max-height: calc(100vh - 26px);
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #cccccc;
|
color: #cccccc;
|
||||||
font-family: BlinkMacSystemFont, "PingFang SC", system-ui, -apple-system, Helvetica Neue, Helvetica, sans-serif;
|
font-family: BlinkMacSystemFont, "PingFang SC", system-ui, -apple-system, Helvetica Neue, Helvetica, sans-serif;
|
||||||
@ -15,12 +16,24 @@ ui-prop[no-label] {
|
|||||||
padding: 0px 4px 0 4px;
|
padding: 0px 4px 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.node-type {
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.life-cycle-input {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.right {
|
.right {
|
||||||
width: 280px;
|
width: 260px;
|
||||||
border-left: 1px solid #050505;
|
border-left: 2px solid #050505;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right > div {
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.node-panel {
|
.node-panel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -29,25 +42,41 @@ ui-prop[no-label] {
|
|||||||
|
|
||||||
.left {
|
.left {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: scroll;
|
width: calc(100% - 260px);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll {
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fix-left {
|
.fix-left {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
left: 13px;
|
left: 14px;
|
||||||
top: 34px;
|
top: 14px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fix-right {
|
.fix-right {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
right: 300px;
|
right: 14px;
|
||||||
top: 34px;
|
top: 14px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fix-bottom {
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
bottom: 24px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fix-bottom ui-button {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.asset {
|
.asset {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +84,7 @@ ui-prop[no-label] {
|
|||||||
color: #e8b116;
|
color: #e8b116;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
font-weight: bold;
|
/* font-weight: bold; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.log div {
|
.log div {
|
||||||
@ -64,6 +93,10 @@ ui-prop[no-label] {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log div:first-child {
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
@ -102,10 +135,22 @@ ui-prop[no-label] {
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mt-12 {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mr-4 {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.mr-6 {
|
.mr-6 {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-8 {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.grow {
|
.grow {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@ -113,3 +158,16 @@ ui-prop[no-label] {
|
|||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: rgba(2, 2, 2, 0.6);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<div>
|
<div style="width: 100%; height: 100%" id="wrapper">
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="left" ref="left">
|
<div class="mask" v-if="maskText">{{maskText}}</div>
|
||||||
|
<div class="left">
|
||||||
|
<div class="scroll" ref="scroll">
|
||||||
|
<div id="tree" ref="tree" />
|
||||||
|
</div>
|
||||||
<div class="fix-left">
|
<div class="fix-left">
|
||||||
<div class="log">
|
<div class="log">
|
||||||
<ui-section header="Logs" class="config" expand>
|
<ui-section header="Logs" class="config" expand>
|
||||||
@ -14,7 +18,9 @@
|
|||||||
</ui-select>
|
</ui-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tree" ref="tree" />
|
<div class="fix-bottom" @click="backRoot">
|
||||||
|
<ui-button>Root</ui-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right">
|
<div class="right">
|
||||||
@ -24,7 +30,7 @@
|
|||||||
<div v-if="currentPanel === 0" class="node-panel">
|
<div v-if="currentPanel === 0" class="node-panel">
|
||||||
<div v-for="category of nodeCategory" class="node-type">
|
<div v-for="category of nodeCategory" class="node-type">
|
||||||
<ui-section :header="category.id" :expand="category.expand">
|
<ui-section :header="category.id" :expand="category.expand">
|
||||||
<ui-button class="mt-4 mr-6" v-for="item of category.items" :key="item.type" @click="addNode(item.type)"
|
<ui-button class="mt-4" v-for="item of category.items" :key="item.type" @click="addNode(item.type)"
|
||||||
>{{ item.type }}</ui-button
|
>{{ item.type }}</ui-button
|
||||||
>
|
>
|
||||||
</ui-section>
|
</ui-section>
|
||||||
@ -53,6 +59,7 @@
|
|||||||
<ui-label slot="label">Abort Type</ui-label>
|
<ui-label slot="label">Abort Type</ui-label>
|
||||||
<ui-select
|
<ui-select
|
||||||
slot="content"
|
slot="content"
|
||||||
|
:disabled="!isComposite(currentNode)"
|
||||||
:value="currentNode.abortType"
|
:value="currentNode.abortType"
|
||||||
@change="handleAbortTypeChange($event.target.value)"
|
@change="handleAbortTypeChange($event.target.value)"
|
||||||
>
|
>
|
||||||
@ -93,9 +100,10 @@
|
|||||||
</ui-select>
|
</ui-select>
|
||||||
</div>
|
</div>
|
||||||
</ui-prop>
|
</ui-prop>
|
||||||
<ui-prop message="EventData">
|
<ui-prop no-label>
|
||||||
<ui-input
|
<ui-input
|
||||||
slot="content"
|
slot="content"
|
||||||
|
class="life-cycle-input"
|
||||||
:value="currentNode.event[onStart].data"
|
:value="currentNode.event[onStart].data"
|
||||||
@input="handleEventDataChange(onStart, $event.target.value)"
|
@input="handleEventDataChange(onStart, $event.target.value)"
|
||||||
></ui-input>
|
></ui-input>
|
||||||
@ -107,7 +115,7 @@
|
|||||||
<ui-label slot="label">{{onUpdate | toUpperCase}}</ui-label>
|
<ui-label slot="label">{{onUpdate | toUpperCase}}</ui-label>
|
||||||
</ui-prop>
|
</ui-prop>
|
||||||
<ui-prop no-label>
|
<ui-prop no-label>
|
||||||
<div slot="content" class="flex items-center">
|
<div slot="content" class="flex items-center gap-4">
|
||||||
<ui-node
|
<ui-node
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
droppable="cc.Node"
|
droppable="cc.Node"
|
||||||
@ -134,9 +142,10 @@
|
|||||||
</ui-select>
|
</ui-select>
|
||||||
</div>
|
</div>
|
||||||
</ui-prop>
|
</ui-prop>
|
||||||
<ui-prop message="EventData">
|
<ui-prop no-label>
|
||||||
<ui-input
|
<ui-input
|
||||||
slot="content"
|
slot="content"
|
||||||
|
class="life-cycle-input"
|
||||||
:value="currentNode.event[onUpdate].data"
|
:value="currentNode.event[onUpdate].data"
|
||||||
@input="handleEventDataChange(onUpdate, $event.target.value)"
|
@input="handleEventDataChange(onUpdate, $event.target.value)"
|
||||||
></ui-input>
|
></ui-input>
|
||||||
@ -148,7 +157,7 @@
|
|||||||
<ui-label slot="label">{{onEnd | toUpperCase}}</ui-label>
|
<ui-label slot="label">{{onEnd | toUpperCase}}</ui-label>
|
||||||
</ui-prop>
|
</ui-prop>
|
||||||
<ui-prop no-label>
|
<ui-prop no-label>
|
||||||
<div slot="content" class="flex items-center">
|
<div slot="content" class="flex items-center gap-4">
|
||||||
<ui-node
|
<ui-node
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
droppable="cc.Node"
|
droppable="cc.Node"
|
||||||
@ -175,9 +184,10 @@
|
|||||||
</ui-select>
|
</ui-select>
|
||||||
</div>
|
</div>
|
||||||
</ui-prop>
|
</ui-prop>
|
||||||
<ui-prop message="EventData">
|
<ui-prop no-label>
|
||||||
<ui-input
|
<ui-input
|
||||||
slot="content"
|
slot="content"
|
||||||
|
class="life-cycle-input"
|
||||||
:value="currentNode.event[onEnd].data"
|
:value="currentNode.event[onEnd].data"
|
||||||
@input="handleEventDataChange(onEnd, $event.target.value)"
|
@input="handleEventDataChange(onEnd, $event.target.value)"
|
||||||
></ui-input>
|
></ui-input>
|
||||||
@ -186,7 +196,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex justify-center mt-4">请选择节点</div>
|
<div v-else class="flex justify-center mt-12">Please select a node</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user